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

View File

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

View File

@ -41,6 +41,9 @@ class ServiceController extends ObjectController
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
$this->host = $this->getOptionalRelatedObjectFromParams('host', 'host');
$this->set = $this->getOptionalRelatedObjectFromParams('service_set', 'set');

View File

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

View File

@ -4,6 +4,15 @@ 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\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\UnorderedList;
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\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 +55,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 +121,15 @@ class SyncruleController extends ActionController
}
$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);
if ($run) {
@ -142,31 +174,97 @@ class SyncruleController extends ActionController
public function previewAction()
{
$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->addTitle('Sync Preview');
$sync = new Sync($rule);
try {
$this->addTitle($this->translate('Sync Preview'));
$sync = new Sync($rule, $store);
$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();
} catch (\Exception $e) {
$this->content()->add(Hint::error($e->getMessage()));
return;
}
if (empty($modifications)) {
$this->content()->add(Hint::ok($this->translate(
'This Sync Rule is in sync and would currently not apply any changes'
)));
if ($tmpBranch) {
try {
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 = [];
$modify = [];
$delete = [];
$modifiedProperties = [];
/** @var IcingaObject $object */
foreach ($modifications as $object) {
if ($object->hasBeenLoadedFromDb()) {
@ -416,9 +514,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 +556,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 +607,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 +622,21 @@ class SyncruleController extends ActionController
$form->getObject()->get('destination_field'),
$rule->get('rule_name')
);
if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
return;
}
} else {
$this->addTitle(
$this->translate('Add sync property: %s'),
$rule->get('rule_name')
);
if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
return;
}
}
$form->setRule($rule);
$form->setSuccessUrl('director/syncrule/property', ['rule_id' => $ruleId]);
$this->actions()->add(new Link(
$this->translate('back'),
'director/syncrule/property',
['rule_id' => $ruleId],
['class' => 'icon-left-big']
));
$this->content()->add($form->handleRequest());
$this->tabs(new SyncRuleTabs($rule))->activate('property');
SyncpropertyTable::create($rule)
->handleSortPriorityActions($this->getRequest(), $this->getResponse())
->renderTo($this);

View File

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

View File

@ -69,7 +69,8 @@ class IcingaServiceSetForm extends DirectorObjectForm
}
$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)
@ -77,6 +78,7 @@ class IcingaServiceSetForm extends DirectorObjectForm
$this->host = $host;
return $this;
}
protected function addSingleImportsElement()
{
$enum = $this->enumAllowedTemplates();

View File

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

View File

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

View File

@ -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[]
*/
public function fetchAppliedServiceSets()
protected function fetchAppliedServiceSets()
{
$sets = array();
$matcher = HostApplyMatches::prepare($this->host);

View File

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

View File

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

View File

@ -31,16 +31,21 @@ class BranchModificationInspection
public function describeBranch(UuidInterface $uuid)
{
$tables = [
$this->translate('API Users') => 'branched_icinga_apiuser',
$this->translate('Endpoints') => 'branched_icinga_endpoint',
$this->translate('Zones') => 'branched_icinga_zone',
$this->translate('Commands') => 'branched_icinga_command',
$this->translate('Hosts') => 'branched_icinga_host',
$this->translate('Hostgroups') => 'branched_icinga_hostgroup',
$this->translate('Services') => 'branched_icinga_service',
$this->translate('Servicegroups') => 'branched_icinga_servicegroup',
$this->translate('Users') => 'branched_icinga_user',
$this->translate('Timeperiods') => 'branched_icinga_timeperiod',
$this->translate('API Users') => BranchSupport::BRANCHED_TABLE_ICINGA_APIUSER,
$this->translate('Endpoints') => BranchSupport::BRANCHED_TABLE_ICINGA_COMMAND,
$this->translate('Zones') => BranchSupport::BRANCHED_TABLE_ICINGA_DEPENDENCY,
$this->translate('Commands') => BranchSupport::BRANCHED_TABLE_ICINGA_ENDPOINT,
$this->translate('Hosts') => BranchSupport::BRANCHED_TABLE_ICINGA_HOST,
$this->translate('Hostgroups') => BranchSupport::BRANCHED_TABLE_ICINGA_HOSTGROUP,
$this->translate('Services') => BranchSupport::BRANCHED_TABLE_ICINGA_NOTIFICATION,
$this->translate('Servicegroups') => BranchSupport::BRANCHED_TABLE_ICINGA_SCHEDULED_DOWNTIME,
$this->translate('Servicesets') => BranchSupport::BRANCHED_TABLE_ICINGA_SERVICE_SET,
$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();

View File

@ -3,17 +3,19 @@
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';
protected $connection;
protected $db;
protected $table = 'director_branch';
public function __construct(Db $connection)
{
$this->connection = $connection;
@ -40,6 +42,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 = 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)
{
if ($row = $this->db->fetchRow($query)) {
@ -78,7 +141,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 +177,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 +191,49 @@ class BranchStore
public function deleteByUuid(UuidInterface $uuid)
{
return $this->db->delete($this->table, $this->db->quoteInto(
return $this->db->delete(self::TABLE, $this->db->quoteInto(
'uuid = ?',
$this->connection->quoteBinary($uuid->getBytes())
));
}
/**
* @param string $name
* @return int
*/
public function deleteByName($name)
{
return $this->db->delete(self::TABLE, $this->db->quoteInto(
'branch_name = ?',
$name
));
}
public function delete(Branch $branch)
{
return $this->deleteByUuid($branch->getUuid());
}
/**
* @param Branch $branch
* @param ?int $after
* @return float|null
*/
public function getLastActivityTime(Branch $branch, $after = null)
{
$db = $this->db;
$query = $db->select()
->from(self::TABLE_ACTIVITY, 'MAX(timestamp_ns)')
->where('branch_uuid = ?', DbUtil::quoteBinaryCompat($branch->getUuid()->getBytes(), $db));
if ($after) {
$query->where('timestamp_ns > ?', (int) $after);
}
$last = $db->fetchOne($query);
if ($last) {
return $last / 1000000;
}
return null;
}
}

View File

@ -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()) {
// 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);
if ($host) {
// TODO: uuid?
$query->add('host = ?', $host->getObjectName());
$query->where('host = ?', $host->getObjectName());
}
if ($set) {
$query->add('service_set = ?', $set->getObjectName());
$query->where('service_set = ?', $set->getObjectName());
}
$uuid = self::fetchOptionalUuid($connection, $query);
}
@ -93,7 +99,12 @@ class UuidLookup
$query = self::addKeyToQuery($connection, $db->select()->from($table, 'uuid'), $key);
$uuid = self::fetchOptionalUuid($connection, $query);
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->where('branch_uuid = ?', $connection->quoteBinary($branch->getUuid()->getBytes()));
$uuid = self::fetchOptionalUuid($connection, $query);
}

View File

@ -7,6 +7,8 @@ use Icinga\Application\Benchmark;
use Icinga\Data\Filter\Filter;
use Icinga\Module\Director\Application\MemoryLimit;
use Icinga\Module\Director\Data\Db\DbObject;
use Icinga\Module\Director\Data\Db\DbObjectStore;
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\Cache\PrefetchCache;
use Icinga\Module\Director\Objects\HostGroupMembershipResolver;
@ -46,6 +48,9 @@ class Sync
/** @var bool Whether we already prepared your sync */
protected $isPrepared = false;
/** @var bool Whether we applied strtolower() to existing object keys */
protected $usedLowerCasedKeys = false;
protected $modify = [];
protected $remove = [];
@ -76,13 +81,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;
}
/**
@ -306,6 +316,9 @@ class Sync
foreach ($rows as $row) {
if ($combinedKey) {
$key = SyncUtils::fillVariables($sourceKeyPattern, $row);
if ($this->usedLowerCasedKeys) {
$key = strtolower($key);
}
if (array_key_exists($key, $this->imported[$sourceId])) {
throw new InvalidArgumentException(sprintf(
@ -334,7 +347,11 @@ class Sync
if ($combinedKey) {
$this->imported[$sourceId][$key] = $row;
} 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) {
throw new InvalidArgumentException(
'Cannot sync datalist entry without list_ist'
'Cannot sync datalist entry without list_id'
);
}
$no = [];
foreach ($this->objects as $k => $o) {
if ((int) $o->get('list_id') !== (int) $listId) {
if ((int) $o->get('list_id') !== $listId) {
$no[] = $k;
}
}
@ -384,15 +401,18 @@ class Sync
Benchmark::measure('Begin loading existing objects');
$ruleObjectType = $this->rule->get('object_type');
$useLowerCaseKeys = $ruleObjectType !== 'datalistEntry';
// TODO: Make object_type (template, object...) and object_name mandatory?
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
@ -409,6 +429,9 @@ class Sync
$destinationKeyPattern,
$object
);
if ($useLowerCaseKeys) {
$key = strtolower($key);
}
if (array_key_exists($key, $this->objects)) {
throw new InvalidArgumentException(sprintf(
@ -421,12 +444,23 @@ class Sync
$this->objects[$key] = $object;
}
} else {
$this->objects = IcingaObject::loadAllByType(
$ruleObjectType,
$this->db
);
if ($this->store) {
$objects = $this->store->loadAll(DbObjectTypeRegistry::tableNameByType($ruleObjectType), 'object_name');
} 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
if ($ruleObjectType === 'datalistEntry') {
$this->removeForeignListEntries();
@ -453,6 +487,9 @@ class Sync
foreach ($this->imported[$sourceId] as $key => $row) {
// Workaround: $a["10"] = "val"; -> array_keys($a) = [(int) 10]
$key = (string) $key;
if ($this->usedLowerCasedKeys) {
$key = strtolower($key);
}
if (! array_key_exists($key, $objects)) {
// Safe default values for object_type and object_name
if ($ruleObjectType === 'datalistEntry') {
@ -759,7 +796,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 +816,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 +828,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 +861,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 +872,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 +894,9 @@ class Sync
protected function prepareCache()
{
if ($this->store) {
return $this;
}
PrefetchCache::initialize($this->db);
IcingaTemplateRepository::clear();

View File

@ -565,7 +565,9 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
} catch (NotFoundError $e) {
// Hint: eventually a NotFoundError would be better
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->getObjectName(),
lcfirst($e->getMessage())

View File

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

View File

@ -3,7 +3,7 @@
namespace Icinga\Module\Director\PropertyModifier;
use Icinga\Module\Director\Hook\PropertyModifierHook;
use Icinga\Module\Director\Web\Form\QuickForm;
use function iconv;
class PropertyModifierFromLatin1 extends PropertyModifierHook
{
@ -18,6 +18,6 @@ class PropertyModifierFromLatin1 extends PropertyModifierHook
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\Db\Branch\Branch;
use Icinga\Module\Director\Db\Branch\BranchStore;
use Icinga\Module\Director\Db\Branch\BranchSupport;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Web\Widget\NotInBranchedHint;
@ -17,23 +18,6 @@ trait BranchHelper
/** @var 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
*/
@ -69,14 +53,9 @@ trait BranchHelper
return $this->getBranchUuid() !== null;
}
protected function tableHasBranchSupport($table)
{
return in_array($table, self::$banchedTables, true);
}
protected function enableStaticObjectLoader($table)
{
if ($this->tableHasBranchSupport($table)) {
if (BranchSupport::existsForTableName($table)) {
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')) {
$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 ($this->showNotInBranch($this->translate('Creating Templates'))) {
$this->addTitle($this->translate('Create a new Template'));
@ -157,6 +150,10 @@ abstract class ObjectController extends ActionController
} else {
$this->addObject();
}
$branch = $this->getBranch();
if ($branch->isBranch() && ! $this->getRequest()->isApiRequest()) {
$this->content()->add(new BranchedObjectHint($branch, $this->Auth()));
}
$form->handleRequest();
$this->content()->add($form);
@ -170,7 +167,11 @@ abstract class ObjectController extends ActionController
$object = $this->requireObject();
$this->tabs()->activate('modify');
$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;
}
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 */
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());

View File

@ -2,18 +2,21 @@
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\Html;
use Icinga\Module\Director\Forms\RemoveLinkForm;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaServiceSet;
use ipl\Html\HtmlElement;
use gipfl\IcingaWeb2\Link;
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
use gipfl\IcingaWeb2\Url;
class IcingaServiceSetServiceTable extends ZfQueryBasedTable
{
use TableWithBranchSupport;
/** @var IcingaServiceSet */
protected $set;
@ -138,89 +141,48 @@ class IcingaServiceSetServiceTable extends ZfQueryBasedTable
$tr = $this::row([
$this->getServiceLink($row)
]);
$classes = $this->getRowClasses($row);
if ($row->disabled === 'y') {
$tr->getAttributes()->add('class', 'disabled');
$classes[] = 'disabled';
}
if ($row->blacklisted === 'y') {
$tr->getAttributes()->add('class', 'strike-links');
$classes[] = 'strike-links';
}
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 getTitle()
{
return $this->title ?: $this->translate('Servicename');
}
/**
* @param HtmlElement $parent
*/
protected function renderTitleColumns()
{
if (! $this->host || ! $this->affectedHost) {
return Html::tag('th', $this->getTitle());
}
if (! $this->host) {
$deleteLink = '';
} elseif ($this->readonly) {
$deleteLink = Html::tag('span', [
'class' => 'icon-paste',
'style' => 'float: right; font-weight: normal',
], $this->host->getObjectName());
if ($this->readonly) {
$link = $this->createFakeRemoveLinkForReadonlyView();
} elseif ($this->affectedHost->get('id') !== $this->host->get('id')) {
$host = $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()
)
]
);
$link = $this->linkToHost($this->host);
} else {
$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();
$link = $this->createRemoveLinkForm();
}
return $this::th([$this->getTitle(), $deleteLink]);
return $this::th([$this->getTitle(), $link]);
}
/**
@ -229,43 +191,60 @@ class IcingaServiceSetServiceTable extends ZfQueryBasedTable
*/
public function prepareQuery()
{
$db = $this->db();
$query = $db->select()->from(
['s' => 'icinga_service'],
[
'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');
$connection = $this->connection();
assert($connection instanceof Db);
$builder = new ServiceSetQueryBuilder($connection, $this->branchUuid);
return $builder->selectServicesForSet($this->set)->limit(100);
}
if ($this->affectedHost) {
$query->joinLeft(
['hsb' => 'icinga_host_service_blacklist'],
$db->quoteInto(
's.id = hsb.service_id AND hsb.host_id = ?',
$this->affectedHost->get('id')
),
[]
)->columns([
'blacklisted' => "CASE WHEN hsb.service_id IS NULL THEN 'n' ELSE 'y' END",
]);
} else {
$query->columns(['blacklisted' => "('n')"]);
}
protected function createFakeRemoveLinkForReadonlyView()
{
return Html::tag('span', [
'class' => 'icon-paste',
'style' => 'float: right; font-weight: normal',
], $this->host->getObjectName());
}
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\Table\ZfQueryBasedTable;
use gipfl\IcingaWeb2\Url;
use Icinga\Module\Director\Db\DbSelectParenthesis;
use Icinga\Module\Director\Restriction\FilterByNameRestriction;
use Ramsey\Uuid\Uuid;
class ObjectSetTable extends ZfQueryBasedTable
{
use TableWithBranchSupport;
protected $searchColumns = [
'os.object_name',
'os.description',
@ -51,7 +54,8 @@ class ObjectSetTable extends ZfQueryBasedTable
$url = Url::fromPath("director/${type}set", $params);
return static::tr([
$classes = $this->getRowClasses($row);
$tr = static::tr([
static::td([
Link::create(sprintf(
$this->translate('%s (%d members)'),
@ -61,24 +65,44 @@ class ObjectSetTable extends ZfQueryBasedTable
$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()
{
$type = $this->getType();
$table = "icinga_${type}_set";
$columns = [
'id' => 'os.id',
'uuid' => 'os.uuid',
'branch_uuid' => '(NULL)',
'object_name' => 'os.object_name',
'object_type' => 'os.object_type',
'assign_filter' => 'os.assign_filter',
'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(
['os' => "icinga_${type}_set"],
['os' => $table],
$columns
)->joinLeft(
['o' => "icinga_${type}"],
@ -92,22 +116,76 @@ class ObjectSetTable extends ZfQueryBasedTable
"${type}_set"
);
$nameFilter->applyToQuery($query, 'os');
// 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)',
if ($this->branchUuid) {
$right = clone($query);
$query
->group('os.id')
->where('os.object_type = ?', 'template')
->order('os.object_name');
/** @var Db $conn */
$conn = $this->connection();
$query->joinLeft(
['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;
}

View File

@ -14,11 +14,12 @@ use gipfl\IcingaWeb2\Link;
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
use gipfl\IcingaWeb2\Url;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Zend_Db_Select as ZfSelect;
class ObjectsTable extends ZfQueryBasedTable
{
use TableWithBranchSupport;
/** @var ObjectRestriction[] */
protected $objectRestrictions;
@ -37,9 +38,6 @@ class ObjectsTable extends ZfQueryBasedTable
protected $type;
/** @var UuidInterface|null */
protected $branchUuid;
protected $baseObjectUrl;
/** @var IcingaObject */
@ -112,13 +110,6 @@ class ObjectsTable extends ZfQueryBasedTable
return $this;
}
public function setBranchUuid(UuidInterface $uuid = null)
{
$this->branchUuid = $uuid;
return $this;
}
public function getColumns()
{
return $this->columns;
@ -256,36 +247,6 @@ class ObjectsTable extends ZfQueryBasedTable
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()
{
$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;
public function __construct(Branch $branch, Auth $auth, BranchedObject $object)
public function __construct(Branch $branch, Auth $auth, BranchedObject $object = null)
{
if (! $branch->isBranch()) {
return;
@ -29,6 +29,13 @@ class BranchedObjectHint extends HtmlDocument
$label = $name;
}
$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()) {
$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
) 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 (
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
(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 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 (
uuid bytea NOT NULL UNIQUE CHECK(LENGTH(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
(schema_version, migration_time)
VALUES (179, NOW());
VALUES (180, NOW());