1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 06:42:42 +01:00

Update bin/storage workflows to accommodate multiple masters

Summary: Depends on D16847. Ref T11044. This updates the remaining storage-related workflows from the CLI to accommodate multiple masters.

Test Plan:
  - Configured multiple masters.
  - Ran all `bin/storage` workflows.
  - Ran `arc unit --everything`.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T11044

Differential Revision: https://secure.phabricator.com/D16848
This commit is contained in:
epriestley 2016-11-12 11:39:45 -08:00
parent bc15eee3f2
commit 558d194302
15 changed files with 381 additions and 209 deletions

View file

@ -34,7 +34,13 @@ try {
'name' => 'host',
'param' => 'hostname',
'help' => pht(
'Connect to __host__ instead of the default host.'),
'Operate on the database server identified by __hostname__.'),
),
array(
'name' => 'ref',
'param' => 'ref',
'help' => pht(
'Operate on the database identified by __ref__.'),
),
array(
'name' => 'user',
@ -81,57 +87,80 @@ try {
// First, test that the Phabricator configuration is set up correctly. After
// we know this works we'll test any administrative credentials specifically.
$refs = PhabricatorDatabaseRef::getActiveDatabaseRefs();
if (!$refs) {
throw new PhutilArgumentUsageException(
pht('No databases are configured.'));
}
$host = $args->getArg('host');
if (strlen($host)) {
$ref = null;
$refs = PhabricatorDatabaseRef::getLiveRefs();
// Include the master in case the user is just specifying a redundant
// "--host" flag for no reason and does not actually have a database
// cluster configured.
foreach (PhabricatorDatabaseRef::getMasterDatabaseRefs() as $master_ref) {
$refs[] = $master_ref;
$ref_key = $args->getArg('ref');
if (strlen($host) || strlen($ref_key)) {
if ($host && $ref_key) {
throw new PhutilArgumentUsageException(
pht(
'Use "--host" or "--ref" to select a database, but not both.'));
}
$refs = PhabricatorDatabaseRef::getActiveDatabaseRefs();
$possible_refs = array();
foreach ($refs as $possible_ref) {
if ($possible_ref->getHost() == $host) {
$ref = $possible_ref;
if ($host && ($possible_ref->getHost() == $host)) {
$possible_refs[] = $possible_ref;
break;
}
if ($ref_key && ($possible_ref->getRefKey() == $ref_key)) {
$possible_refs[] = $possible_ref;
break;
}
}
if (!$ref) {
if (!$possible_refs) {
if ($host) {
throw new PhutilArgumentUsageException(
pht(
'There is no configured database on host "%s". This command can '.
'only interact with configured databases.',
$host));
} else {
throw new PhutilArgumentUsageException(
pht(
'There is no configured database with ref "%s". This command can '.
'only interact with configured databases.',
$ref_key));
}
} else {
$ref = PhabricatorDatabaseRef::getMasterDatabaseRef();
if (!$ref) {
throw new Exception(
pht('No database master is configured.'));
}
if (count($possible_refs) > 1) {
throw new PhutilArgumentUsageException(
pht(
'Host "%s" identifies more than one database. Use "--ref" to select '.
'a specific database.',
$host));
}
$refs = $possible_refs;
}
$default_user = $ref->getUser();
$default_host = $ref->getHost();
$default_port = $ref->getPort();
$apis = array();
foreach ($refs as $ref) {
$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)
->setHost($default_host)
->setPort($default_port)
->setPassword($ref->getPass())
->setNamespace($args->getArg('namespace'));
try {
try {
queryfx(
$test_api->getConn(null),
'SELECT 1');
} catch (AphrontQueryException $ex) {
} catch (AphrontQueryException $ex) {
$message = phutil_console_format(
"**%s**\n\n%s\n\n%s\n\n%s\n\n**%s**: %s\n",
pht('MySQL Credentials Not Configured'),
@ -152,36 +181,36 @@ try {
$ex->getMessage());
echo phutil_console_wrap($message);
exit(1);
}
}
if ($args->getArg('password') === null) {
if ($args->getArg('password') === null) {
// This is already a PhutilOpaqueEnvelope.
$password = $ref->getPass();
} else {
} else {
// Put this in a PhutilOpaqueEnvelope.
$password = new PhutilOpaqueEnvelope($args->getArg('password'));
PhabricatorEnv::overrideConfig('mysql.pass', $args->getArg('password'));
}
}
$selected_user = $args->getArg('user');
if ($selected_user === null) {
$selected_user = $args->getArg('user');
if ($selected_user === null) {
$selected_user = $default_user;
}
}
$api = id(new PhabricatorStorageManagementAPI())
$api = id(new PhabricatorStorageManagementAPI())
->setUser($selected_user)
->setHost($default_host)
->setPort($default_port)
->setPassword($password)
->setNamespace($args->getArg('namespace'))
->setDisableUTF8MB4($args->getArg('disable-utf8mb4'));
PhabricatorEnv::overrideConfig('mysql.user', $api->getUser());
PhabricatorEnv::overrideConfig('mysql.user', $api->getUser());
try {
try {
queryfx(
$api->getConn(null),
'SELECT 1');
} catch (AphrontQueryException $ex) {
} catch (AphrontQueryException $ex) {
$message = phutil_console_format(
"**%s**\n\n%s\n\n**%s**: %s\n",
pht('Bad Administrative Credentials'),
@ -195,6 +224,10 @@ try {
$ex->getMessage());
echo phutil_console_wrap($message);
exit(1);
}
$api->setRef($ref);
$apis[] = $api;
}
$workflows = id(new PhutilClassMapQuery())
@ -204,7 +237,7 @@ $workflows = id(new PhutilClassMapQuery())
$patches = PhabricatorSQLPatchList::buildAllPatches();
foreach ($workflows as $workflow) {
$workflow->setAPI($api);
$workflow->setAPIs($apis);
$workflow->setPatches($patches);
}

View file

@ -2,6 +2,30 @@
final class PhabricatorConfigSchemaQuery extends Phobject {
private $refs;
private $apis;
public function setRefs(array $refs) {
$this->refs = $refs;
return $this;
}
public function getRefs() {
if (!$this->refs) {
return PhabricatorDatabaseRef::getMasterDatabaseRefs();
}
return $this->refs;
}
public function setAPIs(array $apis) {
$map = array();
foreach ($apis as $api) {
$map[$api->getRef()->getRefKey()] = $api;
}
$this->apis = $map;
return $this;
}
private function getDatabaseNames(PhabricatorDatabaseRef $ref) {
$api = $this->getAPI($ref);
$patches = PhabricatorSQLPatchList::buildAllPatches();
@ -11,6 +35,12 @@ final class PhabricatorConfigSchemaQuery extends Phobject {
}
private function getAPI(PhabricatorDatabaseRef $ref) {
$key = $ref->getRefKey();
if (isset($this->apis[$key])) {
return $this->apis[$key];
}
return id(new PhabricatorStorageManagementAPI())
->setUser($ref->getUser())
->setHost($ref->getHost())
@ -20,7 +50,7 @@ final class PhabricatorConfigSchemaQuery extends Phobject {
}
public function loadActualSchemata() {
$refs = PhabricatorDatabaseRef::getMasterDatabaseRefs();
$refs = $this->getRefs();
$schemata = array();
foreach ($refs as $ref) {
@ -183,7 +213,7 @@ final class PhabricatorConfigSchemaQuery extends Phobject {
}
public function loadExpectedSchemata() {
$refs = PhabricatorDatabaseRef::getMasterDatabaseRefs();
$refs = $this->getRefs();
$schemata = array();
foreach ($refs as $ref) {

View file

@ -223,7 +223,7 @@ final class PhabricatorDatabaseRef
);
}
public static function getLiveRefs() {
public static function getClusterRefs() {
$cache = PhabricatorCaches::getRequestCache();
$refs = $cache->getKey(self::KEY_REFS);
@ -457,8 +457,22 @@ final class PhabricatorDatabaseRef
return $this->healthRecord;
}
public static function getActiveDatabaseRefs() {
$refs = array();
foreach (self::getMasterDatabaseRefs() as $ref) {
$refs[] = $ref;
}
foreach (self::getReplicaDatabaseRefs() as $ref) {
$refs[] = $ref;
}
return $refs;
}
public static function getMasterDatabaseRefs() {
$refs = self::getLiveRefs();
$refs = self::getClusterRefs();
if (!$refs) {
return array(self::getLiveIndividualRef());
@ -477,13 +491,6 @@ final class PhabricatorDatabaseRef
return $masters;
}
public static function getMasterDatabaseRef() {
// TODO: Remove this method; it no longer makes sense with application
// partitioning.
return head(self::getMasterDatabaseRefs());
}
public static function getMasterDatabaseRefForDatabase($database) {
$masters = self::getMasterDatabaseRefs();
@ -507,7 +514,7 @@ final class PhabricatorDatabaseRef
}
public static function getReplicaDatabaseRefs() {
$refs = self::getLiveRefs();
$refs = self::getClusterRefs();
if (!$refs) {
return array();

View file

@ -2,6 +2,7 @@
final class PhabricatorStorageManagementAPI extends Phobject {
private $ref;
private $host;
private $user;
private $port;
@ -74,6 +75,15 @@ final class PhabricatorStorageManagementAPI extends Phobject {
return $this->port;
}
public function setRef(PhabricatorDatabaseRef $ref) {
$this->ref = $ref;
return $this;
}
public function getRef() {
return $this->ref;
}
public function getDatabaseName($fragment) {
return $this->namespace.'_'.$fragment;
}

View file

@ -27,12 +27,19 @@ final class PhabricatorStorageManagementAdjustWorkflow
public function didExecute(PhutilArgumentParser $args) {
$unsafe = $args->getArg('unsafe');
$this->requireAllPatchesApplied();
return $this->adjustSchemata($unsafe);
foreach ($this->getMasterAPIs() as $api) {
$this->requireAllPatchesApplied($api);
$err = $this->adjustSchemata($api, $unsafe);
if ($err) {
return $err;
}
}
private function requireAllPatchesApplied() {
$api = $this->getAPI();
return 0;
}
private function requireAllPatchesApplied(
PhabricatorStorageManagementAPI $api) {
$applied = $api->getAppliedPatches();
if ($applied === null) {

View file

@ -15,7 +15,8 @@ final class PhabricatorStorageManagementDatabasesWorkflow
}
public function didExecute(PhutilArgumentParser $args) {
$api = $this->getAPI();
$api = $this->getAnyAPI();
$patches = $this->getPatches();
$databases = $api->getDatabaseList($patches, true);

View file

@ -23,6 +23,8 @@ final class PhabricatorStorageManagementDestroyWorkflow
public function didExecute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$api = $this->getSingleAPI();
if (!$this->isDryRun() && !$this->isForce()) {
$console->writeOut(
phutil_console_wrap(
@ -42,7 +44,6 @@ final class PhabricatorStorageManagementDestroyWorkflow
}
}
$api = $this->getAPI();
$patches = $this->getPatches();
if ($args->getArg('unittest-fixtures')) {

View file

@ -44,7 +44,7 @@ final class PhabricatorStorageManagementDumpWorkflow
}
public function didExecute(PhutilArgumentParser $args) {
$api = $this->getAPI();
$api = $this->getSingleAPI();
$patches = $this->getPatches();
$console = PhutilConsole::getConsole();

View file

@ -15,12 +15,13 @@ final class PhabricatorStorageManagementProbeWorkflow
}
public function didExecute(PhutilArgumentParser $args) {
$api = $this->getSingleAPI();
$console = PhutilConsole::getConsole();
$console->writeErr(
"%s\n",
pht('Analyzing table sizes (this may take a moment)...'));
$api = $this->getAPI();
$patches = $this->getPatches();
$databases = $api->getDatabaseList($patches, true);

View file

@ -36,7 +36,14 @@ final class PhabricatorStorageManagementQuickstartWorkflow
$bin = dirname(phutil_get_library_root('phabricator')).'/bin/storage';
if (!$this->getAPI()->isCharacterSetAvailable('utf8mb4')) {
// We don't care which database we're using to generate a quickstart file,
// since all of the schemata should be identical.
$api = $this->getAnyAPI();
$ref = $api->getRef();
$ref_key = $ref->getRefKey();
if (!$api->isCharacterSetAvailable('utf8mb4')) {
throw new PhutilArgumentUsageException(
pht(
'You can only generate a new quickstart file if MySQL supports '.
@ -47,35 +54,39 @@ final class PhabricatorStorageManagementQuickstartWorkflow
}
$err = phutil_passthru(
'%s upgrade --force --no-quickstart --namespace %s',
'%s upgrade --force --no-quickstart --namespace %s --ref %s',
$bin,
$namespace);
$namespace,
$ref_key);
if ($err) {
return $err;
}
$err = phutil_passthru(
'%s adjust --force --namespace %s',
'%s adjust --force --namespace %s --ref %s',
$bin,
$namespace);
$namespace,
$ref_key);
if ($err) {
return $err;
}
$tmp = new TempFile();
$err = phutil_passthru(
'%s dump --namespace %s > %s',
'%s dump --namespace %s --ref %s > %s',
$bin,
$namespace,
$ref_key,
$tmp);
if ($err) {
return $err;
}
$err = phutil_passthru(
'%s destroy --force --namespace %s',
'%s destroy --force --namespace %s --ref %s',
$bin,
$namespace);
$namespace,
$ref_key);
if ($err) {
return $err;
}

View file

@ -62,7 +62,7 @@ final class PhabricatorStorageManagementRenamespaceWorkflow
if (!strlen($input) && !$is_live) {
throw new PhutilArgumentUsageException(
pht(
'Specify the dumpfile to read with "--in", or use "--live" to '.
'Specify the dumpfile to read with "--input", or use "--live" to '.
'generate one automatically.'));
}
@ -108,11 +108,15 @@ final class PhabricatorStorageManagementRenamespaceWorkflow
}
if ($is_live) {
$api = $this->getSingleAPI();
$ref_key = $api->getRef()->getRefKey();
$root = dirname(phutil_get_library_root('phabricator'));
$future = new ExecFuture(
'%R dump',
$root.'/bin/storage');
'%R dump --ref %s',
$root.'/bin/storage',
$ref_key);
$lines = new LinesOfALargeExecFuture($future);
} else {

View file

@ -15,7 +15,7 @@ final class PhabricatorStorageManagementShellWorkflow
}
public function execute(PhutilArgumentParser $args) {
$api = $this->getAPI();
$api = $this->getSingleAPI();
list($host, $port) = $this->getBareHostAndPort($api->getHost());
$flag_port = $port

View file

@ -15,9 +15,8 @@ final class PhabricatorStorageManagementStatusWorkflow
}
public function didExecute(PhutilArgumentParser $args) {
$api = $this->getAPI();
foreach ($this->getAPIs() as $api) {
$patches = $this->getPatches();
$applied = $api->getAppliedPatches();
if ($applied === null) {
@ -29,9 +28,12 @@ final class PhabricatorStorageManagementStatusWorkflow
return 1;
}
$ref = $api->getRef();
$table = id(new PhutilConsoleTable())
->setShowHeader(false)
->addColumn('id', array('title' => pht('ID')))
->addColumn('host', array('title' => pht('Host')))
->addColumn('status', array('title' => pht('Status')))
->addColumn('duration', array('title' => pht('Duration')))
->addColumn('type', array('title' => pht('Type')))
@ -49,6 +51,7 @@ final class PhabricatorStorageManagementStatusWorkflow
$table->addRow(array(
'id' => $patch->getFullKey(),
'host' => $ref->getRefKey(),
'status' => in_array($patch->getFullKey(), $applied)
? pht('Applied')
: pht('Not Applied'),
@ -59,6 +62,7 @@ final class PhabricatorStorageManagementStatusWorkflow
}
$table->draw();
}
return 0;
}

View file

@ -73,16 +73,24 @@ final class PhabricatorStorageManagementUpgradeWorkflow
$init_only = $args->getArg('init-only');
$no_adjust = $args->getArg('no-adjust');
$this->upgradeSchemata($apply_only, $no_quickstart, $init_only);
$apis = $this->getMasterAPIs();
foreach ($apis as $api) {
$this->upgradeSchemata($api, $apply_only, $no_quickstart, $init_only);
if ($no_adjust || $init_only || $apply_only) {
$console->writeOut(
"%s\n",
pht('Declining to apply storage adjustments.'));
return 0;
} else {
return $this->adjustSchemata(false);
$err = $this->adjustSchemata($api, false);
if ($err) {
return $err;
}
}
}
return 0;
}
}

View file

@ -3,20 +3,56 @@
abstract class PhabricatorStorageManagementWorkflow
extends PhabricatorManagementWorkflow {
private $api;
private $apis = array();
private $dryRun;
private $force;
private $patches;
private $didInitialize;
final public function getAPI() {
return $this->api;
final public function setAPIs(array $apis) {
$this->apis = $apis;
return $this;
}
final public function setAPI(PhabricatorStorageManagementAPI $api) {
$this->api = $api;
return $this;
final public function getAnyAPI() {
return head($this->getAPIs());
}
final public function getMasterAPIs() {
$apis = $this->getAPIs();
$results = array();
foreach ($apis as $api) {
if ($api->getRef()->getIsMaster()) {
$results[] = $api;
}
}
if (!$results) {
throw new PhutilArgumentUsageException(
pht(
'This command only operates on database masters, but the selected '.
'database hosts do not include any masters.'));
}
return $results;
}
final public function getSingleAPI() {
$apis = $this->getAPIs();
if (count($apis) == 1) {
return head($apis);
}
throw new PhutilArgumentUsageException(
pht(
'Phabricator is configured in cluster mode, with multiple database '.
'hosts. Use "--host" to specify which host you want to operate on.'));
}
final public function getAPIs() {
return $this->apis;
}
final protected function isDryRun() {
@ -73,22 +109,34 @@ abstract class PhabricatorStorageManagementWorkflow
public function didExecute(PhutilArgumentParser $args) {}
private function loadSchemata() {
$query = id(new PhabricatorConfigSchemaQuery())
->setAPI($this->getAPI());
private function loadSchemata(PhabricatorStorageManagementAPI $api) {
$query = id(new PhabricatorConfigSchemaQuery());
$actual = $query->loadActualSchema();
$expect = $query->loadExpectedSchema();
$comp = $query->buildComparisonSchema($expect, $actual);
$ref = $api->getRef();
$ref_key = $ref->getRefKey();
return array($comp, $expect, $actual);
$query->setAPIs(array($api));
$query->setRefs(array($ref));
$actual = $query->loadActualSchemata();
$expect = $query->loadExpectedSchemata();
$comp = $query->buildComparisonSchemata($expect, $actual);
return array(
$comp[$ref_key],
$expect[$ref_key],
$actual[$ref_key],
);
}
final protected function adjustSchemata($unsafe) {
$lock = $this->lock();
final protected function adjustSchemata(
PhabricatorStorageManagementAPI $api,
$unsafe) {
$lock = $this->lock($api);
try {
$err = $this->doAdjustSchemata($unsafe);
$err = $this->doAdjustSchemata($api, $unsafe);
} catch (Exception $ex) {
$lock->unlock();
throw $ex;
@ -99,15 +147,19 @@ abstract class PhabricatorStorageManagementWorkflow
return $err;
}
final private function doAdjustSchemata($unsafe) {
final private function doAdjustSchemata(
PhabricatorStorageManagementAPI $api,
$unsafe) {
$console = PhutilConsole::getConsole();
$console->writeOut(
"%s\n",
pht('Verifying database schemata...'));
pht(
'Verifying database schemata on "%s"...',
$api->getRef()->getRefKey()));
list($adjustments, $errors) = $this->findAdjustments();
$api = $this->getAPI();
list($adjustments, $errors) = $this->findAdjustments($api);
if (!$adjustments) {
$console->writeOut(
@ -415,8 +467,9 @@ abstract class PhabricatorStorageManagementWorkflow
return $this->printErrors($errors, $err);
}
private function findAdjustments() {
list($comp, $expect, $actual) = $this->loadSchemata();
private function findAdjustments(
PhabricatorStorageManagementAPI $api) {
list($comp, $expect, $actual) = $this->loadSchemata($api);
$issue_charset = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
$issue_collation = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
@ -766,14 +819,15 @@ abstract class PhabricatorStorageManagementWorkflow
}
final protected function upgradeSchemata(
PhabricatorStorageManagementAPI $api,
$apply_only = null,
$no_quickstart = false,
$init_only = false) {
$lock = $this->lock();
$lock = $this->lock($api);
try {
$this->doUpgradeSchemata($apply_only, $no_quickstart, $init_only);
$this->doUpgradeSchemata($api, $apply_only, $no_quickstart, $init_only);
} catch (Exception $ex) {
$lock->unlock();
throw $ex;
@ -783,13 +837,12 @@ abstract class PhabricatorStorageManagementWorkflow
}
final private function doUpgradeSchemata(
PhabricatorStorageManagementAPI $api,
$apply_only,
$no_quickstart,
$init_only) {
$api = $this->getAPI();
$applied = $this->getApi()->getAppliedPatches();
$applied = $api->getAppliedPatches();
if ($applied === null) {
if ($this->dryRun) {
echo pht(
@ -923,11 +976,13 @@ abstract class PhabricatorStorageManagementWorkflow
if (count($this->patches)) {
throw new Exception(
pht(
'Some patches could not be applied: %s',
'Some patches could not be applied to "%s": %s',
$api->getRef()->getRefKey(),
implode(', ', array_keys($this->patches))));
} else if (!$this->dryRun && !$apply_only) {
echo pht(
"Storage is up to date. Use '%s' for details.",
'Storage is up to date on "%s". Use "%s" for details.',
$api->getRef()->getRefKey(),
'storage status')."\n";
}
break;
@ -955,9 +1010,9 @@ abstract class PhabricatorStorageManagementWorkflow
*
* @return PhabricatorGlobalLock
*/
final protected function lock() {
final protected function lock(PhabricatorStorageManagementAPI $api) {
return PhabricatorGlobalLock::newLock(__CLASS__)
->useSpecificConnection($this->getApi()->getConn(null))
->useSpecificConnection($api->getConn(null))
->lock();
}