mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-12 08:36:13 +01:00
f3d9a0b930
Summary: - Fixes T11995. This got moved but I missed renaming this callsite. - Fixes T11993. If you have valid credentials, but haven't run `storage upgrade` yet, we can hit this exception during setup. Just ignore it instead. Test Plan: - Saved global settings, no more fatal. - Changed `storage-namespace` to junk, loaded web UI with valid database credentials. {F2106358} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11993, T11995 Differential Revision: https://secure.phabricator.com/D17024
301 lines
8.8 KiB
PHP
301 lines
8.8 KiB
PHP
<?php
|
|
|
|
abstract class PhabricatorSetupCheck extends Phobject {
|
|
|
|
private $issues;
|
|
|
|
abstract protected function executeChecks();
|
|
|
|
const GROUP_OTHER = 'other';
|
|
const GROUP_MYSQL = 'mysql';
|
|
const GROUP_PHP = 'php';
|
|
const GROUP_IMPORTANT = 'important';
|
|
|
|
public function getExecutionOrder() {
|
|
if ($this->isPreflightCheck()) {
|
|
return 0;
|
|
} else {
|
|
return 1000;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Should this check execute before we load configuration?
|
|
*
|
|
* The majority of checks (particularly, those checks which examine
|
|
* configuration) should run in the normal setup phase, after configuration
|
|
* loads. However, a small set of critical checks (mostly, tests for PHP
|
|
* setup and extensions) need to run before we can load configuration.
|
|
*
|
|
* @return bool True to execute before configuration is loaded.
|
|
*/
|
|
public function isPreflightCheck() {
|
|
return false;
|
|
}
|
|
|
|
final protected function newIssue($key) {
|
|
$issue = id(new PhabricatorSetupIssue())
|
|
->setIssueKey($key);
|
|
$this->issues[$key] = $issue;
|
|
|
|
if ($this->getDefaultGroup()) {
|
|
$issue->setGroup($this->getDefaultGroup());
|
|
}
|
|
|
|
return $issue;
|
|
}
|
|
|
|
final public function getIssues() {
|
|
return $this->issues;
|
|
}
|
|
|
|
protected function addIssue(PhabricatorSetupIssue $issue) {
|
|
$this->issues[$issue->getIssueKey()] = $issue;
|
|
return $this;
|
|
}
|
|
|
|
public function getDefaultGroup() {
|
|
return null;
|
|
}
|
|
|
|
final public function runSetupChecks() {
|
|
$this->issues = array();
|
|
$this->executeChecks();
|
|
}
|
|
|
|
final public static function getOpenSetupIssueKeys() {
|
|
$cache = PhabricatorCaches::getSetupCache();
|
|
return $cache->getKey('phabricator.setup.issue-keys');
|
|
}
|
|
|
|
final public static function resetSetupState() {
|
|
$cache = PhabricatorCaches::getSetupCache();
|
|
$cache->deleteKey('phabricator.setup.issue-keys');
|
|
|
|
$server_cache = PhabricatorCaches::getServerStateCache();
|
|
$server_cache->deleteKey('phabricator.in-flight');
|
|
|
|
$use_scope = AphrontWriteGuard::isGuardActive();
|
|
if ($use_scope) {
|
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
|
} else {
|
|
AphrontWriteGuard::allowDangerousUnguardedWrites(true);
|
|
}
|
|
|
|
try {
|
|
$db_cache = new PhabricatorKeyValueDatabaseCache();
|
|
$db_cache->deleteKey('phabricator.setup.issue-keys');
|
|
} catch (Exception $ex) {
|
|
// If we hit an exception here, just ignore it. In particular, this can
|
|
// happen on initial startup before the databases are initialized.
|
|
}
|
|
|
|
if ($use_scope) {
|
|
unset($unguarded);
|
|
} else {
|
|
AphrontWriteGuard::allowDangerousUnguardedWrites(false);
|
|
}
|
|
}
|
|
|
|
final public static function setOpenSetupIssueKeys(
|
|
array $keys,
|
|
$update_database) {
|
|
$cache = PhabricatorCaches::getSetupCache();
|
|
$cache->setKey('phabricator.setup.issue-keys', $keys);
|
|
|
|
$server_cache = PhabricatorCaches::getServerStateCache();
|
|
$server_cache->setKey('phabricator.in-flight', 1);
|
|
|
|
if ($update_database) {
|
|
$db_cache = new PhabricatorKeyValueDatabaseCache();
|
|
try {
|
|
$json = phutil_json_encode($keys);
|
|
$db_cache->setKey('phabricator.setup.issue-keys', $json);
|
|
} catch (Exception $ex) {
|
|
// Ignore any write failures, since they likely just indicate that we
|
|
// have a database-related setup issue that needs to be resolved.
|
|
}
|
|
}
|
|
}
|
|
|
|
final public static function getOpenSetupIssueKeysFromDatabase() {
|
|
$db_cache = new PhabricatorKeyValueDatabaseCache();
|
|
try {
|
|
$value = $db_cache->getKey('phabricator.setup.issue-keys');
|
|
if (!strlen($value)) {
|
|
return null;
|
|
}
|
|
return phutil_json_decode($value);
|
|
} catch (Exception $ex) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
final public static function getUnignoredIssueKeys(array $all_issues) {
|
|
assert_instances_of($all_issues, 'PhabricatorSetupIssue');
|
|
$keys = array();
|
|
foreach ($all_issues as $issue) {
|
|
if (!$issue->getIsIgnored()) {
|
|
$keys[] = $issue->getIssueKey();
|
|
}
|
|
}
|
|
return $keys;
|
|
}
|
|
|
|
final public static function getConfigNeedsRepair() {
|
|
$cache = PhabricatorCaches::getSetupCache();
|
|
return $cache->getKey('phabricator.setup.needs-repair');
|
|
}
|
|
|
|
final public static function setConfigNeedsRepair($needs_repair) {
|
|
$cache = PhabricatorCaches::getSetupCache();
|
|
$cache->setKey('phabricator.setup.needs-repair', $needs_repair);
|
|
}
|
|
|
|
final public static function deleteSetupCheckCache() {
|
|
$cache = PhabricatorCaches::getSetupCache();
|
|
$cache->deleteKeys(
|
|
array(
|
|
'phabricator.setup.needs-repair',
|
|
'phabricator.setup.issue-keys',
|
|
));
|
|
}
|
|
|
|
final public static function willPreflightRequest() {
|
|
$checks = self::loadAllChecks();
|
|
|
|
foreach ($checks as $check) {
|
|
if (!$check->isPreflightCheck()) {
|
|
continue;
|
|
}
|
|
|
|
$check->runSetupChecks();
|
|
|
|
foreach ($check->getIssues() as $key => $issue) {
|
|
return self::newIssueResponse($issue);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static function newIssueResponse(PhabricatorSetupIssue $issue) {
|
|
$view = id(new PhabricatorSetupIssueView())
|
|
->setIssue($issue);
|
|
|
|
return id(new PhabricatorConfigResponse())
|
|
->setView($view);
|
|
}
|
|
|
|
final public static function willProcessRequest() {
|
|
$issue_keys = self::getOpenSetupIssueKeys();
|
|
if ($issue_keys === null) {
|
|
$engine = new PhabricatorSetupEngine();
|
|
$response = $engine->execute();
|
|
if ($response) {
|
|
return $response;
|
|
}
|
|
} else if ($issue_keys) {
|
|
// If Phabricator is configured in a cluster with multiple web devices,
|
|
// we can end up with setup issues cached on every device. This can cause
|
|
// a warning banner to show on every device so that each one needs to
|
|
// be dismissed individually, which is pretty annoying. See T10876.
|
|
|
|
// To avoid this, check if the issues we found have already been cleared
|
|
// in the database. If they have, we'll just wipe out our own cache and
|
|
// move on.
|
|
$issue_keys = self::getOpenSetupIssueKeysFromDatabase();
|
|
if ($issue_keys !== null) {
|
|
self::setOpenSetupIssueKeys($issue_keys, $update_database = false);
|
|
}
|
|
}
|
|
|
|
// Try to repair configuration unless we have a clean bill of health on it.
|
|
// We need to keep doing this on every page load until all the problems
|
|
// are fixed, which is why it's separate from setup checks (which run
|
|
// once per restart).
|
|
$needs_repair = self::getConfigNeedsRepair();
|
|
if ($needs_repair !== false) {
|
|
$needs_repair = self::repairConfig();
|
|
self::setConfigNeedsRepair($needs_repair);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test if we've survived through setup on at least one normal request
|
|
* without fataling.
|
|
*
|
|
* If we've made it through setup without hitting any fatals, we switch
|
|
* to render a more friendly error page when encountering issues like
|
|
* database connection failures. This gives users a smoother experience in
|
|
* the face of intermittent failures.
|
|
*
|
|
* @return bool True if we've made it through setup since the last restart.
|
|
*/
|
|
final public static function isInFlight() {
|
|
$cache = PhabricatorCaches::getServerStateCache();
|
|
return (bool)$cache->getKey('phabricator.in-flight');
|
|
}
|
|
|
|
final public static function loadAllChecks() {
|
|
return id(new PhutilClassMapQuery())
|
|
->setAncestorClass(__CLASS__)
|
|
->setSortMethod('getExecutionOrder')
|
|
->execute();
|
|
}
|
|
|
|
final public static function runNormalChecks() {
|
|
$checks = self::loadAllChecks();
|
|
|
|
foreach ($checks as $key => $check) {
|
|
if ($check->isPreflightCheck()) {
|
|
unset($checks[$key]);
|
|
}
|
|
}
|
|
|
|
$issues = array();
|
|
foreach ($checks as $check) {
|
|
$check->runSetupChecks();
|
|
foreach ($check->getIssues() as $key => $issue) {
|
|
if (isset($issues[$key])) {
|
|
throw new Exception(
|
|
pht(
|
|
"Two setup checks raised an issue with key '%s'!",
|
|
$key));
|
|
}
|
|
$issues[$key] = $issue;
|
|
if ($issue->getIsFatal()) {
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
$ignore_issues = PhabricatorEnv::getEnvConfig('config.ignore-issues');
|
|
foreach ($ignore_issues as $ignorable => $derp) {
|
|
if (isset($issues[$ignorable])) {
|
|
$issues[$ignorable]->setIsIgnored(true);
|
|
}
|
|
}
|
|
|
|
return $issues;
|
|
}
|
|
|
|
final public static function repairConfig() {
|
|
$needs_repair = false;
|
|
|
|
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
|
|
foreach ($options as $option) {
|
|
try {
|
|
$option->getGroup()->validateOption(
|
|
$option,
|
|
PhabricatorEnv::getEnvConfig($option->getKey()));
|
|
} catch (PhabricatorConfigValidationException $ex) {
|
|
PhabricatorEnv::repairConfig($option->getKey(), $option->getDefault());
|
|
$needs_repair = true;
|
|
}
|
|
}
|
|
|
|
return $needs_repair;
|
|
}
|
|
|
|
}
|