Sync: support branches

This commit is contained in:
Thomas Gelf 2022-07-01 08:36:01 +02:00
parent 1682175716
commit b2afca2496
10 changed files with 477 additions and 63 deletions

View File

@ -8,8 +8,10 @@ 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\Db\Branch\BranchStore;
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Objects\SyncRule;
use Icinga\Module\Director\PlainObjectRenderer;
use Icinga\Module\Director\Web\Controller\ActionController;
use Icinga\Module\Director\Web\Controller\BranchHelper;
@ -24,6 +26,7 @@ class BranchController extends ActionController
{
parent::init();
IcingaObject::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch()));
SyncRule::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch()));
}
protected function checkDirectorPermissions()
@ -34,9 +37,17 @@ class BranchController extends ActionController
{
$this->assertPermission('director/showconfig');
$ts = $this->params->getRequired('ts');
$this->addSingleTab($this->translate('Activity'));
$this->addTitle($this->translate('Branch Activity'));
$activity = BranchActivity::load($ts, $this->db());
$store = new BranchStore($this->db());
$branch = $store->fetchBranchByUuid($activity->getBranchUuid());
if ($branch->isSyncPreview()) {
$this->addSingleTab($this->translate('Sync Preview'));
$this->addTitle($this->translate('Expected Modification'));
} else {
$this->addSingleTab($this->translate('Activity'));
$this->addTitle($this->translate('Branch Activity'));
}
$this->content()->add($this->prepareActivityInfo($activity));
$this->showActivity($activity);
}

View File

