Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance of Icinga 2 API commands processed in bulk #721

Merged
merged 7 commits into from Aug 10, 2023
2 changes: 1 addition & 1 deletion application/controllers/HostController.php
Expand Up @@ -86,7 +86,7 @@ public function indexAction()
public function sourceAction()
{
$this->assertPermission('icingadb/object/show-source');
$apiResult = (new CommandTransport())->send((new GetObjectCommand())->setObject($this->host));
$apiResult = (new CommandTransport())->send((new GetObjectCommand())->setObjects([$this->host]));

if ($this->host->state->is_overdue) {
$this->controls->addAttributes(['class' => 'overdue']);
Expand Down
2 changes: 1 addition & 1 deletion application/controllers/ServiceController.php
Expand Up @@ -91,7 +91,7 @@ public function indexAction()
public function sourceAction()
{
$this->assertPermission('icingadb/object/show-source');
$apiResult = (new CommandTransport())->send((new GetObjectCommand())->setObject($this->service));
$apiResult = (new CommandTransport())->send((new GetObjectCommand())->setObjects([$this->service]));

if ($this->service->state->is_overdue) {
$this->controls->addAttributes(['class' => 'overdue']);
Expand Down
55 changes: 34 additions & 21 deletions application/forms/Command/CommandForm.php
Expand Up @@ -4,18 +4,23 @@

namespace Icinga\Module\Icingadb\Forms\Command;

use ArrayIterator;
use Exception;
use Generator;
use Icinga\Application\Logger;
use Icinga\Module\Icingadb\Command\IcingaCommand;
use Icinga\Module\Icingadb\Command\Transport\CommandTransport;
use Icinga\Module\Icingadb\Common\Auth;
use Icinga\Web\Notification;
use Icinga\Web\Session;
use ipl\Html\Form;
use ipl\Orm\Model;
use ipl\Web\Common\CsrfCounterMeasure;
use Traversable;

abstract class CommandForm extends Form
{
use Auth;
use CsrfCounterMeasure;

protected $defaultAttributes = ['class' => 'icinga-form icinga-controls'];
Expand Down Expand Up @@ -71,13 +76,13 @@ abstract protected function assembleElements();
abstract protected function assembleSubmitButton();

/**
* Get the command to issue for the given object
* Get the commands to issue for the given objects
*
* @param Model $object
* @param Traversable<Model> $objects
*
* @return IcingaCommand|IcingaCommand[]|null NULL in case no command should be issued for the object
* @return Traversable<IcingaCommand>
*/
abstract protected function getCommand(Model $object);
abstract protected function getCommands(Traversable $objects): Traversable;

protected function assemble()
{
Expand All @@ -89,23 +94,14 @@ protected function assemble()
protected function onSuccess()
{
$errors = [];
foreach ($this->getObjects() as $object) {
$commands = $this->getCommand($object);
if ($commands === null) {
continue;
}

if ($commands instanceof IcingaCommand) {
$commands = [$commands];
}

foreach ($commands as $command) {
try {
$this->sendCommand($command);
} catch (Exception $e) {
Logger::error($e->getMessage());
$errors[] = $e->getMessage();
}
$objects = $this->getObjects();

foreach ($this->getCommands(is_array($objects) ? new ArrayIterator($objects) : $objects) as $command) {
try {
$this->sendCommand($command);
} catch (Exception $e) {
Logger::error($e->getMessage());
$errors[] = $e->getMessage();
}
}

Expand Down Expand Up @@ -133,4 +129,21 @@ protected function sendCommand(IcingaCommand $command)
{
(new CommandTransport())->send($command);
}

/**
* Yield the $objects the currently logged in user has the permission $permission for
*
* @param string $permission
* @param Traversable $objects
*
* @return Generator
*/
protected function filterGrantedOn(string $permission, Traversable $objects): Generator
{
foreach ($objects as $object) {
if ($this->isGrantedOn($permission, $object)) {
yield $object;
}
}
}
}
Expand Up @@ -5,16 +5,13 @@
namespace Icinga\Module\Icingadb\Forms\Command\Instance;

use Icinga\Module\Icingadb\Command\Instance\ToggleInstanceFeatureCommand;
use Icinga\Module\Icingadb\Common\Auth;
use Icinga\Module\Icingadb\Forms\Command\CommandForm;
use Icinga\Web\Notification;
use ipl\Orm\Model;
use ipl\Web\FormDecorator\IcingaFormDecorator;
use Traversable;

class ToggleInstanceFeaturesForm extends CommandForm
{
use Auth;

protected $features;

protected $featureStatus;
Expand Down Expand Up @@ -136,7 +133,7 @@ protected function assembleSubmitButton()
{
}

protected function getCommand(Model $object): \Generator
protected function getCommands(Traversable $objects): Traversable
{
foreach ($this->features as $feature => $spec) {
$featureState = $this->getElement($feature)->isChecked();
Expand Down
42 changes: 18 additions & 24 deletions application/forms/Command/Object/AcknowledgeProblemForm.php
Expand Up @@ -8,22 +8,19 @@
use DateTime;
use Icinga\Application\Config;
use Icinga\Module\Icingadb\Command\Object\AcknowledgeProblemCommand;
use Icinga\Module\Icingadb\Common\Auth;
use Icinga\Module\Icingadb\Forms\Command\CommandForm;
use Icinga\Module\Icingadb\Model\Host;
use Icinga\Web\Notification;
use ipl\Html\Attributes;
use ipl\Html\HtmlElement;
use ipl\Html\Text;
use ipl\Orm\Model;
use ipl\Validator\CallbackValidator;
use ipl\Web\FormDecorator\IcingaFormDecorator;
use ipl\Web\Widget\Icon;
use Traversable;

class AcknowledgeProblemForm extends CommandForm
{
use Auth;

public function __construct()
{
$this->on(self::ON_SUCCESS, function () {
Expand Down Expand Up @@ -187,28 +184,25 @@ protected function assembleSubmitButton()
(new IcingaFormDecorator())->decorate($this->getElement('btn_submit'));
}

/**
* @return ?AcknowledgeProblemCommand
*/
protected function getCommand(Model $object)
protected function getCommands(Traversable $objects): Traversable
{
if (! $this->isGrantedOn('icingadb/command/acknowledge-problem', $object)) {
return null;
}
$granted = $this->filterGrantedOn('icingadb/command/acknowledge-problem', $objects);

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());
}

$command = new AcknowledgeProblemCommand();
$command->setObject($object);
$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;
}

return $command;
}
}
34 changes: 14 additions & 20 deletions application/forms/Command/Object/AddCommentForm.php
Expand Up @@ -8,22 +8,19 @@
use DateTime;
use Icinga\Application\Config;
use Icinga\Module\Icingadb\Command\Object\AddCommentCommand;
use Icinga\Module\Icingadb\Common\Auth;
use Icinga\Module\Icingadb\Forms\Command\CommandForm;
use Icinga\Module\Icingadb\Model\Host;
use Icinga\Web\Notification;
use ipl\Html\Attributes;
use ipl\Html\HtmlElement;
use ipl\Html\Text;
use ipl\Orm\Model;
use ipl\Validator\CallbackValidator;
use ipl\Web\FormDecorator\IcingaFormDecorator;
use ipl\Web\Widget\Icon;
use Traversable;

class AddCommentForm extends CommandForm
{
use Auth;

public function __construct()
{
$this->on(self::ON_SUCCESS, function () {
Expand Down Expand Up @@ -142,25 +139,22 @@ protected function assembleSubmitButton()
(new IcingaFormDecorator())->decorate($this->getElement('btn_submit'));
}

/**
* @return ?AddCommentCommand
*/
protected function getCommand(Model $object)
protected function getCommands(Traversable $objects): Traversable
{
if (! $this->isGrantedOn('icingadb/command/comment/add', $object)) {
return null;
}
$granted = $this->filterGrantedOn('icingadb/command/comment/add', $objects);

$command = new AddCommentCommand();
$command->setObject($object);
$command->setComment($this->getValue('comment'));
$command->setAuthor($this->getAuth()->getUser()->getUsername());
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());
}
if (($expireTime = $this->getValue('expire_time'))) {
/** @var DateTime $expireTime */
$command->setExpireTime($expireTime->getTimestamp());
}

return $command;
yield $command;
}
}
}
45 changes: 23 additions & 22 deletions application/forms/Command/Object/CheckNowForm.php
Expand Up @@ -4,17 +4,15 @@

namespace Icinga\Module\Icingadb\Forms\Command\Object;

use Generator;
use Icinga\Module\Icingadb\Command\Object\ScheduleCheckCommand;
use Icinga\Module\Icingadb\Common\Auth;
use Icinga\Module\Icingadb\Forms\Command\CommandForm;
use Icinga\Web\Notification;
use ipl\Orm\Model;
use ipl\Web\Widget\Icon;
use Traversable;

class CheckNowForm extends CommandForm
{
use Auth;

protected $defaultAttributes = ['class' => 'inline'];

public function __construct()
Expand Down Expand Up @@ -46,26 +44,29 @@ protected function assembleSubmitButton()
);
}

/**
* @return ?ScheduleCheckCommand
*/
protected function getCommand(Model $object)
protected function getCommands(Traversable $objects): Traversable
{
if (
! $this->isGrantedOn('icingadb/command/schedule-check', $object)
&& (
! $object->active_checks_enabled
|| ! $this->isGrantedOn('icingadb/command/schedule-check/active-only', $object)
)
) {
return null;
}
$granted = (function () use ($objects): Generator {
foreach ($objects as $object) {
if (
$this->isGrantedOn('icingadb/command/schedule-check', $object)
|| (
$object->active_checks_enabled
&& $this->isGrantedOn('icingadb/command/schedule-check/active-only', $object)
)
) {
yield $object;
}
}
})();

$command = new ScheduleCheckCommand();
$command->setObject($object);
$command->setCheckTime(time());
$command->setForced();
if ($granted->valid()) {
$command = new ScheduleCheckCommand();
$command->setObjects($granted);
$command->setCheckTime(time());
$command->setForced();

return $command;
yield $command;
}
}
}
30 changes: 16 additions & 14 deletions application/forms/Command/Object/DeleteCommentForm.php
Expand Up @@ -4,17 +4,16 @@

namespace Icinga\Module\Icingadb\Forms\Command\Object;

use Generator;
use Icinga\Module\Icingadb\Command\Object\DeleteCommentCommand;
use Icinga\Module\Icingadb\Common\Auth;
use Icinga\Module\Icingadb\Forms\Command\CommandForm;
use Icinga\Web\Notification;
use ipl\Orm\Model;
use ipl\Web\Common\RedirectOption;
use ipl\Web\Widget\Icon;
use Traversable;

class DeleteCommentForm extends CommandForm
{
use Auth;
use RedirectOption;

protected $defaultAttributes = ['class' => 'inline'];
Expand Down Expand Up @@ -55,19 +54,22 @@ protected function assembleSubmitButton()
);
}

/**
* @return ?DeleteCommentCommand
*/
protected function getCommand(Model $object)
protected function getCommands(Traversable $objects): Traversable
{
if (! $this->isGrantedOn('icingadb/command/comment/delete', $object->{$object->object_type})) {
return null;
}
$granted = (function () use ($objects): Generator {
foreach ($objects as $object) {
if ($this->isGrantedOn('icingadb/command/comment/delete', $object->{$object->object_type})) {
yield $object;
}
}
})();

$command = new DeleteCommentCommand();
$command->setCommentName($object->name);
$command->setAuthor($this->getAuth()->getUser()->getUsername());
if ($granted->valid()) {
$command = new DeleteCommentCommand();
$command->setObjects($granted);
$command->setAuthor($this->getAuth()->getUser()->getUsername());

return $command;
yield $command;
}
}
}