2021-08-16 11:43:09 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Icinga\Module\Director\Db\Branch;
|
|
|
|
|
|
|
|
use Icinga\Module\Director\Data\Db\DbObject;
|
2021-10-05 22:27:32 +02:00
|
|
|
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
|
2021-08-16 11:43:09 +02:00
|
|
|
use Icinga\Module\Director\Db;
|
|
|
|
use Ramsey\Uuid\UuidInterface;
|
|
|
|
|
|
|
|
class BranchMerger
|
|
|
|
{
|
|
|
|
/** @var Branch */
|
|
|
|
protected $branchUuid;
|
|
|
|
|
|
|
|
/** @var Db */
|
|
|
|
protected $connection;
|
|
|
|
|
|
|
|
/** @var \Zend_Db_Adapter_Abstract */
|
|
|
|
protected $db;
|
|
|
|
|
|
|
|
/** @var array */
|
2021-10-05 22:27:32 +02:00
|
|
|
protected $ignoreActivities = [];
|
2021-08-16 11:43:09 +02:00
|
|
|
|
|
|
|
/** @var bool */
|
|
|
|
protected $ignoreDeleteWhenMissing = false;
|
|
|
|
|
|
|
|
/** @var bool */
|
|
|
|
protected $ignoreModificationWhenMissing = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Apply branch modifications
|
|
|
|
*
|
|
|
|
* TODO: allow to skip or ignore modifications, in case modified properties have
|
|
|
|
* been changed in the meantime
|
|
|
|
*
|
|
|
|
* @param UuidInterface $branchUuid
|
|
|
|
* @param Db $connection
|
|
|
|
*/
|
|
|
|
public function __construct(UuidInterface $branchUuid, Db $connection)
|
|
|
|
{
|
|
|
|
$this->branchUuid = $branchUuid;
|
|
|
|
$this->db = $connection->getDbAdapter();
|
|
|
|
$this->connection = $connection;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Skip a delete operation, when the object to be deleted does not exist
|
|
|
|
*
|
|
|
|
* @param bool $ignore
|
|
|
|
*/
|
|
|
|
public function ignoreDeleteWhenMissing($ignore = true)
|
|
|
|
{
|
|
|
|
$this->ignoreDeleteWhenMissing = $ignore;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Skip a modification, when the related object does not exist
|
|
|
|
* @param bool $ignore
|
|
|
|
*/
|
|
|
|
public function ignoreModificationWhenMissing($ignore = true)
|
|
|
|
{
|
|
|
|
$this->ignoreModificationWhenMissing = $ignore;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-10-05 22:27:32 +02:00
|
|
|
* @param int $key
|
2021-08-16 11:43:09 +02:00
|
|
|
*/
|
2021-10-05 22:27:32 +02:00
|
|
|
public function ignoreActivity($key)
|
2021-08-16 11:43:09 +02:00
|
|
|
{
|
2021-10-05 22:27:32 +02:00
|
|
|
$this->ignoreActivities[$key] = true;
|
2021-08-16 11:43:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-10-05 22:27:32 +02:00
|
|
|
* @param BranchActivity $activity
|
|
|
|
* @return bool
|
2021-08-16 11:43:09 +02:00
|
|
|
*/
|
2021-10-05 22:27:32 +02:00
|
|
|
public function ignoresActivity(BranchActivity $activity)
|
2021-08-16 11:43:09 +02:00
|
|
|
{
|
2021-10-05 22:27:32 +02:00
|
|
|
return isset($this->ignoreActivities[$activity->getTimestampNs()]);
|
2021-08-16 11:43:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @throws MergeError
|
|
|
|
*/
|
|
|
|
public function merge()
|
|
|
|
{
|
|
|
|
$this->connection->runFailSafeTransaction(function () {
|
2021-10-05 22:27:32 +02:00
|
|
|
$query = $this->db->select()
|
|
|
|
->from(BranchActivity::DB_TABLE)
|
|
|
|
->where('branch_uuid = ?', $this->connection->quoteBinary($this->branchUuid->getBytes()))
|
|
|
|
->order('timestamp_ns ASC');
|
|
|
|
$rows = $this->db->fetchAll($query);
|
2021-08-16 11:43:09 +02:00
|
|
|
foreach ($rows as $row) {
|
2021-10-05 22:27:32 +02:00
|
|
|
$activity = BranchActivity::fromDbRow($row);
|
|
|
|
$this->applyModification($activity);
|
2021-08-16 11:43:09 +02:00
|
|
|
}
|
2021-10-05 22:27:32 +02:00
|
|
|
(new BranchStore($this->connection))->deleteByUuid($this->branchUuid);
|
2021-08-16 11:43:09 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-10-05 22:27:32 +02:00
|
|
|
* @param BranchActivity $activity
|
2021-08-16 11:43:09 +02:00
|
|
|
* @throws MergeError
|
|
|
|
* @throws \Icinga\Exception\NotFoundError
|
|
|
|
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
|
|
|
|
*/
|
2021-10-05 22:27:32 +02:00
|
|
|
protected function applyModification(BranchActivity $activity)
|
2021-08-16 11:43:09 +02:00
|
|
|
{
|
|
|
|
/** @var string|DbObject $class */
|
2021-10-05 22:27:32 +02:00
|
|
|
$class = DbObjectTypeRegistry::classByType($activity->getObjectTable());
|
|
|
|
$uuid = $activity->getObjectUuid();
|
2021-08-16 11:43:09 +02:00
|
|
|
|
2021-10-05 22:27:32 +02:00
|
|
|
$exists = $class::uniqueIdExists($uuid, $this->connection);
|
|
|
|
if ($activity->isActionCreate()) {
|
2021-08-16 11:43:09 +02:00
|
|
|
if ($exists) {
|
2021-10-05 22:27:32 +02:00
|
|
|
if (! $this->ignoresActivity($activity)) {
|
|
|
|
throw new MergeErrorRecreateOnMerge($activity);
|
2021-08-16 11:43:09 +02:00
|
|
|
}
|
|
|
|
} else {
|
2021-10-05 22:27:32 +02:00
|
|
|
$activity->createDbObject()->store($this->connection);
|
2021-08-16 11:43:09 +02:00
|
|
|
}
|
2021-10-05 22:27:32 +02:00
|
|
|
} elseif ($activity->isActionDelete()) {
|
2021-08-16 11:43:09 +02:00
|
|
|
if ($exists) {
|
2021-10-05 22:27:32 +02:00
|
|
|
$activity->deleteDbObject($this->connection);
|
|
|
|
} elseif (! $this->ignoreDeleteWhenMissing && ! $this->ignoresActivity($activity)) {
|
|
|
|
throw new MergeErrorDeleteMissingObject($activity);
|
2021-08-16 11:43:09 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ($exists) {
|
2021-10-05 22:27:32 +02:00
|
|
|
$activity->applyToDbObject($class::requireWithUniqueId($uuid, $this->connection))->store();
|
|
|
|
// TODO: you modified an object, and related properties have been changed in the meantime.
|
|
|
|
// We're able to detect this with the given data, and might want to offer a rebase.
|
|
|
|
} elseif (! $this->ignoreModificationWhenMissing && ! $this->ignoresActivity($activity)) {
|
|
|
|
throw new MergeErrorModificationForMissingObject($activity);
|
2021-08-16 11:43:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|