Path: blob/master/src/applications/config/check/PhabricatorSetupCheck.php
12256 views
<?php12abstract class PhabricatorSetupCheck extends Phobject {34private $issues;56abstract protected function executeChecks();78const GROUP_OTHER = 'other';9const GROUP_MYSQL = 'mysql';10const GROUP_PHP = 'php';11const GROUP_IMPORTANT = 'important';1213public function getExecutionOrder() {14if ($this->isPreflightCheck()) {15return 0;16} else {17return 1000;18}19}2021/**22* Should this check execute before we load configuration?23*24* The majority of checks (particularly, those checks which examine25* configuration) should run in the normal setup phase, after configuration26* loads. However, a small set of critical checks (mostly, tests for PHP27* setup and extensions) need to run before we can load configuration.28*29* @return bool True to execute before configuration is loaded.30*/31public function isPreflightCheck() {32return false;33}3435final protected function newIssue($key) {36$issue = id(new PhabricatorSetupIssue())37->setIssueKey($key);38$this->issues[$key] = $issue;3940if ($this->getDefaultGroup()) {41$issue->setGroup($this->getDefaultGroup());42}4344return $issue;45}4647final public function getIssues() {48return $this->issues;49}5051protected function addIssue(PhabricatorSetupIssue $issue) {52$this->issues[$issue->getIssueKey()] = $issue;53return $this;54}5556public function getDefaultGroup() {57return null;58}5960final public function runSetupChecks() {61$this->issues = array();62$this->executeChecks();63}6465final public static function getOpenSetupIssueKeys() {66$cache = PhabricatorCaches::getSetupCache();67return $cache->getKey('phabricator.setup.issue-keys');68}6970final public static function resetSetupState() {71$cache = PhabricatorCaches::getSetupCache();72$cache->deleteKey('phabricator.setup.issue-keys');7374$server_cache = PhabricatorCaches::getServerStateCache();75$server_cache->deleteKey('phabricator.in-flight');7677$use_scope = AphrontWriteGuard::isGuardActive();78if ($use_scope) {79$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();80} else {81AphrontWriteGuard::allowDangerousUnguardedWrites(true);82}8384try {85$db_cache = new PhabricatorKeyValueDatabaseCache();86$db_cache->deleteKey('phabricator.setup.issue-keys');87} catch (Exception $ex) {88// If we hit an exception here, just ignore it. In particular, this can89// happen on initial startup before the databases are initialized.90}9192if ($use_scope) {93unset($unguarded);94} else {95AphrontWriteGuard::allowDangerousUnguardedWrites(false);96}97}9899final public static function setOpenSetupIssueKeys(100array $keys,101$update_database) {102$cache = PhabricatorCaches::getSetupCache();103$cache->setKey('phabricator.setup.issue-keys', $keys);104105$server_cache = PhabricatorCaches::getServerStateCache();106$server_cache->setKey('phabricator.in-flight', 1);107108if ($update_database) {109$db_cache = new PhabricatorKeyValueDatabaseCache();110try {111$json = phutil_json_encode($keys);112$db_cache->setKey('phabricator.setup.issue-keys', $json);113} catch (Exception $ex) {114// Ignore any write failures, since they likely just indicate that we115// have a database-related setup issue that needs to be resolved.116}117}118}119120final public static function getOpenSetupIssueKeysFromDatabase() {121$db_cache = new PhabricatorKeyValueDatabaseCache();122try {123$value = $db_cache->getKey('phabricator.setup.issue-keys');124if ($value === null || !strlen($value)) {125return null;126}127return phutil_json_decode($value);128} catch (Exception $ex) {129return null;130}131}132133final public static function getUnignoredIssueKeys(array $all_issues) {134assert_instances_of($all_issues, 'PhabricatorSetupIssue');135$keys = array();136foreach ($all_issues as $issue) {137if (!$issue->getIsIgnored()) {138$keys[] = $issue->getIssueKey();139}140}141return $keys;142}143144final public static function getConfigNeedsRepair() {145$cache = PhabricatorCaches::getSetupCache();146return $cache->getKey('phabricator.setup.needs-repair');147}148149final public static function setConfigNeedsRepair($needs_repair) {150$cache = PhabricatorCaches::getSetupCache();151$cache->setKey('phabricator.setup.needs-repair', $needs_repair);152}153154final public static function deleteSetupCheckCache() {155$cache = PhabricatorCaches::getSetupCache();156$cache->deleteKeys(157array(158'phabricator.setup.needs-repair',159'phabricator.setup.issue-keys',160));161}162163final public static function willPreflightRequest() {164$checks = self::loadAllChecks();165166foreach ($checks as $check) {167if (!$check->isPreflightCheck()) {168continue;169}170171$check->runSetupChecks();172173foreach ($check->getIssues() as $key => $issue) {174return self::newIssueResponse($issue);175}176}177178return null;179}180181public static function newIssueResponse(PhabricatorSetupIssue $issue) {182$view = id(new PhabricatorSetupIssueView())183->setIssue($issue);184185return id(new PhabricatorConfigResponse())186->setView($view);187}188189final public static function willProcessRequest() {190$issue_keys = self::getOpenSetupIssueKeys();191if ($issue_keys === null) {192$engine = new PhabricatorSetupEngine();193$response = $engine->execute();194if ($response) {195return $response;196}197} else if ($issue_keys) {198// If Phabricator is configured in a cluster with multiple web devices,199// we can end up with setup issues cached on every device. This can cause200// a warning banner to show on every device so that each one needs to201// be dismissed individually, which is pretty annoying. See T10876.202203// To avoid this, check if the issues we found have already been cleared204// in the database. If they have, we'll just wipe out our own cache and205// move on.206$issue_keys = self::getOpenSetupIssueKeysFromDatabase();207if ($issue_keys !== null) {208self::setOpenSetupIssueKeys($issue_keys, $update_database = false);209}210}211212// Try to repair configuration unless we have a clean bill of health on it.213// We need to keep doing this on every page load until all the problems214// are fixed, which is why it's separate from setup checks (which run215// once per restart).216$needs_repair = self::getConfigNeedsRepair();217if ($needs_repair !== false) {218$needs_repair = self::repairConfig();219self::setConfigNeedsRepair($needs_repair);220}221}222223/**224* Test if we've survived through setup on at least one normal request225* without fataling.226*227* If we've made it through setup without hitting any fatals, we switch228* to render a more friendly error page when encountering issues like229* database connection failures. This gives users a smoother experience in230* the face of intermittent failures.231*232* @return bool True if we've made it through setup since the last restart.233*/234final public static function isInFlight() {235$cache = PhabricatorCaches::getServerStateCache();236return (bool)$cache->getKey('phabricator.in-flight');237}238239final public static function loadAllChecks() {240return id(new PhutilClassMapQuery())241->setAncestorClass(__CLASS__)242->setSortMethod('getExecutionOrder')243->execute();244}245246final public static function runNormalChecks() {247$checks = self::loadAllChecks();248249foreach ($checks as $key => $check) {250if ($check->isPreflightCheck()) {251unset($checks[$key]);252}253}254255$issues = array();256foreach ($checks as $check) {257$check->runSetupChecks();258foreach ($check->getIssues() as $key => $issue) {259if (isset($issues[$key])) {260throw new Exception(261pht(262"Two setup checks raised an issue with key '%s'!",263$key));264}265$issues[$key] = $issue;266if ($issue->getIsFatal()) {267break 2;268}269}270}271272$ignore_issues = PhabricatorEnv::getEnvConfig('config.ignore-issues');273foreach ($ignore_issues as $ignorable => $derp) {274if (isset($issues[$ignorable])) {275$issues[$ignorable]->setIsIgnored(true);276}277}278279return $issues;280}281282final public static function repairConfig() {283$needs_repair = false;284285$options = PhabricatorApplicationConfigOptions::loadAllOptions();286foreach ($options as $option) {287try {288$option->getGroup()->validateOption(289$option,290PhabricatorEnv::getEnvConfig($option->getKey()));291} catch (PhabricatorConfigValidationException $ex) {292PhabricatorEnv::repairConfig($option->getKey(), $option->getDefault());293$needs_repair = true;294}295}296297return $needs_repair;298}299300}301302303