Merge branch 'feature/serviceset-in-branches-refactored'

This commit is contained in:
Thomas Gelf 2022-09-20 15:27:45 +02:00
commit b6bfe913f8
35 changed files with 1253 additions and 337 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\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\Db\Branch\BranchStore;
use Icinga\Module\Director\IcingaConfig\IcingaConfig; use Icinga\Module\Director\IcingaConfig\IcingaConfig;
use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Objects\SyncRule;
use Icinga\Module\Director\PlainObjectRenderer; use Icinga\Module\Director\PlainObjectRenderer;
use Icinga\Module\Director\Web\Controller\ActionController; use Icinga\Module\Director\Web\Controller\ActionController;
use Icinga\Module\Director\Web\Controller\BranchHelper; use Icinga\Module\Director\Web\Controller\BranchHelper;
@ -24,6 +26,7 @@ class BranchController extends ActionController
{ {
parent::init(); parent::init();
IcingaObject::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch())); IcingaObject::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch()));
SyncRule::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch()));
} }
protected function checkDirectorPermissions() protected function checkDirectorPermissions()
@ -34,9 +37,17 @@ class BranchController extends ActionController
{ {
$this->assertPermission('director/showconfig'); $this->assertPermission('director/showconfig');
$ts = $this->params->getRequired('ts'); $ts = $this->params->getRequired('ts');
$this->addSingleTab($this->translate('Activity'));
$this->addTitle($this->translate('Branch Activity'));
$activity = BranchActivity::load($ts, $this->db()); $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->content()->add($this->prepareActivityInfo($activity));
$this->showActivity($activity); $this->showActivity($activity);
} }

View File

