diff options
Diffstat (limited to 'library/Director/Deployment/ConditionalDeployment.php')
-rw-r--r-- | library/Director/Deployment/ConditionalDeployment.php | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/library/Director/Deployment/ConditionalDeployment.php b/library/Director/Deployment/ConditionalDeployment.php new file mode 100644 index 0000000..0f64028 --- /dev/null +++ b/library/Director/Deployment/ConditionalDeployment.php @@ -0,0 +1,190 @@ +<?php + +namespace Icinga\Module\Director\Deployment; + +use Icinga\Exception\IcingaException; +use Icinga\Module\Director\Core\CoreApi; +use Icinga\Module\Director\Db; +use Icinga\Module\Director\IcingaConfig\IcingaConfig; +use Icinga\Module\Director\Objects\DirectorDeploymentLog; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Psr\Log\NullLogger; + +class ConditionalDeployment implements LoggerAwareInterface +{ + use LoggerAwareTrait; + + /** @var Db */ + protected $db; + + /** @var CoreApi */ + protected $api; + + /** @var ?DeploymentGracePeriod */ + protected $gracePeriod = null; + + protected $force = false; + + protected $hasBeenForced = false; + + /** @var ?string */ + protected $noDeploymentReason = null; + + public function __construct(Db $connection, CoreApi $api = null) + { + $this->setLogger(new NullLogger()); + $this->db = $connection; + if ($api === null) { + $this->api = $connection->getDeploymentEndpoint()->api(); + } else { + $this->api = $api; + } + $this->refresh(); + } + + /** + * @param IcingaConfig $config + * @return ?DirectorDeploymentLog + */ + public function deploy(IcingaConfig $config) + { + $this->hasBeenForced = false; + if ($this->shouldDeploy($config)) { + return $this->reallyDeploy($config); + } elseif ($this->force) { + $deployment = $this->reallyDeploy($config); + $this->hasBeenForced = true; + + return $deployment; + } + + return null; + } + + /** + * @param bool $force + * @return $this + */ + public function force($force = true) + { + $this->force = $force; + return $this; + } + + public function setGracePeriod(DeploymentGracePeriod $gracePeriod) + { + $this->gracePeriod = $gracePeriod; + return $this; + } + + public function refresh() + { + $this->api->collectLogFiles($this->db); + $this->api->wipeInactiveStages($this->db); + } + + public function waitForStartupAfterDeploy(DirectorDeploymentLog $deploymentLog, $timeout) + { + $startTime = time(); + while ((time() - $startTime) <= $timeout) { + $deploymentFromDB = DirectorDeploymentLog::load($deploymentLog->getId(), $this->db); + $stageCollected = $deploymentFromDB->get('stage_collected'); + if ($stageCollected === null) { + usleep(500000); + continue; + } + if ($stageCollected === 'n') { + return 'stage has not been collected (Icinga "lost" the deployment)'; + } + if ($deploymentFromDB->get('startup_succeeded') === 'y') { + return true; + } + return 'deployment failed during startup (usually a Configuration Error)'; + } + return 'deployment timed out (while waiting for an Icinga restart)'; + } + + /** + * @return string|null + */ + public function getNoDeploymentReason() + { + return $this->noDeploymentReason; + } + + public function hasBeenForced() + { + return $this->hasBeenForced; + } + + protected function shouldDeploy(IcingaConfig $config) + { + $this->noDeploymentReason = null; + if ($this->hasNeverDeployed()) { + return true; + } + + if ($this->isWithinGracePeriod()) { + $this->noDeploymentReason = 'Grace period is active'; + return false; + } + + if ($this->deployedConfigMatches($config)) { + $this->noDeploymentReason = 'Config matches last deployed one'; + return false; + } + + if ($this->getActiveChecksum() === $config->getHexChecksum()) { + $this->noDeploymentReason = 'Config matches active stage'; + return false; + } + + return true; + } + + protected function hasNeverDeployed() + { + return !DirectorDeploymentLog::hasDeployments($this->db); + } + + protected function isWithinGracePeriod() + { + return $this->gracePeriod && $this->gracePeriod->isActive(); + } + + protected function deployedConfigMatches(IcingaConfig $config) + { + if ($deployment = DirectorDeploymentLog::optionalLatest($this->db)) { + return $deployment->getConfigHexChecksum() === $config->getHexChecksum(); + } + + return false; + } + + protected function getActiveChecksum() + { + return DirectorDeploymentLog::getConfigChecksumForStageName( + $this->db, + $this->api->getActiveStageName() + ); + } + + /** + * @param IcingaConfig $config + * @return bool|DirectorDeploymentLog + * @throws IcingaException + * @throws \Icinga\Module\Director\Exception\DuplicateKeyException + */ + protected function reallyDeploy(IcingaConfig $config) + { + $checksum = $config->getHexChecksum(); + $this->logger->info(sprintf('Director ConfigJob ready to deploy "%s"', $checksum)); + if ($deployment = $this->api->dumpConfig($config, $this->db)) { + $this->logger->notice(sprintf('Director ConfigJob deployed config "%s"', $checksum)); + return $deployment; + } else { + throw new IcingaException('Failed to deploy config "%s"', $checksum); + } + } +} |