@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
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

When `cluster.databases` is configured, read the master connection from it

Summary:
Ref T4571. Ref T10759. Ref T10758. This isn't complete, but gets most of the job done:

- When `cluster.databases` is set up, most things ignore `mysql.host` now.
- You can `bin/storage upgrade` and stuff works.
- You can browse around in the web UI and stuff works.

There's still a lot of weird tricky stuff to navigate, and this has real no advantages over configuring a single server yet (no automatic failover, etc).

Test Plan:
- Configured `cluster.databases` to point at my `t1.micro` hosts in EC2 (master + replica).
- Ran `bin/storage upgrade`, got a new install setup on them properly.
- Survived setup warnings, browsed around.
- Switched back to local config, ran `bin/storage upgrade`, browsed around, went through setup checks.
- Intentionally broke config (bad hosts, no masters) and things seemed to react reasonably well.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T4571, T10758, T10759

Differential Revision: https://secure.phabricator.com/D15668

+126 -61
+19 -13
scripts/sql/manage_storage.php
··· 19 19 ); 20 20 $args->parseStandardArguments(); 21 21 22 - $conf = PhabricatorEnv::newObjectFromConfig( 23 - 'mysql.configuration-provider', 24 - array($dao = null, 'w')); 25 - 26 - $default_user = $conf->getUser(); 27 - $default_host = $conf->getHost(); 28 - $default_port = $conf->getPort(); 29 22 $default_namespace = PhabricatorLiskDAO::getDefaultStorageNamespace(); 30 23 31 24 try { ··· 41 34 'name' => 'user', 42 35 'short' => 'u', 43 36 'param' => 'username', 44 - 'default' => $default_user, 45 37 'help' => pht( 46 - "Connect with __username__ instead of the configured default ('%s').", 47 - $default_user), 38 + 'Connect with __username__ instead of the configured default.'), 48 39 ), 49 40 array( 50 41 'name' => 'password', ··· 84 75 // First, test that the Phabricator configuration is set up correctly. After 85 76 // we know this works we'll test any administrative credentials specifically. 86 77 78 + $ref = PhabricatorDatabaseRef::getMasterDatabaseRef(); 79 + if (!$ref) { 80 + throw new Exception( 81 + pht('No database master is configured.')); 82 + } 83 + 84 + $default_user = $ref->getUser(); 85 + $default_host = $ref->getHost(); 86 + $default_port = $ref->getPort(); 87 + 87 88 $test_api = id(new PhabricatorStorageManagementAPI()) 88 89 ->setUser($default_user) 89 90 ->setHost($default_host) 90 91 ->setPort($default_port) 91 - ->setPassword($conf->getPassword()) 92 + ->setPassword($ref->getPass()) 92 93 ->setNamespace($args->getArg('namespace')); 93 94 94 95 try { ··· 120 121 121 122 if ($args->getArg('password') === null) { 122 123 // This is already a PhutilOpaqueEnvelope. 123 - $password = $conf->getPassword(); 124 + $password = $ref->getPass(); 124 125 } else { 125 126 // Put this in a PhutilOpaqueEnvelope. 126 127 $password = new PhutilOpaqueEnvelope($args->getArg('password')); 127 128 PhabricatorEnv::overrideConfig('mysql.pass', $args->getArg('password')); 128 129 } 129 130 131 + $selected_user = $args->getArg('user'); 132 + if ($selected_user === null) { 133 + $selected_user = $default_user; 134 + } 135 + 130 136 $api = id(new PhabricatorStorageManagementAPI()) 131 - ->setUser($args->getArg('user')) 137 + ->setUser($selected_user) 132 138 ->setHost($default_host) 133 139 ->setPort($default_port) 134 140 ->setPassword($password)
+9 -24
src/applications/config/check/PhabricatorDatabaseSetupCheck.php
··· 12 12 } 13 13 14 14 protected function executeChecks() { 15 - $conf = PhabricatorEnv::newObjectFromConfig('mysql.configuration-provider'); 16 - $conn_user = $conf->getUser(); 17 - $conn_pass = $conf->getPassword(); 18 - $conn_host = $conf->getHost(); 19 - $conn_port = $conf->getPort(); 15 + $master = PhabricatorDatabaseRef::getMasterDatabaseRef(); 16 + if (!$master) { 17 + // If we're implicitly in read-only mode during disaster recovery, 18 + // don't bother with these setup checks. 19 + return; 20 + } 20 21 21 - ini_set('mysql.connect_timeout', 2); 22 - 23 - $config = array( 24 - 'user' => $conn_user, 25 - 'pass' => $conn_pass, 26 - 'host' => $conn_host, 27 - 'port' => $conn_port, 28 - 'database' => null, 29 - ); 30 - 31 - $conn_raw = PhabricatorEnv::newObjectFromConfig( 32 - 'mysql.implementation', 33 - array($config)); 22 + $conn_raw = $master->newManagementConnection(); 34 23 35 24 try { 36 25 queryfx($conn_raw, 'SELECT 1'); ··· 88 77 ->setIsFatal(true) 89 78 ->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade')); 90 79 } else { 91 - 92 - $config['database'] = $namespace.'_meta_data'; 93 - $conn_meta = PhabricatorEnv::newObjectFromConfig( 94 - 'mysql.implementation', 95 - array($config)); 80 + $conn_meta = $master->newApplicationConnection( 81 + $namespace.'_meta_data'); 96 82 97 83 $applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status'); 98 84 $applied = ipull($applied, 'patch', 'patch'); ··· 112 98 hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade')); 113 99 } 114 100 } 115 - 116 101 117 102 $host = PhabricatorEnv::getEnvConfig('mysql.host'); 118 103 $matches = null;
+61 -10
src/infrastructure/cluster/PhabricatorDatabaseRef.php
··· 239 239 continue; 240 240 } 241 241 242 - $conn = $ref->newConnection(); 242 + $conn = $ref->newManagementConnection(); 243 243 244 244 $t_start = microtime(true); 245 245 try { ··· 303 303 return $refs; 304 304 } 305 305 306 - protected function newConnection() { 306 + public function newManagementConnection() { 307 + return $this->newConnection( 308 + array( 309 + 'retries' => 0, 310 + 'timeout' => 3, 311 + )); 312 + } 313 + 314 + public function newApplicationConnection($database) { 315 + return $this->newConnection( 316 + array( 317 + 'database' => $database, 318 + )); 319 + } 320 + 321 + public static function getMasterDatabaseRef() { 322 + $refs = self::loadAll(); 323 + 324 + if (!$refs) { 325 + $conf = PhabricatorEnv::newObjectFromConfig( 326 + 'mysql.configuration-provider', 327 + array(null, 'w', null)); 328 + 329 + return id(new self()) 330 + ->setHost($conf->getHost()) 331 + ->setPort($conf->getPort()) 332 + ->setUser($conf->getUser()) 333 + ->setPass($conf->getPassword()) 334 + ->setIsMaster(true); 335 + } 336 + 337 + $master = null; 338 + foreach ($refs as $ref) { 339 + if ($ref->getDisabled()) { 340 + continue; 341 + } 342 + if ($ref->getIsMaster()) { 343 + return $ref; 344 + } 345 + } 346 + 347 + return null; 348 + } 349 + 350 + private function newConnection(array $options) { 351 + $spec = $options + array( 352 + 'user' => $this->getUser(), 353 + 'pass' => $this->getPass(), 354 + 'host' => $this->getHost(), 355 + 'port' => $this->getPort(), 356 + 'database' => null, 357 + 'retries' => 3, 358 + 'timeout' => 15, 359 + ); 360 + 361 + // TODO: Remove this once the MySQL connector has proper support 362 + // for it, see T6710. 363 + ini_set('mysql.connect_timeout', $spec['timeout']); 364 + 307 365 return PhabricatorEnv::newObjectFromConfig( 308 366 'mysql.implementation', 309 367 array( 310 - array( 311 - 'user' => $this->getUser(), 312 - 'pass' => $this->getPass(), 313 - 'host' => $this->getHost(), 314 - 'port' => $this->getPort(), 315 - 'database' => null, 316 - 'retries' => 0, 317 - ), 368 + $spec, 318 369 )); 319 370 } 320 371
+37 -14
src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
··· 52 52 */ 53 53 protected function establishLiveConnection($mode) { 54 54 $namespace = self::getStorageNamespace(); 55 + $database = $namespace.'_'.$this->getApplicationName(); 55 56 56 - $conf = PhabricatorEnv::newObjectFromConfig( 57 - 'mysql.configuration-provider', 58 - array($this, $mode, $namespace)); 57 + $is_readonly = PhabricatorEnv::isReadOnly(); 59 58 60 - $is_readonly = PhabricatorEnv::isReadOnly(); 61 59 if ($is_readonly && ($mode != 'r')) { 62 60 throw new Exception( 63 61 pht( 64 62 'Attempting to establish write-mode connection from a read-only '. 65 63 'page (to database "%s").', 66 - $conf->getDatabase())); 64 + $database)); 67 65 } 68 66 69 - $connection = PhabricatorEnv::newObjectFromConfig( 67 + $refs = PhabricatorDatabaseRef::loadAll(); 68 + if ($refs) { 69 + $connection = $this->newClusterConnection($database); 70 + } else { 71 + $connection = $this->newBasicConnection($database, $mode, $namespace); 72 + } 73 + 74 + // TODO: This should be testing if the mode is "r", but that would proably 75 + // break a lot of things. Perform a more narrow test for readonly mode 76 + // until we have greater certainty that this works correctly most of the 77 + // time. 78 + if ($is_readonly) { 79 + $connection->setReadOnly(true); 80 + } 81 + 82 + return $connection; 83 + } 84 + 85 + private function newBasicConnection($database, $mode, $namespace) { 86 + $conf = PhabricatorEnv::newObjectFromConfig( 87 + 'mysql.configuration-provider', 88 + array($this, $mode, $namespace)); 89 + 90 + return PhabricatorEnv::newObjectFromConfig( 70 91 'mysql.implementation', 71 92 array( 72 93 array( ··· 74 95 'pass' => $conf->getPassword(), 75 96 'host' => $conf->getHost(), 76 97 'port' => $conf->getPort(), 77 - 'database' => $conf->getDatabase(), 98 + 'database' => $database, 78 99 'retries' => 3, 79 100 ), 80 101 )); 102 + } 103 + 104 + private function newClusterConnection($database) { 105 + $master = PhabricatorDatabaseRef::getMasterDatabaseRef(); 81 106 82 - // TODO: This should be testing if the mode is "r", but that would proably 83 - // break a lot of things. Perform a more narrow test for readonly mode 84 - // until we have greater certainty that this works correctly most of the 85 - // time. 86 - if ($is_readonly) { 87 - $connection->setReadOnly(true); 107 + if (!$master) { 108 + // TODO: Implicitly degrade to read-only mode. 109 + throw new Exception(pht('No master in database cluster config!')); 88 110 } 89 111 90 - return $connection; 112 + return $master->newApplicationConnection($database); 91 113 } 114 + 92 115 93 116 /** 94 117 * @task config