icingaweb2-module-director/library/Director/Health.php

286 lines
8.5 KiB
PHP

<?php
namespace Icinga\Module\Director;
use Icinga\Application\Config;
use Icinga\Date\DateFormatter;
use Icinga\Module\Director\CheckPlugin\Check;
use Icinga\Module\Director\CheckPlugin\CheckResults;
use Icinga\Module\Director\Db\Migrations;
use Icinga\Module\Director\Objects\DirectorDeploymentLog;
use Icinga\Module\Director\Objects\DirectorJob;
use Icinga\Module\Director\Objects\ImportSource;
use Icinga\Module\Director\Objects\SyncRule;
use Exception;
class Health
{
/** @var Db */
protected $connection;
/** @var string */
protected $dbResourceName;
protected $checks = [
'config' => 'checkConfig',
'sync' => 'checkSyncRules',
'import' => 'checkImportSources',
'jobs' => 'checkDirectorJobs',
'deployment' => 'checkDeployments',
];
public function setDbResourceName($name)
{
$this->dbResourceName = $name;
return $this;
}
public function getCheck($name)
{
if (array_key_exists($name, $this->checks)) {
$func = $this->checks[$name];
$check = $this->$func();
} else {
$check = new CheckResults('Invalid Parameter');
$check->fail("There is no check named '$name'");
}
return $check;
}
public function getAllChecks()
{
/** @var CheckResults[] $checks */
$checks = [$this->checkConfig()];
if ($checks[0]->hasErrors()) {
return $checks;
}
$checks[] = $this->checkSyncRules();
$checks[] = $this->checkImportSources();
$checks[] = $this->checkDirectorJobs();
$checks[] = $this->checkDeployments();
return $checks;
}
protected function hasDeploymentEndpoint()
{
try {
return $this->connection->hasDeploymentEndpoint();
} catch (Exception $e) {
return false;
}
}
public function hasResourceConfig()
{
return $this->getDbResourceName() !== null;
}
protected function getDbResourceName()
{
if ($this->dbResourceName === null) {
$this->dbResourceName = Config::module('director')->get('db', 'resource');
}
return $this->dbResourceName;
}
protected function getConnection()
{
if ($this->connection === null) {
$this->connection = Db::fromResourceName($this->getDbResourceName());
}
return $this->connection;
}
public function checkConfig()
{
$check = new Check('Director configuration');
$name = $this->getDbResourceName();
if ($name) {
$check->succeed("Database resource '$name' has been specified");
} else {
return $check->fail('No database resource has been specified');
}
try {
$db = $this->getConnection();
} catch (Exception $e) {
return $check->fail($e);
}
$migrations = new Migrations($db);
$check->assertTrue(
[$migrations, 'hasSchema'],
'Make sure the DB schema exists'
);
if ($check->hasProblems()) {
return $check;
}
$check->call(function () use ($check, $migrations) {
$count = $migrations->countPendingMigrations();
if ($count === 0) {
$check->succeed('There are no pending schema migrations');
} elseif ($count === 1) {
$check->warn('There is a pending schema migration');
} else {
$check->warn(sprintf(
'There are %s pending schema migrations',
$count
));
}
});
return $check;
}
public function checkSyncRules()
{
$check = new CheckResults('Sync Rules');
$rules = SyncRule::loadAll($this->getConnection(), null, 'rule_name');
if (empty($rules)) {
$check->warn('No Sync Rules have been defined');
return $check;
}
ksort($rules);
foreach ($rules as $rule) {
$state = $rule->get('sync_state');
$name = $rule->get('rule_name');
if ($state === 'failing') {
$message = $rule->get('last_error_message');
$check->fail("'$name' is failing: $message");
} elseif ($state === 'pending-changes') {
$check->succeed("'$name' is fine, but there are pending changes");
} elseif ($state === 'in-sync') {
$check->succeed("'$name' is in sync");
} else {
$check->fail("'$name' has never been checked", 'UNKNOWN');
}
}
return $check;
}
public function checkImportSources()
{
$check = new CheckResults('Import Sources');
$sources = ImportSource::loadAll($this->getConnection(), null, 'source_name');
if (empty($sources)) {
$check->warn('No Import Sources have been defined');
return $check;
}
ksort($sources);
foreach ($sources as $src) {
$state = $src->get('import_state');
$name = $src->get('source_name');
if ($state === 'failing') {
$message = $src->get('last_error_message');
$check->fail("'$name' is failing: $message");
} elseif ($state === 'pending-changes') {
$check->succeed("'$name' is fine, but there are pending changes");
} elseif ($state === 'in-sync') {
$check->succeed("'$name' is in sync");
} else {
$check->fail("'$name' has never been checked", 'UNKNOWN');
}
}
return $check;
}
public function checkDirectorJobs()
{
$check = new CheckResults('Director Jobs');
$jobs = DirectorJob::loadAll($this->getConnection(), null, 'job_name');
if (empty($jobs)) {
$check->warn('No Jobs have been defined');
return $check;
}
ksort($jobs);
foreach ($jobs as $job) {
$name = $job->get('job_name');
if ($job->hasBeenDisabled()) {
$check->succeed("'$name' has been disabled");
} elseif (! $job->lastAttemptSucceeded()) {
$message = $job->get('last_error_message');
$check->fail("Last attempt for '$name' failed: $message");
} elseif ($job->isOverdue()) {
$check->fail("'$name' is overdue");
} elseif ($job->shouldRun()) {
$check->succeed("'$name' is fine, but should run now");
} else {
$check->succeed("'$name' is fine");
}
}
return $check;
}
public function checkDeployments()
{
$check = new Check('Director Deployments');
$db = $this->getConnection();
$check->call(function () use ($check, $db) {
$check->succeed(sprintf(
"Deployment endpoint is '%s'",
$db->getDeploymentEndpointName()
));
})->call(function () use ($check, $db) {
$count = $db->countActivitiesSinceLastDeployedConfig();
if ($count === 1) {
$check->succeed('There is a single un-deployed change');
} else {
$check->succeed(sprintf(
'There are %d un-deployed changes',
$count
));
}
});
if (! DirectorDeploymentLog::hasDeployments($db)) {
$check->warn('Configuration has never been deployed');
return $check;
}
$latest = DirectorDeploymentLog::loadLatest($db);
$ts = $latest->getDeploymentTimestamp();
$time = DateFormatter::timeAgo($ts);
if ($latest->succeeded()) {
$check->succeed("The last Deployment was successful $time");
} elseif ($latest->isPending()) {
if ($ts + 180 < time()) {
$check->warn("The last Deployment started $time and is still pending");
} else {
$check->succeed("The last Deployment started $time and is still pending");
}
} else {
$check->fail("The last Deployment failed $time");
}
return $check;
}
public function __destruct()
{
if ($this->connection !== null) {
// We created our own connection, so let's tear it down
$this->connection->getDbAdapter()->closeConnection();
}
}
}