Path: blob/master/src/applications/config/check/PhabricatorDatabaseSetupCheck.php
12256 views
<?php12final class PhabricatorDatabaseSetupCheck extends PhabricatorSetupCheck {34public function getDefaultGroup() {5return self::GROUP_IMPORTANT;6}78public function getExecutionOrder() {9// This must run after basic PHP checks, but before most other checks.10return 500;11}1213protected function executeChecks() {14$host = PhabricatorEnv::getEnvConfig('mysql.host');15$matches = null;16if (preg_match('/^([^:]+):(\d+)$/', $host, $matches)) {17$host = $matches[1];18$port = $matches[2];1920$this->newIssue('storage.mysql.hostport')21->setName(pht('Deprecated mysql.host Format'))22->setSummary(23pht(24'Move port information from `%s` to `%s` in your config.',25'mysql.host',26'mysql.port'))27->setMessage(28pht(29'Your `%s` configuration contains a port number, but this usage '.30'is deprecated. Instead, put the port number in `%s`.',31'mysql.host',32'mysql.port'))33->addPhabricatorConfig('mysql.host')34->addPhabricatorConfig('mysql.port')35->addCommand(36hsprintf(37'<tt>$</tt> ./bin/config set mysql.host %s',38$host))39->addCommand(40hsprintf(41'<tt>$</tt> ./bin/config set mysql.port %s',42$port));43}4445$refs = PhabricatorDatabaseRef::queryAll();46$refs = mpull($refs, null, 'getRefKey');4748// Test if we can connect to each database first. If we can not connect49// to a particular database, we only raise a warning: this allows new web50// nodes to start during a disaster, when some databases may be correctly51// configured but not reachable.5253$connect_map = array();54$any_connection = false;55foreach ($refs as $ref_key => $ref) {56$conn_raw = $ref->newManagementConnection();5758try {59queryfx($conn_raw, 'SELECT 1');60$database_exception = null;61$any_connection = true;62} catch (AphrontInvalidCredentialsQueryException $ex) {63$database_exception = $ex;64} catch (AphrontConnectionQueryException $ex) {65$database_exception = $ex;66}6768if ($database_exception) {69$connect_map[$ref_key] = $database_exception;70unset($refs[$ref_key]);71}72}7374if ($connect_map) {75// This is only a fatal error if we could not connect to anything. If76// possible, we still want to start if some database hosts can not be77// reached.78$is_fatal = !$any_connection;7980foreach ($connect_map as $ref_key => $database_exception) {81$issue = PhabricatorSetupIssue::newDatabaseConnectionIssue(82$database_exception,83$is_fatal);84$this->addIssue($issue);85}86}8788foreach ($refs as $ref_key => $ref) {89if ($this->executeRefChecks($ref)) {90return;91}92}93}9495private function executeRefChecks(PhabricatorDatabaseRef $ref) {96$conn_raw = $ref->newManagementConnection();97$ref_key = $ref->getRefKey();9899$engines = queryfx_all($conn_raw, 'SHOW ENGINES');100$engines = ipull($engines, 'Support', 'Engine');101102$innodb = idx($engines, 'InnoDB');103if ($innodb != 'YES' && $innodb != 'DEFAULT') {104$message = pht(105'The "InnoDB" engine is not available in MySQL (on host "%s"). '.106'Enable InnoDB in your MySQL configuration.'.107"\n\n".108'(If you aleady created tables, MySQL incorrectly used some other '.109'engine to create them. You need to convert them or drop and '.110'reinitialize them.)',111$ref_key);112113$this->newIssue('mysql.innodb')114->setName(pht('MySQL InnoDB Engine Not Available'))115->setMessage($message)116->setIsFatal(true);117118return true;119}120121$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');122123$databases = queryfx_all($conn_raw, 'SHOW DATABASES');124$databases = ipull($databases, 'Database', 'Database');125126if (empty($databases[$namespace.'_meta_data'])) {127$message = pht(128'Run the storage upgrade script to setup databases (host "%s" has '.129'not been initialized).',130$ref_key);131132$this->newIssue('storage.upgrade')133->setName(pht('Setup MySQL Schema'))134->setMessage($message)135->setIsFatal(true)136->addCommand(hsprintf('<tt>$</tt> ./bin/storage upgrade'));137138return true;139}140141$conn_meta = $ref->newApplicationConnection(142$namespace.'_meta_data');143144$applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status');145$applied = ipull($applied, 'patch', 'patch');146147$all = PhabricatorSQLPatchList::buildAllPatches();148$diff = array_diff_key($all, $applied);149150if ($diff) {151$message = pht(152'Run the storage upgrade script to upgrade databases (host "%s" is '.153'out of date). Missing patches: %s.',154$ref_key,155implode(', ', array_keys($diff)));156157$this->newIssue('storage.patch')158->setName(pht('Upgrade MySQL Schema'))159->setIsFatal(true)160->setMessage($message)161->addCommand(162hsprintf('<tt>$</tt> ./bin/storage upgrade'));163164return true;165}166167// NOTE: It's possible that replication is broken but we have not been168// granted permission to "SHOW SLAVE STATUS" so we can't figure it out.169// We allow this kind of configuration and survive these checks, trusting170// that operations knows what they're doing. This issue is shown on the171// "Database Servers" console.172173switch ($ref->getReplicaStatus()) {174case PhabricatorDatabaseRef::REPLICATION_MASTER_REPLICA:175$message = pht(176'Database host "%s" is configured as a master, but is replicating '.177'another host. This is dangerous and can mangle or destroy data. '.178'Only replicas should be replicating. Stop replication on the '.179'host or adjust configuration.',180$ref->getRefKey());181182$this->newIssue('db.master.replicating')183->setName(pht('Replicating Master'))184->setIsFatal(true)185->setMessage($message);186187return true;188case PhabricatorDatabaseRef::REPLICATION_REPLICA_NONE:189case PhabricatorDatabaseRef::REPLICATION_NOT_REPLICATING:190if (!$ref->getIsMaster()) {191$message = pht(192'Database replica "%s" is listed as a replica, but is not '.193'currently replicating. You are vulnerable to data loss if '.194'the master fails.',195$ref->getRefKey());196197// This isn't a fatal because it can normally only put data at risk,198// not actually do anything destructive or unrecoverable.199200$this->newIssue('db.replica.not-replicating')201->setName(pht('Nonreplicating Replica'))202->setMessage($message);203}204break;205}206207// If we have more than one master, we require that the cluster database208// configuration written to each database node is exactly the same as the209// one we are running with.210$masters = PhabricatorDatabaseRef::getAllMasterDatabaseRefs();211if (count($masters) > 1) {212$state_actual = queryfx_one(213$conn_meta,214'SELECT stateValue FROM %T WHERE stateKey = %s',215PhabricatorStorageManagementAPI::TABLE_HOSTSTATE,216'cluster.databases');217if ($state_actual) {218$state_actual = $state_actual['stateValue'];219}220221$state_expect = $ref->getPartitionStateForCommit();222223if ($state_expect !== $state_actual) {224$message = pht(225'Database host "%s" has a configured cluster state which disagrees '.226'with the state on this host ("%s"). Run `bin/storage partition` '.227'to commit local state to the cluster. This host may have started '.228'with an out-of-date configuration.',229$ref->getRefKey(),230php_uname('n'));231232$this->newIssue('db.state.desync')233->setName(pht('Cluster Configuration Out of Sync'))234->setMessage($message)235->setIsFatal(true);236return true;237}238}239}240241}242243244