mirror of
https://github.com/Icinga/icingaweb2-module-director.git
synced 2025-07-30 01:04:12 +02:00
Sync: support branches
This commit is contained in:
parent
1682175716
commit
b2afca2496
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
31
library/Director/Web/Form/ClickHereForm.php
Normal file
31
library/Director/Web/Form/ClickHereForm.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
|
Loading…
x
Reference in New Issue
Block a user