From 21c1949b07d01e99b60b58a27229c699791969f2 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 27 Sep 2022 15:53:51 +0200 Subject: [PATCH 1/3] Introduce `CleanupNodeForm` action This helps to clean orphaned (missing) nodes --- application/controllers/ProcessController.php | 25 ++++- application/forms/CleanupNodeForm.php | 93 +++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 application/forms/CleanupNodeForm.php diff --git a/application/controllers/ProcessController.php b/application/controllers/ProcessController.php index 475826f..404b8f4 100644 --- a/application/controllers/ProcessController.php +++ b/application/controllers/ProcessController.php @@ -120,7 +120,7 @@ class ProcessController extends Controller $missing = array_slice($missing, 0, 10); $missing[] = '...'; } - $bp->addError('There are %d missing nodes: %s', $count, implode(', ', $missing)); + $bp->addError('There are %d missing nodes: %s ', $count, implode(', ', $missing)); } $this->content()->add($this->showHints($bp)); $this->content()->add($this->showWarnings($bp)); @@ -232,6 +232,12 @@ class ProcessController extends Controller ->setParentNode($node) ->setSession($this->session()) ->handleRequest(); + } elseif ($action === 'cleanup' && $canEdit) { + $form = $this->loadForm('CleanupNode') + ->setSuccessUrl(Url::fromRequest()->without('action')) + ->setProcess($bp) + ->setSession($this->session()) + ->handleRequest(); } elseif ($action === 'editmonitored' && $canEdit) { $form = $this->loadForm('EditNode') ->setSuccessUrl(Url::fromRequest()->without('action')) @@ -324,6 +330,23 @@ class ProcessController extends Controller { $ul = Html::tag('ul', ['class' => 'error']); foreach ($bp->getErrors() as $error) { + if (strpos($error, 'missing nodes')) { + $error = [ + $error, + Html::tag( + 'a', + [ + 'href' => Url::fromPath('businessprocess/process/show') + ->setParams( + $this->getRequest()->getUrl()->getParams() + ->add('action', 'cleanup') + ) + ], + $this->translate('Cleanup') + ) + ]; + } + $ul->add(Html::tag('li')->setContent($error)); } if ($bp->hasChanges()) { diff --git a/application/forms/CleanupNodeForm.php b/application/forms/CleanupNodeForm.php new file mode 100644 index 0000000..22c5c50 --- /dev/null +++ b/application/forms/CleanupNodeForm.php @@ -0,0 +1,93 @@ +addHtml(Html::tag('h2', $this->translate('Cleanup missing nodes'))); + + $this->addElement('checkbox', 'cleanup_all', [ + 'class' => 'autosubmit', + 'label' => $this->translate('Cleanup all missing nodes'), + 'description' => $this->translate('Remove all missing nodes from config') + ]); + + if ($this->getSentValue('cleanup_all') !== '1') { + $this->addElement('multiselect', 'nodes', [ + 'label' => $this->translate('Select nodes to cleanup'), + 'required' => true, + 'size' => 8, + 'multiOptions' => $this->bp->getMissingChildren() + ]); + } + } + + /** + * @param MonitoringBackend|IcingaDbConnection $backend + * @return $this + */ + public function setBackend($backend) + { + $this->backend = $backend; + return $this; + } + + /** + * @param BpConfig $process + * @return $this + */ + public function setProcess(BpConfig $process) + { + $this->bp = $process; + $this->setBackend($process->getBackend()); + return $this; + } + + /** + * @param SessionNamespace $session + * @return $this + */ + public function setSession(SessionNamespace $session) + { + $this->session = $session; + return $this; + } + + public function onSuccess() + { + $changes = ProcessChanges::construct($this->bp, $this->session); + + $nodesToCleanup = $this->getValue('cleanup_all') === '1' + ? array_keys($this->bp->getMissingChildren()) + : $this->getValue('nodes'); + + foreach ($nodesToCleanup as $nodeName) { + $node = $this->bp->getNode($nodeName); + $changes->deleteNode($node); + } + + unset($changes); + + parent::onSuccess(); + } +} From b053a78a131cf39bc4ac085d0328928177d242ba Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 28 Sep 2022 17:55:02 +0200 Subject: [PATCH 2/3] ProcessController: Add `Show` link for missing linked nodes --- application/controllers/ProcessController.php | 92 +++++++++++++------ 1 file changed, 66 insertions(+), 26 deletions(-) diff --git a/application/controllers/ProcessController.php b/application/controllers/ProcessController.php index 404b8f4..ef525c6 100644 --- a/application/controllers/ProcessController.php +++ b/application/controllers/ProcessController.php @@ -27,7 +27,10 @@ use Icinga\Web\Url; use Icinga\Web\Widget\Tabextension\DashboardAction; use Icinga\Web\Widget\Tabextension\OutputFormat; use ipl\Html\Html; +use ipl\Html\HtmlElement; use ipl\Html\HtmlString; +use ipl\Html\TemplateString; +use ipl\Web\Widget\Link; class ProcessController extends Controller { @@ -114,14 +117,6 @@ class ProcessController extends Controller $this->tabs()->extend(new OutputFormat()); - $missing = $bp->getMissingChildren(); - if (! empty($missing)) { - if (($count = count($missing)) > 10) { - $missing = array_slice($missing, 0, 10); - $missing[] = '...'; - } - $bp->addError('There are %d missing nodes: %s ', $count, implode(', ', $missing)); - } $this->content()->add($this->showHints($bp)); $this->content()->add($this->showWarnings($bp)); $this->content()->add($this->showErrors($bp)); @@ -329,26 +324,11 @@ class ProcessController extends Controller protected function showHints(BpConfig $bp) { $ul = Html::tag('ul', ['class' => 'error']); + $this->prepareMissingNodeLinks($ul); foreach ($bp->getErrors() as $error) { - if (strpos($error, 'missing nodes')) { - $error = [ - $error, - Html::tag( - 'a', - [ - 'href' => Url::fromPath('businessprocess/process/show') - ->setParams( - $this->getRequest()->getUrl()->getParams() - ->add('action', 'cleanup') - ) - ], - $this->translate('Cleanup') - ) - ]; - } - - $ul->add(Html::tag('li')->setContent($error)); + $ul->addHtml(Html::tag('li', $error)); } + if ($bp->hasChanges()) { $li = Html::tag('li')->setSeparator(' '); $li->add(sprintf( @@ -389,6 +369,66 @@ class ProcessController extends Controller } } + protected function prepareMissingNodeLinks(HtmlElement $ul): void + { + $missing = $this->bp->getMissingChildren(); + if (! empty($missing)) { + $missingLinkedNodes = null; + foreach ($this->bp->getImportedNodes() as $process) { + if ($process->hasMissingChildren()) { + $missingLinkedNodes = array_keys($process->getMissingChildren()); + $link = Url::fromPath('businessprocess/process/show') + ->addParams(['config' => $process->getConfigName()]); + + $ul->addHtml(Html::tag( + 'li', + [ + TemplateString::create( + tp( + 'Linked node %s has one missing child node: {{#link}}Show{{/link}}', + 'Linked node %s has %d missing child nodes: {{#link}}Show{{/link}}', + count($missingLinkedNodes) + ), + $process->getAlias(), + count($missingLinkedNodes), + ['link' => new Link(null, (string) $link)] + ) + ] + )); + } + } + + if (! empty($missingLinkedNodes)) { + return; + } + + $count = count($missing); + if ($count > 10) { + $missing = array_slice($missing, 0, 10); + $missing[] = '...'; + } + + $link = Url::fromPath('businessprocess/process/show') + ->addParams(['config' => $this->bp->getName(), 'action' => 'cleanup']); + + $ul->addHtml(Html::tag( + 'li', + [ + TemplateString::create( + tp( + '{{#link}}Cleanup{{/link}} one missing node: %2$s', + '{{#link}}Cleanup{{/link}} %d missing nodes: %s', + count($missing) + ), + ['link' => new Link(null, (string) $link)], + $count, + implode(', ', $missing) + ) + ] + )); + } + } + /** * Show the source code for a process */ From 5fcd8246b8b6528c8b6dda049883ff29bfcf16c5 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 29 Sep 2022 12:21:40 +0200 Subject: [PATCH 3/3] Add cli command `Cleanup` --- application/clicommands/CleanupCommand.php | 106 +++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 application/clicommands/CleanupCommand.php diff --git a/application/clicommands/CleanupCommand.php b/application/clicommands/CleanupCommand.php new file mode 100644 index 0000000..04fc781 --- /dev/null +++ b/application/clicommands/CleanupCommand.php @@ -0,0 +1,106 @@ +storage = LegacyStorage::getInstance(); + } + + /** + * Cleanup all missing monitoring nodes from the specified config name + * If no config name is specified, the missing nodes are cleaned from all available configs. + * Invalid config files and file names are ignored + * + * USAGE + * + * icingacli businessprocess cleanup [] + * + * OPTIONS + * + * + */ + public function cleanupAction(): void + { + $configNames = (array) $this->params->shift() ?: $this->storage->listAllProcessNames(); + $foundMissingNode = false; + foreach ($configNames as $configName) { + if (! $this->storage->hasProcess($configName)) { + continue; + } + + try { + $bp = $this->storage->loadProcess($configName); + } catch (Exception $e) { + Logger::error( + 'Failed to scan the %s.conf file for missing nodes. Invalid config found.', + $configName + ); + + continue; + } + + if (Module::exists('icingadb') + && (! $bp->hasBackendName() && IcingadbSupport::useIcingaDbAsBackend()) + ) { + IcingaDbState::apply($bp); + } else { + MonitoringState::apply($bp); + } + + $removedNodes = []; + foreach (array_keys($bp->getMissingChildren()) as $missingNode) { + $node = $bp->getNode($missingNode); + $remove = new NodeRemoveAction($node); + + try { + if ($remove->appliesTo($bp)) { + $remove->applyTo($bp); + $removedNodes[] = $node->getName(); + $this->storage->storeProcess($bp); + $bp->clearAppliedChanges(); + + $foundMissingNode = true; + } + } catch (Exception $e) { + Logger::error(sprintf('(%s.conf) %s', $configName, $e->getMessage())); + + continue; + } + } + + if (! empty($removedNodes)) { + echo sprintf( + 'Removed following %d missing node(s) from %s.conf successfully:', + count($removedNodes), + $configName + ); + + echo "\n" . implode("\n", $removedNodes) . "\n\n"; + } + } + + if (! $foundMissingNode) { + echo "No missing node found.\n"; + } + } +}