mirror of
https://github.com/Icinga/icingadb-web.git
synced 2026-02-03 20:40:15 -05:00
CommandTransport: Implement chunked transmission handling
Previously, command forms were responsible for this. But they don't have any notion of fallback handling and are unable to ensure proper re-submission of failed batches.
This commit is contained in:
parent
d5b1ba7f33
commit
6af3c7e595
12 changed files with 69 additions and 46 deletions
|
|
@ -20,8 +20,6 @@ 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;
|
||||
|
|
@ -220,9 +218,9 @@ class AcknowledgeProblemForm extends CommandForm
|
|||
}
|
||||
|
||||
$granted->rewind(); // Forwards the pointer to the first element
|
||||
while ($granted->valid()) {
|
||||
if ($granted->valid()) {
|
||||
// Chunk objects to avoid timeouts with large sets
|
||||
yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 250));
|
||||
yield $command->setObjects($granted)->setChunkSize(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ 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;
|
||||
|
|
@ -163,9 +161,9 @@ class AddCommentForm extends CommandForm
|
|||
}
|
||||
|
||||
$granted->rewind(); // Forwards the pointer to the first element
|
||||
while ($granted->valid()) {
|
||||
if ($granted->valid()) {
|
||||
// Chunk objects to avoid timeouts with large sets
|
||||
yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 500));
|
||||
yield $command->setObjects($granted)->setChunkSize(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ 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
|
||||
|
|
@ -63,9 +61,9 @@ class CheckNowForm extends CommandForm
|
|||
$command->setForced();
|
||||
|
||||
$granted->rewind(); // Forwards the pointer to the first element
|
||||
while ($granted->valid()) {
|
||||
if ($granted->valid()) {
|
||||
// Chunk objects to avoid timeouts with large sets
|
||||
yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 1000));
|
||||
yield $command->setObjects($granted)->setChunkSize(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ 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
|
||||
|
|
@ -64,9 +62,9 @@ class DeleteCommentForm extends CommandForm
|
|||
$command->setAuthor($this->getAuth()->getUser()->getUsername());
|
||||
|
||||
$granted->rewind(); // Forwards the pointer to the first element
|
||||
while ($granted->valid()) {
|
||||
if ($granted->valid()) {
|
||||
// Chunk objects to avoid timeouts with large sets
|
||||
yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 500));
|
||||
yield $command->setObjects($granted)->setChunkSize(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ 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
|
||||
|
|
@ -77,9 +75,9 @@ class DeleteDowntimeForm extends CommandForm
|
|||
$command->setAuthor($this->getAuth()->getUser()->getUsername());
|
||||
|
||||
$granted->rewind(); // Forwards the pointer to the first element
|
||||
while ($granted->valid()) {
|
||||
if ($granted->valid()) {
|
||||
// Chunk objects to avoid timeouts with large sets
|
||||
yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 250));
|
||||
yield $command->setObjects($granted)->setChunkSize(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ 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;
|
||||
|
|
@ -154,9 +152,9 @@ class ProcessCheckResultForm extends CommandForm
|
|||
$command->setPerformanceData($this->getValue('perfdata'));
|
||||
|
||||
$granted->rewind(); // Forwards the pointer to the first element
|
||||
while ($granted->valid()) {
|
||||
if ($granted->valid()) {
|
||||
// Chunk objects to avoid timeouts with large sets
|
||||
yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 250));
|
||||
yield $command->setObjects($granted)->setChunkSize(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ 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;
|
||||
|
|
@ -77,9 +75,9 @@ class RemoveAcknowledgementForm extends CommandForm
|
|||
$command->setAuthor($this->getAuth()->getUser()->getUsername());
|
||||
|
||||
$granted->rewind(); // Forwards the pointer to the first element
|
||||
while ($granted->valid()) {
|
||||
if ($granted->valid()) {
|
||||
// Chunk objects to avoid timeouts with large sets
|
||||
yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 250));
|
||||
yield $command->setObjects($granted)->setChunkSize(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ 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;
|
||||
|
|
@ -129,9 +127,9 @@ class ScheduleCheckForm extends CommandForm
|
|||
$command->setCheckTime($this->getValue('check_time')->getTimestamp());
|
||||
|
||||
$granted->rewind(); // Forwards the pointer to the first element
|
||||
while ($granted->valid()) {
|
||||
if ($granted->valid()) {
|
||||
// Chunk objects to avoid timeouts with large sets
|
||||
yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 1000));
|
||||
yield $command->setObjects($granted)->setChunkSize(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ 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
|
||||
|
|
@ -290,9 +288,9 @@ class ScheduleServiceDowntimeForm extends CommandForm
|
|||
}
|
||||
|
||||
$granted->rewind(); // Forwards the pointer to the first element
|
||||
while ($granted->valid()) {
|
||||
if ($granted->valid()) {
|
||||
// Chunk objects to avoid timeouts with large sets
|
||||
yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 250));
|
||||
yield $command->setObjects($granted)->setChunkSize(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ 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;
|
||||
|
|
@ -130,9 +128,9 @@ class SendCustomNotificationForm extends CommandForm
|
|||
$command->setAuthor($this->getAuth()->getUser()->getUsername());
|
||||
|
||||
$granted->rewind(); // Forwards the pointer to the first element
|
||||
while ($granted->valid()) {
|
||||
if ($granted->valid()) {
|
||||
// Chunk objects to avoid timeouts with large sets
|
||||
yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 500));
|
||||
yield $command->setObjects($granted)->setChunkSize(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ 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
|
||||
|
|
@ -181,11 +179,11 @@ class ToggleObjectFeaturesForm extends CommandForm
|
|||
$command->setEnabled((int) $state);
|
||||
|
||||
$granted->rewind(); // Forwards the pointer to the first element
|
||||
while ($granted->valid()) {
|
||||
if ($granted->valid()) {
|
||||
$this->submittedFeatures[$command->getFeature()] ??= $command->getEnabled();
|
||||
|
||||
// Chunk objects to avoid timeouts with large sets
|
||||
yield $command->setObjects(new LimitIterator(new NoRewindIterator($granted), 0, 1000));
|
||||
yield $command->setObjects($granted)->setChunkSize(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
|
||||
namespace Icinga\Module\Icingadb\Command\Transport;
|
||||
|
||||
use Exception;
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Module\Icingadb\Command\IcingaCommand;
|
||||
use Icinga\Module\Icingadb\Command\Object\ObjectsCommand;
|
||||
|
||||
/**
|
||||
* Command transport
|
||||
|
|
@ -103,12 +103,57 @@ class CommandTransport implements CommandTransportInterface
|
|||
public function send(IcingaCommand $command, int $now = null)
|
||||
{
|
||||
$errors = [];
|
||||
$results = [];
|
||||
$retryCommand = null;
|
||||
|
||||
foreach (static::getConfig() as $name => $transportConfig) {
|
||||
$transport = static::createTransport($transportConfig);
|
||||
|
||||
if ($retryCommand !== null) {
|
||||
if ($command instanceof ObjectsCommand && $command->getChunkSize() > 0) {
|
||||
$objects = $command->getObjects();
|
||||
|
||||
if ($retryCommand !== null) {
|
||||
try {
|
||||
$results[] = $transport->send($retryCommand, $now);
|
||||
} catch (CommandTransportException) {
|
||||
// It failed prior, so no need to log it again
|
||||
continue;
|
||||
}
|
||||
|
||||
$retryCommand = null;
|
||||
} else {
|
||||
if ($objects->key() === null) {
|
||||
// We traverse the iterator manually here, so we have to rewind it before the first iteration.
|
||||
// That should be the case if the current key is null. May fail if an iterator explicitly yields
|
||||
// null as the key, but I want to see a justified use case for that…
|
||||
$objects->rewind();
|
||||
}
|
||||
}
|
||||
|
||||
while ($objects->valid()) {
|
||||
$batchCommand = clone $command;
|
||||
$batchCommand->setObjects(
|
||||
new \LimitIterator(new \NoRewindIterator($objects), 0, $command->getChunkSize())
|
||||
);
|
||||
|
||||
try {
|
||||
$results[] = $transport->send($batchCommand, $now);
|
||||
} catch (CommandTransportException $e) {
|
||||
Logger::error($e);
|
||||
$errors[] = sprintf('%s: %s.', $name, rtrim($e->getMessage(), '.'));
|
||||
|
||||
$retryCommand = $e->getCommand();
|
||||
if ($retryCommand !== null) {
|
||||
continue 2;
|
||||
} else {
|
||||
// Non-recoverable error, so stop trying to send further commands
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
} elseif ($retryCommand !== null) {
|
||||
try {
|
||||
$result = $transport->send($retryCommand, $now);
|
||||
} catch (CommandTransportException) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue