@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at upstream/main 284 lines 9.7 kB view raw
1<?php 2 3final class PhabricatorDatabaseSetupCheck extends PhabricatorSetupCheck { 4 5 public function getDefaultGroup() { 6 return self::GROUP_IMPORTANT; 7 } 8 9 public function getExecutionOrder() { 10 // This must run after basic PHP checks, but before most other checks. 11 return 500; 12 } 13 14 protected function executeChecks() { 15 $host = PhabricatorEnv::getEnvConfig('mysql.host'); 16 $matches = null; 17 if (preg_match('/^([^:]+):(\d+)$/', $host, $matches)) { 18 $host = $matches[1]; 19 $port = $matches[2]; 20 21 $this->newIssue('storage.mysql.hostport') 22 ->setName(pht('Deprecated mysql.host Format')) 23 ->setSummary( 24 pht( 25 'Move port information from `%s` to `%s` in your config.', 26 'mysql.host', 27 'mysql.port')) 28 ->setMessage( 29 pht( 30 'Your `%s` configuration contains a port number, but this usage '. 31 'is deprecated. Instead, put the port number in `%s`.', 32 'mysql.host', 33 'mysql.port')) 34 ->addPhabricatorConfig('mysql.host') 35 ->addPhabricatorConfig('mysql.port') 36 ->addCommand( 37 hsprintf( 38 '<samp>%s $</samp><kbd>./bin/config set mysql.host %s</kbd>', 39 PlatformSymbols::getPlatformServerPath(), 40 $host)) 41 ->addCommand( 42 hsprintf( 43 '<samp>%s $</samp><kbd>./bin/config set mysql.port %s</kbd>', 44 PlatformSymbols::getPlatformServerPath(), 45 $port)); 46 } 47 48 $refs = PhabricatorDatabaseRef::queryAll(); 49 $refs = mpull($refs, null, 'getRefKey'); 50 51 // Test if we can connect to each database first. If we can not connect 52 // to a particular database, we only raise a warning: this allows new web 53 // nodes to start during a disaster, when some databases may be correctly 54 // configured but not reachable. 55 56 $connect_map = array(); 57 $any_connection = false; 58 foreach ($refs as $ref_key => $ref) { 59 $conn_raw = $ref->newManagementConnection(); 60 61 try { 62 queryfx($conn_raw, 'SELECT 1'); 63 $database_exception = null; 64 $any_connection = true; 65 } catch (AphrontInvalidCredentialsQueryException $ex) { 66 $database_exception = $ex; 67 } catch (AphrontConnectionQueryException $ex) { 68 $database_exception = $ex; 69 } 70 71 if ($database_exception) { 72 $connect_map[$ref_key] = $database_exception; 73 unset($refs[$ref_key]); 74 } 75 } 76 77 if ($connect_map) { 78 // This is only a fatal error if we could not connect to anything. If 79 // possible, we still want to start if some database hosts can not be 80 // reached. 81 $is_fatal = !$any_connection; 82 83 foreach ($connect_map as $ref_key => $database_exception) { 84 $issue = PhabricatorSetupIssue::newDatabaseConnectionIssue( 85 $database_exception, 86 $is_fatal); 87 $this->addIssue($issue); 88 } 89 } 90 91 foreach ($refs as $ref_key => $ref) { 92 if ($this->executeRefChecks($ref)) { 93 return; 94 } 95 } 96 } 97 98 private function executeRefChecks(PhabricatorDatabaseRef $ref) { 99 $conn_raw = $ref->newManagementConnection(); 100 101 $versions = queryfx_one($conn_raw, 'SELECT VERSION() as v'); 102 $server_string = $versions['v']; 103 if (phutil_nonempty_string($server_string)) { 104 $matches = array(); 105 if (preg_match('/^(\d+\.\d+\.\d+)/', $server_string, $matches)) { 106 $server_version = $matches[1]; 107 $is_maria_db = stripos($server_string, 'MariaDB'); 108 // Keep $min_version in sync with 'installation_guide.diviner'! 109 if ($is_maria_db) { 110 $software_name = 'MariaDB'; 111 $min_version = '10.5.1'; 112 } else { 113 $software_name = 'MySQL'; 114 $min_version = '8.0.0'; 115 } 116 if (version_compare($server_version, $min_version, '<')) { 117 $message = pht( 118 'You are running %s version "%s", which is older than the '. 119 'minimum required version, "%s". Update to at least "%s".', 120 $software_name, 121 $server_version, 122 $min_version, 123 $min_version); 124 125 $this->newIssue('mysql.version') 126 ->setName(pht('Update %s', $software_name)) 127 ->setMessage($message) 128 ->setIsFatal(true); 129 130 return true; 131 } 132 } 133 } 134 135 $ref_key = $ref->getRefKey(); 136 137 $engines = queryfx_all($conn_raw, 'SHOW ENGINES'); 138 $engines = ipull($engines, 'Support', 'Engine'); 139 140 $innodb = idx($engines, 'InnoDB'); 141 if ($innodb != 'YES' && $innodb != 'DEFAULT') { 142 $message = pht( 143 'The "InnoDB" engine is not available in MySQL (on host "%s"). '. 144 'Enable InnoDB in your MySQL configuration.'. 145 "\n\n". 146 '(If you already created tables, MySQL incorrectly used some other '. 147 'engine to create them. You need to convert them or drop and '. 148 'reinitialize them.)', 149 $ref_key); 150 151 $this->newIssue('mysql.innodb') 152 ->setName(pht('MySQL InnoDB Engine Not Available')) 153 ->setMessage($message) 154 ->setIsFatal(true); 155 156 return true; 157 } 158 159 $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace'); 160 161 $databases = queryfx_all($conn_raw, 'SHOW DATABASES'); 162 $databases = ipull($databases, 'Database', 'Database'); 163 164 if (empty($databases[$namespace.'_meta_data'])) { 165 $message = pht( 166 'Run the storage upgrade script to setup databases (host "%s" has '. 167 'not been initialized).', 168 $ref_key); 169 170 $this->newIssue('storage.upgrade') 171 ->setName(pht('Setup MySQL Schema')) 172 ->setMessage($message) 173 ->setIsFatal(true) 174 ->addCommand( 175 hsprintf( 176 '<samp>%s $</samp><kbd>./bin/storage upgrade</kbd>', 177 PlatformSymbols::getPlatformServerPath())); 178 179 return true; 180 } 181 182 $conn_meta = $ref->newApplicationConnection( 183 $namespace.'_meta_data'); 184 185 $applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status'); 186 $applied = ipull($applied, 'patch', 'patch'); 187 188 $all = PhabricatorSQLPatchList::buildAllPatches(); 189 $diff = array_diff_key($all, $applied); 190 191 if ($diff) { 192 $message = pht( 193 'Run the storage upgrade script to upgrade databases (host "%s" is '. 194 'out of date). Missing patches: %s.', 195 $ref_key, 196 implode(', ', array_keys($diff))); 197 198 $this->newIssue('storage.patch') 199 ->setName(pht('Upgrade MySQL Schema')) 200 ->setIsFatal(true) 201 ->setMessage($message) 202 ->addCommand( 203 hsprintf( 204 '<samp>%s $</samp><kbd>./bin/storage upgrade</kbd>', 205 PlatformSymbols::getPlatformServerPath())); 206 207 return true; 208 } 209 210 // NOTE: It's possible that replication is broken but we have not been 211 // granted permission to "SHOW REPLICA STATUS" so we can't figure it out. 212 // We allow this kind of configuration and survive these checks, trusting 213 // that operations knows what they're doing. This issue is shown on the 214 // "Database Servers" console. 215 216 switch ($ref->getReplicaStatus()) { 217 case PhabricatorDatabaseRef::REPLICATION_MASTER_REPLICA: 218 $message = pht( 219 'Database host "%s" is configured as a master, but is replicating '. 220 'another host. This is dangerous and can mangle or destroy data. '. 221 'Only replicas should be replicating. Stop replication on the '. 222 'host or adjust configuration.', 223 $ref->getRefKey()); 224 225 $this->newIssue('db.master.replicating') 226 ->setName(pht('Replicating Master')) 227 ->setIsFatal(true) 228 ->setMessage($message); 229 230 return true; 231 case PhabricatorDatabaseRef::REPLICATION_REPLICA_NONE: 232 case PhabricatorDatabaseRef::REPLICATION_NOT_REPLICATING: 233 if (!$ref->getIsMaster()) { 234 $message = pht( 235 'Database replica "%s" is listed as a replica, but is not '. 236 'currently replicating. You are vulnerable to data loss if '. 237 'the master fails.', 238 $ref->getRefKey()); 239 240 // This isn't a fatal because it can normally only put data at risk, 241 // not actually do anything destructive or unrecoverable. 242 243 $this->newIssue('db.replica.not-replicating') 244 ->setName(pht('Nonreplicating Replica')) 245 ->setMessage($message); 246 } 247 break; 248 } 249 250 // If we have more than one master, we require that the cluster database 251 // configuration written to each database node is exactly the same as the 252 // one we are running with. 253 $masters = PhabricatorDatabaseRef::getAllMasterDatabaseRefs(); 254 if (count($masters) > 1) { 255 $state_actual = queryfx_one( 256 $conn_meta, 257 'SELECT stateValue FROM %T WHERE stateKey = %s', 258 PhabricatorStorageManagementAPI::TABLE_HOSTSTATE, 259 'cluster.databases'); 260 if ($state_actual) { 261 $state_actual = $state_actual['stateValue']; 262 } 263 264 $state_expect = $ref->getPartitionStateForCommit(); 265 266 if ($state_expect !== $state_actual) { 267 $message = pht( 268 'Database host "%s" has a configured cluster state which disagrees '. 269 'with the state on this host ("%s"). Run `bin/storage partition` '. 270 'to commit local state to the cluster. This host may have started '. 271 'with an out-of-date configuration.', 272 $ref->getRefKey(), 273 php_uname('n')); 274 275 $this->newIssue('db.state.desync') 276 ->setName(pht('Cluster Configuration Out of Sync')) 277 ->setMessage($message) 278 ->setIsFatal(true); 279 return true; 280 } 281 } 282 } 283 284}