@ -4,6 +4,14 @@ namespace Icinga\Module\Director\Controllers;
use gipfl\IcingaWeb2\Link;
use gipfl\Web\Widget\Hint;
use Icinga\Date\DateFormatter;
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\Web\Controller\BranchHelper;
use Icinga\Module\Director\Web\Form\ClickHereForm;
use Icinga\Module\Director\Web\Table\BranchActivityTable;
use Icinga\Module\Director\Web\Widget\IcingaConfigDiff;
use Icinga\Module\Director\Web\Widget\UnorderedList;
use Icinga\Module\Director\Db\Cache\PrefetchCache;
@ -26,11 +34,14 @@ use Icinga\Module\Director\Web\Table\SyncpropertyTable;
use Icinga\Module\Director\Web\Table\SyncRunTable;
use Icinga\Module\Director\Web\Tabs\SyncRuleTabs;
use Icinga\Module\Director\Web\Widget\SyncRunDetails;
use Icinga\Web\Notification;
use ipl\Html\Form;
use ipl\Html\Html;
class SyncruleController extends ActionController
{
use BranchHelper;
/**
* @throws \Icinga\Exception\NotFoundError
*/
@ -43,7 +54,18 @@ class SyncruleController extends ActionController
$this->addTitle($this->translate('Sync rule: %s'), $ruleName);
$checkForm = SyncCheckForm::load()->setSyncRule($rule)->handleRequest();
$runForm = SyncRunForm::load()->setSyncRule($rule)->handleRequest();
$store = new DbObjectStore($this->db(), $this->getBranch());
$runForm = new SyncRunForm($rule, $store);
$runForm->on(SyncRunForm::ON_SUCCESS, function (SyncRunForm $form) {
$message = $form->getSuccessMessage();
if ($message === null) {
Notification::error($this->translate('Synchronization failed'));
} else {
Notification::success($message);
}
$this->redirectNow($this->url());
});
$runForm->handleRequest($this->getServerRequest());
if ($lastRunId = $rule->getLastSyncRunId()) {
$run = SyncRun::load($lastRunId, $this->db());
@ -98,6 +120,15 @@ class SyncruleController extends ActionController
}
$c->add($checkForm);
if ($this->hasBranch()) {
$objectType = $rule->get('object_type');
$table = DbObjectTypeRegistry::tableNameByType($objectType);
if (! $this->tableHasBranchSupport($table)) {
$this->showNotInBranch(sprintf($this->translate("Synchronizing '%s'"), $objectType));
return;
}
}
$c->add($runForm);
if ($run) {
@ -143,18 +174,61 @@ class SyncruleController extends ActionController
{
$rule = $this->requireSyncRule();
// $rule->set('update_policy', 'replace');
$branchStore = new BranchStore($this->db());
$tmpBranchName = Branch::PREFIX_SYNC_PREVIEW . '/' . $rule->get('id');
$owner = $this->getAuth()->getUser()->getUsername();
if ($this->getBranch()->isBranch()) {
// We could keep changes for preview on branch too
$branchStore->deleteByName($tmpBranchName);
$tmpBranch = $branchStore->cloneBranchForSync($this->getBranch(), $tmpBranchName, $owner);
$after = 1600000000; // a date in 2020, minus 10000000
} else {
$tmpBranch = $branchStore->fetchOrCreateByName($tmpBranchName, $owner);
$after = null;
}
$store = new DbObjectStore($this->db(), $tmpBranch);
$this->tabs(new SyncRuleTabs($rule))->activate('preview');
$this->addTitle('Sync Preview');
$sync = new Sync($rule);
$this->addTitle($this->translate('Sync Preview'));
$sync = new Sync($rule, $store);
$fetchExpected = true;
if ($tmpBranch) {
if ($lastTime = $branchStore->getLastActivityTime($tmpBranch, $after)) {
if ((time() - $lastTime) > 100) {
$branchStore->wipeBranch($tmpBranch, $after);
} else {
$here = (new ClickHereForm())->handleRequest($this->getServerRequest());
if ($here->hasBeenClicked()) {
$branchStore->wipeBranch($tmpBranch, $after);
} else {
$fetchExpected = false;
}
$this->content()->add(Hint::info(Html::sprintf(
$this->translate('This preview has been generated %s, please click %s to regenerate it'),
DateFormatter::timeAgo($lastTime),
$here
)));
}
}
}
try {
$modifications = $sync->getExpectedModifications();
if ($fetchExpected) {
$modifications = $sync->getExpectedModifications();
if ($tmpBranch) {
$sync->apply();
}
} else {
return;
}
} catch (\Exception $e) {
$this->content()->add(Hint::error($e->getMessage()));
return;
}
if (empty($modifications)) {
if (empty($modifications) && $tmpBranch === null) {
$this->content()->add(Hint::ok($this->translate(
'This Sync Rule is in sync and would currently not apply any changes'
)));
@ -162,11 +236,25 @@ class SyncruleController extends ActionController
return;
}
if ($tmpBranch) {
if (!$fetchExpected) {
$sync->apply();
}
$changes = new BranchActivityTable($tmpBranch->getUuid(), $this->db());
$changes->disableObjectLink();
$changes->renderTo($this);
return;
}
$this->showExpectedModificationSummary($modifications);
}
protected function showExpectedModificationSummary($modifications)
{
$create = [];
$modify = [];
$delete = [];
$modifiedProperties = [];
/** @var IcingaObject $object */
foreach ($modifications as $object) {
if ($object->hasBeenLoadedFromDb()) {
@ -416,9 +504,16 @@ class SyncruleController extends ActionController
if (! $rule->hasSyncProperties()) {
$this->addPropertyHint($rule);
}
if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
return;
}
} else {
$this->addTitle($this->translate('Add sync rule'));
$this->tabs(new SyncRuleTabs())->activate('add');
if ($this->showNotInBranch($this->translate('Creating Sync Rules'))) {
return;
}
}
$form->handleRequest();
@ -451,6 +546,9 @@ class SyncruleController extends ActionController
['class' => 'icon-paste']
)
);
if ($this->showNotInBranch($this->translate('Cloning Sync Rules'))) {
return;
}
$form = new CloneSyncRuleForm($rule);
$this->content()->add($form);
@ -499,6 +597,14 @@ class SyncruleController extends ActionController
$ruleId = (int) $rule->get('id');
$form = SyncPropertyForm::load()->setDb($db);
$this->tabs(new SyncRuleTabs($rule))->activate('property');
$this->actions()->add(new Link(
$this->translate('back'),
'director/syncrule/property',
['rule_id' => $ruleId],
['class' => 'icon-left-big']
));
if ($id = $this->params->get('id')) {
$form->loadObject((int) $id);
$this->addTitle(
@ -506,24 +612,21 @@ class SyncruleController extends ActionController
$form->getObject()->get('destination_field'),
$rule->get('rule_name')
);
if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
return;
}
} else {
$this->addTitle(
$this->translate('Add sync property: %s'),
$rule->get('rule_name')
);
if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
return;
}
}
$form->setRule($rule);
$form->setSuccessUrl('director/syncrule/property', ['rule_id' => $ruleId]);
$this->actions()->add(new Link(
$this->translate('back'),
'director/syncrule/property',
['rule_id' => $ruleId],
['class' => 'icon-left-big']
));
$this->content()->add($form->handleRequest());
$this->tabs(new SyncRuleTabs($rule))->activate('property');
SyncpropertyTable::create($rule)
->handleSortPriorityActions($this->getRequest(), $this->getResponse())
->renderTo($this);

