Merge branch 'fix/load-related-branched-objects'

This commit is contained in:
Thomas Gelf 2021-12-17 12:53:09 +01:00
commit 4c24781a11
12 changed files with 218 additions and 14 deletions

View File

@ -5,6 +5,7 @@ namespace Icinga\Module\Director\Controllers;
use gipfl\Diff\HtmlRenderer\SideBySideDiff; use gipfl\Diff\HtmlRenderer\SideBySideDiff;
use gipfl\Diff\PhpDiff; use gipfl\Diff\PhpDiff;
use gipfl\IcingaWeb2\Widget\NameValueTable; use gipfl\IcingaWeb2\Widget\NameValueTable;
use Icinga\Module\Director\Data\Db\DbObjectStore;
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry; use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
use Icinga\Module\Director\Db\Branch\BranchActivity; use Icinga\Module\Director\Db\Branch\BranchActivity;
use Icinga\Module\Director\IcingaConfig\IcingaConfig; use Icinga\Module\Director\IcingaConfig\IcingaConfig;
@ -19,6 +20,12 @@ class BranchController extends ActionController
{ {
use BranchHelper; use BranchHelper;
public function init()
{
parent::init();
IcingaObject::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch()));
}
protected function checkDirectorPermissions() protected function checkDirectorPermissions()
{ {
} }

View File