@ -119,12 +119,10 @@ class HostController extends ObjectController
$host = $this->getHostObject(); $host = $this->getHostObject();
$this->addServicesHeader(); $this->addServicesHeader();
$this->addTitle($this->translate('Add Service Set to %s'), $host->getObjectName()); $this->addTitle($this->translate('Add Service Set to %s'), $host->getObjectName());
if ($this->showNotInBranch($this->translate('Creating Service Sets'))) {
return;
}
$this->content()->add( $this->content()->add(
IcingaServiceSetForm::load() IcingaServiceSetForm::load()
->setBranch($this->getBranch())
->setHost($host) ->setHost($host)
->setDb($this->db()) ->setDb($this->db())
->handleRequest() ->handleRequest()
@ -209,11 +207,11 @@ class HostController extends ObjectController
$branch = $this->getBranch(); $branch = $this->getBranch();
$hostHasBeenCreatedInBranch = $branch->isBranch() && $host->get('id'); $hostHasBeenCreatedInBranch = $branch->isBranch() && $host->get('id');
$content = $this->content(); $content = $this->content();
$table = (new ObjectsTableService($this->db()))->setAuth($this->Auth())->setHost($host) $table = (new ObjectsTableService($this->db()))
->setAuth($this->Auth())
->setHost($host)
->setBranch($branch)
->setTitle($this->translate('Individual Service objects')); ->setTitle($this->translate('Individual Service objects'));
if ($branch->isBranch()) {
$table->setBranchUuid($branch->getUuid());
}
if (count($table)) { if (count($table)) {
$content->add($table); $content->add($table);
@ -225,6 +223,7 @@ class HostController extends ObjectController
foreach ($parents as $parent) { foreach ($parents as $parent) {
$table = (new ObjectsTableService($this->db())) $table = (new ObjectsTableService($this->db()))
->setAuth($this->Auth()) ->setAuth($this->Auth())
->setBranch($branch)
->setHost($parent) ->setHost($parent)
->setInheritedBy($host); ->setInheritedBy($host);
if (count($table)) { if (count($table)) {
@ -251,6 +250,7 @@ class HostController extends ObjectController
$content->add( $content->add(
IcingaServiceSetServiceTable::load($set) IcingaServiceSetServiceTable::load($set)
// ->setHost($host) // ->setHost($host)
->setBranch($branch)
->setAffectedHost($host) ->setAffectedHost($host)
->setTitle($title) ->setTitle($title)
); );
@ -279,6 +279,7 @@ class HostController extends ObjectController
$host = $this->getHostObject(); $host = $this->getHostObject();
$service = $this->params->getRequired('service'); $service = $this->params->getRequired('service');
$db = $this->db(); $db = $this->db();
$branch = $this->getBranch();
$this->controls()->setTabs(new Tabs()); $this->controls()->setTabs(new Tabs());
$this->addSingleTab($this->translate('Configuration (read-only)')); $this->addSingleTab($this->translate('Configuration (read-only)'));
$this->addTitle($this->translate('Services on %s'), $host->getObjectName()); $this->addTitle($this->translate('Services on %s'), $host->getObjectName());
@ -287,6 +288,7 @@ class HostController extends ObjectController
$table = (new ObjectsTableService($db)) $table = (new ObjectsTableService($db))
->setAuth($this->Auth()) ->setAuth($this->Auth())
->setHost($host) ->setHost($host)
->setBranch($branch)
->setReadonly() ->setReadonly()
->highlightService($service) ->highlightService($service)
->setTitle($this->translate('Individual Service objects')); ->setTitle($this->translate('Individual Service objects'));
@ -301,6 +303,7 @@ class HostController extends ObjectController
foreach ($parents as $parent) { foreach ($parents as $parent) {
$table = (new ObjectsTableService($db)) $table = (new ObjectsTableService($db))
->setReadonly() ->setReadonly()
->setBranch($branch)
->setHost($parent) ->setHost($parent)
->highlightService($service) ->highlightService($service)
->setInheritedBy($host); ->setInheritedBy($host);
@ -326,6 +329,7 @@ class HostController extends ObjectController
$content->add( $content->add(
IcingaServiceSetServiceTable::load($set) IcingaServiceSetServiceTable::load($set)
// ->setHost($host) // ->setHost($host)
->setBranch($branch)
->setAffectedHost($host) ->setAffectedHost($host)
->setReadonly() ->setReadonly()
->highlightService($service) ->highlightService($service)
@ -377,6 +381,7 @@ class HostController extends ObjectController
$title = sprintf($this->translate('%s (Service set)'), $name); $title = sprintf($this->translate('%s (Service set)'), $name);
$table = IcingaServiceSetServiceTable::load($set) $table = IcingaServiceSetServiceTable::load($set)
->setHost($host) ->setHost($host)
->setBranch($this->getBranch())
->setAffectedHost($affectedHost) ->setAffectedHost($affectedHost)
->setTitle($title); ->setTitle($title);
if ($roService) { if ($roService) {
@ -413,6 +418,7 @@ class HostController extends ObjectController
$this->content()->add( $this->content()->add(
IcingaServiceForm::load() IcingaServiceForm::load()
->setDb($db) ->setDb($db)
->setBranch($this->getBranch())
->setHost($host) ->setHost($host)
->setApplyGenerated($parent) ->setApplyGenerated($parent)
->setObject($service) ->setObject($service)
@ -453,6 +459,7 @@ class HostController extends ObjectController
$form = IcingaServiceForm::load() $form = IcingaServiceForm::load()
->setDb($db) ->setDb($db)
->setBranch($this->getBranch())
->setHost($host) ->setHost($host)
->setInheritedFrom($from->getObjectName()) ->setInheritedFrom($from->getObjectName())
->setObject($service) ->setObject($service)
@ -530,6 +537,7 @@ class HostController extends ObjectController
$form = IcingaServiceForm::load() $form = IcingaServiceForm::load()
->setDb($db) ->setDb($db)
->setBranch($this->getBranch())
->setHost($host) ->setHost($host)
->setServiceSet($set) ->setServiceSet($set)
->setObject($service) ->setObject($service)

View File

@ -41,6 +41,9 @@ class ServiceController extends ObjectController
public function init() public function init()
{ {
// This happens in parent::init() too, but is required to take place before the next two lines
$this->enableStaticObjectLoader($this->getTableName());
// Hint: having Host and Set loaded first is important for UUID lookups with legacy URLs // Hint: having Host and Set loaded first is important for UUID lookups with legacy URLs
$this->host = $this->getOptionalRelatedObjectFromParams('host', 'host'); $this->host = $this->getOptionalRelatedObjectFromParams('host', 'host');
$this->set = $this->getOptionalRelatedObjectFromParams('service_set', 'set'); $this->set = $this->getOptionalRelatedObjectFromParams('service_set', 'set');

View File

@ -71,7 +71,9 @@ class ServicesetController extends ObjectController
['class' => 'icon-plus'] ['class' => 'icon-plus']
)); ));
IcingaServiceSetServiceTable::load($set)->renderTo($this); IcingaServiceSetServiceTable::load($set)
->setBranch($this->getBranch())
->renderTo($this);
} }
public function hostsAction() public function hostsAction()
@ -98,17 +100,19 @@ class ServicesetController extends ObjectController
protected function addServiceSetTabs() protected function addServiceSetTabs()
{ {
if ($this->branch->isBranch()) { $hexUuid = $this->object->getUniqueId()->toString();
return $this;
}
$tabs = $this->tabs(); $tabs = $this->tabs();
$tabs->add('services', [ $tabs->add('services', [
'url' => 'director/serviceset/services', 'url' => 'director/serviceset/services',
'urlParams' => ['uuid' => $this->object->getUniqueId()], 'urlParams' => ['uuid' => $hexUuid],
'label' => 'Services' 'label' => 'Services'
])->add('hosts', [ ]);
if ($this->branch->isBranch()) {
return $this;
}
$tabs->add('hosts', [
'url' => 'director/serviceset/hosts', 'url' => 'director/serviceset/hosts',
'urlParams' => ['uuid' => $this->object->getUniqueId()], 'urlParams' => ['uuid' => $hexUuid],
'label' => 'Hosts' 'label' => 'Hosts'
]); ]);

View File

@ -4,6 +4,15 @@ namespace Icinga\Module\Director\Controllers;
use gipfl\IcingaWeb2\Link; use gipfl\IcingaWeb2\Link;
use gipfl\Web\Widget\Hint; 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\Db\Branch\BranchSupport;
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\IcingaConfigDiff;
use Icinga\Module\Director\Web\Widget\UnorderedList; use Icinga\Module\Director\Web\Widget\UnorderedList;
use Icinga\Module\Director\Db\Cache\PrefetchCache; use Icinga\Module\Director\Db\Cache\PrefetchCache;
@ -26,11 +35,14 @@ use Icinga\Module\Director\Web\Table\SyncpropertyTable;
use Icinga\Module\Director\Web\Table\SyncRunTable; use Icinga\Module\Director\Web\Table\SyncRunTable;
use Icinga\Module\Director\Web\Tabs\SyncRuleTabs; use Icinga\Module\Director\Web\Tabs\SyncRuleTabs;
use Icinga\Module\Director\Web\Widget\SyncRunDetails; use Icinga\Module\Director\Web\Widget\SyncRunDetails;
use Icinga\Web\Notification;
use ipl\Html\Form; use ipl\Html\Form;
use ipl\Html\Html; use ipl\Html\Html;
class SyncruleController extends ActionController class SyncruleController extends ActionController
{ {
use BranchHelper;
/** /**
* @throws \Icinga\Exception\NotFoundError * @throws \Icinga\Exception\NotFoundError
*/ */
@ -43,7 +55,18 @@ class SyncruleController extends ActionController
$this->addTitle($this->translate('Sync rule: %s'), $ruleName); $this->addTitle($this->translate('Sync rule: %s'), $ruleName);
$checkForm = SyncCheckForm::load()->setSyncRule($rule)->handleRequest(); $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()) { if ($lastRunId = $rule->getLastSyncRunId()) {
$run = SyncRun::load($lastRunId, $this->db()); $run = SyncRun::load($lastRunId, $this->db());
@ -98,6 +121,15 @@ class SyncruleController extends ActionController
} }
$c->add($checkForm); $c->add($checkForm);
if ($this->hasBranch()) {
$objectType = $rule->get('object_type');
$table = DbObjectTypeRegistry::tableNameByType($objectType);
if (! BranchSupport::existsForTableName($table)) {
$this->showNotInBranch(sprintf($this->translate("Synchronizing '%s'"), $objectType));
return;
}
}
$c->add($runForm); $c->add($runForm);
if ($run) { if ($run) {
@ -142,31 +174,97 @@ class SyncruleController extends ActionController
public function previewAction() public function previewAction()
{ {
$rule = $this->requireSyncRule(); $rule = $this->requireSyncRule();
// $rule->set('update_policy', 'replace'); $branchSupport = BranchSupport::existsForSyncRule($rule);
$branchStore = new BranchStore($this->db());
$owner = $this->getAuth()->getUser()->getUsername();
if ($branchSupport) {
if ($this->getBranch()->isBranch()) {
$tmpBranchName = sprintf(
'%s/%s-%s',
Branch::PREFIX_SYNC_PREVIEW,
$this->getBranch()->getUuid()->toString(),
$rule->get('id')
);
// 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 {
$tmpBranchName = Branch::PREFIX_SYNC_PREVIEW . '/' . $rule->get('id');
$tmpBranch = $branchStore->fetchOrCreateByName($tmpBranchName, $owner);
$after = null;
}
$store = new DbObjectStore($this->db(), $tmpBranch);
} else {
$tmpBranch = $store = null;
}
$this->tabs(new SyncRuleTabs($rule))->activate('preview'); $this->tabs(new SyncRuleTabs($rule))->activate('preview');
$this->addTitle('Sync Preview'); $this->addTitle($this->translate('Sync Preview'));
$sync = new Sync($rule); $sync = new Sync($rule, $store);
try { $keepBranchPreview = false;
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);
$this->redirectNow($this->url());
} else {
$keepBranchPreview = true;
}
$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
)));
}
}
}
if (!$keepBranchPreview) {
$modifications = $sync->getExpectedModifications(); $modifications = $sync->getExpectedModifications();
} catch (\Exception $e) {
$this->content()->add(Hint::error($e->getMessage()));
return;
} }
if (empty($modifications)) { if ($tmpBranch) {
$this->content()->add(Hint::ok($this->translate( try {
'This Sync Rule is in sync and would currently not apply any changes' if (!$keepBranchPreview) {
))); $sync->apply();
}
} catch (\Exception $e) {
$this->content()->add(Hint::error($e->getMessage()));
return;
}
return; $changes = new BranchActivityTable($tmpBranch->getUuid(), $this->db());
$changes->disableObjectLink();
if (count($changes) === 0) {
$this->showInSync();
}
$changes->renderTo($this);
} else {
if (empty($modifications)) {
$this->showInSync();
return;
}
$this->showExpectedModificationSummary($modifications);
} }
}
protected function showInSync()
{
$this->content()->add(Hint::ok($this->translate(
'This Sync Rule is in sync and would currently not apply any changes'
)));
}
protected function showExpectedModificationSummary($modifications)
{
$create = []; $create = [];
$modify = []; $modify = [];
$delete = []; $delete = [];
$modifiedProperties = []; $modifiedProperties = [];
/** @var IcingaObject $object */ /** @var IcingaObject $object */
foreach ($modifications as $object) { foreach ($modifications as $object) {
if ($object->hasBeenLoadedFromDb()) { if ($object->hasBeenLoadedFromDb()) {
@ -416,9 +514,16 @@ class SyncruleController extends ActionController
if (! $rule->hasSyncProperties()) { if (! $rule->hasSyncProperties()) {
$this->addPropertyHint($rule); $this->addPropertyHint($rule);
} }
if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
return;
}
} else { } else {
$this->addTitle($this->translate('Add sync rule')); $this->addTitle($this->translate('Add sync rule'));
$this->tabs(new SyncRuleTabs())->activate('add'); $this->tabs(new SyncRuleTabs())->activate('add');
if ($this->showNotInBranch($this->translate('Creating Sync Rules'))) {
return;
}
} }
$form->handleRequest(); $form->handleRequest();
@ -451,6 +556,9 @@ class SyncruleController extends ActionController
['class' => 'icon-paste'] ['class' => 'icon-paste']
) )
); );
if ($this->showNotInBranch($this->translate('Cloning Sync Rules'))) {
return;
}
$form = new CloneSyncRuleForm($rule); $form = new CloneSyncRuleForm($rule);
$this->content()->add($form); $this->content()->add($form);
@ -499,6 +607,14 @@ class SyncruleController extends ActionController
$ruleId = (int) $rule->get('id'); $ruleId = (int) $rule->get('id');
$form = SyncPropertyForm::load()->setDb($db); $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')) { if ($id = $this->params->get('id')) {
$form->loadObject((int) $id); $form->loadObject((int) $id);
$this->addTitle( $this->addTitle(
@ -506,24 +622,21 @@ class SyncruleController extends ActionController
$form->getObject()->get('destination_field'), $form->getObject()->get('destination_field'),
$rule->get('rule_name') $rule->get('rule_name')
); );
if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
return;
}
} else { } else {
$this->addTitle( $this->addTitle(
$this->translate('Add sync property: %s'), $this->translate('Add sync property: %s'),
$rule->get('rule_name') $rule->get('rule_name')
); );
if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
return;
}
} }
$form->setRule($rule); $form->setRule($rule);
$form->setSuccessUrl('director/syncrule/property', ['rule_id' => $ruleId]); $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->content()->add($form->handleRequest());
$this->tabs(new SyncRuleTabs($rule))->activate('property');
SyncpropertyTable::create($rule) SyncpropertyTable::create($rule)
->handleSortPriorityActions($this->getRequest(), $this->getResponse()) ->handleSortPriorityActions($this->getRequest(), $this->getResponse())
->renderTo($this); ->renderTo($this);

View File

@ -128,6 +128,8 @@ class IcingaServiceForm extends DirectorObjectForm
if (! $this->providesOverrides()) { if (! $this->providesOverrides()) {
return; return;
} }
$hasDeleteButton = false;
$isBranch = $this->branch && $this->branch->isBranch();
if ($this->hasBeenBlacklisted()) { if ($this->hasBeenBlacklisted()) {
$this->addHtml( $this->addHtml(
@ -135,7 +137,10 @@ class IcingaServiceForm extends DirectorObjectForm
['name' => 'HINT_blacklisted'] ['name' => 'HINT_blacklisted']
); );
$group = null; $group = null;
$this->addDeleteButton($this->translate('Reactivate')); if (! $isBranch) {
$this->addDeleteButton($this->translate('Reactivate'));
$hasDeleteButton = true;
}
$this->setSubmitLabel(false); $this->setSubmitLabel(false);
} else { } else {
$this->addOverrideHint(); $this->addOverrideHint();
@ -164,10 +169,13 @@ class IcingaServiceForm extends DirectorObjectForm
$this->setSubmitLabel(false); $this->setSubmitLabel(false);
} }
$this->addDeleteButton($this->translate('Deactivate')); if (! $isBranch) {
$this->addDeleteButton($this->translate('Deactivate'));
$hasDeleteButton = true;
}
} }
if (! $this->hasSubmitButton()) { if (! $this->hasSubmitButton() && $hasDeleteButton) {
$this->addDisplayGroup([$this->deleteButtonName], 'buttons', [ $this->addDisplayGroup([$this->deleteButtonName], 'buttons', [
'decorators' => [ 'decorators' => [
'FormElements', 'FormElements',
@ -216,13 +224,19 @@ class IcingaServiceForm extends DirectorObjectForm
if ($this->blacklisted === null) { if ($this->blacklisted === null) {
$host = $this->host; $host = $this->host;
// Safety check, branches
$hostId = $host->get('id');
$service = $this->getServiceToBeBlacklisted(); $service = $this->getServiceToBeBlacklisted();
$serviceId = $service->get('id');
if (! $hostId || ! $serviceId) {
return false;
}
$db = $this->db->getDbAdapter(); $db = $this->db->getDbAdapter();
if ($this->providesOverrides()) { if ($this->providesOverrides()) {
$this->blacklisted = 1 === (int)$db->fetchOne( $this->blacklisted = 1 === (int)$db->fetchOne(
$db->select()->from('icinga_host_service_blacklist', 'COUNT(*)') $db->select()->from('icinga_host_service_blacklist', 'COUNT(*)')
->where('host_id = ?', $host->get('id')) ->where('host_id = ?', $hostId)
->where('service_id = ?', $service->get('id')) ->where('service_id = ?', $serviceId)
); );
} else { } else {
$this->blacklisted = false; $this->blacklisted = false;

View File

@ -69,7 +69,8 @@ class IcingaServiceSetForm extends DirectorObjectForm
} }
$this->addHidden('object_type', 'object'); $this->addHidden('object_type', 'object');
$this->addHidden('host_id', $this->host->id); $this->addHidden('host', $this->host->getObjectName());
$this->groupMainProperties();
} }
public function setHost(IcingaHost $host) public function setHost(IcingaHost $host)
@ -77,6 +78,7 @@ class IcingaServiceSetForm extends DirectorObjectForm
$this->host = $host; $this->host = $host;
return $this; return $this;
} }
protected function addSingleImportsElement() protected function addSingleImportsElement()
{ {
$enum = $this->enumAllowedTemplates(); $enum = $this->enumAllowedTemplates();

View File

@ -2,44 +2,66 @@
namespace Icinga\Module\Director\Forms; 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\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 */ /** @var SyncRule */
protected $rule; protected $rule;
public function setSyncRule(SyncRule $rule) /** @var DbObjectStore */
protected $store;
public function __construct(SyncRule $rule, DbObjectStore $store)
{ {
$this->rule = $rule; $this->rule = $rule;
return $this; $this->store = $store;
} }
public function setup() public function assemble()
{ {
$this->submitLabel = false; if ($this->store->getBranch()->isBranch()) {
$this->addElement('submit', 'submit', array( $label = sprintf($this->translate('Sync to Branch: %s'), $this->store->getBranch()->getName());
'label' => $this->translate('Trigger this Sync'), } else {
'decorators' => array('ViewHelper') $label = $this->translate('Trigger this Sync');
)); }
$this->addElement('submit', 'submit', [
'label' => $label,
]);
}
/**
* @return string|null
*/
public function getSuccessMessage()
{
return $this->successMessage;
} }
public function onSuccess() public function onSuccess()
{ {
$rule = $this->rule; $sync = new Sync($this->rule, $this->store);
$changed = $rule->applyChanges(); if ($sync->hasModifications()) {
if ($sync->apply()) {
if ($changed) { // and changed
$this->setSuccessMessage( $this->successMessage = $this->translate(('Source has successfully been synchronized'));
$this->translate(('Source has successfully been synchronized')) } else {
); $this->successMessage = $this->translate('Nothing changed, rule is in sync');
} elseif ($rule->get('sync_state') === 'in-sync') { }
$this->notifySuccess(
$this->translate('Nothing changed, rule is in sync')
);
} else { } 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\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 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; use Ramsey\Uuid\UuidInterface;
/** /**
@ -48,6 +52,75 @@ class DbObjectStore
return $object; 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) public function exists($tableName, UuidInterface $uuid)
{ {
return BranchedObject::exists($this->connection, $tableName, $uuid, $this->branch->getUuid()); return BranchedObject::exists($this->connection, $tableName, $uuid, $this->branch->getUuid());

View File

@ -0,0 +1,158 @@
<?php
namespace Icinga\Module\Director\Data\Db;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\Branch\BranchSupport;
use Icinga\Module\Director\Db\DbSelectParenthesis;
use Icinga\Module\Director\Db\DbUtil;
use Icinga\Module\Director\Objects\IcingaService;
use Icinga\Module\Director\Objects\IcingaServiceSet;
use Icinga\Module\Director\Web\Table\TableWithBranchSupport;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
class ServiceSetQueryBuilder
{
use TableWithBranchSupport;
const TABLE = BranchSupport::TABLE_ICINGA_SERVICE;
const BRANCHED_TABLE = BranchSupport::BRANCHED_TABLE_ICINGA_SERVICE;
const SET_TABLE = BranchSupport::TABLE_ICINGA_SERVICE_SET;
const BRANCHED_SET_TABLE = BranchSupport::BRANCHED_TABLE_ICINGA_SERVICE_SET;
/** @var Db */
protected $connection;
/** @var \Zend_Db_Adapter_Abstract */
protected $db;
/**
* @param ?UuidInterface $uuid
*/
public function __construct(Db $connection, $uuid = null)
{
$this->connection = $connection;
$this->db = $connection->getDbAdapter();
if ($uuid) {
$this->setBranchUuid($uuid);
}
}
/**
* @return \Zend_Db_Select
* @throws \Zend_Db_Select_Exception
*/
public function selectServicesForSet(IcingaServiceSet $set)
{
$db = $this->connection->getDbAdapter();
if ($this->branchUuid) {
$right = $this->selectRightBranchedServices($set)->columns($this->getRightBranchedColumns());
$left = $this->selectLeftBranchedServices($set)->columns($this->getLeftBranchedColumns());
$query = $this->db->select()->from(['u' => $db->select()->union([
'l' => new DbSelectParenthesis($left),
'r' => new DbSelectParenthesis($right),
])]);
$query->order('service_set');
} else {
$query = $this->selectServices($set)->columns($this->getColumns());
}
return $query;
}
protected function selectServices(IcingaServiceSet $set)
{
return $this->db
->select()
->from(['o' =>self::TABLE], [])
->joinLeft(['os' => self::SET_TABLE], 'os.id = o.service_set_id', [])
->where('os.uuid = ?', $this->connection->quoteBinary($set->getUniqueId()->getBytes()));
}
protected function selectLeftBranchedServices(IcingaServiceSet $set)
{
return $this
->selectServices($set)
->joinLeft(
['bo' => self::BRANCHED_TABLE],
$this->db->quoteInto('bo.uuid = o.uuid AND bo.branch_uuid = ?', $this->getQuotedBranchUuid()),
[]
);
}
protected function selectRightBranchedServices(IcingaServiceSet $set)
{
return $this->db
->select()
->from(['o' => self::TABLE], [])
->joinRight(['bo' => self::BRANCHED_TABLE], 'bo.uuid = o.uuid', [])
->where('bo.service_set = ?', $set->get('object_name'))
->where('bo.branch_uuid = ?', $this->getQuotedBranchUuid());
}
protected static function resetQueryProperties(\Zend_Db_Select $query)
{
// TODO: Keep existing UUID, becomes important when using this for other tables too (w/o UNION)
// $columns = $query->getPart($query::COLUMNS);
$query->reset($query::COLUMNS);
$query->columns('uuid');
return $query;
}
public function fetchServicesWithQuery(\Zend_Db_Select $query)
{
static::resetQueryProperties($query);
$db = $this->connection->getDbAdapter();
$uuids = $db->fetchCol($query);
$services = [];
foreach ($uuids as $uuid) {
$service = IcingaService::loadWithUniqueId(Uuid::fromBytes(DbUtil::binaryResult($uuid)), $this->connection);
$service->set('service_set', null); // TODO: CHECK THIS!!!!
$services[$service->getObjectName()] = $service;
}
return $services;
}
protected function getColumns()
{
return [
'uuid' => 'o.uuid', // MUST be first because of UNION column order, see branchifyColumns()
'id' => 'o.id',
'branch_uuid' => '(null)',
'service_set' => 'os.object_name',
'service' => 'o.object_name',
'disabled' => 'o.disabled',
'object_type' => 'o.object_type',
'blacklisted' => "('n')",
];
}
protected function getLeftBranchedColumns()
{
$columns = $this->getColumns();
$columns['branch_uuid'] = 'bo.branch_uuid';
$columns['service_set'] = 'COALESCE(os.object_name, bo.service_set)';
return $this->branchifyColumns($columns);
}
protected function getRightBranchedColumns()
{
$columns = $this->getColumns();
$columns = $this->branchifyColumns($columns);
$columns['branch_uuid'] = 'bo.branch_uuid';
$columns['service_set'] = 'bo.service_set';
$columns['id'] = '(NULL)';
return $columns;
}
protected function getQuotedBranchUuid()
{
return $this->connection->quoteBinary($this->branchUuid->getBytes());
}
}

View File

@ -28,7 +28,7 @@ class AppliedServiceSetLoader
/** /**
* @return IcingaServiceSet[] * @return IcingaServiceSet[]
*/ */
public function fetchAppliedServiceSets() protected function fetchAppliedServiceSets()
{ {
$sets = array(); $sets = array();
$matcher = HostApplyMatches::prepare($this->host); $matcher = HostApplyMatches::prepare($this->host);

View File

@ -18,6 +18,8 @@ use stdClass;
*/ */
class Branch class Branch
{ {
const PREFIX_SYNC_PREVIEW = '/syncpreview';
/** @var UuidInterface|null */ /** @var UuidInterface|null */
protected $branchUuid; protected $branchUuid;
@ -186,4 +188,9 @@ class Branch
{ {
return $this->owner; 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) public function applyToDbObject(DbObject $object)
{ {
if (!$this->isActionModify()) { if (!$this->isActionModify()) {
@ -260,7 +270,7 @@ class BranchActivity
*/ */
public function getTimestamp() public function getTimestamp()
{ {
return (int) floor($this->timestampNs / 1000000); return (int) floor(BranchActivity::fixFakeTimestamp($this->timestampNs) / 1000000);
} }
/** /**

View File

@ -31,16 +31,21 @@ class BranchModificationInspection
public function describeBranch(UuidInterface $uuid) public function describeBranch(UuidInterface $uuid)
{ {
$tables = [ $tables = [
$this->translate('API Users') => 'branched_icinga_apiuser', $this->translate('API Users') => BranchSupport::BRANCHED_TABLE_ICINGA_APIUSER,
$this->translate('Endpoints') => 'branched_icinga_endpoint', $this->translate('Endpoints') => BranchSupport::BRANCHED_TABLE_ICINGA_COMMAND,
$this->translate('Zones') => 'branched_icinga_zone', $this->translate('Zones') => BranchSupport::BRANCHED_TABLE_ICINGA_DEPENDENCY,
$this->translate('Commands') => 'branched_icinga_command', $this->translate('Commands') => BranchSupport::BRANCHED_TABLE_ICINGA_ENDPOINT,
$this->translate('Hosts') => 'branched_icinga_host', $this->translate('Hosts') => BranchSupport::BRANCHED_TABLE_ICINGA_HOST,
$this->translate('Hostgroups') => 'branched_icinga_hostgroup', $this->translate('Hostgroups') => BranchSupport::BRANCHED_TABLE_ICINGA_HOSTGROUP,
$this->translate('Services') => 'branched_icinga_service', $this->translate('Services') => BranchSupport::BRANCHED_TABLE_ICINGA_NOTIFICATION,
$this->translate('Servicegroups') => 'branched_icinga_servicegroup', $this->translate('Servicegroups') => BranchSupport::BRANCHED_TABLE_ICINGA_SCHEDULED_DOWNTIME,
$this->translate('Users') => 'branched_icinga_user', $this->translate('Servicesets') => BranchSupport::BRANCHED_TABLE_ICINGA_SERVICE_SET,
$this->translate('Timeperiods') => 'branched_icinga_timeperiod', $this->translate('Users') => BranchSupport::BRANCHED_TABLE_ICINGA_SERVICE,
$this->translate('Usergroups') => BranchSupport::BRANCHED_TABLE_ICINGA_SERVICEGROUP,
$this->translate('Timeperiods') => BranchSupport::BRANCHED_TABLE_ICINGA_TIMEPERIOD,
$this->translate('Notifications') => BranchSupport::BRANCHED_TABLE_ICINGA_USER,
$this->translate('Dependencies') => BranchSupport::BRANCHED_TABLE_ICINGA_USERGROUP,
$this->translate('Scheduled Downtimes') => BranchSupport::BRANCHED_TABLE_ICINGA_ZONE,
]; ];
$parts = new HtmlDocument(); $parts = new HtmlDocument();

View File

@ -3,17 +3,19 @@
namespace Icinga\Module\Director\Db\Branch; namespace Icinga\Module\Director\Db\Branch;
use Icinga\Module\Director\Db; use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\DbUtil;
use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface; use Ramsey\Uuid\UuidInterface;
class BranchStore class BranchStore
{ {
const TABLE = 'director_branch';
const TABLE_ACTIVITY = 'director_branch_activity';
protected $connection; protected $connection;
protected $db; protected $db;
protected $table = 'director_branch';
public function __construct(Db $connection) public function __construct(Db $connection)
{ {
$this->connection = $connection; $this->connection = $connection;
@ -40,6 +42,67 @@ class BranchStore
return $this->newFromDbResult($this->select()->where('b.branch_name = ?', $name)); 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 = BranchSupport::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 = BranchSupport::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) protected function newFromDbResult($query)
{ {
if ($row = $this->db->fetchRow($query)) { if ($row = $this->db->fetchRow($query)) {
@ -78,7 +141,7 @@ class BranchStore
'ts_merge_request' => 'b.ts_merge_request', 'ts_merge_request' => 'b.ts_merge_request',
'cnt_activities' => 'COUNT(ba.timestamp_ns)', 'cnt_activities' => 'COUNT(ba.timestamp_ns)',
])->joinLeft( ])->joinLeft(
['ba' => 'director_branch_activity'], ['ba' => self::TABLE_ACTIVITY],
'b.uuid = ba.branch_uuid', 'b.uuid = ba.branch_uuid',
[] []
)->group('b.uuid'); )->group('b.uuid');
@ -114,7 +177,7 @@ class BranchStore
'description' => null, 'description' => null,
'ts_merge_request' => null, 'ts_merge_request' => null,
]; ];
$this->db->insert($this->table, $properties); $this->db->insert(self::TABLE, $properties);
if ($branch = static::fetchBranchByUuid($uuid)) { if ($branch = static::fetchBranchByUuid($uuid)) {
return $branch; return $branch;
@ -128,14 +191,49 @@ class BranchStore
public function deleteByUuid(UuidInterface $uuid) public function deleteByUuid(UuidInterface $uuid)
{ {
return $this->db->delete($this->table, $this->db->quoteInto( return $this->db->delete(self::TABLE, $this->db->quoteInto(
'uuid = ?', 'uuid = ?',
$this->connection->quoteBinary($uuid->getBytes()) $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) public function delete(Branch $branch)
{ {
return $this->deleteByUuid($branch->getUuid()); 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

@ -0,0 +1,91 @@
<?php
namespace Icinga\Module\Director\Db\Branch;
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
use Icinga\Module\Director\Objects\SyncRule;
class BranchSupport
{
const BRANCHED_TABLE_PREFIX = 'branched_';
const TABLE_ICINGA_APIUSER = 'icinga_apiuser';
const TABLE_ICINGA_COMMAND = 'icinga_command';
const TABLE_ICINGA_DEPENDENCY = 'icinga_dependency';
const TABLE_ICINGA_ENDPOINT = 'icinga_endpoint';
const TABLE_ICINGA_HOST = 'icinga_host';
const TABLE_ICINGA_HOSTGROUP = 'icinga_hostgroup';
const TABLE_ICINGA_NOTIFICATION = 'icinga_notification';
const TABLE_ICINGA_SCHEDULED_DOWNTIME = 'icinga_scheduled_downtime';
const TABLE_ICINGA_SERVICE = 'icinga_service';
const TABLE_ICINGA_SERVICEGROUP = 'icinga_servicegroup';
const TABLE_ICINGA_SERVICE_SET = 'icinga_service_set';
const TABLE_ICINGA_TIMEPERIOD = 'icinga_timeperiod';
const TABLE_ICINGA_USER = 'icinga_user';
const TABLE_ICINGA_USERGROUP = 'icinga_usergroup';
const TABLE_ICINGA_ZONE = 'icinga_zone';
const BRANCHED_TABLE_ICINGA_APIUSER = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_APIUSER;
const BRANCHED_TABLE_ICINGA_COMMAND = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_COMMAND;
const BRANCHED_TABLE_ICINGA_DEPENDENCY = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_DEPENDENCY;
const BRANCHED_TABLE_ICINGA_ENDPOINT = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_ENDPOINT;
const BRANCHED_TABLE_ICINGA_HOST = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_HOST;
const BRANCHED_TABLE_ICINGA_HOSTGROUP = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_HOSTGROUP;
const BRANCHED_TABLE_ICINGA_NOTIFICATION = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_NOTIFICATION;
const BRANCHED_TABLE_ICINGA_SCHEDULED_DOWNTIME = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_SCHEDULED_DOWNTIME;
const BRANCHED_TABLE_ICINGA_SERVICE = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_SERVICE;
const BRANCHED_TABLE_ICINGA_SERVICEGROUP = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_SERVICEGROUP;
const BRANCHED_TABLE_ICINGA_SERVICE_SET = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_SERVICE_SET;
const BRANCHED_TABLE_ICINGA_TIMEPERIOD = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_TIMEPERIOD;
const BRANCHED_TABLE_ICINGA_USER = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_USER;
const BRANCHED_TABLE_ICINGA_USERGROUP = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_USERGROUP;
const BRANCHED_TABLE_ICINGA_ZONE = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_ZONE;
const OBJECT_TABLES = [
self::TABLE_ICINGA_APIUSER,
self::TABLE_ICINGA_COMMAND,
self::TABLE_ICINGA_DEPENDENCY,
self::TABLE_ICINGA_ENDPOINT,
self::TABLE_ICINGA_HOST,
self::TABLE_ICINGA_HOSTGROUP,
self::TABLE_ICINGA_NOTIFICATION,
self::TABLE_ICINGA_SCHEDULED_DOWNTIME,
self::TABLE_ICINGA_SERVICE,
self::TABLE_ICINGA_SERVICEGROUP,
self::TABLE_ICINGA_SERVICE_SET,
self::TABLE_ICINGA_TIMEPERIOD,
self::TABLE_ICINGA_USER,
self::TABLE_ICINGA_USERGROUP,
self::TABLE_ICINGA_ZONE,
];
const BRANCHED_TABLES = [
self::BRANCHED_TABLE_ICINGA_APIUSER,
self::BRANCHED_TABLE_ICINGA_COMMAND,
self::BRANCHED_TABLE_ICINGA_DEPENDENCY,
self::BRANCHED_TABLE_ICINGA_ENDPOINT,
self::BRANCHED_TABLE_ICINGA_HOST,
self::BRANCHED_TABLE_ICINGA_HOSTGROUP,
self::BRANCHED_TABLE_ICINGA_NOTIFICATION,
self::BRANCHED_TABLE_ICINGA_SCHEDULED_DOWNTIME,
self::BRANCHED_TABLE_ICINGA_SERVICE,
self::BRANCHED_TABLE_ICINGA_SERVICEGROUP,
self::BRANCHED_TABLE_ICINGA_SERVICE_SET,
self::BRANCHED_TABLE_ICINGA_TIMEPERIOD,
self::BRANCHED_TABLE_ICINGA_USER,
self::BRANCHED_TABLE_ICINGA_USERGROUP,
self::BRANCHED_TABLE_ICINGA_ZONE,
];
public static function existsForTableName($table)
{
return in_array($table, self::OBJECT_TABLES, true);
}
public static function existsForSyncRule(SyncRule $rule)
{
return static::existsForTableName(
DbObjectTypeRegistry::tableNameByType($rule->get('object_type'))
);
}
}

View File

@ -47,15 +47,21 @@ 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('branch_uuid = ?', $connection->quoteBinary($branch->getUuid()->getBytes()));
if ($objectType) {
$query->where('object_type = ?', $objectType);
}
$query = self::addKeyToQuery($connection, $query, $key); $query = self::addKeyToQuery($connection, $query, $key);
if ($host) { if ($host) {
// TODO: uuid? // TODO: uuid?
$query->add('host = ?', $host->getObjectName()); $query->where('host = ?', $host->getObjectName());
} }
if ($set) { if ($set) {
$query->add('service_set = ?', $set->getObjectName()); $query->where('service_set = ?', $set->getObjectName());
} }
$uuid = self::fetchOptionalUuid($connection, $query); $uuid = self::fetchOptionalUuid($connection, $query);
} }
@ -93,7 +99,12 @@ class UuidLookup
$query = self::addKeyToQuery($connection, $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()) {
if (is_array($key) && isset($key['host_id'])) {
$key['host'] = IcingaHost::load($key['host_id'], $connection)->getObjectName();
unset($key['host_id']);
}
$query = self::addKeyToQuery($connection, $db->select()->from("branched_$table", 'uuid'), $key); $query = self::addKeyToQuery($connection, $db->select()->from("branched_$table", 'uuid'), $key);
$query->where('branch_uuid = ?', $connection->quoteBinary($branch->getUuid()->getBytes()));
$uuid = self::fetchOptionalUuid($connection, $query); $uuid = self::fetchOptionalUuid($connection, $query);
} }

View File

@ -7,6 +7,8 @@ use Icinga\Application\Benchmark;
use Icinga\Data\Filter\Filter; use Icinga\Data\Filter\Filter;
use Icinga\Module\Director\Application\MemoryLimit; use Icinga\Module\Director\Application\MemoryLimit;
use Icinga\Module\Director\Data\Db\DbObject; 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;
use Icinga\Module\Director\Db\Cache\PrefetchCache; use Icinga\Module\Director\Db\Cache\PrefetchCache;
use Icinga\Module\Director\Objects\HostGroupMembershipResolver; use Icinga\Module\Director\Objects\HostGroupMembershipResolver;
@ -46,6 +48,9 @@ class Sync
/** @var bool Whether we already prepared your sync */ /** @var bool Whether we already prepared your sync */
protected $isPrepared = false; protected $isPrepared = false;
/** @var bool Whether we applied strtolower() to existing object keys */
protected $usedLowerCasedKeys = false;
protected $modify = []; protected $modify = [];
protected $remove = []; protected $remove = [];
@ -76,13 +81,18 @@ class Sync
/** @var HostGroupMembershipResolver|bool */ /** @var HostGroupMembershipResolver|bool */
protected $hostGroupMembershipResolver; protected $hostGroupMembershipResolver;
/** @var ?DbObjectStore */
protected $store;
/** /**
* @param SyncRule $rule * @param SyncRule $rule
* @param ?DbObjectStore $store
*/ */
public function __construct(SyncRule $rule) public function __construct(SyncRule $rule, DbObjectStore $store = null)
{ {
$this->rule = $rule; $this->rule = $rule;
$this->db = $rule->getConnection(); $this->db = $rule->getConnection();
$this->store = $store;
} }
/** /**
@ -306,6 +316,9 @@ class Sync
foreach ($rows as $row) { foreach ($rows as $row) {
if ($combinedKey) { if ($combinedKey) {
$key = SyncUtils::fillVariables($sourceKeyPattern, $row); $key = SyncUtils::fillVariables($sourceKeyPattern, $row);
if ($this->usedLowerCasedKeys) {
$key = strtolower($key);
}
if (array_key_exists($key, $this->imported[$sourceId])) { if (array_key_exists($key, $this->imported[$sourceId])) {
throw new InvalidArgumentException(sprintf( throw new InvalidArgumentException(sprintf(
@ -334,7 +347,11 @@ class Sync
if ($combinedKey) { if ($combinedKey) {
$this->imported[$sourceId][$key] = $row; $this->imported[$sourceId][$key] = $row;
} else { } else {
$this->imported[$sourceId][$row->$key] = $row; if ($this->usedLowerCasedKeys) {
$this->imported[$sourceId][strtolower($row->$key)] = $row;
} else {
$this->imported[$sourceId][$row->$key] = $row;
}
} }
} }
@ -360,13 +377,13 @@ class Sync
if ($listId === null) { if ($listId === null) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
'Cannot sync datalist entry without list_ist' 'Cannot sync datalist entry without list_id'
); );
} }
$no = []; $no = [];
foreach ($this->objects as $k => $o) { foreach ($this->objects as $k => $o) {
if ((int) $o->get('list_id') !== (int) $listId) { if ((int) $o->get('list_id') !== $listId) {
$no[] = $k; $no[] = $k;
} }
} }
@ -384,15 +401,18 @@ class Sync
Benchmark::measure('Begin loading existing objects'); Benchmark::measure('Begin loading existing objects');
$ruleObjectType = $this->rule->get('object_type'); $ruleObjectType = $this->rule->get('object_type');
$useLowerCaseKeys = $ruleObjectType !== 'datalistEntry';
// TODO: Make object_type (template, object...) and object_name mandatory? // TODO: Make object_type (template, object...) and object_name mandatory?
if ($this->rule->hasCombinedKey()) { if ($this->rule->hasCombinedKey()) {
$this->objects = []; $this->objects = [];
$destinationKeyPattern = $this->rule->getDestinationKeyPattern(); $destinationKeyPattern = $this->rule->getDestinationKeyPattern();
if ($this->store) {
$objects = $this->store->loadAll(DbObjectTypeRegistry::tableNameByType($ruleObjectType));
} else {
$objects = IcingaObject::loadAllByType($ruleObjectType, $this->db);
}
foreach (IcingaObject::loadAllByType( foreach ($objects as $object) {
$ruleObjectType,
$this->db
) as $object) {
if ($object instanceof IcingaService) { if ($object instanceof IcingaService) {
if (strstr($destinationKeyPattern, '${host}') if (strstr($destinationKeyPattern, '${host}')
&& $object->get('host_id') === null && $object->get('host_id') === null
@ -409,6 +429,9 @@ class Sync
$destinationKeyPattern, $destinationKeyPattern,
$object $object
); );
if ($useLowerCaseKeys) {
$key = strtolower($key);
}
if (array_key_exists($key, $this->objects)) { if (array_key_exists($key, $this->objects)) {
throw new InvalidArgumentException(sprintf( throw new InvalidArgumentException(sprintf(
@ -421,12 +444,23 @@ class Sync
$this->objects[$key] = $object; $this->objects[$key] = $object;
} }
} else { } else {
$this->objects = IcingaObject::loadAllByType( if ($this->store) {
$ruleObjectType, $objects = $this->store->loadAll(DbObjectTypeRegistry::tableNameByType($ruleObjectType), 'object_name');
$this->db } else {
); $objects = IcingaObject::loadAllByType($ruleObjectType, $this->db);
}
if ($useLowerCaseKeys) {
$this->objects = [];
foreach ($objects as $key => $object) {
$this->objects[strtolower($key)] = $object;
}
} else {
$this->objects = $objects;
}
} }
$this->usedLowerCasedKeys = $useLowerCaseKeys;
// TODO: should be obsoleted by a better "loadFiltered" method // TODO: should be obsoleted by a better "loadFiltered" method
if ($ruleObjectType === 'datalistEntry') { if ($ruleObjectType === 'datalistEntry') {
$this->removeForeignListEntries(); $this->removeForeignListEntries();
@ -453,6 +487,9 @@ class Sync
foreach ($this->imported[$sourceId] as $key => $row) { foreach ($this->imported[$sourceId] as $key => $row) {
// Workaround: $a["10"] = "val"; -> array_keys($a) = [(int) 10] // Workaround: $a["10"] = "val"; -> array_keys($a) = [(int) 10]
$key = (string) $key; $key = (string) $key;
if ($this->usedLowerCasedKeys) {
$key = strtolower($key);
}
if (! array_key_exists($key, $objects)) { if (! array_key_exists($key, $objects)) {
// Safe default values for object_type and object_name // Safe default values for object_type and object_name
if ($ruleObjectType === 'datalistEntry') { if ($ruleObjectType === 'datalistEntry') {
@ -759,7 +796,9 @@ class Sync
$objects = $this->prepare(); $objects = $this->prepare();
$db = $this->db; $db = $this->db;
$dba = $db->getDbAdapter(); $dba = $db->getDbAdapter();
$dba->beginTransaction(); if (! $this->store) { // store has it's own transaction
$dba->beginTransaction();
}
$object = null; $object = null;
$updateOnly = $this->rule->get('update_policy') === 'update-only'; $updateOnly = $this->rule->get('update_policy') === 'update-only';
@ -777,7 +816,11 @@ class Sync
foreach ($objects as $object) { foreach ($objects as $object) {
$this->setResolver($object); $this->setResolver($object);
if (! $updateOnly && $object->shouldBeRemoved()) { if (! $updateOnly && $object->shouldBeRemoved()) {
$object->delete(); if ($this->store) {
$this->store->delete($object);
} else {
$object->delete();
}
$deleted++; $deleted++;
continue; continue;
} }
@ -785,10 +828,18 @@ class Sync
if ($object->hasBeenModified()) { if ($object->hasBeenModified()) {
$existing = $object->hasBeenLoadedFromDb(); $existing = $object->hasBeenLoadedFromDb();
if ($existing) { if ($existing) {
$object->store($db); if ($this->store) {
$this->store->store($object);
} else {
$object->store($db);
}
$modified++; $modified++;
} elseif ($allowCreate) { } elseif ($allowCreate) {
$object->store($db); if ($this->store) {
$this->store->store($object);
} else {
$object->store($db);
}
$created++; $created++;
} }
} }
@ -810,7 +861,9 @@ class Sync
$this->run->setProperties($runProperties)->store(); $this->run->setProperties($runProperties)->store();
$this->notifyResolvers(); $this->notifyResolvers();
$dba->commit(); if (! $this->store) {
$dba->commit();
}
// Store duration after commit, as the commit might take some time // Store duration after commit, as the commit might take some time
$this->run->set('duration_ms', (int) round( $this->run->set('duration_ms', (int) round(
@ -819,13 +872,15 @@ class Sync
Benchmark::measure('Done applying objects'); Benchmark::measure('Done applying objects');
} catch (Exception $e) { } catch (Exception $e) {
$dba->rollBack(); if (! $this->store) {
$dba->rollBack();
}
if ($object !== null && $object instanceof IcingaObject) { if ($object instanceof IcingaObject) {
throw new IcingaException( throw new IcingaException(
'Exception while syncing %s %s: %s', 'Exception while syncing %s %s: %s',
get_class($object), get_class($object),
$object->get('object_name'), $object->getObjectName(),
$e->getMessage(), $e->getMessage(),
$e $e
); );
@ -839,6 +894,9 @@ class Sync
protected function prepareCache() protected function prepareCache()
{ {
if ($this->store) {
return $this;
}
PrefetchCache::initialize($this->db); PrefetchCache::initialize($this->db);
IcingaTemplateRepository::clear(); IcingaTemplateRepository::clear();

View File

@ -565,7 +565,9 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
} catch (NotFoundError $e) { } catch (NotFoundError $e) {
// Hint: eventually a NotFoundError would be better // Hint: eventually a NotFoundError would be better
throw new RuntimeException(sprintf( throw new RuntimeException(sprintf(
'Unable to load object referenced from %s "%s", %s', 'Unable to load object (%s: %s) referenced from %s "%s", %s',
$short,
$this->unresolvedRelatedProperties[$name],
$this->getShortTableName(), $this->getShortTableName(),
$this->getObjectName(), $this->getObjectName(),
lcfirst($e->getMessage()) lcfirst($e->getMessage())

View File

@ -4,6 +4,7 @@ namespace Icinga\Module\Director\Objects;
use Exception; use Exception;
use Icinga\Data\Filter\Filter; use Icinga\Data\Filter\Filter;
use Icinga\Module\Director\Data\Db\ServiceSetQueryBuilder;
use Icinga\Module\Director\Db; use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\Cache\PrefetchCache; use Icinga\Module\Director\Db\Cache\PrefetchCache;
use Icinga\Module\Director\Db\DbUtil; use Icinga\Module\Director\Db\DbUtil;
@ -94,7 +95,9 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
if (empty($imports)) { if (empty($imports)) {
return array(); return array();
} }
return $this->getServiceObjectsForSet(array_shift($imports)); $parent = array_shift($imports);
assert($parent instanceof IcingaServiceSet);
return $this->getServiceObjectsForSet($parent);
} else { } else {
return $this->getServiceObjectsForSet($this); return $this->getServiceObjectsForSet($this);
} }
@ -102,30 +105,20 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
/** /**
* @param IcingaServiceSet $set * @param IcingaServiceSet $set
* @return array * @return IcingaService[]
* @throws \Icinga\Exception\NotFoundError * @throws \Icinga\Exception\NotFoundError
*/ */
protected function getServiceObjectsForSet(IcingaServiceSet $set) protected function getServiceObjectsForSet(IcingaServiceSet $set)
{ {
if ($set->get('id') === null) {
return array();
}
$connection = $this->getConnection(); $connection = $this->getConnection();
$db = $this->getDb(); if (self::$dbObjectStore !== null) {
$uuids = $db->fetchCol( $branchUuid = self::$dbObjectStore->getBranch()->getUuid();
$db->select()->from('icinga_service', 'uuid') } else {
->where('service_set_id = ?', $set->get('id')) $branchUuid = null;
);
$services = array();
foreach ($uuids as $uuid) {
$service = IcingaService::loadWithUniqueId(Uuid::fromBytes(DbUtil::binaryResult($uuid)), $connection);
$service->set('service_set', null);
$services[$service->getObjectName()] = $service;
} }
return $services; $builder = new ServiceSetQueryBuilder($connection, $branchUuid);
return $builder->fetchServicesWithQuery($builder->selectServicesForSet($set));
} }
public function getUniqueIdentifier() public function getUniqueIdentifier()
@ -276,7 +269,9 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
if ($hostId) { if ($hostId) {
$deleteIds = []; $deleteIds = [];
foreach ($this->getServiceObjects() as $service) { foreach ($this->getServiceObjects() as $service) {
$deleteIds[] = (int) $service->get('id'); if ($idToDelete = $service->get('id')) {
$deleteIds[] = (int) $idToDelete;
}
} }
if (! empty($deleteIds)) { if (! empty($deleteIds)) {
@ -483,11 +478,16 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
public function getRenderingZone(IcingaConfig $config = null) public function getRenderingZone(IcingaConfig $config = null)
{ {
if ($this->get('host_id') === null) { if ($this->get('host_id') === null) {
return $this->connection->getDefaultGlobalZoneName(); if ($hostname = $this->get('host')) {
$host = IcingaHost::load($hostname, $this->getConnection());
} else {
return $this->connection->getDefaultGlobalZoneName();
}
} else { } else {
$host = $this->getRelatedObject('host', $this->get('host_id')); $host = $this->getRelatedObject('host', $this->get('host_id'));
return $host->getRenderingZone($config);
} }
return $host->getRenderingZone($config);
} }
public function createWhere() public function createWhere()
@ -507,17 +507,13 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
*/ */
public function fetchServices() public function fetchServices()
{ {
$connection = $this->getConnection(); if ($store = self::$dbObjectStore) {
$db = $connection->getDbAdapter(); $uuid = $store->getBranch()->getUuid();
} else {
/** @var IcingaService[] $services */ $uuid = null;
$services = IcingaService::loadAll( }
$connection, $builder = new ServiceSetQueryBuilder($this->getConnection(), $uuid);
$db->select()->from('icinga_service') return $builder->fetchServicesWithQuery($builder->selectServicesForSet($this));
->where('service_set_id = ?', $this->get('id'))
);
return $services;
} }
/** /**
@ -557,7 +553,7 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
$name = $this->getObjectName(); $name = $this->getObjectName();
if ($this->isObject() && $this->get('host_id') === null) { if ($this->isObject() && $this->get('host_id') === null && $this->get('host') === null) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
'A Service Set cannot be an object with no related host' 'A Service Set cannot be an object with no related host'
); );
@ -585,7 +581,7 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
$config->configFile( $config->configFile(
'failed-to-render' 'failed-to-render'
)->prepend( )->prepend(
"/** Failed to render this object **/\n" "/** Failed to render this Service Set **/\n"
. '/* ' . $e->getMessage() . ' */' . '/* ' . $e->getMessage() . ' */'
); );
} }

View File

@ -3,7 +3,7 @@
namespace Icinga\Module\Director\PropertyModifier; namespace Icinga\Module\Director\PropertyModifier;
use Icinga\Module\Director\Hook\PropertyModifierHook; use Icinga\Module\Director\Hook\PropertyModifierHook;
use Icinga\Module\Director\Web\Form\QuickForm; use function iconv;
class PropertyModifierFromLatin1 extends PropertyModifierHook class PropertyModifierFromLatin1 extends PropertyModifierHook
{ {
@ -18,6 +18,6 @@ class PropertyModifierFromLatin1 extends PropertyModifierHook
return null; return null;
} }
return utf8_encode($value); return iconv('ISO-8859-15', 'UTF-8', $value);
} }
} }

View File

@ -6,6 +6,7 @@ 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\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\Db\Branch\BranchSupport;
use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Web\Widget\NotInBranchedHint; use Icinga\Module\Director\Web\Widget\NotInBranchedHint;
@ -17,23 +18,6 @@ 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
*/ */
@ -69,14 +53,9 @@ 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) protected function enableStaticObjectLoader($table)
{ {
if ($this->tableHasBranchSupport($table)) { if (BranchSupport::existsForTableName($table)) {
IcingaObject::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch())); IcingaObject::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch()));
} }
} }

View File

@ -140,13 +140,6 @@ abstract class ObjectController extends ActionController
if ($oType = $this->params->get('type', 'object')) { if ($oType = $this->params->get('type', 'object')) {
$form->setPreferredObjectType($oType); $form->setPreferredObjectType($oType);
} }
if ($this->getTableName() === 'icinga_service_set'
&& $this->showNotInBranch($this->translate('Creating Service Sets'))
) {
$this->addTitle($this->translate('Create a new Service Set'));
return;
}
if ($oType === 'template') { if ($oType === 'template') {
if ($this->showNotInBranch($this->translate('Creating Templates'))) { if ($this->showNotInBranch($this->translate('Creating Templates'))) {
$this->addTitle($this->translate('Create a new Template')); $this->addTitle($this->translate('Create a new Template'));
@ -157,6 +150,10 @@ abstract class ObjectController extends ActionController
} else { } else {
$this->addObject(); $this->addObject();
} }
$branch = $this->getBranch();
if ($branch->isBranch() && ! $this->getRequest()->isApiRequest()) {
$this->content()->add(new BranchedObjectHint($branch, $this->Auth()));
}
$form->handleRequest(); $form->handleRequest();
$this->content()->add($form); $this->content()->add($form);
@ -170,7 +167,11 @@ abstract class ObjectController extends ActionController
$object = $this->requireObject(); $object = $this->requireObject();
$this->tabs()->activate('modify'); $this->tabs()->activate('modify');
$this->addObjectTitle(); $this->addObjectTitle();
if ($object->isTemplate() && $this->showNotInBranch($this->translate('Modifying Templates'))) { // Hint: Service Sets are 'templates' (as long as not being assigned to a host
if ($this->getTableName() !== 'icinga_service_set'
&& $object->isTemplate()
&& $this->showNotInBranch($this->translate('Modifying Templates'))
) {
return; return;
} }
if ($object->isApplyRule() && $this->showNotInBranch($this->translate('Modifying Apply Rules'))) { if ($object->isApplyRule() && $this->showNotInBranch($this->translate('Modifying Apply Rules'))) {

View File

@ -367,7 +367,9 @@ abstract class ObjectsController extends ActionController
) )
); );
ObjectSetTable::create($type, $this->db(), $this->getAuth())->renderTo($this); ObjectSetTable::create($type, $this->db(), $this->getAuth())
->setBranch($this->getBranch())
->renderTo($this);
} }
/** /**

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

View File

@ -2,18 +2,21 @@
namespace Icinga\Module\Director\Web\Table; namespace Icinga\Module\Director\Web\Table;
use Icinga\Module\Director\Data\Db\ServiceSetQueryBuilder;
use Icinga\Module\Director\Db;
use ipl\Html\BaseHtmlElement; use ipl\Html\BaseHtmlElement;
use ipl\Html\Html; use ipl\Html\Html;
use Icinga\Module\Director\Forms\RemoveLinkForm; use Icinga\Module\Director\Forms\RemoveLinkForm;
use Icinga\Module\Director\Objects\IcingaHost; use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaServiceSet; use Icinga\Module\Director\Objects\IcingaServiceSet;
use ipl\Html\HtmlElement;
use gipfl\IcingaWeb2\Link; use gipfl\IcingaWeb2\Link;
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable; use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
use gipfl\IcingaWeb2\Url; use gipfl\IcingaWeb2\Url;
class IcingaServiceSetServiceTable extends ZfQueryBasedTable class IcingaServiceSetServiceTable extends ZfQueryBasedTable
{ {
use TableWithBranchSupport;
/** @var IcingaServiceSet */ /** @var IcingaServiceSet */
protected $set; protected $set;
@ -138,89 +141,48 @@ class IcingaServiceSetServiceTable extends ZfQueryBasedTable
$tr = $this::row([ $tr = $this::row([
$this->getServiceLink($row) $this->getServiceLink($row)
]); ]);
$classes = $this->getRowClasses($row);
if ($row->disabled === 'y') { if ($row->disabled === 'y') {
$tr->getAttributes()->add('class', 'disabled'); $classes[] = 'disabled';
} }
if ($row->blacklisted === 'y') { if ($row->blacklisted === 'y') {
$tr->getAttributes()->add('class', 'strike-links'); $classes[] = 'strike-links';
}
if (! empty($classes)) {
$tr->getAttributes()->add('class', $classes);
} }
return $tr; return $tr;
} }
protected function getRowClasses($row)
{
if ($row->branch_uuid !== null) {
return ['branch_modified'];
}
return [];
}
protected function getTitle() protected function getTitle()
{ {
return $this->title ?: $this->translate('Servicename'); return $this->title ?: $this->translate('Servicename');
} }
/**
* @param HtmlElement $parent
*/
protected function renderTitleColumns() protected function renderTitleColumns()
{ {
if (! $this->host || ! $this->affectedHost) { if (! $this->host || ! $this->affectedHost) {
return Html::tag('th', $this->getTitle()); return Html::tag('th', $this->getTitle());
} }
if (! $this->host) { if ($this->readonly) {
$deleteLink = ''; $link = $this->createFakeRemoveLinkForReadonlyView();
} elseif ($this->readonly) {
$deleteLink = Html::tag('span', [
'class' => 'icon-paste',
'style' => 'float: right; font-weight: normal',
], $this->host->getObjectName());
} elseif ($this->affectedHost->get('id') !== $this->host->get('id')) { } elseif ($this->affectedHost->get('id') !== $this->host->get('id')) {
$host = $this->host; $link = $this->linkToHost($this->host);
$deleteLink = Link::create(
$host->getObjectName(),
'director/host/services',
['name' => $host->getObjectName()],
[
'class' => 'icon-paste',
'style' => 'float: right; font-weight: normal',
'data-base-target' => '_next',
'title' => sprintf(
$this->translate('This set has been inherited from %s'),
$host->getObjectName()
)
]
);
} else { } else {
$deleteLink = new RemoveLinkForm( $link = $this->createRemoveLinkForm();
$this->translate('Remove'),
sprintf(
$this->translate('Remove "%s" from this host'),
$this->getTitle()
),
Url::fromPath('director/host/services', [
'name' => $this->host->getObjectName()
]),
['title' => $this->getTitle()]
);
$deleteLink->runOnSuccess(function () {
$conn = $this->set->getConnection();
$db = $conn->getDbAdapter();
$query = $db->select()->from(
['ss' => 'icinga_service_set'],
'ss.id'
)->join(
['ssih' => 'icinga_service_set_inheritance'],
'ssih.service_set_id = ss.id',
[]
)->where(
'ssih.parent_service_set_id = ?',
$this->set->get('id')
)->where('ss.host_id = ?', $this->host->get('id'));
IcingaServiceSet::loadWithAutoIncId(
$db->fetchOne($query),
$conn
)->delete();
});
$deleteLink->handleRequest();
} }
return $this::th([$this->getTitle(), $deleteLink]); return $this::th([$this->getTitle(), $link]);
} }
/** /**
@ -229,43 +191,60 @@ class IcingaServiceSetServiceTable extends ZfQueryBasedTable
*/ */
public function prepareQuery() public function prepareQuery()
{ {
$db = $this->db(); $connection = $this->connection();
$query = $db->select()->from( assert($connection instanceof Db);
['s' => 'icinga_service'], $builder = new ServiceSetQueryBuilder($connection, $this->branchUuid);
[ return $builder->selectServicesForSet($this->set)->limit(100);
'id' => 's.id', }
'uuid' => 's.uuid',
'service_set_id' => 's.service_set_id',
'host_id' => 'ss.host_id',
'service_set' => 'ss.object_name',
'service' => 's.object_name',
'disabled' => 's.disabled',
'object_type' => 's.object_type',
]
)->joinLeft(
['ss' => 'icinga_service_set'],
'ss.id = s.service_set_id',
[]
)->where(
's.service_set_id = ?',
$this->set->get('id')
)->order('s.object_name');
if ($this->affectedHost) { protected function createFakeRemoveLinkForReadonlyView()
$query->joinLeft( {
['hsb' => 'icinga_host_service_blacklist'], return Html::tag('span', [
$db->quoteInto( 'class' => 'icon-paste',
's.id = hsb.service_id AND hsb.host_id = ?', 'style' => 'float: right; font-weight: normal',
$this->affectedHost->get('id') ], $this->host->getObjectName());
), }
[]
)->columns([
'blacklisted' => "CASE WHEN hsb.service_id IS NULL THEN 'n' ELSE 'y' END",
]);
} else {
$query->columns(['blacklisted' => "('n')"]);
}
return $query; protected function linkToHost(IcingaHost $host)
{
$hostname = $host->getObjectName();
return Link::create($hostname, 'director/host/services', ['name' => $hostname], [
'class' => 'icon-paste',
'style' => 'float: right; font-weight: normal',
'data-base-target' => '_next',
'title' => sprintf(
$this->translate('This set has been inherited from %s'),
$hostname
)
]);
}
protected function createRemoveLinkForm()
{
$deleteLink = new RemoveLinkForm(
$this->translate('Remove'),
sprintf(
$this->translate('Remove "%s" from this host'),
$this->getTitle()
),
Url::fromPath('director/host/services', [
'name' => $this->host->getObjectName()
]),
['title' => $this->getTitle()]
);
$deleteLink->runOnSuccess(function () {
$conn = $this->set->getConnection();
$db = $conn->getDbAdapter();
$query = $db->select()->from(['ss' => 'icinga_service_set'], 'ss.id')
->join(['ssih' => 'icinga_service_set_inheritance'], 'ssih.service_set_id = ss.id', [])
->where('ssih.parent_service_set_id = ?', $this->set->get('id'))
->where('ss.host_id = ?', $this->host->get('id'));
IcingaServiceSet::loadWithAutoIncId(
$db->fetchOne($query),
$conn
)->delete();
});
$deleteLink->handleRequest();
return $deleteLink;
} }
} }

View File

@ -7,11 +7,14 @@ use Icinga\Module\Director\Db;
use gipfl\IcingaWeb2\Link; use gipfl\IcingaWeb2\Link;
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable; use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
use gipfl\IcingaWeb2\Url; use gipfl\IcingaWeb2\Url;
use Icinga\Module\Director\Db\DbSelectParenthesis;
use Icinga\Module\Director\Restriction\FilterByNameRestriction; use Icinga\Module\Director\Restriction\FilterByNameRestriction;
use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Uuid;
class ObjectSetTable extends ZfQueryBasedTable class ObjectSetTable extends ZfQueryBasedTable
{ {
use TableWithBranchSupport;
protected $searchColumns = [ protected $searchColumns = [
'os.object_name', 'os.object_name',
'os.description', 'os.description',
@ -51,7 +54,8 @@ class ObjectSetTable extends ZfQueryBasedTable
$url = Url::fromPath("director/${type}set", $params); $url = Url::fromPath("director/${type}set", $params);
return static::tr([ $classes = $this->getRowClasses($row);
$tr = static::tr([
static::td([ static::td([
Link::create(sprintf( Link::create(sprintf(
$this->translate('%s (%d members)'), $this->translate('%s (%d members)'),
@ -61,24 +65,44 @@ class ObjectSetTable extends ZfQueryBasedTable
$row->description ? ': ' . $row->description : null $row->description ? ': ' . $row->description : null
]) ])
]); ]);
if (! empty($classes)) {
$tr->getAttributes()->add('class', $classes);
}
return $tr;
}
protected function getRowClasses($row)
{
if ($row->branch_uuid !== null) {
return ['branch_modified'];
}
return [];
} }
protected function prepareQuery() protected function prepareQuery()
{ {
$type = $this->getType(); $type = $this->getType();
$table = "icinga_${type}_set";
$columns = [ $columns = [
'id' => 'os.id', 'id' => 'os.id',
'uuid' => 'os.uuid', 'uuid' => 'os.uuid',
'branch_uuid' => '(NULL)',
'object_name' => 'os.object_name', 'object_name' => 'os.object_name',
'object_type' => 'os.object_type', 'object_type' => 'os.object_type',
'assign_filter' => 'os.assign_filter', 'assign_filter' => 'os.assign_filter',
'description' => 'os.description', 'description' => 'os.description',
'count_services' => 'COUNT(DISTINCT o.id)', 'count_services' => 'COUNT(DISTINCT o.uuid)',
]; ];
if ($this->branchUuid) {
$columns['branch_uuid'] = 'bos.branch_uuid';
$columns = $this->branchifyColumns($columns);
$this->stripSearchColumnAliases();
}
$query = $this->db()->select()->from( $query = $this->db()->select()->from(
['os' => "icinga_${type}_set"], ['os' => $table],
$columns $columns
)->joinLeft( )->joinLeft(
['o' => "icinga_${type}"], ['o' => "icinga_${type}"],
@ -92,22 +116,76 @@ class ObjectSetTable extends ZfQueryBasedTable
"${type}_set" "${type}_set"
); );
$nameFilter->applyToQuery($query, 'os'); $nameFilter->applyToQuery($query, 'os');
// Disabled for now, check for correctness: if ($this->branchUuid) {
// $query->joinLeft( $right = clone($query);
// ['osi' => "icinga_${type}_set_inheritance"],
// "osi.parent_${type}_set_id = os.id",
// []
// )->joinLeft(
// ['oso' => "icinga_${type}_set"],
// "oso.id = oso.${type}_set_id",
// []
// );
// 'count_hosts' => 'COUNT(DISTINCT oso.id)',
$query /** @var Db $conn */
->group('os.id') $conn = $this->connection();
->where('os.object_type = ?', 'template') $query->joinLeft(
->order('os.object_name'); ['bos' => "branched_$table"],
// TODO: PgHexFunc
$this->db()->quoteInto(
'bos.uuid = os.uuid AND bos.branch_uuid = ?',
$conn->quoteBinary($this->branchUuid->getBytes())
),
[]
)->where("(bos.branch_deleted IS NULL OR bos.branch_deleted = 'n')");
$right->joinRight(
['bos' => "branched_$table"],
'bos.uuid = os.uuid',
[]
)
->where('os.uuid IS NULL')
->where('bos.branch_uuid = ?', $conn->quoteBinary($this->branchUuid->getBytes()));
$query->group('COALESCE(os.uuid, bos.uuid)');
$right->group('COALESCE(os.uuid, bos.uuid)');
if ($conn->isPgsql()) {
// This is ugly, might want to modify the query - even a subselect looks better
$query->group('bos.uuid')->group('os.uuid')->group('os.id')->group('bos.branch_uuid');
$right->group('bos.uuid')->group('os.uuid')->group('os.id')->group('bos.branch_uuid');
}
$query = $this->db()->select()->union([
'l' => new DbSelectParenthesis($query),
'r' => new DbSelectParenthesis($right),
]);
$query = $this->db()->select()->from(['u' => $query]);
$query->order('object_name')->limit(100);
$query
->group('uuid')
->where('object_type = ?', 'template')
->order('object_name');
if ($conn->isPgsql()) {
// BS. Drop count? Sub-select? Better query?
$query
->group('uuid')
->group('id')
->group('branch_uuid')
->group('object_name')
->group('object_type')
->group('assign_filter')
->group('description')
->group('count_services');
};
} else {
// Disabled for now, check for correctness:
// $query->joinLeft(
// ['osi' => "icinga_${type}_set_inheritance"],
// "osi.parent_${type}_set_id = os.id",
// []
// )->joinLeft(
// ['oso' => "icinga_${type}_set"],
// "oso.id = oso.${type}_set_id",
// []
// );
// 'count_hosts' => 'COUNT(DISTINCT oso.id)',
$query
->group('os.uuid')
->where('os.object_type = ?', 'template')
->order('os.object_name');
}
return $query; return $query;
} }

View File

@ -14,11 +14,12 @@ use gipfl\IcingaWeb2\Link;
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable; use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
use gipfl\IcingaWeb2\Url; use gipfl\IcingaWeb2\Url;
use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Zend_Db_Select as ZfSelect; use Zend_Db_Select as ZfSelect;
class ObjectsTable extends ZfQueryBasedTable class ObjectsTable extends ZfQueryBasedTable
{ {
use TableWithBranchSupport;
/** @var ObjectRestriction[] */ /** @var ObjectRestriction[] */
protected $objectRestrictions; protected $objectRestrictions;
@ -37,9 +38,6 @@ class ObjectsTable extends ZfQueryBasedTable
protected $type; protected $type;
/** @var UuidInterface|null */
protected $branchUuid;
protected $baseObjectUrl; protected $baseObjectUrl;
/** @var IcingaObject */ /** @var IcingaObject */
@ -112,13 +110,6 @@ class ObjectsTable extends ZfQueryBasedTable
return $this; return $this;
} }
public function setBranchUuid(UuidInterface $uuid = null)
{
$this->branchUuid = $uuid;
return $this;
}
public function getColumns() public function getColumns()
{ {
return $this->columns; return $this->columns;
@ -256,36 +247,6 @@ class ObjectsTable extends ZfQueryBasedTable
return $this->dummyObject; return $this->dummyObject;
} }
protected function branchifyColumns($columns)
{
$result = [
'uuid' => 'COALESCE(o.uuid, bo.uuid)'
];
$ignore = ['o.id'];
foreach ($columns as $alias => $column) {
if (substr($column, 0, 2) === 'o.' && ! in_array($column, $ignore)) {
// bo.column, o.column
$column = "COALESCE(b$column, $column)";
}
// Used in Service Tables:
if ($column === 'h.object_name' && $alias = 'host') {
$column = "COALESCE(bo.host, $column)";
}
$result[$alias] = $column;
}
return $result;
}
protected function stripSearchColumnAliases()
{
foreach ($this->searchColumns as &$column) {
$column = preg_replace('/^[a-z]+\./', '', $column);
}
}
protected function prepareQuery() protected function prepareQuery()
{ {
$table = $this->getDummyObject()->getTableName(); $table = $this->getDummyObject()->getTableName();

View File

@ -0,0 +1,69 @@
<?php
namespace Icinga\Module\Director\Web\Table;
use Icinga\Module\Director\Db\Branch\Branch;
use Ramsey\Uuid\UuidInterface;
trait TableWithBranchSupport
{
/** @var UuidInterface|null */
protected $branchUuid;
/**
* Convenience method, only UUID is required
*
* @param Branch|null $branch
* @return $this
*/
public function setBranch(Branch $branch = null)
{
if ($branch && $branch->isBranch()) {
$this->setBranchUuid($branch->getUuid());
}
return $this;
}
public function setBranchUuid(UuidInterface $uuid = null)
{
$this->branchUuid = $uuid;
return $this;
}
protected function branchifyColumns($columns)
{
$result = [
'uuid' => 'COALESCE(o.uuid, bo.uuid)'
];
$ignore = ['o.id', 'os.id', 'o.service_set_id', 'os.host_id'];
foreach ($columns as $alias => $column) {
if (substr($column, 0, 2) === 'o.' && ! in_array($column, $ignore)) {
// bo.column, o.column
$column = "COALESCE(b$column, $column)";
}
if (substr($column, 0, 3) === 'os.' && ! in_array($column, $ignore)) {
// bo.column, o.column
$column = "COALESCE(b$column, $column)";
}
// Used in Service Tables:
if ($column === 'h.object_name' && $alias = 'host') {
$column = "COALESCE(bo.host, $column)";
}
$result[$alias] = $column;
}
return $result;
}
protected function stripSearchColumnAliases()
{
foreach ($this->searchColumns as &$column) {
$column = preg_replace('/^[a-z]+\./', '', $column);
}
}
}

View File

@ -15,7 +15,7 @@ class BranchedObjectHint extends HtmlDocument
{ {
use TranslationHelper; use TranslationHelper;
public function __construct(Branch $branch, Auth $auth, BranchedObject $object) public function __construct(Branch $branch, Auth $auth, BranchedObject $object = null)
{ {
if (! $branch->isBranch()) { if (! $branch->isBranch()) {
return; return;
@ -29,6 +29,13 @@ class BranchedObjectHint extends HtmlDocument
$label = $name; $label = $name;
} }
$link = $hook->linkToBranch($branch, $auth, $label); $link = $hook->linkToBranch($branch, $auth, $label);
if ($object === null) {
$this->add(Hint::info(Html::sprintf($this->translate(
'This object will be created in %s. It will not be part of any deployment'
. ' unless being merged'
), $link)));
return;
}
if (! $object->hasBeenTouchedByBranch()) { if (! $object->hasBeenTouchedByBranch()) {
$this->add(Hint::info(Html::sprintf($this->translate( $this->add(Hint::info(Html::sprintf($this->translate(

View File

@ -0,0 +1,26 @@
CREATE TABLE branched_icinga_service_set (
uuid VARBINARY(16) NOT NULL,
branch_uuid VARBINARY(16) NOT NULL,
branch_created ENUM('y', 'n') NOT NULL DEFAULT 'n',
branch_deleted ENUM('y', 'n') NOT NULL DEFAULT 'n',
object_name VARCHAR(128) DEFAULT NULL,
object_type ENUM('object', 'template', 'external_object') DEFAULT NULL,
host VARCHAR(255) DEFAULT NULL,
description TEXT DEFAULT NULL,
assign_filter TEXT DEFAULT NULL,
imports TEXT DEFAULT NULL,
set_null TEXT DEFAULT NULL,
PRIMARY KEY (branch_uuid, uuid),
INDEX search_object_name (object_name),
CONSTRAINT icinga_service_set_branch
FOREIGN KEY branch (branch_uuid)
REFERENCES director_branch (uuid)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO director_schema_migration
(schema_version, migration_time)
VALUES (180, NOW());

View File

@ -2309,6 +2309,31 @@ CREATE TABLE branched_icinga_service (
ON UPDATE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE branched_icinga_service_set (
uuid VARBINARY(16) NOT NULL,
branch_uuid VARBINARY(16) NOT NULL,
branch_created ENUM('y', 'n') NOT NULL DEFAULT 'n',
branch_deleted ENUM('y', 'n') NOT NULL DEFAULT 'n',
object_name VARCHAR(128) DEFAULT NULL,
object_type ENUM('object', 'template', 'external_object') DEFAULT NULL,
host VARCHAR(255) DEFAULT NULL,
description TEXT DEFAULT NULL,
assign_filter TEXT DEFAULT NULL,
imports TEXT DEFAULT NULL,
imports TEXT DEFAULT NULL,
set_null TEXT DEFAULT NULL,
PRIMARY KEY (branch_uuid, uuid),
INDEX search_object_name (object_name),
CONSTRAINT icinga_service_set_branch
FOREIGN KEY branch (branch_uuid)
REFERENCES director_branch (uuid)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE branched_icinga_notification ( CREATE TABLE branched_icinga_notification (
uuid VARBINARY(16) NOT NULL, uuid VARBINARY(16) NOT NULL,
branch_uuid VARBINARY(16) NOT NULL, branch_uuid VARBINARY(16) NOT NULL,
@ -2415,4 +2440,4 @@ CREATE TABLE branched_icinga_dependency (
INSERT INTO director_schema_migration INSERT INTO director_schema_migration
(schema_version, migration_time) (schema_version, migration_time)
VALUES (179, NOW()); VALUES (180, NOW());

View File

@ -0,0 +1,32 @@
CREATE TABLE branched_icinga_service_set (
uuid bytea NOT NULL UNIQUE CHECK(LENGTH(uuid) = 16),
branch_uuid bytea NOT NULL CHECK(LENGTH(branch_uuid) = 16),
branch_created enum_boolean NOT NULL DEFAULT 'n',
branch_deleted enum_boolean NOT NULL DEFAULT 'n',
object_name character varying(255) DEFAULT NULL,
object_type enum_object_type_all DEFAULT NULL,
disabled enum_boolean DEFAULT NULL,
host character varying(255) DEFAULT NULL,
description TEXT DEFAULT NULL,
assign_filter text DEFAULT NULL,
imports TEXT DEFAULT NULL,
vars TEXT DEFAULT NULL,
set_null TEXT DEFAULT NULL,
PRIMARY KEY (branch_uuid, uuid),
CONSTRAINT icinga_service_branch
FOREIGN KEY (branch_uuid)
REFERENCES director_branch (uuid)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE UNIQUE INDEX service_set_branch_object_name ON branched_icinga_service_set (branch_uuid, object_name);
CREATE INDEX branched_service_set_search_object_name ON branched_icinga_service_set (object_name);
INSERT INTO director_schema_migration
(schema_version, migration_time)
VALUES (180, NOW());

View File

@ -2637,6 +2637,35 @@ CREATE INDEX branched_service_search_object_name ON branched_icinga_service (obj
CREATE INDEX branched_service_search_display_name ON branched_icinga_service (display_name); CREATE INDEX branched_service_search_display_name ON branched_icinga_service (display_name);
CREATE TABLE branched_icinga_service_set (
uuid bytea NOT NULL UNIQUE CHECK(LENGTH(uuid) = 16),
branch_uuid bytea NOT NULL CHECK(LENGTH(branch_uuid) = 16),
branch_created enum_boolean NOT NULL DEFAULT 'n',
branch_deleted enum_boolean NOT NULL DEFAULT 'n',
object_name character varying(255) DEFAULT NULL,
object_type enum_object_type_all DEFAULT NULL,
disabled enum_boolean DEFAULT NULL,
host character varying(255) DEFAULT NULL,
description TEXT DEFAULT NULL,
assign_filter text DEFAULT NULL,
imports TEXT DEFAULT NULL,
vars TEXT DEFAULT NULL,
set_null TEXT DEFAULT NULL,
PRIMARY KEY (branch_uuid, uuid),
CONSTRAINT icinga_service_branch
FOREIGN KEY (branch_uuid)
REFERENCES director_branch (uuid)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE UNIQUE INDEX service_set_branch_object_name ON branched_icinga_service_set (branch_uuid, object_name);
CREATE INDEX branched_service_set_search_object_name ON branched_icinga_service_set (object_name);
CREATE TABLE branched_icinga_notification ( CREATE TABLE branched_icinga_notification (
uuid bytea NOT NULL UNIQUE CHECK(LENGTH(uuid) = 16), uuid bytea NOT NULL UNIQUE CHECK(LENGTH(uuid) = 16),
branch_uuid bytea NOT NULL CHECK(LENGTH(branch_uuid) = 16), branch_uuid bytea NOT NULL CHECK(LENGTH(branch_uuid) = 16),
@ -2749,4 +2778,4 @@ CREATE INDEX branched_dependency_search_object_name ON branched_icinga_dependenc
INSERT INTO director_schema_migration INSERT INTO director_schema_migration
(schema_version, migration_time) (schema_version, migration_time)
VALUES (179, NOW()); VALUES (180, NOW());