View File

@ -2,44 +2,66 @@
namespace Icinga\Module\Director\Forms;
use gipfl\Translation\TranslationHelper;
use gipfl\Web\Form;
use Icinga\Module\Director\Data\Db\DbObjectStore;
use Icinga\Module\Director\Import\Sync;
use Icinga\Module\Director\Objects\SyncRule;
use Icinga\Module\Director\Web\Form\DirectorForm;
class SyncRunForm extends DirectorForm
class SyncRunForm extends Form
{
use TranslationHelper;
protected $defaultDecoratorClass = null;
/** @var ?string */
protected $successMessage = null;
/** @var SyncRule */
protected $rule;
public function setSyncRule(SyncRule $rule)
/** @var DbObjectStore */
protected $store;
public function __construct(SyncRule $rule, DbObjectStore $store)
{
$this->rule = $rule;
return $this;
$this->store = $store;
}
public function setup()
public function assemble()
{
$this->submitLabel = false;
$this->addElement('submit', 'submit', array(
'label' => $this->translate('Trigger this Sync'),
'decorators' => array('ViewHelper')
));
if ($this->store->getBranch()->isBranch()) {
$label = sprintf($this->translate('Sync to Branch: %s'), $this->store->getBranch()->getName());
} else {
$label = $this->translate('Trigger this Sync');
}
$this->addElement('submit', 'submit', [
'label' => $label,
]);
}
/**
* @return string|null
*/
public function getSuccessMessage()
{
return $this->successMessage;
}
public function onSuccess()
{
$rule = $this->rule;
$changed = $rule->applyChanges();
if ($changed) {
$this->setSuccessMessage(
$this->translate(('Source has successfully been synchronized'))
);
} elseif ($rule->get('sync_state') === 'in-sync') {
$this->notifySuccess(
$this->translate('Nothing changed, rule is in sync')
);
$sync = new Sync($this->rule, $this->store);
if ($sync->hasModifications()) {
if ($sync->apply()) {
// and changed
$this->successMessage = $this->translate(('Source has successfully been synchronized'));
} else {
$this->successMessage = $this->translate('Nothing changed, rule is in sync');
}
} else {
$this->addError($this->translate('Synchronization failed'));
// Used to be $rule->get('sync_state') === 'in-sync', $changed = $rule->applyChanges();
$this->successMessage = $this->translate('Nothing to do, rule is in sync');
}
}
}

View File

@ -6,6 +6,10 @@ 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 Icinga\Module\Director\Db\Branch\MergeErrorDeleteMissingObject;
use Icinga\Module\Director\Db\Branch\MergeErrorModificationForMissingObject;
use Icinga\Module\Director\Db\Branch\MergeErrorRecreateOnMerge;
use Icinga\Module\Director\Objects\IcingaObject;
use Ramsey\Uuid\UuidInterface;
/**
@ -48,6 +52,75 @@ class DbObjectStore
return $object;
}
/**
* @param string $tableName
* @param string $arrayIdx
* @return DbObject[]|IcingaObject[]
* @throws MergeErrorRecreateOnMerge
* @throws MergeErrorDeleteMissingObject
* @throws MergeErrorModificationForMissingObject
*/
public function loadAll($tableName, $arrayIdx = 'uuid')
{
$db = $this->connection->getDbAdapter();
$class = DbObjectTypeRegistry::classByType($tableName);
$query = $db->select()->from($tableName)->order('uuid');
$result = [];
foreach ($db->fetchAll($query) as $row) {
$result[$row->uuid] = $class::create((array) $row, $this->connection);
$result[$row->uuid]->setBeingLoadedFromDb();
}
if ($this->branch && $this->branch->isBranch()) {
$query = $db->select()
->from(BranchActivity::DB_TABLE)
->where('branch_uuid = ?', $this->connection->quoteBinary($this->branch->getUuid()->getBytes()))
->order('timestamp_ns ASC');
$rows = $db->fetchAll($query);
foreach ($rows as $row) {
$activity = BranchActivity::fromDbRow($row);
if ($activity->getObjectTable() !== $tableName) {
continue;
}
$uuid = $activity->getObjectUuid();
$binaryUuid = $uuid->getBytes();
$exists = isset($result[$binaryUuid]);
if ($activity->isActionCreate()) {
if ($exists) {
throw new MergeErrorRecreateOnMerge($activity);
} else {
$new = $activity->createDbObject($this->connection);
$new->setBeingLoadedFromDb();
$result[$binaryUuid] = $new;
}
} elseif ($activity->isActionDelete()) {
if ($exists) {
unset($result[$binaryUuid]);
} else {
throw new MergeErrorDeleteMissingObject($activity);
}
} else {
if ($exists) {
$activity->applyToDbObject($result[$binaryUuid])->setBeingLoadedFromDb();
} else {
throw new MergeErrorModificationForMissingObject($activity);
}
}
}
}
if ($arrayIdx === 'uuid') {
return $result;
}
$indexedResult = [];
foreach ($result as $object) {
$indexedResult[$object->get($arrayIdx)] = $object;
}
return $indexedResult;
}
public function exists($tableName, UuidInterface $uuid)
{
return BranchedObject::exists($this->connection, $tableName, $uuid, $this->branch->getUuid());

View File

@ -18,6 +18,8 @@ use stdClass;
*/
class Branch
{
const PREFIX_SYNC_PREVIEW = '/syncpreview';
/** @var UuidInterface|null */
protected $branchUuid;
@ -186,4 +188,9 @@ class Branch
{
return $this->owner;
}
public function isSyncPreview()
{
return (bool) preg_match('/^' . preg_quote(self::PREFIX_SYNC_PREVIEW, '/') . '\//', $this->getName());
}
}

View File

@ -121,6 +121,16 @@ class BranchActivity
);
}
public static function fixFakeTimestamp($timestampNs)
{
if ($timestampNs < 1600000000 * 1000000) {
// fake TS for cloned branch in sync preview
return (int) $timestampNs * 1000000;
}
return $timestampNs;
}
public function applyToDbObject(DbObject $object)
{
if (!$this->isActionModify()) {
@ -260,7 +270,7 @@ class BranchActivity
*/
public function getTimestamp()
{
return (int) floor($this->timestampNs / 1000000);
return (int) floor(BranchActivity::fixFakeTimestamp($this->timestampNs) / 1000000);
}
/**

View File

@ -3,17 +3,36 @@
namespace Icinga\Module\Director\Db\Branch;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\DbUtil;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
class BranchStore
{
const TABLE = 'director_branch';
const TABLE_ACTIVITY = 'director_branch_activity';
const OBJECT_TABLES = [
'branched_icinga_apiuser',
'branched_icinga_command',
'branched_icinga_dependency',
'branched_icinga_endpoint',
'branched_icinga_host',
'branched_icinga_hostgroup',
'branched_icinga_notification',
'branched_icinga_scheduled_downtime',
'branched_icinga_service',
'branched_icinga_service_set',
'branched_icinga_servicegroup',
'branched_icinga_timeperiod',
'branched_icinga_user',
'branched_icinga_usergroup',
'branched_icinga_zone',
];
protected $connection;
protected $db;
protected $table = 'director_branch';
public function __construct(Db $connection)
{
$this->connection = $connection;
@ -40,6 +59,67 @@ class BranchStore
return $this->newFromDbResult($this->select()->where('b.branch_name = ?', $name));
}
public function cloneBranchForSync(Branch $branch, $newName, $owner)
{
$this->runTransaction(function ($db) use ($branch, $newName, $owner) {
$tables = self::OBJECT_TABLES;
$tables[] = self::TABLE_ACTIVITY;
$newBranch = $this->createBranchByName($newName, $owner);
$oldQuotedUuid = DbUtil::quoteBinaryCompat($branch->getUuid()->getBytes(), $db);
$quotedUuid = DbUtil::quoteBinaryCompat($newBranch->getUuid()->getBytes(), $db);
// $timestampNs = (int)floor(microtime(true) * 1000000);
// Hint: would love to do SELECT *, $quotedUuid AS branch_uuid FROM $table INTO $table
foreach ($tables as $table) {
$rows = $db->fetchAll($db->select()->from($table)->where('branch_uuid = ?', $oldQuotedUuid));
foreach ($rows as $row) {
$modified = (array)$row;
$modified['branch_uuid'] = $quotedUuid;
if ($table === self::TABLE_ACTIVITY) {
$modified['timestamp_ns'] = round($modified['timestamp_ns'] / 1000000);
}
$db->insert($table, $modified);
}
}
});
return $this->fetchBranchByName($newName);
}
protected function runTransaction($callback)
{
$db = $this->db;
$db->beginTransaction();
try {
$callback($db);
$db->commit();
} catch (\Exception $e) {
try {
$db->rollBack();
} catch (\Exception $ignored) {
//
}
throw $e;
}
}
public function wipeBranch(Branch $branch, $after = null)
{
$this->runTransaction(function ($db) use ($branch, $after) {
$tables = self::OBJECT_TABLES;
$tables[] = self::TABLE_ACTIVITY;
$quotedUuid = DbUtil::quoteBinaryCompat($branch->getUuid()->getBytes(), $db);
$where = $db->quoteInto('branch_uuid = ?', $quotedUuid);
foreach ($tables as $table) {
if ($after && $table === self::TABLE_ACTIVITY) {
$db->delete($table, $where . ' AND timestamp_ns > ' . (int) $after);
} else {
$db->delete($table, $where);
}
}
});
}
protected function newFromDbResult($query)
{
if ($row = $this->db->fetchRow($query)) {
@ -78,7 +158,7 @@ class BranchStore
'ts_merge_request' => 'b.ts_merge_request',
'cnt_activities' => 'COUNT(ba.timestamp_ns)',
])->joinLeft(
['ba' => 'director_branch_activity'],
['ba' => self::TABLE_ACTIVITY],
'b.uuid = ba.branch_uuid',
[]
)->group('b.uuid');
@ -114,7 +194,7 @@ class BranchStore
'description' => null,
'ts_merge_request' => null,
];
$this->db->insert($this->table, $properties);
$this->db->insert(self::TABLE, $properties);
if ($branch = static::fetchBranchByUuid($uuid)) {
return $branch;
@ -128,14 +208,49 @@ class BranchStore
public function deleteByUuid(UuidInterface $uuid)
{
return $this->db->delete($this->table, $this->db->quoteInto(
return $this->db->delete(self::TABLE, $this->db->quoteInto(
'uuid = ?',
$this->connection->quoteBinary($uuid->getBytes())
));
}
/**
* @param string $name
* @return int
*/
public function deleteByName($name)
{
return $this->db->delete(self::TABLE, $this->db->quoteInto(
'branch_name = ?',
$name
));
}
public function delete(Branch $branch)
{
return $this->deleteByUuid($branch->getUuid());
}
/**
* @param Branch $branch
* @param ?int $after
* @return float|null
*/
public function getLastActivityTime(Branch $branch, $after = null)
{
$db = $this->db;
$query = $db->select()
->from(self::TABLE_ACTIVITY, 'MAX(timestamp_ns)')
->where('branch_uuid = ?', DbUtil::quoteBinaryCompat($branch->getUuid()->getBytes(), $db));
if ($after) {
$query->where('timestamp_ns > ?', (int) $after);
}
$last = $db->fetchOne($query);
if ($last) {
return $last / 1000000;
}
return null;
}
}

View File

@ -7,6 +7,8 @@ use Icinga\Application\Benchmark;
use Icinga\Data\Filter\Filter;
use Icinga\Module\Director\Application\MemoryLimit;
use Icinga\Module\Director\Data\Db\DbObject;
use Icinga\Module\Director\Data\Db\DbObjectStore;
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\Cache\PrefetchCache;
use Icinga\Module\Director\Objects\HostGroupMembershipResolver;
@ -76,13 +78,18 @@ class Sync
/** @var HostGroupMembershipResolver|bool */
protected $hostGroupMembershipResolver;
/** @var ?DbObjectStore */
protected $store;
/**
* @param SyncRule $rule
* @param ?DbObjectStore $store
*/
public function __construct(SyncRule $rule)
public function __construct(SyncRule $rule, DbObjectStore $store = null)
{
$this->rule = $rule;
$this->db = $rule->getConnection();
$this->store = $store;
}
/**
@ -388,11 +395,13 @@ class Sync
if ($this->rule->hasCombinedKey()) {
$this->objects = [];
$destinationKeyPattern = $this->rule->getDestinationKeyPattern();
if ($this->store) {
$objects = $this->store->loadAll(DbObjectTypeRegistry::tableNameByType($ruleObjectType));
} else {
$objects = IcingaObject::loadAllByType($ruleObjectType, $this->db);
}
foreach (IcingaObject::loadAllByType(
$ruleObjectType,
$this->db
) as $object) {
foreach ($objects as $object) {
if ($object instanceof IcingaService) {
if (strstr($destinationKeyPattern, '${host}')
&& $object->get('host_id') === null
@ -421,10 +430,11 @@ class Sync
$this->objects[$key] = $object;
}
} else {
$this->objects = IcingaObject::loadAllByType(
$ruleObjectType,
$this->db
);
if ($this->store) {
$this->objects = $this->store->loadAll(DbObjectTypeRegistry::tableNameByType($ruleObjectType), 'object_name');
} else {
$this->objects = IcingaObject::loadAllByType($ruleObjectType, $this->db);
}
}
// TODO: should be obsoleted by a better "loadFiltered" method
@ -759,7 +769,9 @@ class Sync
$objects = $this->prepare();
$db = $this->db;
$dba = $db->getDbAdapter();
$dba->beginTransaction();
if (! $this->store) { // store has it's own transaction
$dba->beginTransaction();
}
$object = null;
$updateOnly = $this->rule->get('update_policy') === 'update-only';
@ -777,7 +789,11 @@ class Sync
foreach ($objects as $object) {
$this->setResolver($object);
if (! $updateOnly && $object->shouldBeRemoved()) {
$object->delete();
if ($this->store) {
$this->store->delete($object);
} else {
$object->delete();
}
$deleted++;
continue;
}
@ -785,10 +801,18 @@ class Sync
if ($object->hasBeenModified()) {
$existing = $object->hasBeenLoadedFromDb();
if ($existing) {
$object->store($db);
if ($this->store) {
$this->store->store($object);
} else {
$object->store($db);
}
$modified++;
} elseif ($allowCreate) {
$object->store($db);
if ($this->store) {
$this->store->store($object);
} else {
$object->store($db);
}
$created++;
}
}
@ -810,7 +834,9 @@ class Sync
$this->run->setProperties($runProperties)->store();
$this->notifyResolvers();
$dba->commit();
if (! $this->store) {
$dba->commit();
}
// Store duration after commit, as the commit might take some time
$this->run->set('duration_ms', (int) round(
@ -819,13 +845,15 @@ class Sync
Benchmark::measure('Done applying objects');
} catch (Exception $e) {
$dba->rollBack();
if (! $this->store) {
$dba->rollBack();
}
if ($object !== null && $object instanceof IcingaObject) {
if ($object instanceof IcingaObject) {
throw new IcingaException(
'Exception while syncing %s %s: %s',
get_class($object),
$object->get('object_name'),
$object->getObjectName(),
$e->getMessage(),
$e
);
@ -839,6 +867,9 @@ class Sync
protected function prepareCache()
{
if ($this->store) {
return $this;
}
PrefetchCache::initialize($this->db);
IcingaTemplateRepository::clear();

View File

@ -0,0 +1,31 @@
<?php
namespace Icinga\Module\Director\Web\Form;
use gipfl\Translation\TranslationHelper;
use gipfl\Web\InlineForm;
class ClickHereForm extends InlineForm
{
use TranslationHelper;
protected $hasBeenClicked = false;
protected function assemble()
{
$this->addElement('submit', 'submit', [
'label' => $this->translate('here'),
'class' => 'link-button'
]);
}
public function hasBeenClicked()
{
return $this->hasBeenClicked;
}
public function onSuccess()
{
$this->hasBeenClicked = true;
}
}

View File

@ -23,6 +23,8 @@ class BranchActivityTable extends ZfQueryBasedTable
/** @var LocalTimeFormat */
protected $timeFormat;
protected $linkToObject = true;
public function __construct(UuidInterface $branchUuid, $db, UuidInterface $objectUuid = null)
{
$this->branchUuid = $branchUuid;
@ -38,7 +40,7 @@ class BranchActivityTable extends ZfQueryBasedTable
public function renderRow($row)
{
$ts = (int) floor($row->timestamp_ns / 1000000);
$ts = (int) floor(BranchActivity::fixFakeTimestamp($row->timestamp_ns) / 1000000);
$this->splitByDay($ts);
$activity = BranchActivity::fromDbRow($row);
return $this::tr([
@ -47,8 +49,17 @@ class BranchActivityTable extends ZfQueryBasedTable
])->addAttributes(['class' => ['action-' . $activity->getAction(), 'branched']]);
}
public function disableObjectLink()
{
$this->linkToObject = false;
return $this;
}
protected function linkObject(BranchActivity $activity)
{
if (! $this->linkToObject) {
return $activity->getObjectName();
}
// $type, UuidInterface $uuid
// Later on replacing, service_set -> serviceset
$type = preg_replace('/^icinga_/', '', $activity->getObjectTable());