diff --git a/application/controllers/BranchController.php b/application/controllers/BranchController.php index 4ac526cf..0afd5973 100644 --- a/application/controllers/BranchController.php +++ b/application/controllers/BranchController.php @@ -5,6 +5,7 @@ namespace Icinga\Module\Director\Controllers; use gipfl\Diff\HtmlRenderer\SideBySideDiff; use gipfl\Diff\PhpDiff; use gipfl\IcingaWeb2\Widget\NameValueTable; +use Icinga\Module\Director\Data\Db\DbObjectStore; use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry; use Icinga\Module\Director\Db\Branch\BranchActivity; use Icinga\Module\Director\IcingaConfig\IcingaConfig; @@ -19,6 +20,12 @@ class BranchController extends ActionController { use BranchHelper; + public function init() + { + parent::init(); + IcingaObject::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch())); + } + protected function checkDirectorPermissions() { } diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index 83bc2924..72363e7f 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -7,6 +7,7 @@ use Icinga\Module\Director\Data\Db\DbObjectStore; use Icinga\Module\Director\Db\Branch\UuidLookup; use Icinga\Module\Director\Forms\IcingaServiceForm; use Icinga\Module\Director\Monitoring; +use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Web\Controller\ObjectController; use Icinga\Module\Director\Objects\IcingaService; use Icinga\Module\Director\Objects\IcingaHost; @@ -49,11 +50,13 @@ class ServiceController extends ObjectController { $host = $this->params->get('host', $this->params->get('host_id')); if ($host === null && $this->object) { - if ($host = $this->object->get('host_id')) { - $host = (int) $host; - } else { - $host = $this->object->get('host'); - // We reach this when accessing Service Template Fields + if (null === $host = $this->object->getUnresolvedRelated('host')) { + if ($host = $this->object->get('host_id')) { + $host = (int) $host; + } else { + $host = $this->object->get('host'); + // We reach this when accessing Service Template Fields + } } } diff --git a/application/forms/IcingaAddServiceForm.php b/application/forms/IcingaAddServiceForm.php index d8245e52..e5514246 100644 --- a/application/forms/IcingaAddServiceForm.php +++ b/application/forms/IcingaAddServiceForm.php @@ -153,7 +153,11 @@ class IcingaAddServiceForm extends DirectorObjectForm public function onSuccess() { if ($this->host !== null) { - $this->object->set('host_id', $this->host->get('id')); + if ($id = $this->host->get('id')) { + $this->object->set('host_id', $this->host->get('id')); + } else { + $this->object->set('host', $this->host->getObjectName()); + } parent::onSuccess(); return; } diff --git a/library/Director/Data/Db/DbObject.php b/library/Director/Data/Db/DbObject.php index fb2402a0..0ed91ebc 100644 --- a/library/Director/Data/Db/DbObject.php +++ b/library/Director/Data/Db/DbObject.php @@ -5,6 +5,7 @@ namespace Icinga\Module\Director\Data\Db; use Icinga\Exception\NotFoundError; use Icinga\Module\Director\Data\InvalidDataException; use Icinga\Module\Director\Db; +use Icinga\Module\Director\Db\Branch\UuidLookup; use Icinga\Module\Director\Exception\DuplicateKeyException; use InvalidArgumentException; use LogicException; @@ -91,6 +92,9 @@ abstract class DbObject protected static $prefetchStats = array(); + /** @var ?DbObjectStore */ + protected static $dbObjectStore; + /** * Constructor is not accessible and should not be overridden */ @@ -225,6 +229,11 @@ abstract class DbObject return $this; } + public static function setDbObjectStore(DbObjectStore $store) + { + self::$dbObjectStore = $store; + } + /** * Getter * @@ -992,6 +1001,12 @@ abstract class DbObject public function createWhere() { + if ($this->hasUuidColumn() && $this->properties[$this->uuidColumn] !== null) { + return $this->db->quoteInto( + sprintf('%s = ?', $this->getUuidColumn()), + $this->connection->quoteBinary($this->getUniqueId()->getBytes()) + ); + } if ($id = $this->getAutoincId()) { if ($originalId = $this->getOriginalProperty($this->autoincKeyName)) { return $this->db->quoteInto( @@ -1220,8 +1235,15 @@ abstract class DbObject return $prefetched; } - /** @var DbObject $obj */ $obj = new static; + if (self::$dbObjectStore !== null && $obj->hasUuidColumn()) { + $table = $obj->getTableName(); + assert($connection instanceof Db); + $uuid = UuidLookup::findUuidForKey($id, $table, $connection, self::$dbObjectStore->getBranch()); + + return self::$dbObjectStore->load($table, $uuid); + } + $obj->setConnection($connection) ->set($obj->autoincKeyName, $id) ->loadFromDb(); @@ -1240,9 +1262,17 @@ abstract class DbObject if ($prefetched = static::getPrefetched($id)) { return $prefetched; } - /** @var DbObject $obj */ $obj = new static; + + if (self::$dbObjectStore !== null) { + $table = $obj->getTableName(); + assert($connection instanceof Db); + $uuid = UuidLookup::findUuidForKey($id, $table, $connection, self::$dbObjectStore->getBranch()); + + return self::$dbObjectStore->load($table, $uuid); + } + $obj->setConnection($connection)->setKey($id)->loadFromDb(); return $obj; @@ -1341,6 +1371,14 @@ abstract class DbObject /** @var DbObject $obj */ $obj = new static; + if (self::$dbObjectStore !== null) { + $table = $obj->getTableName(); + assert($connection instanceof Db); + $uuid = UuidLookup::findUuidForKey($id, $table, $connection, self::$dbObjectStore->getBranch()); + + return self::$dbObjectStore->exists($table, $uuid); + } + $obj->setConnection($connection)->setKey($id); return $obj->existsInDb(); } @@ -1376,6 +1414,13 @@ abstract class DbObject { $db = $connection->getDbAdapter(); $obj = new static; + + if (self::$dbObjectStore !== null) { + $table = $obj->getTableName(); + assert($connection instanceof Db); + return self::$dbObjectStore->load($table, $uuid); + } + $query = $db->select() ->from($obj->getTableName()) ->where($obj->getUuidColumn() . ' = ?', $connection->quoteBinary($uuid->getBytes())); diff --git a/library/Director/Data/Db/DbObjectStore.php b/library/Director/Data/Db/DbObjectStore.php index 5aab5ac7..89721108 100644 --- a/library/Director/Data/Db/DbObjectStore.php +++ b/library/Director/Data/Db/DbObjectStore.php @@ -6,6 +6,7 @@ use Icinga\Module\Director\Db; use Icinga\Module\Director\Db\Branch\Branch; use Icinga\Module\Director\Db\Branch\BranchActivity; use Icinga\Module\Director\Db\Branch\BranchedObject; +use Ramsey\Uuid\UuidInterface; /** * Loader for Icinga/DbObjects @@ -28,6 +29,30 @@ class DbObjectStore $this->branch = $branch; } + /** + * @param $tableName + * @param UuidInterface $uuid + * @return DbObject|null + * @throws \Icinga\Exception\NotFoundError + */ + public function load($tableName, UuidInterface $uuid) + { + $branchedObject = BranchedObject::load($this->connection, $tableName, $uuid, $this->branch); + $object = $branchedObject->getBranchedDbObject($this->connection); + if ($object === null) { + return null; + } + + $object->setBeingLoadedFromDb(); + + return $object; + } + + public function exists($tableName, UuidInterface $uuid) + { + return BranchedObject::exists($this->connection, $tableName, $uuid, $this->branch->getUuid()); + } + public function store(DbObject $object) { if ($this->branch && $this->branch->isBranch()) { @@ -61,4 +86,9 @@ class DbObjectStore return $object->delete(); } + + public function getBranch() + { + return $this->branch; + } } diff --git a/library/Director/Db/Branch/BranchedObject.php b/library/Director/Db/Branch/BranchedObject.php index c0e41d41..d4eafcf4 100644 --- a/library/Director/Db/Branch/BranchedObject.php +++ b/library/Director/Db/Branch/BranchedObject.php @@ -344,6 +344,23 @@ class BranchedObject return $self; } + public static function exists( + Db $connection, + $table, + UuidInterface $uuid, + UuidInterface $branchUuid = null + ) { + if (static::optionalTableRowByUuid($connection, $table, $uuid)) { + return true; + } + + if ($branchUuid && static::optionalBranchedTableRowByUuid($connection, $table, $uuid, $branchUuid)) { + return true; + } + + return false; + } + /** * @param Db $connection * @param string $table diff --git a/library/Director/Db/Branch/UuidLookup.php b/library/Director/Db/Branch/UuidLookup.php index 5ec5b830..67678b52 100644 --- a/library/Director/Db/Branch/UuidLookup.php +++ b/library/Director/Db/Branch/UuidLookup.php @@ -31,7 +31,7 @@ class UuidLookup ) { $db = $connection->getDbAdapter(); $query = $db->select()->from('icinga_service', 'uuid')->where('object_type = ?', $objectType); - $query = self::addKeyToQuery($query, $key); + $query = self::addKeyToQuery($connection, $query, $key); if ($host) { $query->add('host_id = ?', $host->get('id')); } @@ -43,7 +43,7 @@ class UuidLookup if ($uuid === null && $branch->isBranch()) { // TODO: use different tables? $query = $db->select()->from('branched_icinga_service', 'uuid')->where('object_type = ?', $objectType); - $query = self::addKeyToQuery($query, $key); + $query = self::addKeyToQuery($connection, $query, $key); if ($host) { // TODO: uuid? $query->add('host = ?', $host->getObjectName()); @@ -60,7 +60,7 @@ class UuidLookup public static function findUuidForKey($key, $table, Db $connection, Branch $branch) { $db = $connection->getDbAdapter(); - $query = self::addKeyToQuery($db->select()->from($table, 'uuid'), $key); + $query = self::addKeyToQuery($connection, $db->select()->from($table, 'uuid'), $key); $uuid = self::fetchOptionalUuid($connection, $query); if ($uuid === null && $branch->isBranch()) { $query = $db->select()->from("branched_$table", 'uuid')->where('object_name = ?', $key); @@ -70,14 +70,16 @@ class UuidLookup return $uuid; } - protected static function addKeyToQuery($query, $key) + protected static function addKeyToQuery(Db $connection, $query, $key) { if (is_int($key)) { $query->where('id = ?', $key); } elseif (is_string($key)) { $query->where('object_name = ?', $key); } else { - throw new RuntimeException('Cannot deal with non-int/string keys for UUID fallback'); + foreach ($key as $k => $v) { + $query->where($connection->getDbAdapter()->quoteIdentifier($k) . ' = ?', $v); + } } return $query; diff --git a/library/Director/Objects/IcingaObject.php b/library/Director/Objects/IcingaObject.php index f06c5516..3f45df8b 100644 --- a/library/Director/Objects/IcingaObject.php +++ b/library/Director/Objects/IcingaObject.php @@ -9,6 +9,7 @@ use Icinga\Data\Filter\FilterExpression; use Icinga\Exception\NotFoundError; use Icinga\Module\Director\CustomVariable\CustomVariables; use Icinga\Module\Director\Data\Db\DbDataFormatter; +use Icinga\Module\Director\Data\Db\DbObjectStore; use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry; use Icinga\Module\Director\IcingaConfig\AssignRenderer; use Icinga\Module\Director\Data\Db\DbObject; @@ -530,6 +531,25 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer return $this; } + public function getUnresolvedRelated($property) + { + if ($this->hasRelation($property)) { + $property .= '_id'; + if (isset($this->unresolvedRelatedProperties[$property])) { + return $this->unresolvedRelatedProperties[$property]; + } + + return null; + } + + throw new RuntimeException(sprintf( + '%s "%s" has no %s reference', + $this->getShortTableName(), + $this->getObjectName(), + $property + )); + } + /** * @param $name */ @@ -1695,11 +1715,16 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer try { $object->renderToConfig($config); } catch (Exception $e) { + $message = $e->getMessage(); + $showTrace = false; + if ($showTrace) { + $message .= "\n" . $e->getTraceAsString(); + } $config->configFile( 'failed-to-render' )->prepend( "/** Failed to render this object **/\n" - . '/* ' . $e->getMessage() . ' */' + . '/* ' . $message . ' */' ); } if ($wasExternal) { diff --git a/library/Director/Objects/IcingaService.php b/library/Director/Objects/IcingaService.php index 8f813bca..5dddc703 100644 --- a/library/Director/Objects/IcingaService.php +++ b/library/Director/Objects/IcingaService.php @@ -473,6 +473,10 @@ class IcingaService extends IcingaObject implements ExportInterface */ public function hasBeenAssignedToHostTemplate() { + // Branches would fail + if ($this->properties['host_id'] === null) { + return null; + } $hostId = $this->get('host_id'); return $hostId && $this->getRelatedObject( diff --git a/library/Director/Web/Controller/BranchHelper.php b/library/Director/Web/Controller/BranchHelper.php index 6402fbf3..1b75a256 100644 --- a/library/Director/Web/Controller/BranchHelper.php +++ b/library/Director/Web/Controller/BranchHelper.php @@ -2,8 +2,11 @@ namespace Icinga\Module\Director\Web\Controller; +use Icinga\Module\Director\Data\Db\DbObjectStore; +use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry; use Icinga\Module\Director\Db\Branch\Branch; use Icinga\Module\Director\Db\Branch\BranchStore; +use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Web\Widget\NotInBranchedHint; trait BranchHelper @@ -14,6 +17,23 @@ trait BranchHelper /** @var BranchStore */ protected $branchStore; + protected static $banchedTables = [ + 'icinga_apiuser', + 'icinga_command', + 'icinga_dependency', + 'icinga_endpoint', + 'icinga_host', + 'icinga_hostgroup', + 'icinga_notification', + 'icinga_scheduled_downtime', + 'icinga_service', + 'icinga_servicegroup', + 'icinga_timeperiod', + 'icinga_user', + 'icinga_usergroup', + 'icinga_zone', + ]; + /** * @return false|\Ramsey\Uuid\UuidInterface */ @@ -49,6 +69,18 @@ trait BranchHelper return $this->getBranchUuid() !== null; } + protected function tableHasBranchSupport($table) + { + return in_array($table, self::$banchedTables, true); + } + + protected function enableStaticObjectLoader($table) + { + if ($this->tableHasBranchSupport($table)) { + IcingaObject::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch())); + } + } + /** * @param string $subject * @return bool diff --git a/library/Director/Web/Controller/ObjectController.php b/library/Director/Web/Controller/ObjectController.php index 28a1d418..e7482efe 100644 --- a/library/Director/Web/Controller/ObjectController.php +++ b/library/Director/Web/Controller/ObjectController.php @@ -62,6 +62,7 @@ abstract class ObjectController extends ActionController public function init() { parent::init(); + $this->enableStaticObjectLoader($this->getTableName()); if ($this->getRequest()->isApiRequest()) { $handler = new IcingaObjectHandler($this->getRequest(), $this->getResponse(), $this->db()); diff --git a/library/Director/Web/Table/ObjectsTable.php b/library/Director/Web/Table/ObjectsTable.php index c96c1797..ca81bfaa 100644 --- a/library/Director/Web/Table/ObjectsTable.php +++ b/library/Director/Web/Table/ObjectsTable.php @@ -343,4 +343,38 @@ class ObjectsTable extends ZfQueryBasedTable return $query; } + + protected static function branchifyQuery(Db $connection, $query, $table, UuidInterface $branchUuid) + { + $right = clone($query); + /** @var Db $conn */ + $conn = $connection; + $db = $connection->getDbAdapter(); + $query->joinLeft( + ['bo' => "branched_$table"], + // TODO: PgHexFunc + $db->quoteInto( + 'bo.uuid = o.uuid AND bo.branch_uuid = ?', + $conn->quoteBinary($branchUuid->getBytes()) + ), + [] + )->where("(bo.branch_deleted IS NULL OR bo.branch_deleted = 'n')"); + $this->applyObjectTypeFilter($query, $right); + $right->joinRight( + ['bo' => "branched_$table"], + 'bo.uuid = o.uuid', + [] + ) + ->where('o.uuid IS NULL') + ->where('bo.branch_uuid = ?', $conn->quoteBinary($branchUuid->getBytes())); + $this->leftSubQuery = $query; + $this->rightSubQuery = $right; + $query = $db->select()->union([ + 'l' => new DbSelectParenthesis($query), + 'r' => new DbSelectParenthesis($right), + ]); + $query = $db->select()->from(['u' => $query]); + $query->order('object_name')->limit(100); + + } }