From 2da207bedd5a4e5b081f9e6c5c157220695d00b1 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 4 Jun 2025 16:22:55 +0200 Subject: [PATCH 1/5] commands: Send multiple commands while bulk processing Reduces risk of timeouts in case Icinga takes its time to respond. The timeout of 15 seconds introduced earlier has been kept. Chunk sizes were chosen as follows: - 1000: Cheap calculations (object features, check scheduling) - 500: Expected disk writes (comments) - 250: Process check result, Downtimes, Acks --- .../Command/Object/AcknowledgeProblemForm.php | 32 +++++++++-------- .../forms/Command/Object/AddCommentForm.php | 26 +++++++------- .../forms/Command/Object/CheckNowForm.php | 16 +++++---- .../Command/Object/DeleteCommentForm.php | 14 ++++---- .../Command/Object/DeleteDowntimeForm.php | 14 ++++---- .../Command/Object/ProcessCheckResultForm.php | 18 +++++----- .../Object/RemoveAcknowledgementForm.php | 14 ++++---- .../Command/Object/ScheduleCheckForm.php | 16 +++++---- .../Object/ScheduleServiceDowntimeForm.php | 36 ++++++++++--------- .../Object/SendCustomNotificationForm.php | 18 +++++----- .../Object/ToggleObjectFeaturesForm.php | 25 ++++++------- 11 files changed, 125 insertions(+), 104 deletions(-) diff --git a/application/forms/Command/Object/AcknowledgeProblemForm.php b/application/forms/Command/Object/AcknowledgeProblemForm.php index fad3d9d2..bffa72e2 100644 --- a/application/forms/Command/Object/AcknowledgeProblemForm.php +++ b/application/forms/Command/Object/AcknowledgeProblemForm.php @@ -20,6 +20,8 @@ use ipl\Validator\CallbackValidator; use ipl\Web\FormDecorator\IcingaFormDecorator; use ipl\Web\Widget\Icon; use Iterator; +use LimitIterator; +use NoRewindIterator; use Traversable; use function ipl\Stdlib\iterable_value_first; @@ -196,22 +198,22 @@ class AcknowledgeProblemForm extends CommandForm return $this->isGrantedOn('icingadb/command/acknowledge-problem', $object); }); + $command = new AcknowledgeProblemCommand(); + $command->setComment($this->getValue('comment')); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); + $command->setNotify($this->getElement('notify')->isChecked()); + $command->setSticky($this->getElement('sticky')->isChecked()); + $command->setPersistent($this->getElement('persistent')->isChecked()); + + if (($expireTime = $this->getValue('expire_time')) !== null) { + /** @var DateTime $expireTime */ + $command->setExpireTime($expireTime->getTimestamp()); + } + $granted->rewind(); // Forwards the pointer to the first element - if ($granted->valid()) { - $command = new AcknowledgeProblemCommand(); - $command->setObjects($granted); - $command->setComment($this->getValue('comment')); - $command->setAuthor($this->getAuth()->getUser()->getUsername()); - $command->setNotify($this->getElement('notify')->isChecked()); - $command->setSticky($this->getElement('sticky')->isChecked()); - $command->setPersistent($this->getElement('persistent')->isChecked()); - - if (($expireTime = $this->getValue('expire_time')) !== null) { - /** @var DateTime $expireTime */ - $command->setExpireTime($expireTime->getTimestamp()); - } - - yield $command; + while ($granted->valid()) { + // Chunk objects to avoid timeouts with large sets + yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 250)); } } } diff --git a/application/forms/Command/Object/AddCommentForm.php b/application/forms/Command/Object/AddCommentForm.php index e991e84f..fbf07b18 100644 --- a/application/forms/Command/Object/AddCommentForm.php +++ b/application/forms/Command/Object/AddCommentForm.php @@ -20,6 +20,8 @@ use ipl\Validator\CallbackValidator; use ipl\Web\FormDecorator\IcingaFormDecorator; use ipl\Web\Widget\Icon; use Iterator; +use LimitIterator; +use NoRewindIterator; use Traversable; use function ipl\Stdlib\iterable_value_first; @@ -150,19 +152,19 @@ class AddCommentForm extends CommandForm return $this->isGrantedOn('icingadb/command/comment/add', $object); }); + $command = new AddCommentCommand(); + $command->setComment($this->getValue('comment')); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); + + if (($expireTime = $this->getValue('expire_time'))) { + /** @var DateTime $expireTime */ + $command->setExpireTime($expireTime->getTimestamp()); + } + $granted->rewind(); // Forwards the pointer to the first element - if ($granted->valid()) { - $command = new AddCommentCommand(); - $command->setObjects($granted); - $command->setComment($this->getValue('comment')); - $command->setAuthor($this->getAuth()->getUser()->getUsername()); - - if (($expireTime = $this->getValue('expire_time'))) { - /** @var DateTime $expireTime */ - $command->setExpireTime($expireTime->getTimestamp()); - } - - yield $command; + while ($granted->valid()) { + // Chunk objects to avoid timeouts with large sets + yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 500)); } } } diff --git a/application/forms/Command/Object/CheckNowForm.php b/application/forms/Command/Object/CheckNowForm.php index eb1e03ba..ac75aa6b 100644 --- a/application/forms/Command/Object/CheckNowForm.php +++ b/application/forms/Command/Object/CheckNowForm.php @@ -11,6 +11,8 @@ use Icinga\Web\Notification; use ipl\Orm\Model; use ipl\Web\Widget\Icon; use Iterator; +use LimitIterator; +use NoRewindIterator; use Traversable; class CheckNowForm extends CommandForm @@ -56,14 +58,14 @@ class CheckNowForm extends CommandForm ); }); - $granted->rewind(); // Forwards the pointer to the first element - if ($granted->valid()) { - $command = new ScheduleCheckCommand(); - $command->setObjects($granted); - $command->setCheckTime(time()); - $command->setForced(); + $command = new ScheduleCheckCommand(); + $command->setCheckTime(time()); + $command->setForced(); - yield $command; + $granted->rewind(); // Forwards the pointer to the first element + while ($granted->valid()) { + // Chunk objects to avoid timeouts with large sets + yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 1000)); } } } diff --git a/application/forms/Command/Object/DeleteCommentForm.php b/application/forms/Command/Object/DeleteCommentForm.php index 46c3f2b0..2fa73661 100644 --- a/application/forms/Command/Object/DeleteCommentForm.php +++ b/application/forms/Command/Object/DeleteCommentForm.php @@ -11,6 +11,8 @@ use Icinga\Web\Notification; use ipl\Orm\Model; use ipl\Web\Widget\Icon; use Iterator; +use LimitIterator; +use NoRewindIterator; use Traversable; class DeleteCommentForm extends CommandForm @@ -58,13 +60,13 @@ class DeleteCommentForm extends CommandForm return $this->isGrantedOn('icingadb/command/comment/delete', $object->{$object->object_type}); }); - $granted->rewind(); // Forwards the pointer to the first element - if ($granted->valid()) { - $command = new DeleteCommentCommand(); - $command->setObjects($granted); - $command->setAuthor($this->getAuth()->getUser()->getUsername()); + $command = new DeleteCommentCommand(); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); - yield $command; + $granted->rewind(); // Forwards the pointer to the first element + while ($granted->valid()) { + // Chunk objects to avoid timeouts with large sets + yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 500)); } } } diff --git a/application/forms/Command/Object/DeleteDowntimeForm.php b/application/forms/Command/Object/DeleteDowntimeForm.php index c381a381..607f74c9 100644 --- a/application/forms/Command/Object/DeleteDowntimeForm.php +++ b/application/forms/Command/Object/DeleteDowntimeForm.php @@ -11,6 +11,8 @@ use Icinga\Web\Notification; use ipl\Orm\Model; use ipl\Web\Widget\Icon; use Iterator; +use LimitIterator; +use NoRewindIterator; use Traversable; class DeleteDowntimeForm extends CommandForm @@ -71,13 +73,13 @@ class DeleteDowntimeForm extends CommandForm && $this->isGrantedOn('icingadb/command/downtime/delete', $object->{$object->object_type}); }); - $granted->rewind(); // Forwards the pointer to the first element - if ($granted->valid()) { - $command = new DeleteDowntimeCommand(); - $command->setObjects($granted); - $command->setAuthor($this->getAuth()->getUser()->getUsername()); + $command = new DeleteDowntimeCommand(); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); - yield $command; + $granted->rewind(); // Forwards the pointer to the first element + while ($granted->valid()) { + // Chunk objects to avoid timeouts with large sets + yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 250)); } } } diff --git a/application/forms/Command/Object/ProcessCheckResultForm.php b/application/forms/Command/Object/ProcessCheckResultForm.php index 7c612bfa..022180ad 100644 --- a/application/forms/Command/Object/ProcessCheckResultForm.php +++ b/application/forms/Command/Object/ProcessCheckResultForm.php @@ -16,6 +16,8 @@ use ipl\Orm\Model; use ipl\Web\FormDecorator\IcingaFormDecorator; use ipl\Web\Widget\Icon; use Iterator; +use LimitIterator; +use NoRewindIterator; use Traversable; use function ipl\Stdlib\iterable_value_first; @@ -141,15 +143,15 @@ class ProcessCheckResultForm extends CommandForm && $this->isGrantedOn('icingadb/command/process-check-result', $object); }); - $granted->rewind(); // Forwards the pointer to the first element - if ($granted->valid()) { - $command = new ProcessCheckResultCommand(); - $command->setObjects($granted); - $command->setStatus($this->getValue('status')); - $command->setOutput($this->getValue('output')); - $command->setPerformanceData($this->getValue('perfdata')); + $command = new ProcessCheckResultCommand(); + $command->setStatus($this->getValue('status')); + $command->setOutput($this->getValue('output')); + $command->setPerformanceData($this->getValue('perfdata')); - yield $command; + $granted->rewind(); // Forwards the pointer to the first element + while ($granted->valid()) { + // Chunk objects to avoid timeouts with large sets + yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 250)); } } } diff --git a/application/forms/Command/Object/RemoveAcknowledgementForm.php b/application/forms/Command/Object/RemoveAcknowledgementForm.php index d81fe7f9..c66f9d1d 100644 --- a/application/forms/Command/Object/RemoveAcknowledgementForm.php +++ b/application/forms/Command/Object/RemoveAcknowledgementForm.php @@ -12,6 +12,8 @@ use Icinga\Web\Notification; use ipl\Orm\Model; use ipl\Web\Widget\Icon; use Iterator; +use LimitIterator; +use NoRewindIterator; use Traversable; use function ipl\Stdlib\iterable_value_first; @@ -71,13 +73,13 @@ class RemoveAcknowledgementForm extends CommandForm return $this->isGrantedOn('icingadb/command/remove-acknowledgement', $object); }); - $granted->rewind(); // Forwards the pointer to the first element - if ($granted->valid()) { - $command = new RemoveAcknowledgementCommand(); - $command->setObjects($granted); - $command->setAuthor($this->getAuth()->getUser()->getUsername()); + $command = new RemoveAcknowledgementCommand(); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); - yield $command; + $granted->rewind(); // Forwards the pointer to the first element + while ($granted->valid()) { + // Chunk objects to avoid timeouts with large sets + yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 250)); } } } diff --git a/application/forms/Command/Object/ScheduleCheckForm.php b/application/forms/Command/Object/ScheduleCheckForm.php index bdeba53c..1993ae34 100644 --- a/application/forms/Command/Object/ScheduleCheckForm.php +++ b/application/forms/Command/Object/ScheduleCheckForm.php @@ -18,6 +18,8 @@ use ipl\Orm\Model; use ipl\Web\FormDecorator\IcingaFormDecorator; use ipl\Web\Widget\Icon; use Iterator; +use LimitIterator; +use NoRewindIterator; use Traversable; use function ipl\Stdlib\iterable_value_first; @@ -121,14 +123,14 @@ class ScheduleCheckForm extends CommandForm ); }); - $granted->rewind(); // Forwards the pointer to the first element - if ($granted->valid()) { - $command = new ScheduleCheckCommand(); - $command->setObjects($granted); - $command->setForced($this->getElement('force_check')->isChecked()); - $command->setCheckTime($this->getValue('check_time')->getTimestamp()); + $command = new ScheduleCheckCommand(); + $command->setForced($this->getElement('force_check')->isChecked()); + $command->setCheckTime($this->getValue('check_time')->getTimestamp()); - yield $command; + $granted->rewind(); // Forwards the pointer to the first element + while ($granted->valid()) { + // Chunk objects to avoid timeouts with large sets + yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 1000)); } } } diff --git a/application/forms/Command/Object/ScheduleServiceDowntimeForm.php b/application/forms/Command/Object/ScheduleServiceDowntimeForm.php index 5605a0c0..f3a936a1 100644 --- a/application/forms/Command/Object/ScheduleServiceDowntimeForm.php +++ b/application/forms/Command/Object/ScheduleServiceDowntimeForm.php @@ -19,6 +19,8 @@ use ipl\Validator\CallbackValidator; use ipl\Web\FormDecorator\IcingaFormDecorator; use ipl\Web\Widget\Icon; use Iterator; +use LimitIterator; +use NoRewindIterator; use Traversable; class ScheduleServiceDowntimeForm extends CommandForm @@ -272,24 +274,24 @@ class ScheduleServiceDowntimeForm extends CommandForm return $this->isGrantedOn('icingadb/command/downtime/schedule', $object); }); + $command = new ScheduleDowntimeCommand(); + $command->setComment($this->getValue('comment')); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); + $command->setStart($this->getValue('start')->getTimestamp()); + $command->setEnd($this->getValue('end')->getTimestamp()); + $command->setChildOption((int) $this->getValue('child_options')); + + if ($this->getElement('flexible')->isChecked()) { + $command->setFixed(false); + $command->setDuration( + $this->getValue('hours') * 3600 + $this->getValue('minutes') * 60 + ); + } + $granted->rewind(); // Forwards the pointer to the first element - if ($granted->valid()) { - $command = new ScheduleDowntimeCommand(); - $command->setObjects($granted); - $command->setComment($this->getValue('comment')); - $command->setAuthor($this->getAuth()->getUser()->getUsername()); - $command->setStart($this->getValue('start')->getTimestamp()); - $command->setEnd($this->getValue('end')->getTimestamp()); - $command->setChildOption((int) $this->getValue('child_options')); - - if ($this->getElement('flexible')->isChecked()) { - $command->setFixed(false); - $command->setDuration( - $this->getValue('hours') * 3600 + $this->getValue('minutes') * 60 - ); - } - - yield $command; + while ($granted->valid()) { + // Chunk objects to avoid timeouts with large sets + yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 250)); } } } diff --git a/application/forms/Command/Object/SendCustomNotificationForm.php b/application/forms/Command/Object/SendCustomNotificationForm.php index 4b0539e0..065d158d 100644 --- a/application/forms/Command/Object/SendCustomNotificationForm.php +++ b/application/forms/Command/Object/SendCustomNotificationForm.php @@ -17,6 +17,8 @@ use ipl\Orm\Model; use ipl\Web\FormDecorator\IcingaFormDecorator; use ipl\Web\Widget\Icon; use Iterator; +use LimitIterator; +use NoRewindIterator; use Traversable; use function ipl\Stdlib\iterable_value_first; @@ -117,15 +119,15 @@ class SendCustomNotificationForm extends CommandForm return $this->isGrantedOn('icingadb/command/send-custom-notification', $object); }); - $granted->rewind(); // Forwards the pointer to the first element - if ($granted->valid()) { - $command = new SendCustomNotificationCommand(); - $command->setObjects($granted); - $command->setComment($this->getValue('comment')); - $command->setForced($this->getElement('forced')->isChecked()); - $command->setAuthor($this->getAuth()->getUser()->getUsername()); + $command = new SendCustomNotificationCommand(); + $command->setComment($this->getValue('comment')); + $command->setForced($this->getElement('forced')->isChecked()); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); - yield $command; + $granted->rewind(); // Forwards the pointer to the first element + while ($granted->valid()) { + // Chunk objects to avoid timeouts with large sets + yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 500)); } } } diff --git a/application/forms/Command/Object/ToggleObjectFeaturesForm.php b/application/forms/Command/Object/ToggleObjectFeaturesForm.php index 63530f6a..296b1041 100644 --- a/application/forms/Command/Object/ToggleObjectFeaturesForm.php +++ b/application/forms/Command/Object/ToggleObjectFeaturesForm.php @@ -12,6 +12,8 @@ use ipl\Html\FormElement\CheckboxElement; use ipl\Orm\Model; use ipl\Web\FormDecorator\IcingaFormDecorator; use Iterator; +use LimitIterator; +use NoRewindIterator; use Traversable; class ToggleObjectFeaturesForm extends CommandForm @@ -25,7 +27,7 @@ class ToggleObjectFeaturesForm extends CommandForm /** * ToggleFeature(s) being used to submit this form * - * @var ToggleObjectFeatureCommand[] + * @var array */ protected $submittedFeatures = []; @@ -62,9 +64,8 @@ class ToggleObjectFeaturesForm extends CommandForm return; } - foreach ($this->submittedFeatures as $feature) { - $enabled = $feature->getEnabled(); - switch ($feature->getFeature()) { + foreach ($this->submittedFeatures as $feature => $enabled) { + switch ($feature) { case ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS: if ($enabled) { $message = t('Enabled active checks successfully'); @@ -175,16 +176,16 @@ class ToggleObjectFeaturesForm extends CommandForm return $this->isGrantedOn($spec['permission'], $object); }); + $command = new ToggleObjectFeatureCommand(); + $command->setFeature($feature); + $command->setEnabled((int) $state); + $granted->rewind(); // Forwards the pointer to the first element - if ($granted->valid()) { - $command = new ToggleObjectFeatureCommand(); - $command->setObjects($granted); - $command->setFeature($feature); - $command->setEnabled((int) $state); + while ($granted->valid()) { + $this->submittedFeatures[$command->getFeature()] ??= $command->getEnabled(); - $this->submittedFeatures[] = $command; - - yield $command; + // Chunk objects to avoid timeouts with large sets + yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 1000)); } } } From 3f1cdc0a85c547b883f0ba3059707b30acbe3ba9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 4 Jun 2025 17:13:09 +0200 Subject: [PATCH 2/5] forms/Command/Object: Show progress labels upon submit fixes #1204 --- .../forms/Command/Object/AcknowledgeProblemForm.php | 13 +++++++++++-- application/forms/Command/Object/AddCommentForm.php | 5 +++-- .../forms/Command/Object/ProcessCheckResultForm.php | 5 +++++ .../forms/Command/Object/ScheduleCheckForm.php | 5 +++-- .../Command/Object/ScheduleServiceDowntimeForm.php | 5 +++-- .../Command/Object/SendCustomNotificationForm.php | 7 ++++++- 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/application/forms/Command/Object/AcknowledgeProblemForm.php b/application/forms/Command/Object/AcknowledgeProblemForm.php index bffa72e2..6fe77923 100644 --- a/application/forms/Command/Object/AcknowledgeProblemForm.php +++ b/application/forms/Command/Object/AcknowledgeProblemForm.php @@ -184,8 +184,17 @@ class AcknowledgeProblemForm extends CommandForm 'submit', 'btn_submit', [ - 'required' => true, - 'label' => tp('Acknowledge problem', 'Acknowledge problems', count($this->getObjects())) + 'required' => true, + 'label' => tp( + 'Acknowledge problem', + 'Acknowledge problems', + count($this->getObjects()) + ), + 'data-progress-label' => tp( + 'Acknowledging problem', + 'Acknowledging problems', + count($this->getObjects()) + ) ] ); diff --git a/application/forms/Command/Object/AddCommentForm.php b/application/forms/Command/Object/AddCommentForm.php index fbf07b18..9bc0942a 100644 --- a/application/forms/Command/Object/AddCommentForm.php +++ b/application/forms/Command/Object/AddCommentForm.php @@ -138,8 +138,9 @@ class AddCommentForm extends CommandForm 'submit', 'btn_submit', [ - 'required' => true, - 'label' => tp('Add comment', 'Add comments', count($this->getObjects())) + 'required' => true, + 'label' => tp('Add comment', 'Add comments', count($this->getObjects())), + 'data-progress-label' => tp('Adding comment', 'Adding comments', count($this->getObjects())) ] ); diff --git a/application/forms/Command/Object/ProcessCheckResultForm.php b/application/forms/Command/Object/ProcessCheckResultForm.php index 022180ad..545ace27 100644 --- a/application/forms/Command/Object/ProcessCheckResultForm.php +++ b/application/forms/Command/Object/ProcessCheckResultForm.php @@ -129,6 +129,11 @@ class ProcessCheckResultForm extends CommandForm 'Submit Passive Check Result', 'Submit Passive Check Results', count($this->getObjects()) + ), + 'data-progress-label' => tp( + 'Submitting Passive Check Result', + 'Submitting Passive Check Results', + count($this->getObjects()) ) ] ); diff --git a/application/forms/Command/Object/ScheduleCheckForm.php b/application/forms/Command/Object/ScheduleCheckForm.php index 1993ae34..043a8a78 100644 --- a/application/forms/Command/Object/ScheduleCheckForm.php +++ b/application/forms/Command/Object/ScheduleCheckForm.php @@ -105,8 +105,9 @@ class ScheduleCheckForm extends CommandForm 'submit', 'btn_submit', [ - 'required' => true, - 'label' => tp('Schedule check', 'Schedule checks', count($this->getObjects())) + 'required' => true, + 'label' => tp('Schedule check', 'Schedule checks', count($this->getObjects())), + 'data-progress-label' => tp('Scheduling check', 'Scheduling checks', count($this->getObjects())) ] ); diff --git a/application/forms/Command/Object/ScheduleServiceDowntimeForm.php b/application/forms/Command/Object/ScheduleServiceDowntimeForm.php index f3a936a1..afe578ac 100644 --- a/application/forms/Command/Object/ScheduleServiceDowntimeForm.php +++ b/application/forms/Command/Object/ScheduleServiceDowntimeForm.php @@ -260,8 +260,9 @@ class ScheduleServiceDowntimeForm extends CommandForm 'submit', 'btn_submit', [ - 'required' => true, - 'label' => tp('Schedule downtime', 'Schedule downtimes', count($this->getObjects())) + 'required' => true, + 'label' => tp('Schedule downtime', 'Schedule downtimes', count($this->getObjects())), + 'data-progress-label' => tp('Scheduling downtime', 'Scheduling downtimes', count($this->getObjects())) ] ); diff --git a/application/forms/Command/Object/SendCustomNotificationForm.php b/application/forms/Command/Object/SendCustomNotificationForm.php index 065d158d..5f098bc0 100644 --- a/application/forms/Command/Object/SendCustomNotificationForm.php +++ b/application/forms/Command/Object/SendCustomNotificationForm.php @@ -106,7 +106,12 @@ class SendCustomNotificationForm extends CommandForm 'btn_submit', [ 'required' => true, - 'label' => tp('Send custom notification', 'Send custom notifications', count($this->getObjects())) + 'label' => tp('Send custom notification', 'Send custom notifications', count($this->getObjects())), + 'data-progress-label' => tp( + 'Sending custom notification', + 'Sending custom notifications', + count($this->getObjects()) + ) ] ); From 3082353eac61c36edef59a58eeac77a1ab5c959d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Jun 2025 12:01:41 +0200 Subject: [PATCH 3/5] CommandActions: Raise time and memory limit --- library/Icingadb/Common/CommandActions.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/Icingadb/Common/CommandActions.php b/library/Icingadb/Common/CommandActions.php index eb161995..673d1033 100644 --- a/library/Icingadb/Common/CommandActions.php +++ b/library/Icingadb/Common/CommandActions.php @@ -17,6 +17,7 @@ use Icinga\Module\Icingadb\Forms\Command\Object\ScheduleServiceDowntimeForm; use Icinga\Module\Icingadb\Forms\Command\Object\SendCustomNotificationForm; use Icinga\Module\Icingadb\Forms\Command\Object\ToggleObjectFeaturesForm; use Icinga\Security\SecurityException; +use Icinga\Util\Environment; use Icinga\Web\Notification; use ipl\Orm\Model; use ipl\Orm\Query; @@ -151,6 +152,10 @@ trait CommandActions $this->httpBadRequest('Responding with JSON during a Web request is not supported'); } + // Bulk operations may require more memory and time + Environment::raiseMemoryLimit(); + Environment::raiseExecutionTime(); + if (is_string($form)) { /** @var CommandForm $form */ $form = new $form(); From d28787a7d9126bddf22071df05a130e82c2d2529 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Jun 2025 15:08:30 +0200 Subject: [PATCH 4/5] ApiCommandTransport: Ensure timed out commands are not retried This fundamentally changes the expectation in #1138. But since we cannot guarantee that Icinga 2 did not actually perform the action, we also should not retry the command. Otherwise there may be duplicated downtimes, comments, etc. --- library/Icingadb/Command/Transport/ApiCommandTransport.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/Icingadb/Command/Transport/ApiCommandTransport.php b/library/Icingadb/Command/Transport/ApiCommandTransport.php index 3325d8ed..7ea70f42 100644 --- a/library/Icingadb/Command/Transport/ApiCommandTransport.php +++ b/library/Icingadb/Command/Transport/ApiCommandTransport.php @@ -246,6 +246,13 @@ class ApiCommandTransport implements CommandTransportInterface 'verify' => false ]); } catch (GuzzleException $e) { + if (str_starts_with(ltrim($e->getMessage()), 'cURL error 28:')) { + throw new ApiCommandException(t( + 'No response from the Icinga 2 API received after %d seconds.' + . ' Please make sure the action has not been performed, before retrying' + ), 15, $e); + } + throw new CommandTransportException( 'Can\'t connect to the Icinga 2 API: %u %s', $e->getCode(), From 455c3b6cd43f88d02545f32023522330e7e16dc1 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Jun 2025 17:21:18 +0200 Subject: [PATCH 5/5] ApiCommandTransport: Introduce constant `SEND_TIMEOUT` --- .../Icingadb/Command/Transport/ApiCommandTransport.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/library/Icingadb/Command/Transport/ApiCommandTransport.php b/library/Icingadb/Command/Transport/ApiCommandTransport.php index 7ea70f42..6d9433d6 100644 --- a/library/Icingadb/Command/Transport/ApiCommandTransport.php +++ b/library/Icingadb/Command/Transport/ApiCommandTransport.php @@ -19,6 +19,9 @@ use Icinga\Util\Json; */ class ApiCommandTransport implements CommandTransportInterface { + /** @var int Used timeout when sending a command */ + protected const SEND_TIMEOUT = 15; + /** * Transport identifier */ @@ -237,7 +240,7 @@ class ApiCommandTransport implements CommandTransportInterface } try { - $response = (new Client(['timeout' => 15])) + $response = (new Client(['timeout' => static::SEND_TIMEOUT])) ->post($this->getUriFor($command->getEndpoint()), [ 'auth' => [$this->getUsername(), $this->getPassword()], 'headers' => $headers, @@ -250,7 +253,7 @@ class ApiCommandTransport implements CommandTransportInterface throw new ApiCommandException(t( 'No response from the Icinga 2 API received after %d seconds.' . ' Please make sure the action has not been performed, before retrying' - ), 15, $e); + ), static::SEND_TIMEOUT, $e); } throw new CommandTransportException( @@ -318,7 +321,7 @@ class ApiCommandTransport implements CommandTransportInterface public function probe() { try { - $response = (new Client(['timeout' => 15])) + $response = (new Client(['timeout' => static::SEND_TIMEOUT])) ->get($this->getUriFor(''), [ 'auth' => [$this->getUsername(), $this->getPassword()], 'headers' => ['Accept' => 'application/json'],