diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:44:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:44:51 +0000 |
commit | a1ec78bf0dc93d0e05e5f066f1949dc3baecea06 (patch) | |
tree | ee596ce1bc9840661386f96f9b8d1f919a106317 /vendor/gipfl/icinga-cli-daemon/src | |
parent | Initial commit. (diff) | |
download | icingaweb2-module-incubator-upstream.tar.xz icingaweb2-module-incubator-upstream.zip |
Adding upstream version 0.20.0.upstream/0.20.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gipfl/icinga-cli-daemon/src')
-rw-r--r-- | vendor/gipfl/icinga-cli-daemon/src/DbResourceConfigWatch.php | 163 | ||||
-rw-r--r-- | vendor/gipfl/icinga-cli-daemon/src/FinishedProcessState.php | 66 | ||||
-rw-r--r-- | vendor/gipfl/icinga-cli-daemon/src/IcingaCli.php | 144 | ||||
-rw-r--r-- | vendor/gipfl/icinga-cli-daemon/src/IcingaCliRpc.php | 45 | ||||
-rw-r--r-- | vendor/gipfl/icinga-cli-daemon/src/IcingaCliRunner.php | 88 | ||||
-rw-r--r-- | vendor/gipfl/icinga-cli-daemon/src/RetryUnless.php | 244 | ||||
-rw-r--r-- | vendor/gipfl/icinga-cli-daemon/src/StateMachine.php | 113 |
7 files changed, 863 insertions, 0 deletions
diff --git a/vendor/gipfl/icinga-cli-daemon/src/DbResourceConfigWatch.php b/vendor/gipfl/icinga-cli-daemon/src/DbResourceConfigWatch.php new file mode 100644 index 0000000..08b188f --- /dev/null +++ b/vendor/gipfl/icinga-cli-daemon/src/DbResourceConfigWatch.php @@ -0,0 +1,163 @@ +<?php + +namespace gipfl\IcingaCliDaemon; + +use Icinga\Application\Config; +use InvalidArgumentException; +use React\EventLoop\LoopInterface; + +/** + * DbResourceConfigWatch + * + * Checks every $interval = 3 seconds for changed DB resource configuration. + * Notifies registered callbacksin case this happens. + */ +class DbResourceConfigWatch +{ + /** @var string */ + protected $configFile; + + /** @var string */ + protected $resourceConfigFile; + + /** @var string|null */ + protected $dbResourceName; + + /** @var array|null|false It's false on initialization to trigger */ + protected $resourceConfig = false; + + /** @var int|float */ + protected $interval = 3; + + /** @var callable[] */ + protected $callbacks = []; + + /** + * @param string $dbResourceName + * @return DbResourceConfigWatch + */ + public static function name($dbResourceName) + { + $self = new static(); + $self->dbResourceName = $dbResourceName; + + return $self; + } + + /** + * @param string $moduleName + * @return DbResourceConfigWatch + */ + public static function module($moduleName) + { + $self = new static(); + $self->configFile = Config::module($moduleName)->getConfigFile(); + $self->resourceConfigFile = Config::app('resources')->getConfigFile(); + + return $self; + } + + /** + * @param int|float $interval + * @return $this + */ + public function setInterval($interval) + { + if (! \is_int($interval) && ! \is_float($interval)) { + throw new InvalidArgumentException( + '$interval needs to be either int or float' + ); + } + $this->interval = $interval; + + return $this; + } + + /** + * @param callable $callable + * @return $this + */ + public function notify($callable) + { + if (! \is_callable($callable)) { + throw new InvalidArgumentException('$callable needs to be callable'); + } + $this->callbacks[] = $callable; + + return $this; + } + + /** + * @param LoopInterface $loop + */ + public function run(LoopInterface $loop) + { + $check = function () { + $this->checkForFreshConfig(); + }; + $loop->addPeriodicTimer($this->interval, $check); + $loop->futureTick($check); + } + + protected function checkForFreshConfig() + { + if ($this->configHasBeenChanged()) { + $this->emitNewConfig($this->resourceConfig); + } + } + + protected function emitNewConfig($config) + { + foreach ($this->callbacks as $callback) { + $callback($config); + } + } + + protected function getResourceName() + { + if ($this->dbResourceName) { + return $this->dbResourceName; + } else { + return $this->loadDbResourceName(); + } + } + + protected function loadDbResourceName() + { + $parsed = @\parse_ini_file($this->configFile, true); + if (isset($parsed['db']['resource'])) { + return $parsed['db']['resource']; + } else { + return null; + } + } + + protected function loadDbConfigFromDisk($name) + { + if ($name === null) { + return null; + } + + $parsed = @\parse_ini_file($this->resourceConfigFile, true); + if (isset($parsed[$name])) { + $section = $parsed[$name]; + \ksort($section); + + return $section; + } else { + return null; + } + } + + protected function configHasBeenChanged() + { + $resource = $this->loadDbConfigFromDisk($this->loadDbResourceName()); + if ($resource !== $this->resourceConfig) { + $this->resourceConfig = $resource; + + return true; + } else { + return false; + } + } +} diff --git a/vendor/gipfl/icinga-cli-daemon/src/FinishedProcessState.php b/vendor/gipfl/icinga-cli-daemon/src/FinishedProcessState.php new file mode 100644 index 0000000..9ca5e5f --- /dev/null +++ b/vendor/gipfl/icinga-cli-daemon/src/FinishedProcessState.php @@ -0,0 +1,66 @@ +<?php + +namespace gipfl\IcingaCliDaemon; + +class FinishedProcessState +{ + /** @var int|null */ + protected $exitCode; + + /** @var int|null */ + protected $termSignal; + + public function __construct($exitCode, $termSignal) + { + $this->exitCode = $exitCode; + $this->termSignal = $termSignal; + } + + public function succeeded() + { + return $this->exitCode === 0; + } + + /** + * @return int|null + */ + public function getExitCode() + { + return $this->exitCode; + } + + public function getTermSignal() + { + return $this->termSignal; + } + + public function getCombinedExitCode() + { + if ($this->exitCode === null) { + if ($this->termSignal === null) { + return 255; + } else { + return 255 + $this->termSignal; + } + } else { + return $this->exitCode; + } + } + + public function getReason() + { + if ($this->exitCode === null) { + if ($this->termSignal === null) { + return 'Process died'; + } else { + return 'Process got terminated with SIGNAL ' . $this->termSignal; + } + } else { + if ($this->exitCode === 0) { + return 'Process finished successfully'; + } else { + return 'Process exited with exit code ' . $this->exitCode; + } + } + } +} diff --git a/vendor/gipfl/icinga-cli-daemon/src/IcingaCli.php b/vendor/gipfl/icinga-cli-daemon/src/IcingaCli.php new file mode 100644 index 0000000..8aad4c6 --- /dev/null +++ b/vendor/gipfl/icinga-cli-daemon/src/IcingaCli.php @@ -0,0 +1,144 @@ +<?php + +namespace gipfl\IcingaCliDaemon; + +use Exception; +use Evenement\EventEmitterTrait; +use gipfl\Protocol\JsonRpc\Connection; +use React\EventLoop\LoopInterface; +use React\Promise\Deferred; +use React\Promise\Stream; + +class IcingaCli +{ + use EventEmitterTrait; + + /** @var IcingaCliRunner */ + protected $runner; + + /** @var Connection|null */ + protected $rpc; + + protected $arguments = []; + + /** @var \React\Stream\WritableStreamInterface|null */ + protected $stdin; + + /** @var Deferred|null */ + protected $deferredStdin; + + /** @var \React\Stream\ReadableStreamInterface|null */ + protected $stdout; + + /** @var Deferred|null */ + protected $deferredStdout; + + /** @var \React\Stream\ReadableStreamInterface|null */ + protected $stderr; + + /** @var Deferred|null */ + protected $deferredStderr; + + public function __construct(IcingaCliRunner $runner = null) + { + if ($runner === null) { + $runner = new IcingaCliRunner(); + } + $this->runner = $runner; + $this->init(); + } + + protected function init() + { + // Override this if you want. + } + + public function setArguments($arguments) + { + $this->arguments = $arguments; + + return $this; + } + + public function getArguments() + { + return $this->arguments; + } + + public function run(LoopInterface $loop) + { + $process = $this->runner->command($this->getArguments()); + $canceller = function () use ($process) { + // TODO: first soft, then hard + $process->terminate(); + }; + $deferred = new Deferred($canceller); + $process->on('exit', function ($exitCode, $termSignal) use ($deferred) { + $state = new FinishedProcessState($exitCode, $termSignal); + if ($state->succeeded()) { + $deferred->resolve(); + } else { + $deferred->reject(new Exception($state->getReason())); + } + }); + + $process->start($loop); + if ($this->deferredStdin instanceof Deferred) { + $this->deferredStdin->resolve($process->stdin); + } else { + $this->stdin = $process->stdin; + } + if ($this->deferredStdout instanceof Deferred) { + $this->deferredStdout->resolve($process->stdout); + } else { + $this->stdout = $process->stdout; + } + if ($this->deferredStderr instanceof Deferred) { + $this->deferredStderr->resolve($process->stderr); + } else { + $this->stderr = $process->stderr; + } + $this->emit('start', [$process]); + + return $deferred->promise(); + } + + /** + * @return \React\Stream\WritableStreamInterface + */ + public function stdin() + { + if ($this->stdin === null) { + $this->deferredStdin = new Deferred(); + $this->stdin = Stream\unwrapWritable($this->deferredStdin->promise()); + } + + return $this->stdin; + } + + /** + * @return \React\Stream\ReadableStreamInterface + */ + public function stdout() + { + if ($this->stdout === null) { + $this->deferredStdout = new Deferred(); + $this->stdout = Stream\unwrapReadable($this->deferredStdout->promise()); + } + + return $this->stdout; + } + + /** + * @return \React\Stream\ReadableStreamInterface + */ + public function stderr() + { + if ($this->stderr === null) { + $this->deferredStderr = new Deferred(); + $this->stderr = Stream\unwrapReadable($this->deferredStderr->promise()); + } + + return $this->stderr; + } +} diff --git a/vendor/gipfl/icinga-cli-daemon/src/IcingaCliRpc.php b/vendor/gipfl/icinga-cli-daemon/src/IcingaCliRpc.php new file mode 100644 index 0000000..473d4ed --- /dev/null +++ b/vendor/gipfl/icinga-cli-daemon/src/IcingaCliRpc.php @@ -0,0 +1,45 @@ +<?php + +namespace gipfl\IcingaCliDaemon; + +use Exception; +use gipfl\Protocol\JsonRpc\Connection; +use gipfl\Protocol\NetString\StreamWrapper; +use React\ChildProcess\Process; + +class IcingaCliRpc extends IcingaCli +{ + /** @var IcingaCliRunner */ + protected $runner; + + /** @var Connection|null */ + protected $rpc; + + protected $arguments = []; + + protected function init() + { + $this->on('start', function (Process $process) { + $netString = new StreamWrapper( + $process->stdout, + $process->stdin + ); + $netString->on('error', function (Exception $e) { + $this->emit('error', [$e]); + }); + $this->rpc()->handle($netString); + }); + } + + /** + * @return Connection + */ + public function rpc() + { + if ($this->rpc === null) { + $this->rpc = new Connection(); + } + + return $this->rpc; + } +} diff --git a/vendor/gipfl/icinga-cli-daemon/src/IcingaCliRunner.php b/vendor/gipfl/icinga-cli-daemon/src/IcingaCliRunner.php new file mode 100644 index 0000000..60a25cc --- /dev/null +++ b/vendor/gipfl/icinga-cli-daemon/src/IcingaCliRunner.php @@ -0,0 +1,88 @@ +<?php + +namespace gipfl\IcingaCliDaemon; + +use React\ChildProcess\Process; +use gipfl\Cli\Process as CliProcess; + +class IcingaCliRunner +{ + /** @var string */ + protected $binary; + + /** @var string|null */ + protected $cwd; + + /** @var array|null */ + protected $env; + + public function __construct($binary = null) + { + if ($binary === null) { + $this->binary = CliProcess::getBinaryPath(); + $this->cwd = CliProcess::getInitialCwd(); + } else { + $this->binary = $binary; + } + } + + /** + * @param mixed array|...$arguments + * @return Process + */ + public function command($arguments = null) + { + if (! \is_array($arguments)) { + $arguments = \func_get_args(); + } + + return new Process( + $this->escapedCommand($arguments), + $this->cwd, + $this->env + ); + } + + /** + * @param string|null $cwd + */ + public function setCwd($cwd) + { + if ($cwd === null) { + $this->cwd = $cwd; + } else { + $this->cwd = (string) $cwd; + } + } + + /** + * @param array|null $env + */ + public function setEnv($env) + { + if ($env === null) { + $this->env = $env; + } else { + $this->env = (array) $env; + } + } + + /** + * @param $arguments + * @return string + */ + protected function escapedCommand($arguments) + { + $command = ['exec', \escapeshellcmd($this->binary)]; + + foreach ($arguments as $argument) { + if (\ctype_alnum(preg_replace('/^-{1,2}/', '', $argument))) { + $command[] = $argument; + } else { + $command[] = \escapeshellarg($argument); + } + } + + return \implode(' ', $command); + } +} diff --git a/vendor/gipfl/icinga-cli-daemon/src/RetryUnless.php b/vendor/gipfl/icinga-cli-daemon/src/RetryUnless.php new file mode 100644 index 0000000..0303b2c --- /dev/null +++ b/vendor/gipfl/icinga-cli-daemon/src/RetryUnless.php @@ -0,0 +1,244 @@ +<?php + +namespace gipfl\IcingaCliDaemon; + +use Exception; +use Icinga\Application\Logger; +use React\EventLoop\LoopInterface; +use React\EventLoop\TimerInterface; +use React\Promise\Deferred; +use React\Promise\PromiseInterface; +use RuntimeException; + +class RetryUnless +{ + /** @var LoopInterface */ + protected $loop; + + /** @var Deferred */ + protected $deferred; + + /** @var TimerInterface */ + protected $timer; + + /** @var callable */ + protected $callback; + + /** @var bool */ + protected $expectsSuccess; + + /** @var int Regular interval */ + protected $interval = 1; + + /** @var int|null Optional, interval will be changed after $burst attempts */ + protected $burst = null; + + /** @var int|null Interval after $burst attempts */ + protected $finalInterval = null; + + /** @var int Current attempt count */ + protected $attempts = 0; + + /** @var bool No attempts will be made while paused */ + protected $paused = false; + + protected $lastError; + + protected function __construct($callback, $expectsSuccess = true) + { + $this->callback = $callback; + $this->expectsSuccess = $expectsSuccess; + } + + public static function succeeding($callback) + { + return new static($callback); + } + + public static function failing($callback) + { + return new static($callback, false); + } + + public function run(LoopInterface $loop) + { + $this->assertNotRunning(); + $this->deferred = $deferred = new Deferred(); + $this->loop = $loop; + $loop->futureTick(function () { + $this->nextAttempt(); + }); + + return $deferred->promise(); + } + + public function getLastError() + { + return $this->lastError; + } + + public function setInterval($interval) + { + $this->interval = $interval; + + return $this; + } + + public function slowDownAfter($burst, $interval) + { + $this->burst = $burst; + $this->finalInterval = $interval; + + return $this; + } + + public function pause() + { + $this->removeEventualTimer(); + $this->paused = true; + + return $this; + } + + public function resume() + { + if ($this->paused) { + $this->paused = false; + if ($this->timer === null) { + $this->nextAttempt(); + } + } + } + + public function reset() + { + $this->attempts = 0; + $this->paused = false; + $this->removeEventualTimer(); + $this->rejectEventualDeferred('RetryUnless has been reset'); + + return $this; + } + + public function getAttempts() + { + return $this->attempts; + } + + protected function nextAttempt() + { + if ($this->paused) { + return; + } + + $this->removeEventualTimer(); + $this->attempts++; + try { + $callback = $this->callback; + $this->handleResult($callback()); + } catch (Exception $e) { + $this->handleResult($e); + } + } + + protected function logError(Exception $e) + { + if ($this->lastError !== $e->getMessage()) { + $this->lastError = $e->getMessage(); + Logger::error($e); + } + } + + protected function handleResult($result) + { + if ($this->expectsSuccess) { + if ($result instanceof Exception) { + $this->logError($result); + $this->scheduleNextAttempt(); + } elseif ($result instanceof PromiseInterface) { + $later = function ($result) { + $this->handleResult($result); + }; + $result->then($later, $later); + } else { + $this->succeed($result); + } + } else { + if ($result instanceof Exception) { + $this->succeed($result); + } else { + $this->scheduleNextAttempt(); + } + } + } + + protected function scheduleNextAttempt() + { + if ($this->timer !== null) { + throw new RuntimeException( + 'RetryUnless schedules next attempt while already scheduled' + ); + } + $this->timer = $this->loop->addTimer($this->getNextInterval(), function () { + $this->nextAttempt(); + }); + } + + protected function succeed($result) + { + $this->removeEventualTimer(); + if ($this->deferred === null) { + Logger::warning('RetryUnless tries to resolve twice'); + + return; + } + $this->deferred->resolve($result); + $this->deferred = null; + $this->reset(); + } + + protected function getNextInterval() + { + if ($this->burst === null) { + return $this->interval; + } + + return $this->attempts >= $this->burst + ? $this->finalInterval + : $this->interval; + } + + protected function assertNotRunning() + { + if ($this->deferred) { + throw new RuntimeException( + 'Cannot re-run RetryUnless while already running' + ); + } + } + + protected function removeEventualTimer() + { + if ($this->timer) { + $this->loop->cancelTimer($this->timer); + $this->timer = null; + } + } + + protected function rejectEventualDeferred($reason) + { + if ($this->deferred !== null) { + $deferred = $this->deferred; + $this->deferred = null; + $deferred->reject($reason); + } + } + + public function __destruct() + { + $this->removeEventualTimer(); + $this->rejectEventualDeferred('RetryUnless has been destructed'); + + $this->loop = null; + } +} diff --git a/vendor/gipfl/icinga-cli-daemon/src/StateMachine.php b/vendor/gipfl/icinga-cli-daemon/src/StateMachine.php new file mode 100644 index 0000000..d8ec470 --- /dev/null +++ b/vendor/gipfl/icinga-cli-daemon/src/StateMachine.php @@ -0,0 +1,113 @@ +<?php + +namespace gipfl\IcingaCliDaemon; + +use RuntimeException; + +trait StateMachine +{ + /** @var string */ + private $currentState; + + /** @var array [fromState][toState] = [callback, ...] */ + private $allowedTransitions = []; + + /** @var array [state] = [callback, ...] */ + private $onState = []; + + public function initializeStateMachine($initialState) + { + if ($this->currentState !== null) { + throw new RuntimeException('StateMachine has already been initialized'); + } + $this->currentState = $initialState; + } + + /** + * @param string|array $fromState + * @param string $toState + * @param callable $callback + * @return $this + */ + public function onTransition($fromState, $toState, $callback) + { + if (is_array($fromState)) { + foreach ($fromState as $state) { + $this->onTransition($state, $toState, $callback); + } + } else { + $this->allowTransition($fromState, $toState); + $this->allowedTransitions[$fromState][$toState][] = $callback; + } + + return $this; + } + + public function allowTransition($fromState, $toState) + { + if (! isset($this->allowedTransitions[$fromState][$toState])) { + $this->allowedTransitions[$fromState][$toState] = []; + } + + return $this; + } + + /** + * @param $state + * @param $callback + * @return $this + */ + public function onState($state, $callback) + { + if (! isset($this->onState[$state])) { + $this->onState[$state] = []; + } + + $this->onState[$state][] = $callback; + + return $this; + } + + public function getState() + { + if ($this->currentState === null) { + throw new RuntimeException('StateMachine has not been initialized'); + } + + return $this->currentState; + } + + public function setState($state) + { + $fromState = $this->getState(); + if ($this->canTransit($fromState, $state)) { + $this->currentState = $state; + $this->runStateTransition($fromState, $state); + } else { + throw new RuntimeException(sprintf( + 'A transition from %s to %s is not allowed', + $fromState, + $state + )); + } + } + + private function runStateTransition($fromState, $toState) + { + if (isset($this->allowedTransitions[$fromState][$toState])) { + foreach ($this->allowedTransitions[$fromState][$toState] as $callback) { + $callback(); + } + } + if (isset($this->onState[$toState])) { + foreach ($this->onState[$toState] as $callback) { + $callback(); + } + } + } + + public function canTransit($fromState, $toState) + { + return isset($this->allowedTransitions[$fromState][$toState]); + } +} |