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
This commit is contained in:
		| @@ -19,13 +19,6 @@ EOHELP | |||||||
| ); | ); | ||||||
| $args->parseStandardArguments(); | $args->parseStandardArguments(); | ||||||
|  |  | ||||||
| $conf = PhabricatorEnv::newObjectFromConfig( |  | ||||||
|   'mysql.configuration-provider', |  | ||||||
|   array($dao = null, 'w')); |  | ||||||
|  |  | ||||||
| $default_user       = $conf->getUser(); |  | ||||||
| $default_host       = $conf->getHost(); |  | ||||||
| $default_port       = $conf->getPort(); |  | ||||||
| $default_namespace  = PhabricatorLiskDAO::getDefaultStorageNamespace(); | $default_namespace  = PhabricatorLiskDAO::getDefaultStorageNamespace(); | ||||||
|  |  | ||||||
| try { | try { | ||||||
| @@ -41,10 +34,8 @@ try { | |||||||
|         'name'    => 'user', |         'name'    => 'user', | ||||||
|         'short'   => 'u', |         'short'   => 'u', | ||||||
|         'param'   => 'username', |         'param'   => 'username', | ||||||
|         'default' => $default_user, |  | ||||||
|         'help'    => pht( |         'help'    => pht( | ||||||
|           "Connect with __username__ instead of the configured default ('%s').", |           'Connect with __username__ instead of the configured default.'), | ||||||
|           $default_user), |  | ||||||
|       ), |       ), | ||||||
|       array( |       array( | ||||||
|         'name'    => 'password', |         'name'    => 'password', | ||||||
| @@ -84,11 +75,21 @@ try { | |||||||
| // First, test that the Phabricator configuration is set up correctly. After | // First, test that the Phabricator configuration is set up correctly. After | ||||||
| // we know this works we'll test any administrative credentials specifically. | // we know this works we'll test any administrative credentials specifically. | ||||||
|  |  | ||||||
|  | $ref = PhabricatorDatabaseRef::getMasterDatabaseRef(); | ||||||
|  | if (!$ref) { | ||||||
|  |   throw new Exception( | ||||||
|  |     pht('No database master is configured.')); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | $default_user = $ref->getUser(); | ||||||
|  | $default_host = $ref->getHost(); | ||||||
|  | $default_port = $ref->getPort(); | ||||||
|  |  | ||||||
| $test_api = id(new PhabricatorStorageManagementAPI()) | $test_api = id(new PhabricatorStorageManagementAPI()) | ||||||
|   ->setUser($default_user) |   ->setUser($default_user) | ||||||
|   ->setHost($default_host) |   ->setHost($default_host) | ||||||
|   ->setPort($default_port) |   ->setPort($default_port) | ||||||
|   ->setPassword($conf->getPassword()) |   ->setPassword($ref->getPass()) | ||||||
|   ->setNamespace($args->getArg('namespace')); |   ->setNamespace($args->getArg('namespace')); | ||||||
|  |  | ||||||
| try { | try { | ||||||
| @@ -120,15 +121,20 @@ try { | |||||||
|  |  | ||||||
| if ($args->getArg('password') === null) { | if ($args->getArg('password') === null) { | ||||||
|   // This is already a PhutilOpaqueEnvelope. |   // This is already a PhutilOpaqueEnvelope. | ||||||
|   $password = $conf->getPassword(); |   $password = $ref->getPass(); | ||||||
| } else { | } else { | ||||||
|   // Put this in a PhutilOpaqueEnvelope. |   // Put this in a PhutilOpaqueEnvelope. | ||||||
|   $password = new PhutilOpaqueEnvelope($args->getArg('password')); |   $password = new PhutilOpaqueEnvelope($args->getArg('password')); | ||||||
|   PhabricatorEnv::overrideConfig('mysql.pass', $args->getArg('password')); |   PhabricatorEnv::overrideConfig('mysql.pass', $args->getArg('password')); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | $selected_user = $args->getArg('user'); | ||||||
|  | if ($selected_user === null) { | ||||||
|  |   $selected_user = $default_user; | ||||||
|  | } | ||||||
|  |  | ||||||
| $api = id(new PhabricatorStorageManagementAPI()) | $api = id(new PhabricatorStorageManagementAPI()) | ||||||
|   ->setUser($args->getArg('user')) |   ->setUser($selected_user) | ||||||
|   ->setHost($default_host) |   ->setHost($default_host) | ||||||
|   ->setPort($default_port) |   ->setPort($default_port) | ||||||
|   ->setPassword($password) |   ->setPassword($password) | ||||||
|   | |||||||
| @@ -12,25 +12,14 @@ final class PhabricatorDatabaseSetupCheck extends PhabricatorSetupCheck { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected function executeChecks() { |   protected function executeChecks() { | ||||||
|     $conf = PhabricatorEnv::newObjectFromConfig('mysql.configuration-provider'); |     $master = PhabricatorDatabaseRef::getMasterDatabaseRef(); | ||||||
|     $conn_user = $conf->getUser(); |     if (!$master) { | ||||||
|     $conn_pass = $conf->getPassword(); |       // If we're implicitly in read-only mode during disaster recovery, | ||||||
|     $conn_host = $conf->getHost(); |       // don't bother with these setup checks. | ||||||
|     $conn_port = $conf->getPort(); |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     ini_set('mysql.connect_timeout', 2); |     $conn_raw = $master->newManagementConnection(); | ||||||
|  |  | ||||||
|     $config = array( |  | ||||||
|       'user'      => $conn_user, |  | ||||||
|       'pass'      => $conn_pass, |  | ||||||
|       'host'      => $conn_host, |  | ||||||
|       'port'      => $conn_port, |  | ||||||
|       'database'  => null, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     $conn_raw = PhabricatorEnv::newObjectFromConfig( |  | ||||||
|       'mysql.implementation', |  | ||||||
|       array($config)); |  | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       queryfx($conn_raw, 'SELECT 1'); |       queryfx($conn_raw, 'SELECT 1'); | ||||||
| @@ -88,11 +77,8 @@ final class PhabricatorDatabaseSetupCheck extends PhabricatorSetupCheck { | |||||||
|         ->setIsFatal(true) |         ->setIsFatal(true) | ||||||
|         ->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade')); |         ->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade')); | ||||||
|     } else { |     } else { | ||||||
|  |       $conn_meta = $master->newApplicationConnection( | ||||||
|       $config['database'] = $namespace.'_meta_data'; |         $namespace.'_meta_data'); | ||||||
|       $conn_meta = PhabricatorEnv::newObjectFromConfig( |  | ||||||
|         'mysql.implementation', |  | ||||||
|         array($config)); |  | ||||||
|  |  | ||||||
|       $applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status'); |       $applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status'); | ||||||
|       $applied = ipull($applied, 'patch', 'patch'); |       $applied = ipull($applied, 'patch', 'patch'); | ||||||
| @@ -113,7 +99,6 @@ final class PhabricatorDatabaseSetupCheck extends PhabricatorSetupCheck { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     $host = PhabricatorEnv::getEnvConfig('mysql.host'); |     $host = PhabricatorEnv::getEnvConfig('mysql.host'); | ||||||
|     $matches = null; |     $matches = null; | ||||||
|     if (preg_match('/^([^:]+):(\d+)$/', $host, $matches)) { |     if (preg_match('/^([^:]+):(\d+)$/', $host, $matches)) { | ||||||
|   | |||||||
| @@ -239,7 +239,7 @@ final class PhabricatorDatabaseRef | |||||||
|         continue; |         continue; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       $conn = $ref->newConnection(); |       $conn = $ref->newManagementConnection(); | ||||||
|  |  | ||||||
|       $t_start = microtime(true); |       $t_start = microtime(true); | ||||||
|       try { |       try { | ||||||
| @@ -303,18 +303,69 @@ final class PhabricatorDatabaseRef | |||||||
|     return $refs; |     return $refs; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected function newConnection() { |   public function newManagementConnection() { | ||||||
|  |     return $this->newConnection( | ||||||
|  |       array( | ||||||
|  |         'retries' => 0, | ||||||
|  |         'timeout' => 3, | ||||||
|  |       )); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function newApplicationConnection($database) { | ||||||
|  |     return $this->newConnection( | ||||||
|  |       array( | ||||||
|  |         'database' => $database, | ||||||
|  |       )); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public static function getMasterDatabaseRef() { | ||||||
|  |     $refs = self::loadAll(); | ||||||
|  |  | ||||||
|  |     if (!$refs) { | ||||||
|  |       $conf = PhabricatorEnv::newObjectFromConfig( | ||||||
|  |         'mysql.configuration-provider', | ||||||
|  |         array(null, 'w', null)); | ||||||
|  |  | ||||||
|  |       return id(new self()) | ||||||
|  |         ->setHost($conf->getHost()) | ||||||
|  |         ->setPort($conf->getPort()) | ||||||
|  |         ->setUser($conf->getUser()) | ||||||
|  |         ->setPass($conf->getPassword()) | ||||||
|  |         ->setIsMaster(true); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $master = null; | ||||||
|  |     foreach ($refs as $ref) { | ||||||
|  |       if ($ref->getDisabled()) { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |       if ($ref->getIsMaster()) { | ||||||
|  |         return $ref; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function newConnection(array $options) { | ||||||
|  |     $spec = $options + array( | ||||||
|  |       'user' => $this->getUser(), | ||||||
|  |       'pass' => $this->getPass(), | ||||||
|  |       'host' => $this->getHost(), | ||||||
|  |       'port' => $this->getPort(), | ||||||
|  |       'database' => null, | ||||||
|  |       'retries' => 3, | ||||||
|  |       'timeout' => 15, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     // TODO: Remove this once the MySQL connector has proper support | ||||||
|  |     // for it, see T6710. | ||||||
|  |     ini_set('mysql.connect_timeout', $spec['timeout']); | ||||||
|  |  | ||||||
|     return PhabricatorEnv::newObjectFromConfig( |     return PhabricatorEnv::newObjectFromConfig( | ||||||
|       'mysql.implementation', |       'mysql.implementation', | ||||||
|       array( |       array( | ||||||
|         array( |         $spec, | ||||||
|           'user'      => $this->getUser(), |  | ||||||
|           'pass'      => $this->getPass(), |  | ||||||
|           'host'      => $this->getHost(), |  | ||||||
|           'port'      => $this->getPort(), |  | ||||||
|           'database'  => null, |  | ||||||
|           'retries'   => 0, |  | ||||||
|         ), |  | ||||||
|       )); |       )); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -52,32 +52,24 @@ abstract class PhabricatorLiskDAO extends LiskDAO { | |||||||
|    */ |    */ | ||||||
|   protected function establishLiveConnection($mode) { |   protected function establishLiveConnection($mode) { | ||||||
|     $namespace = self::getStorageNamespace(); |     $namespace = self::getStorageNamespace(); | ||||||
|  |     $database = $namespace.'_'.$this->getApplicationName(); | ||||||
|     $conf = PhabricatorEnv::newObjectFromConfig( |  | ||||||
|       'mysql.configuration-provider', |  | ||||||
|       array($this, $mode, $namespace)); |  | ||||||
|  |  | ||||||
|     $is_readonly = PhabricatorEnv::isReadOnly(); |     $is_readonly = PhabricatorEnv::isReadOnly(); | ||||||
|  |  | ||||||
|     if ($is_readonly && ($mode != 'r')) { |     if ($is_readonly && ($mode != 'r')) { | ||||||
|       throw new Exception( |       throw new Exception( | ||||||
|         pht( |         pht( | ||||||
|           'Attempting to establish write-mode connection from a read-only '. |           'Attempting to establish write-mode connection from a read-only '. | ||||||
|           'page (to database "%s").', |           'page (to database "%s").', | ||||||
|           $conf->getDatabase())); |           $database)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     $connection = PhabricatorEnv::newObjectFromConfig( |     $refs = PhabricatorDatabaseRef::loadAll(); | ||||||
|       'mysql.implementation', |     if ($refs) { | ||||||
|       array( |       $connection = $this->newClusterConnection($database); | ||||||
|         array( |     } else { | ||||||
|           'user'      => $conf->getUser(), |       $connection = $this->newBasicConnection($database, $mode, $namespace); | ||||||
|           'pass'      => $conf->getPassword(), |     } | ||||||
|           'host'      => $conf->getHost(), |  | ||||||
|           'port'      => $conf->getPort(), |  | ||||||
|           'database'  => $conf->getDatabase(), |  | ||||||
|           'retries'   => 3, |  | ||||||
|         ), |  | ||||||
|       )); |  | ||||||
|  |  | ||||||
|     // TODO: This should be testing if the mode is "r", but that would proably |     // TODO: This should be testing if the mode is "r", but that would proably | ||||||
|     // break a lot of things. Perform a more narrow test for readonly mode |     // break a lot of things. Perform a more narrow test for readonly mode | ||||||
| @@ -90,6 +82,37 @@ abstract class PhabricatorLiskDAO extends LiskDAO { | |||||||
|     return $connection; |     return $connection; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   private function newBasicConnection($database, $mode, $namespace) { | ||||||
|  |     $conf = PhabricatorEnv::newObjectFromConfig( | ||||||
|  |       'mysql.configuration-provider', | ||||||
|  |       array($this, $mode, $namespace)); | ||||||
|  |  | ||||||
|  |     return PhabricatorEnv::newObjectFromConfig( | ||||||
|  |       'mysql.implementation', | ||||||
|  |       array( | ||||||
|  |         array( | ||||||
|  |           'user'      => $conf->getUser(), | ||||||
|  |           'pass'      => $conf->getPassword(), | ||||||
|  |           'host'      => $conf->getHost(), | ||||||
|  |           'port'      => $conf->getPort(), | ||||||
|  |           'database'  => $database, | ||||||
|  |           'retries'   => 3, | ||||||
|  |         ), | ||||||
|  |       )); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function newClusterConnection($database) { | ||||||
|  |     $master = PhabricatorDatabaseRef::getMasterDatabaseRef(); | ||||||
|  |  | ||||||
|  |     if (!$master) { | ||||||
|  |       // TODO: Implicitly degrade to read-only mode. | ||||||
|  |       throw new Exception(pht('No master in database cluster config!')); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $master->newApplicationConnection($database); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * @task config |    * @task config | ||||||
|    */ |    */ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 epriestley
					epriestley