icingaweb2-module-director/library/Director/Db.php

760 lines
21 KiB
PHP

<?php
namespace Icinga\Module\Director;
use DateTime;
use DateTimeZone;
use Exception;
use Icinga\Data\ResourceFactory;
use Icinga\Exception\ConfigurationError;
use Icinga\Module\Director\Data\Db\DbConnection;
use Icinga\Module\Director\Objects\IcingaEndpoint;
use Icinga\Module\Director\Objects\IcingaObject;
use RuntimeException;
use Zend_Db_Select;
class Db extends DbConnection
{
protected $modules = array();
protected static $zoneCache;
protected static $commandCache;
protected $settings;
protected $masterZoneName;
protected function db()
{
return $this->getDbAdapter();
}
/**
* @param $callable
* @return $this
* @throws Exception
*/
public function runFailSafeTransaction($callable)
{
if (! is_callable($callable)) {
throw new RuntimeException(__METHOD__ . ' needs a Callable');
}
$db = $this->db();
$db->beginTransaction();
try {
$callable();
$db->commit();
} catch (Exception $e) {
try {
$db->rollback();
} catch (Exception $e) {
// Well... there is nothing we can do here.
}
throw $e;
}
return $this;
}
public static function fromResourceName($name)
{
$connection = new static(ResourceFactory::getResourceConfig($name));
if ($connection->isMysql()) {
$connection->setClientTimezoneForMysql();
} elseif ($connection->isPgsql()) {
$connection->setClientTimezoneForPgsql();
}
return $connection;
}
protected function getTimezoneOffset()
{
$tz = new DateTimeZone(date_default_timezone_get());
$offset = $tz->getOffset(new DateTime());
$prefix = $offset >= 0 ? '+' : '-';
$offset = abs($offset);
$hours = (int) floor($offset / 3600);
$minutes = (int) floor(($offset % 3600) / 60);
return sprintf('%s%d:%02d', $prefix, $hours, $minutes);
}
protected function setClientTimezoneForMysql()
{
$db = $this->getDbAdapter();
$db->query($db->quoteInto('SET time_zone = ?', $this->getTimezoneOffset()));
}
protected function setClientTimezoneForPgsql()
{
$db = $this->getDbAdapter();
$db->query($db->quoteInto('SET TIME ZONE INTERVAL ? HOUR TO MINUTE', $this->getTimezoneOffset()));
}
public function countActivitiesSinceLastDeployedConfig(IcingaObject $object = null)
{
$db = $this->db();
$query = 'SELECT COUNT(*) FROM director_activity_log WHERE id > COALESCE(('
. ' SELECT id FROM director_activity_log WHERE checksum = ('
. ' SELECT last_activity_checksum FROM director_generated_config WHERE checksum = ('
. ' SELECT config_checksum FROM director_deployment_log ORDER by id desc limit 1'
. ' )'
. ' )'
. '), 0)';
if ($object !== null) {
$query .= $db->quoteInto(' AND object_type = ?', $object->getTableName());
$query .= $db->quoteInto(' AND object_name = ?', $object->getObjectName());
}
return (int) $db->fetchOne($query);
}
// TODO: use running config?!
public function getLastDeploymentActivityLogId()
{
$db = $this->db();
$query = ' SELECT COALESCE(id, 0) AS id FROM director_activity_log WHERE checksum = ('
. ' SELECT last_activity_checksum FROM director_generated_config WHERE checksum = ('
. ' SELECT config_checksum FROM director_deployment_log ORDER by id desc limit 1'
. ' )'
. ')';
return (int) $db->fetchOne($query);
}
public function settings()
{
if ($this->settings === null) {
$this->settings = new Settings($this);
}
return $this->settings;
}
public function getMasterZoneName()
{
if ($this->masterZoneName === null) {
$this->masterZoneName = $this->detectMasterZoneName();
}
return $this->masterZoneName;
}
protected function detectMasterZoneName()
{
if ($zone = $this->settings()->master_zone) {
return $zone;
}
$db = $this->db();
$query = $db->select()
->from('icinga_zone', 'object_name')
->where('parent_id IS NULL')
->where('is_global = ?', 'n');
$zones = $db->fetchCol($query);
if (count($zones) === 1) {
return $zones[0];
}
return 'master';
}
public function getDefaultGlobalZoneName()
{
return $this->settings()->default_global_zone;
}
public function hasDeploymentEndpoint()
{
$db = $this->db();
$query = $db->select()->from(
array('z' => 'icinga_zone'),
array('cnt' => 'COUNT(*)')
)->join(
array('e' => 'icinga_endpoint'),
'e.zone_id = z.id',
array()
)->join(
array('au' => 'icinga_apiuser'),
'e.apiuser_id = au.id',
array()
)->where('z.object_name = ?', $this->getMasterZoneName());
return $db->fetchOne($query) > 0;
}
public function getEndpointNamesInDeploymentZone()
{
$db = $this->db();
$query = $db->select()->from(
array('z' => 'icinga_zone'),
array('object_name' => 'e.object_name')
)->join(
array('e' => 'icinga_endpoint'),
'e.zone_id = z.id',
array()
)->join(
array('au' => 'icinga_apiuser'),
'e.apiuser_id = au.id',
array()
)->where('z.object_name = ?', $this->getMasterZoneName())
->order('e.object_name ASC');
return $db->fetchCol($query) ?: [];
}
public function getDeploymentEndpointName()
{
$db = $this->db();
$query = $db->select()->from(
array('z' => 'icinga_zone'),
array('object_name' => 'e.object_name')
)->join(
array('e' => 'icinga_endpoint'),
'e.zone_id = z.id',
array()
)->join(
array('au' => 'icinga_apiuser'),
'e.apiuser_id = au.id',
array()
)->where('z.object_name = ?', $this->getMasterZoneName())
->order('e.object_name ASC')
->limit(1);
$name = $db->fetchOne($query);
if (! $name) {
throw new ConfigurationError(
'Unable to detect your deployment endpoint. I was looking for'
. ' the first endpoint configured with an assigned API user'
. ' in the "%s" zone.',
$this->getMasterZoneName()
);
}
return $name;
}
/**
* @return IcingaEndpoint
*/
public function getDeploymentEndpoint()
{
return IcingaEndpoint::load($this->getDeploymentEndpointName(), $this);
}
public function getActivitylogNeighbors($id, $type = null, $name = null)
{
$db = $this->db();
$greater = $db->select()->from(
array('g' => 'director_activity_log'),
array('id' => 'MIN(g.id)')
)->where('id > ?', (int) $id);
$smaller = $db->select()->from(
array('l' => 'director_activity_log'),
array('id' => 'MAX(l.id)')
)->where('id < ?', (int) $id);
if ($type !== null) {
$greater->where('object_type = ?', $type);
$smaller->where('object_type = ?', $type);
}
if ($name !== null) {
$greater->where('object_name = ?', $name);
$smaller->where('object_name = ?', $name);
}
$query = $db->select()->from(
array('gt' => $greater),
array(
'prev' => 'lt.id',
'next' => 'gt.id'
)
)->join(
array('lt' => $smaller),
'1 = 1',
array()
);
return $db->fetchRow($query);
}
public function fetchActivityLogEntryById($id)
{
$sql = 'SELECT id, object_type, object_name, action_name,'
. ' old_properties, new_properties, author, change_time,'
. ' UNIX_TIMESTAMP(change_time) AS change_time_ts,'
. ' %s AS checksum, %s AS parent_checksum'
. ' FROM director_activity_log WHERE id = %d';
$sql = sprintf(
$sql,
$this->dbHexFunc('checksum'),
$this->dbHexFunc('parent_checksum'),
$id
);
return $this->db()->fetchRow($sql);
}
public function fetchActivityLogChecksumById($id, $binary = true)
{
$sql = sprintf(
'SELECT' . ' %s AS checksum FROM director_activity_log WHERE id = %d',
$this->dbHexFunc('checksum'),
(int) $id
);
$result = $this->db()->fetchOne($sql);
if ($binary) {
return hex2bin($result);
} else {
return $result;
}
}
public function fetchActivityLogIdByChecksum($checksum)
{
$sql = 'SELECT id FROM director_activity_log WHERE checksum = ?';
return $this->db()->fetchOne(
$this->db()->quoteInto($sql, $this->quoteBinary($checksum))
);
}
public function fetchActivityLogEntry($checksum)
{
$db = $this->db();
$sql = 'SELECT id, object_type, object_name, action_name,'
. ' old_properties, new_properties, author, change_time,'
. ' UNIX_TIMESTAMP(change_time) AS change_time_ts,'
. ' %s AS checksum, %s AS parent_checksum'
. ' FROM director_activity_log WHERE checksum = ?';
$sql = sprintf(
$sql,
$this->dbHexFunc('checksum'),
$this->dbHexFunc('parent_checksum')
);
return $db->fetchRow(
$db->quoteInto($sql, $this->quoteBinary(hex2bin($checksum)))
);
}
public function getLastActivityChecksum()
{
$select = "SELECT checksum FROM (SELECT * FROM (SELECT 1 AS pos, "
. $this->dbHexFunc('checksum')
. " AS checksum"
. " FROM director_activity_log ORDER BY id DESC LIMIT 1) a"
. " UNION SELECT 2 AS pos, '' AS checksum) u ORDER BY pos LIMIT 1";
return $this->db()->fetchOne($select);
}
public function fetchImportStatistics()
{
$query = "SELECT 'imported_properties' AS stat_name, COUNT(*) AS stat_value"
. " FROM import_run i"
. " JOIN imported_rowset_row rs ON i.rowset_checksum = rs.rowset_checksum"
. " JOIN imported_row_property rp ON rp.row_checksum = rs.row_checksum"
. " UNION ALL"
. " SELECT 'imported_rows' AS stat_name, COUNT(*) AS stat_value"
. " FROM import_run i"
. " JOIN imported_rowset_row rs ON i.rowset_checksum = rs.rowset_checksum"
. " UNION ALL"
. " SELECT 'unique_rows' AS stat_name, COUNT(*) AS stat_value"
. " FROM imported_row"
. " UNION ALL"
. " SELECT 'unique_properties' AS stat_name, COUNT(*) AS stat_value"
. " FROM imported_property"
;
return $this->db()->fetchPairs($query);
}
public function getImportrunRowsetChecksum($id)
{
$db = $this->db();
$query = $db->select()
->from(array('r' => 'import_run'), $this->dbHexFunc('r.rowset_checksum'))
->where('r.id = ?', $id);
return $db->fetchOne($query);
}
protected function fetchTemplateRelations($type)
{
$db = $this->db();
$query = $db->select()->from(
array('p' => 'icinga_' . $type),
array(
'name' => 'o.object_name',
'parent' => 'p.object_name'
)
)->join(
array('i' => 'icinga_' . $type . '_inheritance'),
'p.id = i.parent_' . $type . '_id',
array()
)->join(
array('o' => 'icinga_' . $type),
'o.id = i.' . $type . '_id',
array()
)->where("o.object_type = 'template'")
->order('p.object_name')
->order('o.object_name');
return $db->fetchAll($query);
}
public function fetchTemplateTree($type)
{
$relations = $this->fetchTemplateRelations($type);
$children = array();
$objects = array();
foreach ($relations as $rel) {
foreach (array('name', 'parent') as $col) {
if (! array_key_exists($rel->$col, $objects)) {
$objects[$rel->$col] = (object) array(
'name' => $rel->$col,
'children' => array()
);
}
}
}
foreach ($relations as $rel) {
$objects[$rel->parent]->children[$rel->name] = $objects[$rel->name];
$children[$rel->name] = $rel->parent;
}
foreach ($children as $name => $object) {
unset($objects[$name]);
}
return $objects;
}
public function getLatestImportedChecksum($source)
{
$db = $this->db();
$lastRun = $db->select()->from(
array('r' => 'import_run'),
array('last_checksum' => $this->dbHexFunc('r.rowset_checksum'))
);
if (is_int($source) || ctype_digit($source)) {
$lastRun->where('source_id = ?', (int) $source);
} else {
$lastRun->where('source_name = ?', $source);
}
$lastRun->order('start_time DESC')->limit(1);
return $db->fetchOne($lastRun);
}
public function getObjectSummary()
{
$types = array(
'host',
'hostgroup',
'service',
'servicegroup',
'user',
'usergroup',
'command',
'timeperiod',
'scheduled_downtime',
'notification',
'apiuser',
'endpoint',
'zone',
'dependency',
);
$queries = array();
$db = $this->db();
$cnt = "COALESCE(SUM(CASE WHEN o.object_type = '%s' THEN 1 ELSE 0 END), 0)";
foreach ($types as $type) {
$queries[] = $db->select()->from(
array('o' => 'icinga_' . $type),
array(
'icinga_type' => "('" . $type . "')",
'cnt_object' => sprintf($cnt, 'object'),
'cnt_template' => sprintf($cnt, 'template'),
'cnt_external' => sprintf($cnt, 'external_object'),
'cnt_total' => 'COUNT(*)',
)
);
}
$query = $this->db()->select()->union($queries, Zend_Db_Select::SQL_UNION_ALL);
$result = array();
foreach ($db->fetchAll($query) as $row) {
$result[$row->icinga_type] = $row;
}
return $result;
}
public function enumCommands()
{
return $this->enumIcingaObjects('command');
}
public function enumCommandTemplates()
{
return $this->enumIcingaTemplates('command');
}
public function enumTimeperiods()
{
return $this->enumIcingaObjects('timeperiod');
}
public function enumCheckcommands()
{
$filters = array(
'methods_execute IN (?)' => array('PluginCheck', 'IcingaCheck'),
);
return $this->enumIcingaObjects('command', $filters);
}
public function enumEventcommands()
{
$filters = array(
'methods_execute = ?' => 'PluginEvent',
);
return $this->enumIcingaObjects('command', $filters);
}
public function enumNotificationCommands()
{
$filters = array(
'methods_execute IN (?)' => array('PluginNotification'),
);
return $this->enumIcingaObjects('command', $filters);
}
public function getZoneName($id)
{
$objects = $this->enumZones();
return $objects[$id];
}
public function getCommandName($id)
{
$objects = $this->enumCommands();
return $objects[$id];
}
public function enumZones()
{
return $this->enumIcingaObjects('zone');
}
public function enumNonglobalZones()
{
$filters = array('is_global = ?' => 'n');
return $this->enumIcingaObjects('zone', $filters);
}
public function enumZoneTemplates()
{
return $this->enumIcingaTemplates('zone');
}
public function enumHosts()
{
return $this->enumIcingaObjects('host');
}
public function enumHostTemplates()
{
return $this->enumIcingaTemplates('host');
}
public function enumHostgroups()
{
return $this->enumIcingaObjects('hostgroup');
}
public function enumServices()
{
return $this->enumIcingaObjects('service');
}
public function enumServiceTemplates()
{
return $this->enumIcingaTemplates('service');
}
public function enumServicegroups()
{
return $this->enumIcingaObjects('servicegroup');
}
public function enumUsers()
{
return $this->enumIcingaObjects('user');
}
public function enumUserTemplates()
{
return $this->enumIcingaTemplates('user');
}
public function enumUsergroups()
{
return $this->enumIcingaObjects('usergroup');
}
public function enumApiUsers()
{
return $this->enumIcingaObjects('apiuser');
}
public function enumSyncRule()
{
return $this->enum('sync_rule', array('id', 'rule_name'));
}
public function enumImportSource()
{
return $this->enum('import_source', array('id', 'source_name'));
}
public function enumDatalist()
{
return $this->enum('director_datalist', array('id', 'list_name'));
}
public function enumDatafields()
{
return $this->enum('director_datafield', array(
'id',
"caption || ' (' || varname || ')'",
));
}
public function enum($table, $columns = null, $filters = array())
{
if ($columns === null) {
$columns = array('id', 'object_name');
}
$select = $this->db()->select()->from($table, $columns)->order($columns[1]);
foreach ($filters as $key => $val) {
$select->where($key, $val);
}
return $this->db()->fetchPairs($select);
}
public function enumIcingaObjects($type, $filters = array())
{
$filters = array(
'object_type IN (?)' => array('object', 'external_object')
) + $filters;
return $this->enum('icinga_' . $type, null, $filters);
}
public function enumIcingaTemplates($type, $filters = array())
{
$filters = array('object_type = ?' => 'template') + $filters;
return $this->enum('icinga_' . $type, null, $filters);
}
public function fetchDistinctHostVars()
{
$select = $this->db()->select()->distinct()->from(
array('hv' => 'icinga_host_var'),
array(
'varname' => 'hv.varname',
'format' => 'hv.format',
'caption' => 'df.caption',
'datatype' => 'df.datatype'
)
)->joinLeft(
array('df' => 'director_datafield'),
'df.varname = hv.varname',
array()
)->order('varname');
return $this->db()->fetchAll($select);
}
public function fetchDistinctServiceVars()
{
$select = $this->db()->select()->distinct()->from(
array('sv' => 'icinga_service_var'),
array(
'varname' => 'sv.varname',
'format' => 'sv.format',
'caption' => 'df.caption',
'datatype' => 'df.datatype'
)
)->joinLeft(
array('df' => 'director_datafield'),
'df.varname = sv.varname',
array()
)->order('varname');
return $this->db()->fetchAll($select);
}
public function dbHexFunc($column)
{
if ($this->isPgsql()) {
return sprintf("LOWER(ENCODE(%s, 'hex'))", $column);
} else {
return sprintf("LOWER(HEX(%s))", $column);
}
}
public function enumDeployedConfigs()
{
$db = $this->db();
$columns = array(
'checksum' => $this->dbHexFunc('c.checksum'),
);
if ($this->isPgsql()) {
$columns['caption'] = 'SUBSTRING(' . $columns['checksum'] . ' FROM 1 FOR 7)';
} else {
$columns['caption'] = 'SUBSTRING(' . $columns['checksum'] . ', 1, 7)';
}
$query = $db->select()->from(
array('l' => 'director_deployment_log'),
$columns
)->joinLeft(
array('c' => 'director_generated_config'),
'c.checksum = l.config_checksum',
array()
)->order('l.start_time DESC');
return $db->fetchPairs($query);
}
}