1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-23 07:12:41 +01:00

Run all minor setup checks on all configured database hosts

Summary:
Fixes T10759. Fixes T11817. This runs all the general sanity/configuration checks on all the active servers.

None of these warnings are very important, and this doesn't change any logical stuff.

Depends on D16904.

Test Plan: Painstakingly triggered each warning, verified that they rendered correctly and that messages told me which host was affected.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10759, T11817

Differential Revision: https://secure.phabricator.com/D16905
This commit is contained in:
epriestley 2016-11-21 09:07:47 -08:00
parent 326d5bf800
commit bcfd515b32
4 changed files with 120 additions and 68 deletions

View file

@ -6,54 +6,49 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
return self::GROUP_MYSQL; return self::GROUP_MYSQL;
} }
public static function loadRawConfigValue($key) { protected function executeChecks() {
$conn_raw = id(new PhabricatorUser())->establishConnection('w'); $refs = PhabricatorDatabaseRef::getActiveDatabaseRefs();
foreach ($refs as $ref) {
try { $this->executeRefChecks($ref);
$value = queryfx_one($conn_raw, 'SELECT @@%Q', $key);
$value = $value['@@'.$key];
} catch (AphrontQueryException $ex) {
$value = null;
} }
return $value;
} }
protected function executeChecks() { private function executeRefChecks(PhabricatorDatabaseRef $ref) {
// TODO: These checks should be executed against every reachable replica? $max_allowed_packet = $ref->loadRawMySQLConfigValue('max_allowed_packet');
// See T10759.
if (PhabricatorEnv::isReadOnly()) {
return;
}
$max_allowed_packet = self::loadRawConfigValue('max_allowed_packet'); $host_name = $ref->getRefKey();
// This primarily supports setting the filesize limit for MySQL to 8MB, // This primarily supports setting the filesize limit for MySQL to 8MB,
// which may produce a >16MB packet after escaping. // which may produce a >16MB packet after escaping.
$recommended_minimum = (32 * 1024 * 1024); $recommended_minimum = (32 * 1024 * 1024);
if ($max_allowed_packet < $recommended_minimum) { if ($max_allowed_packet < $recommended_minimum) {
$message = pht( $message = pht(
"MySQL is configured with a small '%s' (%d), ". 'On host "%s", MySQL is configured with a small "%s" (%d), which '.
"which may cause some large writes to fail.", 'may cause some large writes to fail. The recommended minimum value '.
'for this setting is "%d".',
$host_name,
'max_allowed_packet', 'max_allowed_packet',
$max_allowed_packet); $max_allowed_packet,
$recommended_minimum);
$this->newIssue('mysql.max_allowed_packet') $this->newIssue('mysql.max_allowed_packet')
->setName(pht('Small MySQL "%s"', 'max_allowed_packet')) ->setName(pht('Small MySQL "%s"', 'max_allowed_packet'))
->setMessage($message) ->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('max_allowed_packet'); ->addMySQLConfig('max_allowed_packet');
} }
$modes = self::loadRawConfigValue('sql_mode'); $modes = $ref->loadRawMySQLConfigValue('sql_mode');
$modes = explode(',', $modes); $modes = explode(',', $modes);
if (!in_array('STRICT_ALL_TABLES', $modes)) { if (!in_array('STRICT_ALL_TABLES', $modes)) {
$summary = pht( $summary = pht(
'MySQL is not in strict mode, but using strict mode is strongly '. 'MySQL is not in strict mode (on host "%s"), but using strict mode '.
'encouraged.'); 'is strongly encouraged.',
$host_name);
$message = pht( $message = pht(
"On your MySQL instance, the global %s is not set to %s. ". "On database host \"%s\", the global %s is not set to %s. ".
"It is strongly encouraged that you enable this mode when running ". "It is strongly encouraged that you enable this mode when running ".
"Phabricator.\n\n". "Phabricator.\n\n".
"By default MySQL will silently ignore some types of errors, which ". "By default MySQL will silently ignore some types of errors, which ".
@ -67,6 +62,7 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
"(Note that if you run other applications against the same database, ". "(Note that if you run other applications against the same database, ".
"they may not work in strict mode. Be careful about enabling it in ". "they may not work in strict mode. Be careful about enabling it in ".
"these cases.)", "these cases.)",
$host_name,
phutil_tag('tt', array(), 'sql_mode'), phutil_tag('tt', array(), 'sql_mode'),
phutil_tag('tt', array(), 'STRICT_ALL_TABLES'), phutil_tag('tt', array(), 'STRICT_ALL_TABLES'),
phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), 'my.cnf'),
@ -78,15 +74,18 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
->setName(pht('MySQL %s Mode Not Set', 'STRICT_ALL_TABLES')) ->setName(pht('MySQL %s Mode Not Set', 'STRICT_ALL_TABLES'))
->setSummary($summary) ->setSummary($summary)
->setMessage($message) ->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('sql_mode'); ->addMySQLConfig('sql_mode');
} }
if (in_array('ONLY_FULL_GROUP_BY', $modes)) { if (in_array('ONLY_FULL_GROUP_BY', $modes)) {
$summary = pht( $summary = pht(
'MySQL is in ONLY_FULL_GROUP_BY mode, but using this mode is strongly '. 'MySQL is in ONLY_FULL_GROUP_BY mode (on host "%s"), but using this '.
'discouraged.'); 'mode is strongly discouraged.',
$host_name);
$message = pht( $message = pht(
"On your MySQL instance, the global %s is set to %s. ". "On database host \"%s\", the global %s is set to %s. ".
"It is strongly encouraged that you disable this mode when running ". "It is strongly encouraged that you disable this mode when running ".
"Phabricator.\n\n". "Phabricator.\n\n".
"With %s enabled, MySQL rejects queries for which the select list ". "With %s enabled, MySQL rejects queries for which the select list ".
@ -101,6 +100,7 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
"they may not work with %s. Be careful about enabling ". "they may not work with %s. Be careful about enabling ".
"it in these cases and consider migrating Phabricator to a different ". "it in these cases and consider migrating Phabricator to a different ".
"database.)", "database.)",
$host_name,
phutil_tag('tt', array(), 'sql_mode'), phutil_tag('tt', array(), 'sql_mode'),
phutil_tag('tt', array(), 'ONLY_FULL_GROUP_BY'), phutil_tag('tt', array(), 'ONLY_FULL_GROUP_BY'),
phutil_tag('tt', array(), 'ONLY_FULL_GROUP_BY'), phutil_tag('tt', array(), 'ONLY_FULL_GROUP_BY'),
@ -117,31 +117,34 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
->setName(pht('MySQL %s Mode Set', 'ONLY_FULL_GROUP_BY')) ->setName(pht('MySQL %s Mode Set', 'ONLY_FULL_GROUP_BY'))
->setSummary($summary) ->setSummary($summary)
->setMessage($message) ->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('sql_mode'); ->addMySQLConfig('sql_mode');
} }
$stopword_file = self::loadRawConfigValue('ft_stopword_file'); $stopword_file = $ref->loadRawMySQLConfigValue('ft_stopword_file');
if ($this->shouldUseMySQLSearchEngine()) { if ($this->shouldUseMySQLSearchEngine()) {
if ($stopword_file === null) { if ($stopword_file === null) {
$summary = pht( $summary = pht(
'Your version of MySQL does not support configuration of a '. 'Your version of MySQL (on database host "%s") does not support '.
'stopword file. You will not be able to find search results for '. 'configuration of a stopword file. You will not be able to find '.
'common words.'); 'search results for common words.',
$host_name);
$message = pht( $message = pht(
"Your MySQL instance does not support the %s option. You will not ". "Database host \"%s\" does not support the %s option. You will not ".
"be able to find search results for common words. You can gain ". "be able to find search results for common words. You can gain ".
"access to this option by upgrading MySQL to a more recent ". "access to this option by upgrading MySQL to a more recent ".
"version.\n\n". "version.\n\n".
"You can ignore this warning if you plan to configure ElasticSearch ". "You can ignore this warning if you plan to configure ElasticSearch ".
"later, or aren't concerned about searching for common words.", "later, or aren't concerned about searching for common words.",
$host_name,
phutil_tag('tt', array(), 'ft_stopword_file')); phutil_tag('tt', array(), 'ft_stopword_file'));
$this->newIssue('mysql.ft_stopword_file') $this->newIssue('mysql.ft_stopword_file')
->setName(pht('MySQL %s Not Supported', 'ft_stopword_file')) ->setName(pht('MySQL %s Not Supported', 'ft_stopword_file'))
->setSummary($summary) ->setSummary($summary)
->setMessage($message) ->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('ft_stopword_file'); ->addMySQLConfig('ft_stopword_file');
} else if ($stopword_file == '(built-in)') { } else if ($stopword_file == '(built-in)') {
@ -152,11 +155,12 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace'); $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
$summary = pht( $summary = pht(
'MySQL is using a default stopword file, which will prevent '. 'MySQL (on host "%s") is using a default stopword file, which '.
'searching for many common words.'); 'will prevent searching for many common words.',
$host_name);
$message = pht( $message = pht(
"Your MySQL instance is using the builtin stopword file for ". "Database host \"%s\" is using the builtin stopword file for ".
"building search indexes. This can make Phabricator's search ". "building search indexes. This can make Phabricator's search ".
"feature less useful.\n\n". "feature less useful.\n\n".
"Stopwords are common words which are not indexed and thus can not ". "Stopwords are common words which are not indexed and thus can not ".
@ -177,6 +181,7 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
"Finally, run this command to rebuild indexes using the new ". "Finally, run this command to rebuild indexes using the new ".
"rules:\n\n". "rules:\n\n".
"%s", "%s",
$host_name,
phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), 'my.cnf'),
phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), '[mysqld]'),
phutil_tag('tt', array(), 'mysqld'), phutil_tag('tt', array(), 'mysqld'),
@ -190,22 +195,24 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
->setName(pht('MySQL is Using Default Stopword File')) ->setName(pht('MySQL is Using Default Stopword File'))
->setSummary($summary) ->setSummary($summary)
->setMessage($message) ->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('ft_stopword_file'); ->addMySQLConfig('ft_stopword_file');
} }
} }
$min_len = self::loadRawConfigValue('ft_min_word_len'); $min_len = $ref->loadRawMySQLConfigValue('ft_min_word_len');
if ($min_len >= 4) { if ($min_len >= 4) {
if ($this->shouldUseMySQLSearchEngine()) { if ($this->shouldUseMySQLSearchEngine()) {
$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace'); $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
$summary = pht( $summary = pht(
'MySQL is configured to only index words with at least %d '. 'MySQL is configured (on host "%s") to only index words with at '.
'characters.', 'least %d characters.',
$host_name,
$min_len); $min_len);
$message = pht( $message = pht(
"Your MySQL instance is configured to use the default minimum word ". "Database host \"%s\" is configured to use the default minimum word ".
"length when building search indexes, which is 4. This means words ". "length when building search indexes, which is 4. This means words ".
"which are only 3 characters long will not be indexed and can not ". "which are only 3 characters long will not be indexed and can not ".
"be searched for.\n\n". "be searched for.\n\n".
@ -222,6 +229,7 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
"Finally, run this command to rebuild indexes using the new ". "Finally, run this command to rebuild indexes using the new ".
"rules:\n\n". "rules:\n\n".
"%s", "%s",
$host_name,
phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), 'my.cnf'),
phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), '[mysqld]'),
phutil_tag('tt', array(), 'mysqld'), phutil_tag('tt', array(), 'mysqld'),
@ -235,19 +243,22 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
->setName(pht('MySQL is Using Default Minimum Word Length')) ->setName(pht('MySQL is Using Default Minimum Word Length'))
->setSummary($summary) ->setSummary($summary)
->setMessage($message) ->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('ft_min_word_len'); ->addMySQLConfig('ft_min_word_len');
} }
} }
$bool_syntax = self::loadRawConfigValue('ft_boolean_syntax'); $bool_syntax = $ref->loadRawMySQLConfigValue('ft_boolean_syntax');
if ($bool_syntax != ' |-><()~*:""&^') { if ($bool_syntax != ' |-><()~*:""&^') {
if ($this->shouldUseMySQLSearchEngine()) { if ($this->shouldUseMySQLSearchEngine()) {
$summary = pht( $summary = pht(
'MySQL is configured to search on fulltext indexes using "OR" by '. 'MySQL (on host "%s") is configured to search on fulltext indexes '.
'default. Using "AND" is usually the desired behaviour.'); 'using "OR" by default. Using "AND" is usually the desired '.
'behaviour.',
$host_name);
$message = pht( $message = pht(
"Your MySQL instance is configured to use the default Boolean ". "Database host \"%s\" is configured to use the default Boolean ".
"search syntax when using fulltext indexes. This means searching ". "search syntax when using fulltext indexes. This means searching ".
"for 'search words' will yield the query 'search OR words' ". "for 'search words' will yield the query 'search OR words' ".
"instead of the desired 'search AND words'.\n\n". "instead of the desired 'search AND words'.\n\n".
@ -260,6 +271,7 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
"To change this setting, add this to your %s file ". "To change this setting, add this to your %s file ".
"(in the %s section) and then restart %s:\n\n". "(in the %s section) and then restart %s:\n\n".
"%s\n", "%s\n",
$host_name,
phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), 'my.cnf'),
phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), '[mysqld]'),
phutil_tag('tt', array(), 'mysqld'), phutil_tag('tt', array(), 'mysqld'),
@ -269,11 +281,12 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
->setName(pht('MySQL is Using the Default Boolean Syntax')) ->setName(pht('MySQL is Using the Default Boolean Syntax'))
->setSummary($summary) ->setSummary($summary)
->setMessage($message) ->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('ft_boolean_syntax'); ->addMySQLConfig('ft_boolean_syntax');
} }
} }
$innodb_pool = self::loadRawConfigValue('innodb_buffer_pool_size'); $innodb_pool = $ref->loadRawMySQLConfigValue('innodb_buffer_pool_size');
$innodb_bytes = phutil_parse_bytes($innodb_pool); $innodb_bytes = phutil_parse_bytes($innodb_pool);
$innodb_readable = phutil_format_bytes($innodb_bytes); $innodb_readable = phutil_format_bytes($innodb_bytes);
@ -286,11 +299,12 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
$minimum_bytes = phutil_parse_bytes($minimum_readable); $minimum_bytes = phutil_parse_bytes($minimum_readable);
if ($innodb_bytes < $minimum_bytes) { if ($innodb_bytes < $minimum_bytes) {
$summary = pht( $summary = pht(
'MySQL is configured with a very small innodb_buffer_pool_size, '. 'MySQL (on host "%s") is configured with a very small '.
'which may impact performance.'); 'innodb_buffer_pool_size, which may impact performance.',
$host_name);
$message = pht( $message = pht(
"Your MySQL instance is configured with a very small %s (%s). ". "Database host \"%s\" is configured with a very small %s (%s). ".
"This may cause poor database performance and lock exhaustion.\n\n". "This may cause poor database performance and lock exhaustion.\n\n".
"There are no hard-and-fast rules to setting an appropriate value, ". "There are no hard-and-fast rules to setting an appropriate value, ".
"but a reasonable starting point for a standard install is something ". "but a reasonable starting point for a standard install is something ".
@ -307,6 +321,7 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
"%s\n". "%s\n".
"If you're satisfied with the current setting, you can safely ". "If you're satisfied with the current setting, you can safely ".
"ignore this setup warning.", "ignore this setup warning.",
$host_name,
phutil_tag('tt', array(), 'innodb_buffer_pool_size'), phutil_tag('tt', array(), 'innodb_buffer_pool_size'),
phutil_tag('tt', array(), $innodb_readable), phutil_tag('tt', array(), $innodb_readable),
phutil_tag('tt', array(), '1600M'), phutil_tag('tt', array(), '1600M'),
@ -320,33 +335,38 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
->setName(pht('MySQL May Run Slowly')) ->setName(pht('MySQL May Run Slowly'))
->setSummary($summary) ->setSummary($summary)
->setMessage($message) ->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('innodb_buffer_pool_size'); ->addMySQLConfig('innodb_buffer_pool_size');
} }
$conn_w = id(new PhabricatorUser())->establishConnection('w'); $conn = $ref->newManagementConnection();
$ok = PhabricatorStorageManagementAPI::isCharacterSetAvailableOnConnection( $ok = PhabricatorStorageManagementAPI::isCharacterSetAvailableOnConnection(
'utf8mb4', 'utf8mb4',
$conn_w); $conn);
if (!$ok) { if (!$ok) {
$summary = pht( $summary = pht(
'You are using an old version of MySQL, and should upgrade.'); 'You are using an old version of MySQL (on host "%s"), and should '.
'upgrade.',
$host_name);
$message = pht( $message = pht(
'You are using an old version of MySQL which has poor unicode '. 'You are using an old version of MySQL (on host "%s") which has poor '.
'support (it does not support the "utf8mb4" collation set). You will '. 'unicode support (it does not support the "utf8mb4" collation set). '.
'encounter limitations when working with some unicode data.'. 'You will encounter limitations when working with some unicode data.'.
"\n\n". "\n\n".
'We strongly recommend you upgrade to MySQL 5.5 or newer.'); 'We strongly recommend you upgrade to MySQL 5.5 or newer.',
$host_name);
$this->newIssue('mysql.utf8mb4') $this->newIssue('mysql.utf8mb4')
->setName(pht('Old MySQL Version')) ->setName(pht('Old MySQL Version'))
->setSummary($summary) ->setSummary($summary)
->setDatabaseRef($ref)
->setMessage($message); ->setMessage($message);
} }
$info = queryfx_one( $info = queryfx_one(
$conn_w, $conn,
'SELECT UNIX_TIMESTAMP() epoch'); 'SELECT UNIX_TIMESTAMP() epoch');
$epoch = (int)$info['epoch']; $epoch = (int)$info['epoch'];
@ -357,12 +377,17 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
->setName(pht('Major Web/Database Clock Skew')) ->setName(pht('Major Web/Database Clock Skew'))
->setSummary( ->setSummary(
pht( pht(
'This host is set to a very different time than the database.')) 'This web host ("%s") is set to a very different time than a '.
'database host "%s".',
php_uname('n'),
$host_name))
->setMessage( ->setMessage(
pht( pht(
'The database host and this host ("%s") disagree on the current '. 'A database host ("%s") and this web host ("%s") disagree on the '.
'time by more than 60 seconds (absolute skew is %s seconds). '. 'current time by more than 60 seconds (absolute skew is %s '.
'Check that the current time is set correctly everywhere.', 'seconds). Check that the current time is set correctly '.
'everywhere.',
$host_name,
php_uname('n'), php_uname('n'),
new PhutilNumber($delta))); new PhutilNumber($delta)));
} }

