288 lines
8.8 KiB
PHP
Executable File
288 lines
8.8 KiB
PHP
Executable File
<?php
|
|
|
|
namespace Icinga\Protocol\Livestatus;
|
|
|
|
use Icinga\Application\Benchmark;
|
|
use Exception;
|
|
|
|
/**
|
|
* Backend class managing handling MKI Livestatus connections
|
|
*
|
|
* Usage example:
|
|
*
|
|
* <code>
|
|
* $lconf = new Connection((object) array(
|
|
* 'hostname' => 'localhost',
|
|
* 'root_dn' => 'dc=monitoring,dc=...',
|
|
* 'bind_dn' => 'cn=Mangager,dc=monitoring,dc=...',
|
|
* 'bind_pw' => '***'
|
|
* ));
|
|
* </code>
|
|
*
|
|
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
|
|
* @author Icinga-Web Team <info@icinga.org>
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
|
|
*/
|
|
class Connection
|
|
{
|
|
const TYPE_UNIX = 1;
|
|
const TYPE_TCP = 2;
|
|
|
|
protected $available_tables = array(
|
|
'hosts', // hosts
|
|
'services', // services, joined with all data from hosts
|
|
'hostgroups', // hostgroups
|
|
'servicegroups', // servicegroups
|
|
'contactgroups', // contact groups
|
|
'servicesbygroup', // all services grouped by service groups
|
|
'servicesbyhostgroup', // all services grouped by host groups
|
|
'hostsbygroup', // all hosts grouped by host groups
|
|
'contacts', // contacts
|
|
'commands', // defined commands
|
|
'timeperiods', // time period definitions (currently only name
|
|
// and alias)
|
|
'downtimes', // all scheduled host and service downtimes,
|
|
// joined with data from hosts and services.
|
|
'comments', // all host and service comments
|
|
'log', // a transparent access to the nagios logfiles
|
|
// (include archived ones)ones
|
|
'status', // general performance and status information.
|
|
// This table contains exactly one dataset.
|
|
'columns', // a complete list of all tables and columns
|
|
// available via Livestatus, including
|
|
// descriptions!
|
|
'statehist', // 1.2.1i2 sla statistics for hosts and services,
|
|
// joined with data from hosts, services and log.
|
|
);
|
|
|
|
protected $socket_path;
|
|
protected $socket_host;
|
|
protected $socket_port;
|
|
protected $socket_type;
|
|
protected $connection;
|
|
|
|
public function hasTable($name)
|
|
{
|
|
return in_array($name, $this->available_tables);
|
|
}
|
|
|
|
public function __construct($socket = '/var/lib/icinga/rw/live')
|
|
{
|
|
$this->assertPhpExtensionLoaded('sockets');
|
|
if ($socket[0] === '/') {
|
|
if (! is_writable($socket)) {
|
|
throw new \Exception(
|
|
sprintf(
|
|
'Cannot write to livestatus socket "%s"',
|
|
$socket
|
|
)
|
|
);
|
|
}
|
|
$this->socket_type = self::TYPE_UNIX;
|
|
$this->socket_path = $socket;
|
|
} else {
|
|
if (! preg_match('~^tcp://([^:]+):(\d+)~', $socket, $m)) {
|
|
throw new \Exception(
|
|
sprintf(
|
|
'Invalid TCP socket syntax: "%s"',
|
|
$socket
|
|
)
|
|
);
|
|
}
|
|
// TODO: Better 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;
|
|
}
|
|
|
|
public function count(Query $query)
|
|
{
|
|
$count = clone($query);
|
|
$count->count();
|
|
Benchmark::measure('Sending Livestatus Count Query');
|
|
$data = $this->doFetch((string) $count);
|
|
Benchmark::measure('Got Livestatus count result');
|
|
return $data[0][0];
|
|
}
|
|
|
|
public function fetchAll(Query $query)
|
|
{
|
|
Benchmark::measure('Sending Livestatus Query');
|
|
$data = $this->doFetch((string) $query);
|
|
Benchmark::measure('Got Livestatus Data');
|
|
if ($query->hasColumns()) {
|
|
$headers = $query->getColumnAliases();
|
|
} else {
|
|
$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;
|
|
}
|
|
}
|
|
if ($query->hasOrder()) {
|
|
usort($result, array($query, 'compare'));
|
|
}
|
|
if ($query->hasLimit()) {
|
|
$result = array_slice(
|
|
$result,
|
|
$query->getOffset(),
|
|
$query->getLimit()
|
|
);
|
|
}
|
|
Benchmark::measure('Data sorted, limits applied');
|
|
|
|
return $result;
|
|
}
|
|
|
|
protected function doFetch($raw_query)
|
|
{
|
|
$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 Exception(
|
|
sprintf(
|
|
'Problem while reading %d bytes from livestatus: %s',
|
|
$length,
|
|
$body
|
|
)
|
|
);
|
|
}
|
|
$result = json_decode($body);
|
|
if ($result === null) {
|
|
throw new Exception('Got invalid response body from livestatus');
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
protected function readFromSocket($length)
|
|
{
|
|
$offset = 0;
|
|
$buffer = '';
|
|
|
|
while ($offset < $length) {
|
|
$data = socket_read($this->connection, $length - $offset);
|
|
if ($data === false) {
|
|
throw new Exception(
|
|
sprintf(
|
|
'Failed to read from livestatus socket: %s',
|
|
socket_strerror(socket_last_error($this->connection))
|
|
)
|
|
);
|
|
}
|
|
$size = strlen($data);
|
|
$offset += $size;
|
|
$buffer .= $data;
|
|
|
|
if ($size === 0) {
|
|
break;
|
|
}
|
|
}
|
|
if ($offset !== $length) {
|
|
throw new \Exception(
|
|
sprintf(
|
|
'Got only %d instead of %d bytes from livestatus socket',
|
|
$offset,
|
|
$length
|
|
)
|
|
);
|
|
}
|
|
|
|
return $buffer;
|
|
}
|
|
|
|
protected function writeToSocket($data)
|
|
{
|
|
$res = socket_write($this->connection, $data);
|
|
if ($res === false) {
|
|
throw new \Exception('Writing to livestatus socket failed');
|
|
}
|
|
return true;
|
|
}
|
|
|
|
protected function assertPhpExtensionLoaded($name)
|
|
{
|
|
if (! extension_loaded($name)) {
|
|
throw new \Exception(
|
|
sprintf(
|
|
'The extension "%s" is not loaded',
|
|
$name
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
protected function getConnection()
|
|
{
|
|
if ($this->connection === null) {
|
|
if ($this->socket_type === self::TYPE_TCP) {
|
|
$this->establishTcpConnection();
|
|
} else {
|
|
$this->establishSocketConnection();
|
|
}
|
|
}
|
|
return $this->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 \Exception(
|
|
sprintf(
|
|
'Cannot connect to livestatus TCP socket "%s:%d": %s',
|
|
$this->socket_host,
|
|
$this->socket_port,
|
|
socket_strerror(socket_last_error($this->connection))
|
|
)
|
|
);
|
|
}
|
|
socket_set_option($this->connection, SOL_TCP, TCP_NODELAY, 1);
|
|
}
|
|
|
|
protected function establishSocketConnection()
|
|
{
|
|
$this->connection = socket_create(AF_UNIX, SOCK_STREAM, 0);
|
|
if (! socket_connect($this->connection, $this->socket_path)) {
|
|
throw new \Exception(
|
|
sprintf(
|
|
'Cannot connect to livestatus local socket "%s"',
|
|
$this->socket_path
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
public function disconnect()
|
|
{
|
|
if ($this->connection) {
|
|
socket_close($this->connection);
|
|
}
|
|
}
|
|
|
|
public function __destruct()
|
|
{
|
|
$this->disconnect();
|
|
}
|
|
}
|