diff --git a/library/Icinga/Data/Db/DbQuery.php b/library/Icinga/Data/Db/DbQuery.php index 5062c8caf..e89236df5 100644 --- a/library/Icinga/Data/Db/DbQuery.php +++ b/library/Icinga/Data/Db/DbQuery.php @@ -134,11 +134,11 @@ class DbQuery extends SimpleQuery return $select; } - public function applyFilterSql($query) + protected function applyFilterSql($select) { $where = $this->renderFilter($this->filter); if ($where !== '') { - $query->where($where); + $select->where($where); } } @@ -251,10 +251,12 @@ class DbQuery extends SimpleQuery if (is_array($expression) && $sign === '=') { // TODO: Should we support this? Doesn't work for blub* return $col . ' IN (' . $this->escapeForSql($expression) . ')'; - } elseif (strpos($expression, '*') === false) { - return $col . ' ' . $sign . ' ' . $this->escapeForSql($expression); - } else { + } elseif ($sign === '=' && strpos($expression, '*') !== false) { return $col . ' LIKE ' . $this->escapeForSql($this->escapeWildcards($expression)); + } elseif ($sign === '!=' && strpos($expression, '*') !== false) { + return $col . ' NOT LIKE ' . $this->escapeForSql($this->escapeWildcards($expression)); + } else { + return $col . ' ' . $sign . ' ' . $this->escapeForSql($expression); } } @@ -313,6 +315,13 @@ class DbQuery extends SimpleQuery . "\n\n"; } + public function __clone() + { + if ($this->select) { + $this->select = clone $this->select; + } + } + /** * @return string */ diff --git a/library/Icinga/Data/Filter/Filter.php b/library/Icinga/Data/Filter/Filter.php index a0a56e4bb..5793a4749 100644 --- a/library/Icinga/Data/Filter/Filter.php +++ b/library/Icinga/Data/Filter/Filter.php @@ -62,6 +62,8 @@ abstract class Filter return false === strpos($this->id, '-'); } + abstract public function listFilteredColumns(); + public function applyChanges($changes) { $filter = $this; diff --git a/library/Icinga/Data/Filter/FilterChain.php b/library/Icinga/Data/Filter/FilterChain.php index 76c56a7af..cdb3a1123 100644 --- a/library/Icinga/Data/Filter/FilterChain.php +++ b/library/Icinga/Data/Filter/FilterChain.php @@ -117,6 +117,20 @@ abstract class FilterChain extends Filter return $this->operatorSymbol; } + public function listFilteredColumns() + { + $columns = array(); + foreach ($this->filters as $filter) { + if ($filter instanceof FilterExpression) { + $columns[] = $filter->getColumn(); + } else { + $columns += $filter->listFilteredColumns(); + } + } + // array_unique? + return $columns; + } + public function toQueryString() { $parts = array(); diff --git a/library/Icinga/Data/Filter/FilterExpression.php b/library/Icinga/Data/Filter/FilterExpression.php index f87f3602f..0782bcd89 100644 --- a/library/Icinga/Data/Filter/FilterExpression.php +++ b/library/Icinga/Data/Filter/FilterExpression.php @@ -69,6 +69,11 @@ class FilterExpression extends Filter return $this; } + public function listFilteredColumns() + { + return array($this->getColumn()); + } + public function __toString() { $expression = is_array($this->expression) ? diff --git a/library/Icinga/Data/Filter/FilterGreaterThan.php b/library/Icinga/Data/Filter/FilterGreaterThan.php index bda8665b1..62ba16fca 100644 --- a/library/Icinga/Data/Filter/FilterGreaterThan.php +++ b/library/Icinga/Data/Filter/FilterGreaterThan.php @@ -6,9 +6,12 @@ namespace Icinga\Data\Filter; class FilterGreaterThan extends FilterExpression { - public function matches($row) { + if (! isset($row->{$this->column})) { + // TODO: REALLY? Exception? + return false; + } return (string) $row->{$this->column} > (string) $this->expression; } } diff --git a/library/Icinga/Data/Filter/FilterMatch.php b/library/Icinga/Data/Filter/FilterMatch.php index ef3cad801..159feb04e 100644 --- a/library/Icinga/Data/Filter/FilterMatch.php +++ b/library/Icinga/Data/Filter/FilterMatch.php @@ -8,6 +8,10 @@ class FilterMatch extends FilterExpression { public function matches($row) { + if (! isset($row->{$this->column})) { + // TODO: REALLY? Exception? + return false; + } $expression = (string) $this->expression; if (strpos($expression, '*') === false) { return (string) $row->{$this->column} === $expression; diff --git a/library/Icinga/Data/SimpleQuery.php b/library/Icinga/Data/SimpleQuery.php index 6a0b71c35..41d050ea2 100644 --- a/library/Icinga/Data/SimpleQuery.php +++ b/library/Icinga/Data/SimpleQuery.php @@ -210,7 +210,7 @@ class SimpleQuery implements QueryInterface, Queryable $dir = $this->order[$col_num][1]; // TODO: throw Exception if column is missing //$res = strnatcmp(strtolower($a->$col), strtolower($b->$col)); - $res = strcmp(strtolower($a->$col), strtolower($b->$col)); + $res = @strcmp(strtolower($a->$col), strtolower($b->$col)); if ($res === 0) { // return $this->compare($a, $b, $col_num++); diff --git a/library/Icinga/Protocol/Livestatus/Connection.php b/library/Icinga/Protocol/Livestatus/Connection.php index c014ba70f..b2340eeec 100644 --- a/library/Icinga/Protocol/Livestatus/Connection.php +++ b/library/Icinga/Protocol/Livestatus/Connection.php @@ -5,8 +5,11 @@ namespace Icinga\Protocol\Livestatus; use Icinga\Application\Benchmark; -use Exception; +use Icinga\Exception\ConfigurationError; +use Icinga\Exception\SystemPermissionException; use Icinga\Exception\IcingaException; +use Exception; +use SplFixedArray; /** * Backend class managing handling MKI Livestatus connections @@ -31,6 +34,15 @@ class Connection const TYPE_UNIX = 1; const TYPE_TCP = 2; + const FIELD_SEPARATOR = '`'; + + protected $bytesRead = 0; + protected $responseSize; + protected $status; + protected $headers; + + // List of available Livestatus tables. Kept here as we otherwise get no + // useful error message protected $available_tables = array( 'hosts', // hosts 'services', // services, joined with all data from hosts @@ -64,6 +76,13 @@ class Connection protected $socket_type; protected $connection; + /** + * Whether the given table name is valid + * + * @param string $name table name + * + * @return bool + */ public function hasTable($name) { return in_array($name, $this->available_tables); @@ -74,7 +93,7 @@ class Connection $this->assertPhpExtensionLoaded('sockets'); if ($socket[0] === '/') { if (! is_writable($socket)) { - throw new IcingaException( + throw new SystemPermissionException( 'Cannot write to livestatus socket "%s"', $socket ); @@ -83,52 +102,106 @@ class Connection $this->socket_path = $socket; } else { if (! preg_match('~^tcp://([^:]+):(\d+)~', $socket, $m)) { - throw new IcingaException( + throw new ConfigurationError( 'Invalid TCP socket syntax: "%s"', $socket ); } - // TODO: Better syntax checks + // TODO: Better config syntax checks $this->socket_host = $m[1]; $this->socket_port = (int) $m[2]; $this->socket_type = self::TYPE_TCP; } } - public function select() - { - $select = new Query($this); - return $select; - } - + /** + * Count unlimited rows matching the query filter + * + * TODO: Currently hardcoded value, as the old variant was stupid + * Create a working variant doing this->execute(query->renderCount())... + * + * @param Query $query the query object + * + * @return int + */ public function count(Query $query) { + return 100; $count = clone($query); - $count->count(); + // WTF? $count->count(); Benchmark::measure('Sending Livestatus Count Query'); - $data = $this->doFetch((string) $count); + $this->execute($query); + $data = $this->fetchRowFromSocket(); Benchmark::measure('Got Livestatus count result'); return $data[0][0]; } + /** + * Fetch a single row + * + * TODO: Currently based on fetchAll, that's bullshit + * + * @param Query $query the query object + * + * @return object the first result row + */ + public function fetchRow(Query $query) + { + $all = $this->fetchAll($query); + return array_shift($all); + } + + /** + * Fetch key/value pairs + * + * TODO: Currently slow, needs improvement + * + * @param Query $query the query object + * + * @return array + */ + public function fetchPairs(Query $query) + { + $res = array(); + $all = $this->fetchAll($query); + foreach ($all as $row) { + // slow + $keys = array_keys((array) $row); + $res[$row->{$keys[0]}] = $row->{$keys[1]}; + } + return $res; + } + + /** + * Fetch all result rows + * + * @param Query $query the query object + * + * @return array + */ public function fetchAll(Query $query) { Benchmark::measure('Sending Livestatus Query'); - $data = $this->doFetch((string) $query); + $this->execute($query); Benchmark::measure('Got Livestatus Data'); + if ($query->hasColumns()) { $headers = $query->getColumnAliases(); } else { + // TODO: left this here, find out how to handle it better + die('F*** no data'); $headers = array_shift($data); } $result = array(); - foreach ($data as $row) { - $result_row = & $result[]; - $result_row = (object) array(); - foreach ($row as $key => $val) { - $result_row->{$headers[$key]} = $val; - } + $filter = $query->filterIsSupported() ? null : $query->getFilter(); + + while ($row = $this->fetchRowFromSocket()) { + $r = new ResponseRow($row, $query); + $res = $query->resultRow($row); + if ($filter !== null && ! $filter->matches($res)) continue; + $result[] = $res; } + if ($query->hasOrder()) { usort($result, array($query, 'compare')); } @@ -144,70 +217,137 @@ class Connection return $result; } - protected function doFetch($raw_query) + protected function hasBeenExecuted() { - $conn = $this->getConnection(); - $this->writeToSocket($raw_query); - $header = $this->readFromSocket(16); - $status = (int) substr($header, 0, 3); - $length = (int) trim(substr($header, 4)); - $body = $this->readFromSocket($length); - if ($status !== 200) { - throw new IcingaException( - 'Problem while reading %d bytes from livestatus: %s', - $length, - $body - ); - } - $result = json_decode($body); - if ($result === null) { - throw new IcingaException('Got invalid response body from livestatus'); - } - - return $result; + return $this->status !== null; } - protected function readFromSocket($length) + protected function execute($query) { - $offset = 0; - $buffer = ''; + // Reset state + $this->status = null; + $this->responseSize = null; + $this->bytesRead = 0; - while ($offset < $length) { - $data = socket_read($this->connection, $length - $offset); - if ($data === false) { - throw new IcingaException( - 'Failed to read from livestatus socket: %s', - socket_strerror(socket_last_error($this->connection)) - ); - } - $size = strlen($data); - $offset += $size; - $buffer .= $data; + $raw = $query->toString(); - if ($size === 0) { - break; - } - } - if ($offset !== $length) { - throw new IcingaException( - 'Got only %d instead of %d bytes from livestatus socket', - $offset, - $length + Benchmark::measure($raw); + + // "debug" + // echo $raw . "\n
"; + $this->writeToSocket($raw); + $header = $this->readLineFromSocket(); + + if (! preg_match('~^(\d{3})\s\s*(\d+)$~', $header, $m)) { + $this->disconnect(); + throw new Exception( + sprintf('Got invalid header. First 16 Bytes: %s', $header) ); } - - return $buffer; + $this->status = (int) $m[1]; + $this->bytesRead = 0; + $this->responseSize = (int) $m[2]; + if ($this->status !== 200) { + // "debug" + //die(var_export($raw, 1)); + throw new Exception( + sprintf( + 'Error %d while querying livestatus: %s %s', + $this->status, + $raw, + $this->readLineFromSocket() + ) + ); + } + $this->discoverColumnHeaders($query); } + protected function discoverColumnHeaders($query) + { + if ($query->hasColumns()) { + $this->headers = $query->getColumnAliases(); + } else { + $this->headers = $this->splitLine($this->readLineFromSocket()); + } + } + + protected function splitLine(& $line) + { + if ($this->headers === null) { + $res = array(); + } else { + $res = new SplFixedArray(count($this->headers)); + $size = count($res); + } + $start = 0; + $col = 0; + while (false !== ($pos = strpos($line, self::FIELD_SEPARATOR, $start))) { +// TODO: safety measure for not killing the SPL. To be removed once code is clean +if ($col > $size -1 ) return $res; // ??? + $res[$col] = substr($line, $start, $pos - $start); + $start = $pos + 1; + $col++; + } +// TODO: safety measure for not killing the SPL. To be removed once code is clean +if ($col > $size - 1) return $res; + $res[$col] = rtrim(substr($line, $start), "\r\n"); + return $res; + } + + public function fetchRowFromSocket() + { + $line = $this->readLineFromSocket(); + if (! $line) { + return false; + } + return $this->splitLine($line); + } + + protected function readLineFromSocket() + { + if ($this->bytesRead === $this->responseSize) { + return false; + } + $maxRowLength = 100 * 1024; + $row = socket_read($this->getConnection(), $maxRowLength, PHP_NORMAL_READ); + $this->bytesRead += strlen($row); + + if ($row === false) { + $this->socketError('Failed to read next row from livestatus socket'); + } + return $row; + } + + /** + * Write given string to livestatus socket + * + * @param string $data Data string to write to the socket + * + * @return boolean + */ protected function writeToSocket($data) { - $res = socket_write($this->connection, $data); + $res = @socket_write($this->getConnection(), $data); if ($res === false) { - throw new IcingaException('Writing to livestatus socket failed'); + $this->socketError('Writing to livestatus socket failed'); } return true; } + /** + * Raise an exception showing given message string and last socket error + * + * TODO: Find a better exception type for such errors + * + * @throws IcingaException + */ + protected function socketError($msg) + { + throw new IcingaException( + $msg . ': ' . socket_strerror(socket_last_error($this->connection)) + ); + } + protected function assertPhpExtensionLoaded($name) { if (! extension_loaded($name)) { @@ -221,22 +361,24 @@ class Connection protected function getConnection() { if ($this->connection === null) { + Benchmark::measure('Establishing livestatus connection...'); + if ($this->socket_type === self::TYPE_TCP) { $this->establishTcpConnection(); + Benchmark::measure('...got TCP socket'); } else { $this->establishSocketConnection(); + Benchmark::measure('...got local socket'); } } return $this->connection; } + /** + * Establish a TCP socket connection + */ protected function establishTcpConnection() { - // TODO: find a bedder place for this - if (! defined('TCP_NODELAY')) { - define('TCP_NODELAY', 1); - } - $this->connection = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if (! @socket_connect($this->connection, $this->socket_host, $this->socket_port)) { throw new IcingaException( @@ -249,6 +391,9 @@ class Connection socket_set_option($this->connection, SOL_TCP, TCP_NODELAY, 1); } + /** + * Establish a UNIX socket connection + */ protected function establishSocketConnection() { $this->connection = socket_create(AF_UNIX, SOCK_STREAM, 0); @@ -269,13 +414,26 @@ class Connection return $this; } + /** + * Disconnect in case we are connected to a Livestatus socket + * + * @return self + */ public function disconnect() { - if ($this->connection) { + if (is_resource($this->connection) + && get_resource_type($this->connection) === 'Socket') + { + Benchmark::measure('Disconnecting livestatus...'); socket_close($this->connection); + Benchmark::measure('...socket closed'); } + return $this; } + /** + * Try to cleanly close the socket on shutdown + */ public function __destruct() { $this->disconnect(); diff --git a/library/Icinga/Protocol/Livestatus/Query.php b/library/Icinga/Protocol/Livestatus/Query.php index 7c1e73c8a..a421c7e71 100644 --- a/library/Icinga/Protocol/Livestatus/Query.php +++ b/library/Icinga/Protocol/Livestatus/Query.php @@ -4,135 +4,49 @@ namespace Icinga\Protocol\Livestatus; -use Icinga\Protocol\AbstractQuery; +use Icinga\Data\SimpleQuery; use Icinga\Exception\IcingaException; +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterChain; +use Icinga\Data\Filter\FilterExpression; +use Icinga\Data\Filter\FilterOr; +use Icinga\Data\Filter\FilterAnd; +use Icinga\Data\Filter\FilterNot; +use Exception; -class Query extends AbstractQuery +class Query extends SimpleQuery { + protected $customvars = array(); + + /** + * Columns in this array are always "combined" ones creating their value + * based on a filter expression. The result is always either "1" or "0" + */ + protected $filter_flags = array(); + + /** + * Columns that return arrays. Will be decoded. + */ + protected $arrayColumns = array( + 'members' => true, + ); + + /** + * Columns to be fetched for sorting / filtering, will not be returned + */ + protected $extraFiltercolumns = array(); + + /** + * All available columns. To be overridden by specific query implementations + */ + protected $available_columns = array(); - protected $connection; - protected $table; - protected $filters = array(); - protected $limit_count; - protected $limit_offset; - protected $columns; - protected $order_columns = array(); protected $count = false; - public function __construct(Connection $connection) - { - $this->connection = $connection; - } - - public function getAdapter() - { - return $this->connection; - } - - public function compare(& $a, & $b, $col_num = 0) - { - if (! array_key_exists($col_num, $this->order_columns)) { - return 0; - } - $col = $this->order_columns[$col_num][0]; - $dir = $this->order_columns[$col_num][1]; - - //$res = strnatcmp(strtolower($a->$col), strtolower($b->$col)); - $res = strcmp(strtolower($a->$col), strtolower($b->$col)); - if ($res === 0) { - if (array_key_exists(++$col_num, $this->order_columns)) { - return $this->compare($a, $b, $col_num); - } else { - return 0; - } - } - if ($dir === self::SORT_ASC) { - return $res; - } else { - return $res * -1; - } - } - - public function hasOrder() - { - return ! empty($this->order_columns); - } - - public function where($key, $val = null) - { - $this->filters[$key] = $val; - return $this; - } - - public function order($col) - { - if (($pos = strpos($col, ' ')) === false) { - $col = $col; - $dir = self::SORT_ASC; - } else { - $dir = strtoupper(substr($col, $pos + 1)); - if ($dir === 'DESC') { - $dir = self::SORT_DESC; - } else { - $dir = self::SORT_ASC; - } - $col = substr($col, 0, $pos); - } - $this->order_columns[] = array($col, $dir); - return $this; - } - - // Nur wenn keine stats, sonst im RAM!! - // Offset gibt es nicht, muss simuliert werden - public function limit($count = null, $offset = null) - { - if (! preg_match('~^\d+~', $count . $offset)) { - throw new IcingaException( - 'Got invalid limit: %s, %s', - $count, - $offset - ); - } - $this->limit_count = (int) $count; - $this->limit_offset = (int) $offset; - return $this; - } - - public function hasLimit() - { - return $this->limit_count !== null; - } - - public function hasOffset() - { - return $this->limit_offset > 0; - } - - public function getLimit() - { - return $this->limit_count; - } - - public function getOffset() - { - return $this->limit_offset; - } - - public function from($table, $columns = null) - { - if (! $this->connection->hasTable($table)) { - throw new IcingaException( - 'This livestatus connection does not provide "%s"', - $table - ); - } - $this->table = $table; - if (is_array($columns)) { - // TODO: check for valid names? - $this->columns = $columns; - } - return $this; - } + /** + * Headers for columns sent to Livestatus socket + */ + protected $preparedHeaders = array(); public function hasColumns() { @@ -144,10 +58,133 @@ class Query extends AbstractQuery return $this->columns; } + public function withHeaders(& $row) + { + return array_combine($this->preparedHeaders, $row->toArray()); + } + + /** + * Whether the named columns value is generated by a filter expression + */ + public function isFilterFlag($column) + { + return array_key_exists($column, $this->filter_flags); + } + + // completes a given row + public function resultRow(& $row) + { + // $row -> raw SplArray + // $res -> object + // $cv -> + // $result -> object to be returned + $result = (object) array(); + $res = $this->withHeaders($row); + $cv = array(); + if (array_key_exists('custom_variables', $res)) { + foreach ($this->parseArray($res['custom_variables']) as $cvp) { + $cv[$cvp[0]] = $cvp[1]; + } + } + + $combined = array(); + + foreach ($this->columns as $alias => $col) { + if (is_int($alias)) { + $alias = $col; + } + if ($col[0] === '_') { + $result->$alias = array_key_exists($alias, $cv) ? $cv[$alias] : null; + } else { + $func = 'mungeResult_' . $col; + if (method_exists($this, $func)) { + $this->$func($res[$this->available_columns[$col]], $result); + } elseif (is_array($this->available_columns[$col])) { + $combined[$alias] = $col; + $result->$alias = null; + } else { + if (strpos($this->available_columns[$col], ' ') === false) { + $result->$alias = $res[$this->available_columns[$col]]; + } else { + $result->$alias = $res[$alias]; + } + } + } + } + // TODO: Quite some redundancy here :( + if (! $this->filterIsSupported()) { + foreach ($this->filter->listFilteredColumns() as $col) { + if ($this->isFilterFlag($col)) { + $result->$col = (string) (int) $this->filterStringToFilter( + $this->filter_flags[$col] + )->matches((object) $res); + } else { + $func = 'combineResult_' . $col; + if (method_exists($this, $func)) { + $result->$col = $this->$func($result, $res); + } + } + } + } + + foreach ($combined as $alias => $col) { + if ($this->isFilterFlag($col)) { + $result->$alias = (string) (int) $this->filterStringToFilter( + $this->filter_flags[$col] + )->matches((object) $res); + continue; + } + $func = 'combineResult_' . $col; + if (method_exists($this, $func)) { + $result->$alias = $this->$func($result, $res); + } else { + $result->$alias = implode(' - ', $this->available_columns[$col]); + } + } + + + return $result; + } + + /** + * Parse the given encoded array + * + * @param string $str the encoded array string + * + * @return array + */ + public function parseArray($str) + { + if (empty($str)) { + return array(); + } + + $result = array(); + $entries = preg_split('/,/', $str); + foreach ($entries as $e) { + $result[] = preg_split('/;/', $e); + } + + return $result; + } + public function getColumnAliases() { + $this->columnsToString(); + return $this->preparedHeaders; + + // TODO: Remove once no longer needed: $aliases = array(); + $hasCustom = false; foreach ($this->getColumns() as $key => $val) { + if ($val[0] === '_') { + $this->customvars[$val] = null; + if (! $hasCustom) { + $aliases[] = 'custom_variables'; + $hasCustom = true; + } + continue; + } if (is_int($key)) { $aliases[] = $val; } else { @@ -156,57 +193,277 @@ class Query extends AbstractQuery } return $aliases; } - +/* public function count() { $this->count = true; return $this; } +*/ + /** + * Automagic string casting + * + * @return string + */ public function __toString() + { + try { + return $this->toString(); + } catch (Exception $e) { + trigger_error( + sprintf( + '%s in %s on line %d', + $e->getMessage(), + $e->getFile(), + $e->getLine() + ), + E_USER_ERROR + ); + } + } + + /** + * Render query string + * + * @return string + */ + public function toString() { if ($this->table === null) { throw new IcingaException('Table is required'); } + + // Headers we always send $default_headers = array( - 'OutputFormat: json', + // Our preferred output format is CSV as it allows us to fetch and + // process the result row by row + 'OutputFormat: csv', 'ResponseHeader: fixed16', + // Tried to find a save list of separators, this might be subject to + // change and eventually be transforment into constants + 'Separators: ' . implode(' ', array(ord("\n"), ord('`'), ord(','), ord(';'))), + // We always use the keepalive feature, connection teardown happens + // in the connection destructor 'KeepAlive: on' ); $parts = array( sprintf('GET %s', $this->table) ); - if ($this->count === false && $this->columns !== null) { - $parts[] = 'Columns: ' . implode(' ', $this->columns); - } - foreach ($this->filters as $key => $val) { - if ($key === 'search') { - $parts[] = 'Filter: host_name ~~ ' . $val; - $parts[] = 'Filter: description ~~ ' . $val; - $parts[] = 'Or: 2'; - continue; - } - if ($val === null) { - $parts[] = 'Filter: ' . $key; - } elseif (strpos($key, '?') === false) { - $parts[] = sprintf('Filter: %s = %s', $key, $val); - } else { - $parts[] = sprintf('Filter: %s', str_replace('?', $val, $key)); - } + + // Fetch all required columns + $parts[] = $this->columnsToString(); + + // In case we need to apply a userspace filter as of Livestatus lacking + // support for some of them we also need to fetch all filtered columns + if ($this->filterIsSupported() && $filter = $this->filterToString()) { + $parts[] = $filter; } + + // TODO: Old way of rendering a count query, this should definitively be + // improved if ($this->count === true) { $parts[] = 'Stats: state >= 0'; } + + // TODO: Well... ordering is still missing if (! $this->count && $this->hasLimit() && ! $this->hasOrder()) { - $parts[] = 'Limit: ' . ($this->limit_count + $this->limit_offset); + $parts[] = 'Limit: ' . ($this->getLimit() + $this->getOffset()); } $lql = implode("\n", $parts) . "\n" . implode("\n", $default_headers) . "\n\n"; + return $lql; } + /** + * Get all available columns + * + * @return array + */ + public function getAvailableColumns() + { + return $this->available_columns; + } + + protected function columnsToString() + { + $columns = array(); + $this->preparedHeaders = array(); + + + $usedColumns = $this->columns; + if (! $this->filterIsSupported()) { + foreach ($this->filter->listFilteredColumns() as $col) { + if (! in_array($col, $usedColumns)) { + $usedColumns[] = $col; + } + } + } + foreach ($usedColumns as $col) { +// TODO: No alias if filter??? + if (array_key_exists($col, $this->available_columns)) { + // Alias if such + $col = $this->available_columns[$col]; + } + if ($col[0] === '_') { + $columns['custom_variables'] = true; + } elseif (is_array($col)) { + foreach ($col as $k) { + $columns[$k] = true; + } + } else { + $columns[$col] = true; + } + } + + $this->preparedHeaders = array_keys($columns); + + if ($this->count === false && $this->columns !== null) { + return 'Columns: ' . implode(' ', array_keys($columns)); + } else { + return ''; // TODO: 'Stats: state >= 0'; when count + } + } + + /** + * Whether Livestatus is able to apply the current filter + * + * TODO: find a better method name + * TODO: more granular checks, also render filter-flag columns with lql + * + * @return bool + */ + public function filterIsSupported() + { + foreach ($this->filter->listFilteredColumns() as $column) { + if (is_array($this->available_columns[$column])) { + // Combined column, hardly filterable. Is it? May work! + return false; + } + } + return true; + } + + /** + * Create a Filter object for a given URL-like filter string. We allow + * for spaces as we do not search for custom string values here. This is + * internal voodoo. + * + * @param string $string Filter string + * + * @return Filter + */ + protected function filterStringToFilter($string) + { + return Filter::fromQueryString(str_replace(' ', '', $string)); + } + + /** + * Render the current filter to LQL + * + * @return string + */ + protected function filterToString() + { + return $this->renderFilter($this->filter); + } + + /** + * Filter rendering + * + * Happens recursively, useful for filters and for Stats expressions + * + * @param Filter $filter The filter that should be rendered + * @param string $type Filter type. Usually "Filter" or "Stats" + * @param int $level Nesting level during recursion. Don't touch + * @param bool $keylookup Whether to resolve alias names + * + * @return string + */ + protected function renderFilter(Filter $filter, $type = 'Filter', $level = 0, $keylookup = true) + { + $str = ''; + if ($filter instanceof FilterChain) { + if ($filter instanceof FilterAnd) { + $op = 'And'; + } elseif ($filter instanceof FilterOr) { + $op = 'Or'; + } elseif ($filter instanceof FilterNot) { + $op = 'Negate'; + } else { + throw new IcingaException( + 'Cannot render filter: %s', + $filter + ); + } + $parts = array(); + if (! $filter->isEmpty()) { + foreach ($filter->filters() as $f) { + $parts[] = $this->renderFilter($f, $type, $level + 1, $keylookup); + } + $str .= implode("\n", $parts); + if ($type === 'Filter') { + if (count($parts) > 1) { + $str .= "\n" . $op . ': ' . count($parts); + } + } else { + $str .= "\n" . $type . $op . ': ' . count($parts); + } + } + } else { + $str .= $type . ': ' . $this->renderFilterExpression($filter, $keylookup); + } + + return $str; + } + + /** + * Produce a safe regex string as required by LQL + * + * @param string $expression search expression + * + * @return string + */ + protected function safeRegex($expression) + { + return '^' . preg_replace('/\*/', '.*', $expression) . '$'; + } + + /** + * Render a single filter expression + * + * @param FilterExpression $filter the filter expression + * @param bool $keylookup whether to resolve alias names + * + * @return string + */ + public function renderFilterExpression(FilterExpression $filter, $keylookup = true) + { + if ($keylookup) { + $col = $this->available_columns[$filter->getColumn()]; + } else { + $col = $filter->getColumn(); + } + + $isArray = array_key_exists($col, $this->arrayColumns); + + $sign = $filter->getSign(); + if ($isArray && $sign === '=') { + $sign = '>='; + } + $expression = $filter->getExpression(); + if ($sign === '=' && strpos($expression, '*') !== false) { + return $col . ' ~~ ' . $this->safeRegex($expression); + } elseif ($sign === '!=' && strpos($expression, '*') !== false) { + return $col . ' !~~ ' . $this->safeRegex($expression); + } else { + return $col . ' ' . $sign . ' ' . $expression; + } + } + public function __destruct() { unset($this->connection); diff --git a/library/Icinga/Protocol/Livestatus/ResponseRow.php b/library/Icinga/Protocol/Livestatus/ResponseRow.php new file mode 100644 index 000000000..d4b0fd7cb --- /dev/null +++ b/library/Icinga/Protocol/Livestatus/ResponseRow.php @@ -0,0 +1,18 @@ +raw = $raw; + $this->query = $query; + } +} diff --git a/modules/monitoring/application/controllers/ShowController.php b/modules/monitoring/application/controllers/ShowController.php index dd36154d1..6c295e641 100644 --- a/modules/monitoring/application/controllers/ShowController.php +++ b/modules/monitoring/application/controllers/ShowController.php @@ -227,15 +227,17 @@ class Monitoring_ShowController extends Controller 'urlParams' => $params, ) ); - $tabs->add( - 'history', - array( - 'title' => 'History', - 'icon' => 'rewind', - 'url' => 'monitoring/show/history', - 'urlParams' => $params, - ) - ); + if ($this->backend->hasQuery('eventHistory')) { + $tabs->add( + 'history', + array( + 'title' => 'History', + 'icon' => 'rewind', + 'url' => 'monitoring/show/history', + 'urlParams' => $params, + ) + ); + } $tabs->extend(new OutputFormat()) ->extend(new DashboardAction()); } diff --git a/modules/monitoring/library/Monitoring/Backend/Livestatus/LivestatusBackend.php b/modules/monitoring/library/Monitoring/Backend/Livestatus/LivestatusBackend.php new file mode 100644 index 000000000..12de5a9ee --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Livestatus/LivestatusBackend.php @@ -0,0 +1,9 @@ + '! fixed', + 'downtime_is_in_effect' => 'fixed | ! fixed', // just true + ); + + protected $available_columns = array( + 'downtime_author' => 'author', + 'downtime_comment' => 'comment', + 'downtime_entry_time' => 'entry_time', + 'downtime_is_fixed' => 'fixed', + 'downtime_is_flexible' => array('fixed'), + 'downtime_triggered_by_id' => 'triggered_by', + 'downtime_scheduled_start' => 'start_time', // ?? + 'downtime_scheduled_end' => 'end_time', // ?? + 'downtime_start' => 'start_time', + 'downtime_end' => 'end_time', + 'downtime_duration' => 'duration', + 'downtime_is_in_effect' => array('fixed'), + 'downtime_internal_id' => 'id', + 'downtime_host' => 'host_name', // #7278, #7279 + 'host' => 'host_name', + 'downtime_service' => 'service_description', + 'service' => 'service_description', // #7278, #7279 + 'downtime_objecttype' => array('is_service'), + 'downtime_host_state' => 'host_state', + 'downtime_service_state' => 'service_state' + ); + + public function combineResult_downtime_objecttype(& $row, & $res) + { + return $res['is_service'] ? 'service' : 'host'; + } + +} diff --git a/modules/monitoring/library/Monitoring/Backend/Livestatus/Query/HostgroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Livestatus/Query/HostgroupQuery.php new file mode 100644 index 000000000..0ffc821bb --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Livestatus/Query/HostgroupQuery.php @@ -0,0 +1,20 @@ + 'name', + 'hostgroup_name' => 'name', + 'hostgroup_alias' => 'alias', + 'host' => 'members', + 'host_name' => 'members', + ); +} diff --git a/modules/monitoring/library/Monitoring/Backend/Livestatus/Query/ServicegroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Livestatus/Query/ServicegroupQuery.php new file mode 100644 index 000000000..0587f7cea --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Livestatus/Query/ServicegroupQuery.php @@ -0,0 +1,36 @@ + 'name', + 'servicegroup_alias' => 'alias', + 'host' => array('members'), + 'host_name' => array('members'), + 'service' => array('members'), + 'service_host_name' => array('members'), + 'service_description' => array('members'), + ); + + public function xxcombineResult_service_host_name(& $row, & $res) + { + return; + var_dump($res); + die('Here you go'); + } + + + public function completeRow(& $row) + { + die('FU'); + $row->severity = 12; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Livestatus/Query/StatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Livestatus/Query/StatusQuery.php index 20ffe57e5..5a638c9ca 100644 --- a/modules/monitoring/library/Monitoring/Backend/Livestatus/Query/StatusQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Livestatus/Query/StatusQuery.php @@ -2,28 +2,155 @@ // {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}} -namespace \Icinga\Module\Monitoring\Backend\Livestatus\Query; +namespace Icinga\Module\Monitoring\Backend\Livestatus\Query; -use Icinga\Data\SimpleQuery; +use Icinga\Protocol\Livestatus\Query; -class StatusQuery extends SimpleQuery implements Filterable +class StatusQuery extends Query { - protected $available_columns = array( - 'host_name', - 'host_display_name', - 'host_alias', - 'host_address', - 'host_ipv4' => 'host_address', // TODO - 'host_icon_image', + /** + * This mode represents whether we are in HostStatus or ServiceStatus + * + * Implemented for `distinct as workaround + * + * @TODO Subject to change, see #7344 + * + * @var string + */ + protected $mode; + /** + * Sets the mode of the current query + * + * @TODO Subject to change, see #7344 + * + * @param string $mode + */ + public function setMode($mode) + { + $this->mode = $mode; + } + + protected $table = 'services'; + + protected $filter_flags = array( + 'host_handled' => 'host_state > 0 & (host_acknowledged | host_in_downtime)', + 'host_problem' => 'host_state > 0', + 'service_problem' => 'service_state > 0', + 'service_handled' => 'service_state > 0 & (host_state > 0 | service_acknowledged | service_in_downtime)', + 'service_unhandled' => 'service_state > 0 & host_state = 0 & !service_acknowledged & !service_in_downtime', + ); + + protected $available_columns = array( + 'host' => 'host_name', + 'host_name' => 'host_name', + 'host_display_name' => 'host_display_name', + 'host_alias' => 'host_alias', + 'host_address' => 'host_address', + 'host_ipv4' => 'host_address', // TODO + 'host_icon_image' => 'host_icon_image', +'host_contacts' => 'host_contacts', + 'host_problem' => array('host_state'), + 'host_handled' => array('host_state', 'host_acknowledged', 'host_scheduled_downtime_depth'), + 'service_problem' => array('state', 'acknowledged', 'scheduled_downtime_depth'), + 'service_handled' => array('host_state', 'state', 'acknowledged', 'scheduled_downtime_depth'), + 'service_unhandled' => array('host_state', 'state', 'acknowledged', 'scheduled_downtime_depth'), + + +// 'host_unhandled_services' => 'services_with_state', // Needs handler +// 'host_unhandled_services' => 'host_services_with_state', -> bringt nix, ist [service, state, has_been_checked] +'host_unhandled_services' => 'state', // Needs handler + +'host_severity' => array('host_state', 'host_acknowledged', 'host_scheduled_downtime_depth'), +'service_severity' => array('host_state', 'state', 'acknowledged', 'scheduled_downtime_depth'), + + + + + +// TODO: Make these 1 if > 1 +'host_in_downtime' => 'host_scheduled_downtime_depth', +'service_in_downtime' => 'scheduled_downtime_depth', + + +'host_check_latency' => 'host_latency', +'host_check_execution_time' => 'host_execution_time', + +'host_long_output' => 'host_long_plugin_output', + + +'host_passive_checks_enabled_changed' => 'state', +'host_obsessing' => 'state', +'host_obsessing_changed' => 'state', +'host_notifications_enabled_changed' => 'state', +'host_event_handler_enabled_changed' => 'state', +'host_flap_detection_enabled_changed' => 'state', +'host_active_checks_enabled_changed' => 'state', + +// TODO: Do we need two of them? +'host_current_check_attempt' => 'host_current_attempt', +'host_attempt' => 'host_current_attempt', + +'host_modified_host_attributes' => 'host_modified_attributes', + +'service_modified_service_attributes' => 'modified_attributes', + +'service_notifications_enabled_changed' => 'modified_attributes_list', +'service_active_checks_enabled_changed' => 'modified_attributes_list', +'service_passive_checks_enabled_changed' => 'modified_attributes_list', +'service_flap_detection_enabled_changed' => 'modified_attributes_list', +'service_event_handler_enabled_changed' => 'modified_attributes_list', + +'service_check_execution_time' => 'execution_time', +'service_check_latency' => 'latency', +'service_obsessing' => 'state', +'service_obsessing_changed' => 'state', + +'service_hard_state' => 'state', + +'service_attempt' => 'current_attempt', +'service_current_check_attempt' => 'current_attempt', + +'host' => 'host_name', +'service_host_name' => 'host_name', +'service' => 'description', +'service_is_flapping' => 'is_flapping', +'service_long_output' => 'long_plugin_output', + +'service_icon_image' => 'icon_image', +'service_action_url' => 'action_url', +'service_notes_url' => 'notes_url', +'host_max_check_attempts' => 'host_max_check_attempts', +'service_max_check_attempts' => 'max_check_attempts', + + // Host comments + 'host_last_comment' => 'comments_with_info', + 'host_last_ack' => 'comments_with_info', + 'host_last_downtime' => 'comments_with_info', +'host_check_command' => 'host_check_command', // Host state - 'host_state', + 'host_state' => 'host_state', + 'host_state_type' => 'host_state_type', 'host_output' => 'host_plugin_output', 'host_perfdata' => 'host_perf_data', - 'host_acknowledged', - 'host_does_active_checks' => 'host_active_checks_enabled', - 'host_accepts_passive_checks' => 'host_accept_passive_checks', - 'host_last_state_change', + 'host_acknowledged' => 'host_acknowledged', + 'host_active_checks_enabled' => 'host_active_checks_enabled', + 'host_passive_checks_enabled' => 'host_accept_passive_checks', + 'host_last_state_change' => 'host_last_state_change', + +'host_event_handler_enabled' => 'host_event_handler_enabled', +'host_flap_detection_enabled' => 'host_flap_detection_enabled', +'host_current_notification_number' => 'host_current_notification_number', +'host_percent_state_change' => 'host_percent_state_change', +'host_process_performance_data' => 'host_process_performance_data', +'host_event_handler_enabled' => 'host_event_handler_enabled', +'host_flap_detection_enabled' => 'host_flap_detection_enabled', + +'service_percent_state_change' => 'percent_state_change', + +'host_last_notification' => 'host_last_notification', +'host_next_check' => 'host_next_check', +'host_check_source' => 'state', // Service config 'service_description' => 'description', @@ -32,19 +159,102 @@ class StatusQuery extends SimpleQuery implements Filterable // Service state 'service_state' => 'state', 'service_output' => 'plugin_output', + + + 'service_state_type' => 'state_type', + 'service_perfdata' => 'perf_data', 'service_acknowledged' => 'acknowledged', - 'service_does_active_checks' => 'active_checks_enabled', - 'service_accepts_passive_checks' => 'accept_passive_checks', + 'service_active_checks_enabled' => 'active_checks_enabled', + 'service_passive_checks_enabled' => 'accept_passive_checks', + 'service_last_check' => 'last_check', 'service_last_state_change' => 'last_state_change', + 'service_notifications_enabled' => 'notifications_enabled', + 'service_last_notification' => 'last_notification', +'service_next_check' => 'next_check', +'service_last_time_unknown' => 'last_time_unknown', +'service_event_handler_enabled' => 'event_handler_enabled', // Service comments - 'comments_with_info', - 'downtimes_with_info', + 'service_last_comment' => 'comments_with_info', + 'service_last_ack' => 'comments_with_info', + 'service_last_downtime' => 'comments_with_info', + 'downtimes_with_info' => 'downtimes_with_info', +'service_check_command' => 'check_command', +'service_check_source' => 'state', +'service_current_notification_number' => 'current_notification_number', +'host_is_flapping' => 'host_is_flapping', +'host_last_check' => 'host_last_check', +'host_notifications_enabled' => 'host_notifications_enabled', +'host_action_url' => 'host_action_url', +'host_notes_url' => 'host_notes_url', +'host_last_hard_state' => 'host_last_hard_state', +'host_last_hard_state_change' => 'host_last_hard_state_change', +'host_last_time_up' => 'host_last_time_up', +'host_last_time_down' => 'host_last_time_down', +'host_last_time_unreachable' => 'host_last_time_unreachable', +'service_last_hard_state' => 'last_hard_state', +'service_last_hard_state_change' => 'last_hard_state_change', +'service_last_time_ok' => 'last_time_ok', +'service_last_time_warning' => 'last_time_warning', +'service_last_time_critical' => 'last_time_critical', +'service_flap_detection_enabled' => 'flap_detection_enabled', +'service_process_performance_data' => 'process_performance_data', ); - protected function createQuery() + public function mungeResult_custom_variables($val, & $row) { - return $this->connection->getConnection()->select()->from('services', $this->available_columns); + $notseen = $this->customvars; + foreach ($val as $cv) { + $name = '_service_' . $cv[0]; + $row->$name = $cv[1]; + unset($notseen[$name]); + } + foreach ($notseen as $k => $v) { + $row->$k = $v; + } + } + + public function mungeResult_service_last_comment($val, & $row) + { + $this->mungeResult_comments_with_info($val, $row); + } + + public function mungeResult_service_last_ack($val, & $row) + { + $this->mungeResult_comments_with_info($val, $row); + } + + public function mungeResult_service_last_downtime($val, & $row) + { + $this->mungeResult_comments_with_info($val, $row); + } + + public function mungeResult_comments_with_info($val, & $row) + { + if (empty($val)) { + $row->service_last_comment = $row->service_last_ack + = $row->service_last_downtime = null; + } else { + $row->service_last_comment = $row->service_last_ack + = $row->service_last_downtime = preg_replace('/\n/', ' ', print_r($val, 1)); + } + } + + public function mungeResult_host_unhandled_services($val, & $row) + { + $cnt = 0; + foreach ($this->parseArray($val) as $service) { + if (! isset($service[1])) { + continue; + // TODO: More research is required here, on Icinga2 I got + // array(1) { [0]=> array(1) { [0]=> string(1) "2" } } + var_dump($this->parseArray($val)); + } + if ($service[1] > 0) { + $cnt++; + } + } + $row->host_unhandled_services = $cnt; } } diff --git a/modules/monitoring/library/Monitoring/Backend/Livestatus/Query/StatusSummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Livestatus/Query/StatusSummaryQuery.php new file mode 100644 index 000000000..f431a566e --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Livestatus/Query/StatusSummaryQuery.php @@ -0,0 +1,65 @@ + 'host_name', + + 'services_total' => 'state != 9999', + 'services_problem' => 'state > 0', + 'services_problem_handled' => 'state > 0 & (scheduled_downtime_depth > 0 | acknowledged = 1 | host_state > 0)', + 'services_problem_unhandled' => 'state > 0 & scheduled_downtime_depth = 0 & acknowledged = 0 & host_state = 0', + 'services_ok' => 'state = 0', + 'services_ok_not_checked' => 'state = 0 & accept_passive_checks = 0 & active_checks_enabled = 0', + 'services_pending' => 'has_been_checked = 0', + 'services_pending_not_checked' => 'has_been_checked = 0 & accept_passive_checks = 0 & active_checks_enabled = 0', + 'services_warning' => 'state = 1', + 'services_warning_handled' => 'state = 1 & (scheduled_downtime_depth > 0 | acknowledged = 1 | host_state > 0)', + 'services_warning_unhandled' => 'state = 1 & scheduled_downtime_depth = 0 & acknowledged = 0 & host_state = 0', + 'services_warning_passive' => 'state = 1 & accept_passive_checks = 1 & active_checks_enabled = 0', + 'services_warning_not_checked' => 'state = 1 & accept_passive_checks = 0 & active_checks_enabled = 0', + 'services_critical' => 'state = 2', + 'services_critical_handled' => 'state = 2 & (scheduled_downtime_depth > 0 | acknowledged = 1 | host_state > 0)', + 'services_critical_unhandled' => 'state = 2 & scheduled_downtime_depth = 0 & acknowledged = 0 & host_state = 0', + 'services_critical_passive' => 'state = 2 & accept_passive_checks = 1 & active_checks_enabled = 0', + 'services_critical_not_checked' => 'state = 2 & accept_passive_checks = 0 & active_checks_enabled = 0', + 'services_unknown' => 'state = 3', + 'services_unknown_handled' => 'state = 3 & (scheduled_downtime_depth > 0 | acknowledged = 1 | host_state > 0)', + 'services_unknown_unhandled' => 'state = 3 & scheduled_downtime_depth = 0 & acknowledged = 0 & host_state = 0', + 'services_unknown_passive' => 'state = 3 & accept_passive_checks = 1 & active_checks_enabled = 0', + 'services_unknown_not_checked' => 'state = 3 & accept_passive_checks = 0 & active_checks_enabled = 0', + 'services_active' => 'active_checks_enabled = 1', + 'services_passive' => 'accept_passive_checks = 1 & active_checks_enabled = 0', + 'services_not_checked' => 'active_checks_enabled = 0 & accept_passive_checks = 0', + ); + + protected function columnsToString() + { + $parts = array(); + foreach ($this->columns as $col) { +if (! array_key_exists($col, $this->available_columns)) { + throw new ProgrammingError('No such column: %s', $col); +} + $filter = $this->filterStringToFilter($this->available_columns[$col]); + + //Filter::fromQueryString(str_replace(' ', '', $this->available_columns[$col])); + $parts[] = $this->renderFilter( $filter, 'Stats', 0, false); + } + $this->preparedHeaders = $this->columns; + return implode("\n", $parts); + } + + protected function renderkkFilter($filter, $type = 'Filter', $level = 0, $keylookup = true) + { + return parent::renderFilter($filter, 'Stats', $level, $keylookup); + } +} diff --git a/modules/monitoring/library/Monitoring/Object/Host.php b/modules/monitoring/library/Monitoring/Object/Host.php index 831579a57..fd08f6a20 100644 --- a/modules/monitoring/library/Monitoring/Object/Host.php +++ b/modules/monitoring/library/Monitoring/Object/Host.php @@ -89,7 +89,7 @@ class Host extends MonitoredObject */ protected function getDataView() { - return $this->backend->select()->from('hostStatus', array( + $columns = array( 'host_name', 'host_alias', 'host_address', @@ -132,7 +132,11 @@ class Host extends MonitoredObject 'host_problem', 'host_process_performance_data', 'process_perfdata' => 'host_process_performance_data' - )) + ); + if ($this->backend->getType() === 'livestatus') { + $columns[] = 'host_contacts'; + } + return $this->backend->select()->from('hostStatus', $columns) ->where('host_name', $this->host); } diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php index 8e88b861b..fefcba929 100644 --- a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -141,7 +141,21 @@ abstract class MonitoredObject public function fetch() { $this->properties = $this->getDataView()->getQuery()->fetchRow(); - return $this->properties !== false; + if ($this->properties === false) { + return false; + } + if (isset($this->properties->host_contacts)) { + $this->contacts = array(); + foreach (preg_split('~,~', $this->properties->host_contacts) as $contact) { + $this->contacts[] = (object) array( + 'contact_name' => $contact, + 'contact_alias' => $contact, + 'contact_email' => null, + 'contact_pager' => null, + ); + } + } + return true; } /** @@ -190,6 +204,10 @@ abstract class MonitoredObject */ public function fetchComments() { + if ($this->backend->is('livestatus')) { + $this->comments = array(); + return $this; + } $comments = $this->backend->select()->from('comment', array( 'id' => 'comment_internal_id', 'timestamp' => 'comment_timestamp', @@ -264,6 +282,11 @@ abstract class MonitoredObject */ public function fetchCustomvars() { + if ($this->backend->is('livestatus')) { + $this->customvars = array(); + return $this; + } + $blacklist = array(); $blacklistPattern = '/^(.*pw.*|.*pass.*|community)$/i'; @@ -316,6 +339,11 @@ abstract class MonitoredObject */ public function fetchContacts() { + if ($this->backend->is('livestatus')) { + $this->contacts = array(); + return $this; + } + $contacts = $this->backend->select()->from('contact', array( 'contact_name', 'contact_alias', @@ -356,6 +384,11 @@ abstract class MonitoredObject */ public function fetchContactgroups() { + if ($this->backend->is('livestatus')) { + $this->contactgroups = array(); + return $this; + } + $contactsGroups = $this->backend->select()->from('contactgroup', array( 'contactgroup_name', 'contactgroup_alias' diff --git a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php index 4b4dd6e86..c6b252d1c 100644 --- a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php +++ b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php @@ -212,15 +212,17 @@ abstract class MonitoredObjectController extends Controller 'urlParams' => $params ) ); - $tabs->add( - 'history', - array( - 'title' => 'History', - 'icon' => 'rewind', - 'url' => 'monitoring/show/history', - 'urlParams' => $params - ) - ); + if ($this->backend->hasQuery('eventHistory')) { + $tabs->add( + 'history', + array( + 'title' => 'History', + 'icon' => 'rewind', + 'url' => 'monitoring/show/history', + 'urlParams' => $params + ) + ); + } $tabs ->extend(new OutputFormat()) ->extend(new DashboardAction());