@ -7,6 +7,7 @@ use Icinga\Module\Director\Data\Db\DbObjectStore;
use Icinga\Module\Director\Db\Branch\UuidLookup; use Icinga\Module\Director\Db\Branch\UuidLookup;
use Icinga\Module\Director\Forms\IcingaServiceForm; use Icinga\Module\Director\Forms\IcingaServiceForm;
use Icinga\Module\Director\Monitoring; use Icinga\Module\Director\Monitoring;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Web\Controller\ObjectController; use Icinga\Module\Director\Web\Controller\ObjectController;
use Icinga\Module\Director\Objects\IcingaService; use Icinga\Module\Director\Objects\IcingaService;
use Icinga\Module\Director\Objects\IcingaHost; use Icinga\Module\Director\Objects\IcingaHost;
@ -49,11 +50,13 @@ class ServiceController extends ObjectController
{ {
$host = $this->params->get('host', $this->params->get('host_id')); $host = $this->params->get('host', $this->params->get('host_id'));
if ($host === null && $this->object) { if ($host === null && $this->object) {
if ($host = $this->object->get('host_id')) { if (null === $host = $this->object->getUnresolvedRelated('host')) {
$host = (int) $host; if ($host = $this->object->get('host_id')) {
} else { $host = (int) $host;
$host = $this->object->get('host'); } else {
// We reach this when accessing Service Template Fields $host = $this->object->get('host');
// We reach this when accessing Service Template Fields
}
} }
} }

View File

@ -153,7 +153,11 @@ class IcingaAddServiceForm extends DirectorObjectForm
public function onSuccess() public function onSuccess()
{ {
if ($this->host !== null) { 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(); parent::onSuccess();
return; return;
} }

View File

@ -5,6 +5,7 @@ namespace Icinga\Module\Director\Data\Db;
use Icinga\Exception\NotFoundError; use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Data\InvalidDataException; use Icinga\Module\Director\Data\InvalidDataException;
use Icinga\Module\Director\Db; use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\Branch\UuidLookup;
use Icinga\Module\Director\Exception\DuplicateKeyException; use Icinga\Module\Director\Exception\DuplicateKeyException;
use InvalidArgumentException; use InvalidArgumentException;
use LogicException; use LogicException;
@ -91,6 +92,9 @@ abstract class DbObject
protected static $prefetchStats = array(); protected static $prefetchStats = array();
/** @var ?DbObjectStore */
protected static $dbObjectStore;
/** /**
* Constructor is not accessible and should not be overridden * Constructor is not accessible and should not be overridden
*/ */
@ -225,6 +229,11 @@ abstract class DbObject
return $this; return $this;
} }
public static function setDbObjectStore(DbObjectStore $store)
{
self::$dbObjectStore = $store;
}
/** /**
* Getter * Getter
* *
@ -992,6 +1001,12 @@ abstract class DbObject
public function createWhere() 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 ($id = $this->getAutoincId()) {
if ($originalId = $this->getOriginalProperty($this->autoincKeyName)) { if ($originalId = $this->getOriginalProperty($this->autoincKeyName)) {
return $this->db->quoteInto( return $this->db->quoteInto(
@ -1220,8 +1235,15 @@ abstract class DbObject
return $prefetched; return $prefetched;
} }
/** @var DbObject $obj */
$obj = new static; $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) $obj->setConnection($connection)
->set($obj->autoincKeyName, $id) ->set($obj->autoincKeyName, $id)
->loadFromDb(); ->loadFromDb();
@ -1240,9 +1262,17 @@ abstract class DbObject
if ($prefetched = static::getPrefetched($id)) { if ($prefetched = static::getPrefetched($id)) {
return $prefetched; return $prefetched;
} }
/** @var DbObject $obj */ /** @var DbObject $obj */
$obj = new static; $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(); $obj->setConnection($connection)->setKey($id)->loadFromDb();
return $obj; return $obj;
@ -1341,6 +1371,14 @@ abstract class DbObject
/** @var DbObject $obj */ /** @var DbObject $obj */
$obj = new static; $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); $obj->setConnection($connection)->setKey($id);
return $obj->existsInDb(); return $obj->existsInDb();
} }
@ -1376,6 +1414,13 @@ abstract class DbObject
{ {
$db = $connection->getDbAdapter(); $db = $connection->getDbAdapter();
$obj = new static; $obj = new static;
if (self::$dbObjectStore !== null) {
$table = $obj->getTableName();
assert($connection instanceof Db);
return self::$dbObjectStore->load($table, $uuid);
}
$query = $db->select() $query = $db->select()
->from($obj->getTableName()) ->from($obj->getTableName())
->where($obj->getUuidColumn() . ' = ?', $connection->quoteBinary($uuid->getBytes())); ->where($obj->getUuidColumn() . ' = ?', $connection->quoteBinary($uuid->getBytes()));

View File

@ -6,6 +6,7 @@ use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\Branch\Branch; use Icinga\Module\Director\Db\Branch\Branch;
use Icinga\Module\Director\Db\Branch\BranchActivity; use Icinga\Module\Director\Db\Branch\BranchActivity;
use Icinga\Module\Director\Db\Branch\BranchedObject; use Icinga\Module\Director\Db\Branch\BranchedObject;
use Ramsey\Uuid\UuidInterface;
/** /**
* Loader for Icinga/DbObjects * Loader for Icinga/DbObjects
@ -28,6 +29,30 @@ class DbObjectStore
$this->branch = $branch; $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) public function store(DbObject $object)
{ {
if ($this->branch && $this->branch->isBranch()) { if ($this->branch && $this->branch->isBranch()) {
@ -61,4 +86,9 @@ class DbObjectStore
return $object->delete(); return $object->delete();
} }
public function getBranch()
{
return $this->branch;
}
} }

View File

@ -344,6 +344,23 @@ class BranchedObject
return $self; 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 Db $connection
* @param string $table * @param string $table

View File

@ -31,7 +31,7 @@ class UuidLookup
) { ) {
$db = $connection->getDbAdapter(); $db = $connection->getDbAdapter();
$query = $db->select()->from('icinga_service', 'uuid')->where('object_type = ?', $objectType); $query = $db->select()->from('icinga_service', 'uuid')->where('object_type = ?', $objectType);
$query = self::addKeyToQuery($query, $key); $query = self::addKeyToQuery($connection, $query, $key);
if ($host) { if ($host) {
$query->add('host_id = ?', $host->get('id')); $query->add('host_id = ?', $host->get('id'));
} }
@ -43,7 +43,7 @@ class UuidLookup
if ($uuid === null && $branch->isBranch()) { if ($uuid === null && $branch->isBranch()) {
// TODO: use different tables? // TODO: use different tables?
$query = $db->select()->from('branched_icinga_service', 'uuid')->where('object_type = ?', $objectType); $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) { if ($host) {
// TODO: uuid? // TODO: uuid?
$query->add('host = ?', $host->getObjectName()); $query->add('host = ?', $host->getObjectName());
@ -60,7 +60,7 @@ class UuidLookup
public static function findUuidForKey($key, $table, Db $connection, Branch $branch) public static function findUuidForKey($key, $table, Db $connection, Branch $branch)
{ {
$db = $connection->getDbAdapter(); $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); $uuid = self::fetchOptionalUuid($connection, $query);
if ($uuid === null && $branch->isBranch()) { if ($uuid === null && $branch->isBranch()) {
$query = $db->select()->from("branched_$table", 'uuid')->where('object_name = ?', $key); $query = $db->select()->from("branched_$table", 'uuid')->where('object_name = ?', $key);
@ -70,14 +70,16 @@ class UuidLookup
return $uuid; return $uuid;
} }
protected static function addKeyToQuery($query, $key) protected static function addKeyToQuery(Db $connection, $query, $key)
{ {
if (is_int($key)) { if (is_int($key)) {
$query->where('id = ?', $key); $query->where('id = ?', $key);
} elseif (is_string($key)) { } elseif (is_string($key)) {
$query->where('object_name = ?', $key); $query->where('object_name = ?', $key);
} else { } 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; return $query;

View File

@ -9,6 +9,7 @@ use Icinga\Data\Filter\FilterExpression;
use Icinga\Exception\NotFoundError; use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\CustomVariable\CustomVariables; use Icinga\Module\Director\CustomVariable\CustomVariables;
use Icinga\Module\Director\Data\Db\DbDataFormatter; 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\Data\Db\DbObjectTypeRegistry;
use Icinga\Module\Director\IcingaConfig\AssignRenderer; use Icinga\Module\Director\IcingaConfig\AssignRenderer;
use Icinga\Module\Director\Data\Db\DbObject; use Icinga\Module\Director\Data\Db\DbObject;
@ -530,6 +531,25 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
return $this; 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 * @param $name
*/ */
@ -1695,11 +1715,16 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
try { try {
$object->renderToConfig($config); $object->renderToConfig($config);
} catch (Exception $e) { } catch (Exception $e) {
$message = $e->getMessage();
$showTrace = false;
if ($showTrace) {
$message .= "\n" . $e->getTraceAsString();
}
$config->configFile( $config->configFile(
'failed-to-render' 'failed-to-render'
)->prepend( )->prepend(
"/** Failed to render this object **/\n" "/** Failed to render this object **/\n"
. '/* ' . $e->getMessage() . ' */' . '/* ' . $message . ' */'
); );
} }
if ($wasExternal) { if ($wasExternal) {

View File

@ -473,6 +473,10 @@ class IcingaService extends IcingaObject implements ExportInterface
*/ */
public function hasBeenAssignedToHostTemplate() public function hasBeenAssignedToHostTemplate()
{ {
// Branches would fail
if ($this->properties['host_id'] === null) {
return null;
}
$hostId = $this->get('host_id'); $hostId = $this->get('host_id');
return $hostId && $this->getRelatedObject( return $hostId && $this->getRelatedObject(

View File

@ -2,8 +2,11 @@
namespace Icinga\Module\Director\Web\Controller; 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\Branch;
use Icinga\Module\Director\Db\Branch\BranchStore; use Icinga\Module\Director\Db\Branch\BranchStore;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Web\Widget\NotInBranchedHint; use Icinga\Module\Director\Web\Widget\NotInBranchedHint;
trait BranchHelper trait BranchHelper
@ -14,6 +17,23 @@ trait BranchHelper
/** @var BranchStore */ /** @var BranchStore */
protected $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 * @return false|\Ramsey\Uuid\UuidInterface
*/ */
@ -49,6 +69,18 @@ trait BranchHelper
return $this->getBranchUuid() !== null; 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 * @param string $subject
* @return bool * @return bool

View File

@ -62,6 +62,7 @@ abstract class ObjectController extends ActionController
public function init() public function init()
{ {
parent::init(); parent::init();
$this->enableStaticObjectLoader($this->getTableName());
if ($this->getRequest()->isApiRequest()) { if ($this->getRequest()->isApiRequest()) {
$handler = new IcingaObjectHandler($this->getRequest(), $this->getResponse(), $this->db()); $handler = new IcingaObjectHandler($this->getRequest(), $this->getResponse(), $this->db());

View File

@ -343,4 +343,38 @@ class ObjectsTable extends ZfQueryBasedTable
return $query; 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);
}
} }