View file

@ -9,6 +9,7 @@ final class PhabricatorSetupIssue extends Phobject {
private $summary; private $summary;
private $shortName; private $shortName;
private $group; private $group;
private $databaseRef;
private $isIgnored = false; private $isIgnored = false;
private $phpExtensions = array(); private $phpExtensions = array();
@ -68,6 +69,15 @@ final class PhabricatorSetupIssue extends Phobject {
return $this->shortName; return $this->shortName;
} }
public function setDatabaseRef(PhabricatorDatabaseRef $database_ref) {
$this->databaseRef = $database_ref;
return $this;
}
public function getDatabaseRef() {
return $this->databaseRef;
}
public function setGroup($group) { public function setGroup($group) {
$this->group = $group; $this->group = $group;
return $this; return $this;

View file

@ -470,15 +470,19 @@ final class PhabricatorSetupIssueView extends AphrontView {
private function renderMySQLConfig(array $config) { private function renderMySQLConfig(array $config) {
$values = array(); $values = array();
foreach ($config as $key) { $issue = $this->getIssue();
$value = PhabricatorMySQLSetupCheck::loadRawConfigValue($key); $ref = $issue->getDatabaseRef();
if ($value === null) { if ($ref) {
$value = phutil_tag( foreach ($config as $key) {
'em', $value = $ref->loadRawMySQLConfigValue($key);
array(), if ($value === null) {
pht('(Not Supported)')); $value = phutil_tag(
'em',
array(),
pht('(Not Supported)'));
}
$values[$key] = $value;
} }
$values[$key] = $value;
} }
$table = $this->renderValueTable($values); $table = $this->renderValueTable($values);

View file

@ -518,6 +518,19 @@ final class PhabricatorDatabaseRef
return isset($this->applicationMap[$database]); return isset($this->applicationMap[$database]);
} }
public function loadRawMySQLConfigValue($key) {
$conn = $this->newManagementConnection();
try {
$value = queryfx_one($conn, 'SELECT @@%Q', $key);
$value = $value['@@'.$key];
} catch (AphrontQueryException $ex) {
$value = null;
}
return $value;
}
public static function getMasterDatabaseRefForApplication($application) { public static function getMasterDatabaseRefForApplication($application) {
$masters = self::getMasterDatabaseRefs(); $masters = self::getMasterDatabaseRefs();