protected function buildSchemaQuery()
 {
     $ref = PhabricatorDatabaseRef::getMasterDatabaseRef();
     $api = id(new PhabricatorStorageManagementAPI())->setUser($ref->getUser())->setHost($ref->getHost())->setPort($ref->getPort())->setNamespace(PhabricatorLiskDAO::getDefaultStorageNamespace())->setPassword($ref->getPass());
     $query = id(new PhabricatorConfigSchemaQuery())->setAPI($api);
     return $query;
 }
 private function newClusterConnection($application, $database, $mode)
 {
     $master = PhabricatorDatabaseRef::getMasterDatabaseRefForApplication($application);
     if ($master && !$master->isSevered()) {
         $connection = $master->newApplicationConnection($database);
         if ($master->isReachable($connection)) {
             return $connection;
         } else {
             if ($mode == 'w') {
                 $this->raiseImpossibleWrite($database);
             }
             PhabricatorEnv::setReadOnly(true, PhabricatorEnv::READONLY_UNREACHABLE);
         }
     }
     $replica = PhabricatorDatabaseRef::getReplicaDatabaseRefForApplication($application);
     if ($replica) {
         $connection = $replica->newApplicationConnection($database);
         $connection->setReadOnly(true);
         if ($replica->isReachable($connection)) {
             return $connection;
         }
     }
     if (!$master && !$replica) {
         $this->raiseUnconfigured($database);
     }
     $this->raiseUnreachable($database);
 }
 protected function executeChecks()
 {
     $master = PhabricatorDatabaseRef::getMasterDatabaseRef();
     if (!$master) {
         // If we're implicitly in read-only mode during disaster recovery,
         // don't bother with these setup checks.
         return;
     }
     $conn_raw = $master->newManagementConnection();
     try {
         queryfx($conn_raw, 'SELECT 1');
         $database_exception = null;
     } catch (AphrontInvalidCredentialsQueryException $ex) {
         $database_exception = $ex;
     } catch (AphrontConnectionQueryException $ex) {
         $database_exception = $ex;
     }
     if ($database_exception) {
         $issue = PhabricatorSetupIssue::newDatabaseConnectionIssue($database_exception);
         $this->addIssue($issue);
         return;
     }
     $engines = queryfx_all($conn_raw, 'SHOW ENGINES');
     $engines = ipull($engines, 'Support', 'Engine');
     $innodb = idx($engines, 'InnoDB');
     if ($innodb != 'YES' && $innodb != 'DEFAULT') {
         $message = pht("The 'InnoDB' engine is not available in MySQL. Enable InnoDB in " . "your MySQL configuration." . "\n\n" . "(If you aleady created tables, MySQL incorrectly used some other " . "engine to create them. You need to convert them or drop and " . "reinitialize them.)");
         $this->newIssue('mysql.innodb')->setName(pht('MySQL InnoDB Engine Not Available'))->setMessage($message)->setIsFatal(true);
         return;
     }
     $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
     $databases = queryfx_all($conn_raw, 'SHOW DATABASES');
     $databases = ipull($databases, 'Database', 'Database');
     if (empty($databases[$namespace . '_meta_data'])) {
         $message = pht("Run the storage upgrade script to setup Phabricator's database " . "schema.");
         $this->newIssue('storage.upgrade')->setName(pht('Setup MySQL Schema'))->setMessage($message)->setIsFatal(true)->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade'));
     } else {
         $conn_meta = $master->newApplicationConnection($namespace . '_meta_data');
         $applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status');
         $applied = ipull($applied, 'patch', 'patch');
         $all = PhabricatorSQLPatchList::buildAllPatches();
         $diff = array_diff_key($all, $applied);
         if ($diff) {
             $this->newIssue('storage.patch')->setName(pht('Upgrade MySQL Schema'))->setMessage(pht("Run the storage upgrade script to upgrade Phabricator's " . "database schema. Missing patches:<br />%s<br />", phutil_implode_html(phutil_tag('br'), array_keys($diff))))->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade'));
         }
     }
     $host = PhabricatorEnv::getEnvConfig('mysql.host');
     $matches = null;
     if (preg_match('/^([^:]+):(\\d+)$/', $host, $matches)) {
         $host = $matches[1];
         $port = $matches[2];
         $this->newIssue('storage.mysql.hostport')->setName(pht('Deprecated mysql.host Format'))->setSummary(pht('Move port information from `%s` to `%s` in your config.', 'mysql.host', 'mysql.port'))->setMessage(pht('Your `%s` configuration contains a port number, but this usage ' . 'is deprecated. Instead, put the port number in `%s`.', 'mysql.host', 'mysql.port'))->addPhabricatorConfig('mysql.host')->addPhabricatorConfig('mysql.port')->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/config set mysql.host %s', $host))->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/config set mysql.port %s', $port));
     }
 }
 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())->setPort($ref->getPort())->setNamespace(PhabricatorLiskDAO::getDefaultStorageNamespace())->setPassword($ref->getPass());
 }
 private function buildClusterDatabaseStatus()
 {
     $viewer = $this->getViewer();
     $databases = PhabricatorDatabaseRef::queryAll();
     $connection_map = PhabricatorDatabaseRef::getConnectionStatusMap();
     $replica_map = PhabricatorDatabaseRef::getReplicaStatusMap();
     Javelin::initBehavior('phabricator-tooltips');
     $rows = array();
     foreach ($databases as $database) {
         $messages = array();
         if ($database->getIsMaster()) {
             $role_icon = id(new PHUIIconView())->setIcon('fa-database sky')->addSigil('has-tooltip')->setMetadata(array('tip' => pht('Master')));
         } else {
             $role_icon = id(new PHUIIconView())->setIcon('fa-download')->addSigil('has-tooltip')->setMetadata(array('tip' => pht('Replica')));
         }
         if ($database->getDisabled()) {
             $conn_icon = 'fa-times';
             $conn_color = 'grey';
             $conn_label = pht('Disabled');
         } else {
             $status = $database->getConnectionStatus();
             $info = idx($connection_map, $status, array());
             $conn_icon = idx($info, 'icon');
             $conn_color = idx($info, 'color');
             $conn_label = idx($info, 'label');
             if ($status === PhabricatorDatabaseRef::STATUS_OKAY) {
                 $latency = $database->getConnectionLatency();
                 $latency = (int) (1000000 * $latency);
                 $conn_label = pht('%s us', new PhutilNumber($latency));
             }
         }
         $connection = array(id(new PHUIIconView())->setIcon("{$conn_icon} {$conn_color}"), ' ', $conn_label);
         if ($database->getDisabled()) {
             $replica_icon = 'fa-times';
             $replica_color = 'grey';
             $replica_label = pht('Disabled');
         } else {
             $status = $database->getReplicaStatus();
             $info = idx($replica_map, $status, array());
             $replica_icon = idx($info, 'icon');
             $replica_color = idx($info, 'color');
             $replica_label = idx($info, 'label');
             if ($database->getIsMaster()) {
                 if ($status === PhabricatorDatabaseRef::REPLICATION_OKAY) {
                     $replica_icon = 'fa-database';
                 }
             } else {
                 switch ($status) {
                     case PhabricatorDatabaseRef::REPLICATION_OKAY:
                     case PhabricatorDatabaseRef::REPLICATION_SLOW:
                         $delay = $database->getReplicaDelay();
                         if ($delay) {
                             $replica_label = pht('%ss Behind', new PhutilNumber($delay));
                         } else {
                             $replica_label = pht('Up to Date');
                         }
                         break;
                 }
             }
         }
         $replication = array(id(new PHUIIconView())->setIcon("{$replica_icon} {$replica_color}"), ' ', $replica_label);
         $health = $database->getHealthRecord();
         $health_up = $health->getUpEventCount();
         $health_down = $health->getDownEventCount();
         if ($health->getIsHealthy()) {
             $health_icon = id(new PHUIIconView())->setIcon('fa-plus green');
         } else {
             $health_icon = id(new PHUIIconView())->setIcon('fa-times red');
             $messages[] = pht('UNHEALTHY: This database has failed recent health checks. Traffic ' . 'will not be sent to it until it recovers.');
         }
         $health_count = pht('%s / %s', new PhutilNumber($health_up), new PhutilNumber($health_up + $health_down));
         $health_status = array($health_icon, ' ', $health_count);
         $conn_message = $database->getConnectionMessage();
         if ($conn_message) {
             $messages[] = $conn_message;
         }
         $replica_message = $database->getReplicaMessage();
         if ($replica_message) {
             $messages[] = $replica_message;
         }
         $messages = phutil_implode_html(phutil_tag('br'), $messages);
         $rows[] = array($role_icon, $database->getHost(), $database->getPort(), $database->getUser(), $connection, $replication, $health_status, $messages);
     }
     $table = id(new AphrontTableView($rows))->setNoDataString(pht('Phabricator is not configured in cluster mode.'))->setHeaders(array(null, pht('Host'), pht('Port'), pht('User'), pht('Connection'), pht('Replication'), pht('Health'), pht('Messages')))->setColumnClasses(array(null, null, null, null, null, null, null, 'wide'));
     $doc_href = PhabricatorEnv::getDoclink('Cluster: Databases');
     $header = id(new PHUIHeaderView())->setHeader(pht('Cluster Database Status'))->addActionLink(id(new PHUIButtonView())->setIcon('fa-book')->setHref($doc_href)->setTag('a')->setText(pht('Documentation')));
     return id(new PHUIObjectBoxView())->setHeader($header)->setTable($table);
 }
 public function getConn($fragment)
 {
     $database = $this->getDatabaseName($fragment);
     $return =& $this->conns[$this->host][$this->user][$database];
     if (!$return) {
         $return = PhabricatorDatabaseRef::newRawConnection(array('user' => $this->user, 'pass' => $this->password, 'host' => $this->host, 'port' => $this->port, 'database' => $fragment ? $database : null));
     }
     return $return;
 }
Пример #7
0
    $args->printUsageException($ex);
    exit(77);
}
// 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');
$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 ($host && $possible_ref->getHost() == $host) {
            $possible_refs[] = $possible_ref;
            break;
        }
        if ($ref_key && $possible_ref->getRefKey() == $ref_key) {
            $possible_refs[] = $possible_ref;
            break;
        }
    }
    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 {
 /**
  * @phutil-external-symbol class PhabricatorStartup
  */
 public function generateData()
 {
     $should_analyze = self::isQueryAnalyzerRequested();
     $log = PhutilServiceProfiler::getInstance()->getServiceCallLog();
     foreach ($log as $key => $entry) {
         $config = idx($entry, 'config', array());
         unset($log[$key]['config']);
         if (!$should_analyze) {
             $log[$key]['explain'] = array('sev' => 7, 'size' => null, 'reason' => pht('Disabled'));
             // Query analysis is disabled for this request, so don't do any of it.
             continue;
         }
         if ($entry['type'] != 'query') {
             continue;
         }
         // For each SELECT query, go issue an EXPLAIN on it so we can flag stuff
         // causing table scans, etc.
         if (preg_match('/^\\s*SELECT\\b/i', $entry['query'])) {
             $conn = PhabricatorDatabaseRef::newRawConnection($entry['config']);
             try {
                 $explain = queryfx_all($conn, 'EXPLAIN %Q', $entry['query']);
                 $badness = 0;
                 $size = 1;
                 $reason = null;
                 foreach ($explain as $table) {
                     $size *= (int) $table['rows'];
                     switch ($table['type']) {
                         case 'index':
                             $cur_badness = 1;
                             $cur_reason = 'Index';
                             break;
                         case 'const':
                             $cur_badness = 1;
                             $cur_reason = 'Const';
                             break;
                         case 'eq_ref':
                             $cur_badness = 2;
                             $cur_reason = 'EqRef';
                             break;
                         case 'range':
                             $cur_badness = 3;
                             $cur_reason = 'Range';
                             break;
                         case 'ref':
                             $cur_badness = 3;
                             $cur_reason = 'Ref';
                             break;
                         case 'fulltext':
                             $cur_badness = 3;
                             $cur_reason = 'Fulltext';
                             break;
                         case 'ALL':
                             if (preg_match('/Using where/', $table['Extra'])) {
                                 if ($table['rows'] < 256 && !empty($table['possible_keys'])) {
                                     $cur_badness = 2;
                                     $cur_reason = pht('Small Table Scan');
                                 } else {
                                     $cur_badness = 6;
                                     $cur_reason = pht('TABLE SCAN!');
                                 }
                             } else {
                                 $cur_badness = 3;
                                 $cur_reason = pht('Whole Table');
                             }
                             break;
                         default:
                             if (preg_match('/No tables used/i', $table['Extra'])) {
                                 $cur_badness = 1;
                                 $cur_reason = pht('No Tables');
                             } else {
                                 if (preg_match('/Impossible/i', $table['Extra'])) {
                                     $cur_badness = 1;
                                     $cur_reason = pht('Empty');
                                 } else {
                                     $cur_badness = 4;
                                     $cur_reason = pht("Can't Analyze");
                                 }
                             }
                             break;
                     }
                     if ($cur_badness > $badness) {
                         $badness = $cur_badness;
                         $reason = $cur_reason;
                     }
                 }
                 $log[$key]['explain'] = array('sev' => $badness, 'size' => $size, 'reason' => $reason);
             } catch (Exception $ex) {
                 $log[$key]['explain'] = array('sev' => 5, 'size' => null, 'reason' => $ex->getMessage());
             }
         }
     }
     return array('start' => PhabricatorStartup::getStartTime(), 'end' => microtime(true), 'log' => $log, 'analyzeURI' => (string) $this->getRequestURI()->alter('__analyze__', true), 'didAnalyze' => $should_analyze);
 }
Пример #9
0
    $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.
    $refs[] = PhabricatorDatabaseRef::getMasterDatabaseRef();
    foreach ($refs as $possible_ref) {
        if ($possible_ref->getHost() == $host) {
            $ref = $possible_ref;
            break;
        }
    }
    if (!$ref) {
        throw new PhutilArgumentUsageException(pht('There is no configured database on host "%s". This command can ' . 'only interact with configured databases.', $host));
    }
} else {
    $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())->setUser($default_user)->setHost($default_host)->setPort($default_port)->setPassword($ref->getPass())->setNamespace($args->getArg('namespace'));
try {
    queryfx($test_api->getConn(null), 'SELECT 1');
} 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'), pht('Unable to connect to MySQL using the configured credentials. ' . 'You must configure standard credentials before you can upgrade ' . 'storage. Run these commands to set up credentials:'), "  phabricator/ \$ ./bin/config set mysql.host __host__\n" . "  phabricator/ \$ ./bin/config set mysql.user __username__\n" . "  phabricator/ \$ ./bin/config set mysql.pass __password__", pht('These standard credentials are separate from any administrative ' . 'credentials provided to this command with __%s__ or ' . '__%s__, and must be configured correctly before you can proceed.', '--user', '--password'), pht('Raw MySQL Error'), $ex->getMessage());
    echo phutil_console_wrap($message);
    exit(1);
}
 private function executeRefChecks(PhabricatorDatabaseRef $ref)
 {
     $conn_raw = $ref->newManagementConnection();
     $ref_key = $ref->getRefKey();
     $engines = queryfx_all($conn_raw, 'SHOW ENGINES');
     $engines = ipull($engines, 'Support', 'Engine');
     $innodb = idx($engines, 'InnoDB');
     if ($innodb != 'YES' && $innodb != 'DEFAULT') {
         $message = pht('The "InnoDB" engine is not available in MySQL (on host "%s"). ' . 'Enable InnoDB in your MySQL configuration.' . "\n\n" . '(If you aleady created tables, MySQL incorrectly used some other ' . 'engine to create them. You need to convert them or drop and ' . 'reinitialize them.)', $ref_key);
         $this->newIssue('mysql.innodb')->setName(pht('MySQL InnoDB Engine Not Available'))->setMessage($message)->setIsFatal(true);
         return true;
     }
     $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
     $databases = queryfx_all($conn_raw, 'SHOW DATABASES');
     $databases = ipull($databases, 'Database', 'Database');
     if (empty($databases[$namespace . '_meta_data'])) {
         $message = pht('Run the storage upgrade script to setup databases (host "%s" has ' . 'not been initialized).', $ref_key);
         $this->newIssue('storage.upgrade')->setName(pht('Setup MySQL Schema'))->setMessage($message)->setIsFatal(true)->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade'));
         return true;
     }
     $conn_meta = $ref->newApplicationConnection($namespace . '_meta_data');
     $applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status');
     $applied = ipull($applied, 'patch', 'patch');
     $all = PhabricatorSQLPatchList::buildAllPatches();
     $diff = array_diff_key($all, $applied);
     if ($diff) {
         $message = pht('Run the storage upgrade script to upgrade databases (host "%s" is ' . 'out of date). Missing patches: %s.', $ref_key, implode(', ', array_keys($diff)));
         $this->newIssue('storage.patch')->setName(pht('Upgrade MySQL Schema'))->setIsFatal(true)->setMessage($message)->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade'));
         return true;
     }
     // NOTE: It's possible that replication is broken but we have not been
     // granted permission to "SHOW SLAVE STATUS" so we can't figure it out.
     // We allow this kind of configuration and survive these checks, trusting
     // that operations knows what they're doing. This issue is shown on the
     // "Database Servers" console.
     switch ($ref->getReplicaStatus()) {
         case PhabricatorDatabaseRef::REPLICATION_MASTER_REPLICA:
             $message = pht('Database host "%s" is configured as a master, but is replicating ' . 'another host. This is dangerous and can mangle or destroy data. ' . 'Only replicas should be replicating. Stop replication on the ' . 'host or reconfigure Phabricator.', $ref->getRefKey());
             $this->newIssue('db.master.replicating')->setName(pht('Replicating Master'))->setIsFatal(true)->setMessage($message);
             return true;
         case PhabricatorDatabaseRef::REPLICATION_REPLICA_NONE:
         case PhabricatorDatabaseRef::REPLICATION_NOT_REPLICATING:
             if (!$ref->getIsMaster()) {
                 $message = pht('Database replica "%s" is listed as a replica, but is not ' . 'currently replicating. You are vulnerable to data loss if ' . 'the master fails.', $ref->getRefKey());
                 // This isn't a fatal because it can normally only put data at risk,
                 // not actually do anything destructive or unrecoverable.
                 $this->newIssue('db.replica.not-replicating')->setName(pht('Nonreplicating Replica'))->setMessage($message);
             }
             break;
     }
     // If we have more than one master, we require that the cluster database
     // configuration written to each database node is exactly the same as the
     // one we are running with.
     $masters = PhabricatorDatabaseRef::getAllMasterDatabaseRefs();
     if (count($masters) > 1) {
         $state_actual = queryfx_one($conn_meta, 'SELECT stateValue FROM %T WHERE stateKey = %s', PhabricatorStorageManagementAPI::TABLE_HOSTSTATE, 'cluster.databases');
         if ($state_actual) {
             $state_actual = $state_actual['stateValue'];
         }
         $state_expect = $ref->getPartitionStateForCommit();
         if ($state_expect !== $state_actual) {
             $message = pht('Database host "%s" has a configured cluster state which disagrees ' . 'with the state on this host ("%s"). Run `bin/storage partition` ' . 'to commit local state to the cluster. This host may have started ' . 'with an out-of-date configuration.', $ref->getRefKey(), php_uname('n'));
             $this->newIssue('db.state.desync')->setName(pht('Cluster Configuration Out of Sync'))->setMessage($message)->setIsFatal(true);
             return true;
         }
     }
 }
Пример #11
0
 private static function buildConfigurationSourceStack($config_optional)
 {
     self::dropConfigCache();
     $stack = new PhabricatorConfigStackSource();
     self::$sourceStack = $stack;
     $default_source = id(new PhabricatorConfigDefaultSource())->setName(pht('Global Default'));
     $stack->pushSource($default_source);
     $env = self::getSelectedEnvironmentName();
     if ($env) {
         $stack->pushSource(id(new PhabricatorConfigFileSource($env))->setName(pht("File '%s'", $env)));
     }
     $stack->pushSource(id(new PhabricatorConfigLocalSource())->setName(pht('Local Config')));
     // If the install overrides the database adapter, we might need to load
     // the database adapter class before we can push on the database config.
     // This config is locked and can't be edited from the web UI anyway.
     foreach (self::getEnvConfig('load-libraries') as $library) {
         phutil_load_library($library);
     }
     // If custom libraries specify config options, they won't get default
     // values as the Default source has already been loaded, so we get it to
     // pull in all options from non-phabricator libraries now they are loaded.
     $default_source->loadExternalOptions();
     // If this install has site config sources, load them now.
     $site_sources = id(new PhutilClassMapQuery())->setAncestorClass('PhabricatorConfigSiteSource')->setSortMethod('getPriority')->execute();
     foreach ($site_sources as $site_source) {
         $stack->pushSource($site_source);
     }
     $master = PhabricatorDatabaseRef::getMasterDatabaseRef();
     if (!$master) {
         self::setReadOnly(true, self::READONLY_MASTERLESS);
     } else {
         if ($master->isSevered()) {
             $master->checkHealth();
             if ($master->isSevered()) {
                 self::setReadOnly(true, self::READONLY_SEVERED);
             }
         }
     }
     try {
         $stack->pushSource(id(new PhabricatorConfigDatabaseSource('default'))->setName(pht('Database')));
     } catch (AphrontSchemaQueryException $exception) {
         // If the database is not available, just skip this configuration
         // source. This happens during `bin/storage upgrade`, `bin/conf` before
         // schema setup, etc.
     } catch (AphrontConnectionQueryException $ex) {
         if (!$config_optional) {
             throw $ex;
         }
     } catch (AphrontInvalidCredentialsQueryException $ex) {
         if (!$config_optional) {
             throw $ex;
         }
     }
 }
 private function executeRefChecks(PhabricatorDatabaseRef $ref)
 {
     $max_allowed_packet = $ref->loadRawMySQLConfigValue('max_allowed_packet');
     $host_name = $ref->getRefKey();
     // This primarily supports setting the filesize limit for MySQL to 8MB,
     // which may produce a >16MB packet after escaping.
     $recommended_minimum = 32 * 1024 * 1024;
     if ($max_allowed_packet < $recommended_minimum) {
         $message = pht('On host "%s", MySQL is configured with a small "%s" (%d), which ' . 'may cause some large writes to fail. The recommended minimum value ' . 'for this setting is "%d".', $host_name, 'max_allowed_packet', $max_allowed_packet, $recommended_minimum);
         $this->newIssue('mysql.max_allowed_packet')->setName(pht('Small MySQL "%s"', 'max_allowed_packet'))->setMessage($message)->setDatabaseRef($ref)->addMySQLConfig('max_allowed_packet');
     }
     $modes = $ref->loadRawMySQLConfigValue('sql_mode');
     $modes = explode(',', $modes);
     if (!in_array('STRICT_ALL_TABLES', $modes)) {
         $summary = pht('MySQL is not in strict mode (on host "%s"), but using strict mode ' . 'is strongly encouraged.', $host_name);
         $message = pht("On database host \"%s\", the global %s is not set to %s. " . "It is strongly encouraged that you enable this mode when running " . "Phabricator.\n\n" . "By default MySQL will silently ignore some types of errors, which " . "can cause data loss and raise security concerns. Enabling strict " . "mode makes MySQL raise an explicit error instead, and prevents this " . "entire class of problems from doing any damage.\n\n" . "You can find more information about this mode (and how to configure " . "it) in the MySQL manual. Usually, it is sufficient to add this to " . "your %s file (in the %s section) and then restart %s:\n\n" . "%s\n" . "(Note that if you run other applications against the same database, " . "they may not work in strict mode. Be careful about enabling it in " . "these cases.)", $host_name, phutil_tag('tt', array(), 'sql_mode'), phutil_tag('tt', array(), 'STRICT_ALL_TABLES'), phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'sql_mode=STRICT_ALL_TABLES'));
         $this->newIssue('mysql.mode')->setName(pht('MySQL %s Mode Not Set', 'STRICT_ALL_TABLES'))->setSummary($summary)->setMessage($message)->setDatabaseRef($ref)->addMySQLConfig('sql_mode');
     }
     if (in_array('ONLY_FULL_GROUP_BY', $modes)) {
         $summary = pht('MySQL is in ONLY_FULL_GROUP_BY mode (on host "%s"), but using this ' . 'mode is strongly discouraged.', $host_name);
         $message = pht("On database host \"%s\", the global %s is set to %s. " . "It is strongly encouraged that you disable this mode when running " . "Phabricator.\n\n" . "With %s enabled, MySQL rejects queries for which the select list " . "or (as of MySQL 5.0.23) %s list refer to nonaggregated columns " . "that are not named in the %s clause. More importantly, Phabricator " . "does not work properly with this mode enabled.\n\n" . "You can find more information about this mode (and how to configure " . "it) in the MySQL manual. Usually, it is sufficient to change the %s " . "in your %s file (in the %s section) and then restart %s:\n\n" . "%s\n" . "(Note that if you run other applications against the same database, " . "they may not work with %s. Be careful about enabling " . "it in these cases and consider migrating Phabricator to a different " . "database.)", $host_name, 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(), 'HAVING'), phutil_tag('tt', array(), 'GROUP BY'), phutil_tag('tt', array(), 'sql_mode'), phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'sql_mode=STRICT_ALL_TABLES'), phutil_tag('tt', array(), 'ONLY_FULL_GROUP_BY'));
         $this->newIssue('mysql.mode')->setName(pht('MySQL %s Mode Set', 'ONLY_FULL_GROUP_BY'))->setSummary($summary)->setMessage($message)->setDatabaseRef($ref)->addMySQLConfig('sql_mode');
     }
     $stopword_file = $ref->loadRawMySQLConfigValue('ft_stopword_file');
     if ($this->shouldUseMySQLSearchEngine()) {
         if ($stopword_file === null) {
             $summary = pht('Your version of MySQL (on database host "%s") does not support ' . 'configuration of a stopword file. You will not be able to find ' . 'search results for common words.', $host_name);
             $message = pht("Database host \"%s\" does not support the %s option. You will not " . "be able to find search results for common words. You can gain " . "access to this option by upgrading MySQL to a more recent " . "version.\n\n" . "You can ignore this warning if you plan to configure ElasticSearch " . "later, or aren't concerned about searching for common words.", $host_name, phutil_tag('tt', array(), 'ft_stopword_file'));
             $this->newIssue('mysql.ft_stopword_file')->setName(pht('MySQL %s Not Supported', 'ft_stopword_file'))->setSummary($summary)->setMessage($message)->setDatabaseRef($ref)->addMySQLConfig('ft_stopword_file');
         } else {
             if ($stopword_file == '(built-in)') {
                 $root = dirname(phutil_get_library_root('phabricator'));
                 $stopword_path = $root . '/resources/sql/stopwords.txt';
                 $stopword_path = Filesystem::resolvePath($stopword_path);
                 $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
                 $summary = pht('MySQL (on host "%s") is using a default stopword file, which ' . 'will prevent searching for many common words.', $host_name);
                 $message = pht("Database host \"%s\" is using the builtin stopword file for " . "building search indexes. This can make Phabricator's search " . "feature less useful.\n\n" . "Stopwords are common words which are not indexed and thus can not " . "be searched for. The default stopword file has about 500 words, " . "including various words which you are likely to wish to search " . "for, such as 'various', 'likely', 'wish', and 'zero'.\n\n" . "To make search more useful, you can use an alternate stopword " . "file with fewer words. Alternatively, if you aren't concerned " . "about searching for common words, you can ignore this warning. " . "If you later plan to configure ElasticSearch, you can also ignore " . "this warning: this stopword file only affects MySQL fulltext " . "indexes.\n\n" . "To choose a different stopword file, add this to your %s file " . "(in the %s section) and then restart %s:\n\n" . "%s\n" . "(You can also use a different file if you prefer. The file " . "suggested above has about 50 of the most common English words.)\n\n" . "Finally, run this command to rebuild indexes using the new " . "rules:\n\n" . "%s", $host_name, phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'ft_stopword_file=' . $stopword_path), phutil_tag('pre', array(), "mysql> REPAIR TABLE {$namespace}_search.search_documentfield;"));
                 $this->newIssue('mysql.ft_stopword_file')->setName(pht('MySQL is Using Default Stopword File'))->setSummary($summary)->setMessage($message)->setDatabaseRef($ref)->addMySQLConfig('ft_stopword_file');
             }
         }
     }
     $min_len = $ref->loadRawMySQLConfigValue('ft_min_word_len');
     if ($min_len >= 4) {
         if ($this->shouldUseMySQLSearchEngine()) {
             $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
             $summary = pht('MySQL is configured (on host "%s") to only index words with at ' . 'least %d characters.', $host_name, $min_len);
             $message = pht("Database host \"%s\" is configured to use the default minimum word " . "length when building search indexes, which is 4. This means words " . "which are only 3 characters long will not be indexed and can not " . "be searched for.\n\n" . "For example, you will not be able to find search results for words " . "like 'SMS', 'web', or 'DOS'.\n\n" . "You can change this setting to 3 to allow these words to be " . "indexed. Alternatively, you can ignore this warning if you are " . "not concerned about searching for 3-letter words. If you later " . "plan to configure ElasticSearch, you can also ignore this warning: " . "only MySQL fulltext search is affected.\n\n" . "To reduce the minimum word length to 3, add this to your %s file " . "(in the %s section) and then restart %s:\n\n" . "%s\n" . "Finally, run this command to rebuild indexes using the new " . "rules:\n\n" . "%s", $host_name, phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'ft_min_word_len=3'), phutil_tag('pre', array(), "mysql> REPAIR TABLE {$namespace}_search.search_documentfield;"));
             $this->newIssue('mysql.ft_min_word_len')->setName(pht('MySQL is Using Default Minimum Word Length'))->setSummary($summary)->setMessage($message)->setDatabaseRef($ref)->addMySQLConfig('ft_min_word_len');
         }
     }
     $bool_syntax = $ref->loadRawMySQLConfigValue('ft_boolean_syntax');
     if ($bool_syntax != ' |-><()~*:""&^') {
         if ($this->shouldUseMySQLSearchEngine()) {
             $summary = pht('MySQL (on host "%s") is configured to search on fulltext indexes ' . 'using "OR" by default. Using "AND" is usually the desired ' . 'behaviour.', $host_name);
             $message = pht("Database host \"%s\" is configured to use the default Boolean " . "search syntax when using fulltext indexes. This means searching " . "for 'search words' will yield the query 'search OR words' " . "instead of the desired 'search AND words'.\n\n" . "This might produce unexpected search results. \n\n" . "You can change this setting to a more sensible default. " . "Alternatively, you can ignore this warning if " . "using 'OR' is the desired behaviour. If you later plan " . "to configure ElasticSearch, you can also ignore this warning: " . "only MySQL fulltext search is affected.\n\n" . "To change this setting, add this to your %s file " . "(in the %s section) and then restart %s:\n\n" . "%s\n", $host_name, phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'ft_boolean_syntax=\' |-><()~*:""&^\''));
             $this->newIssue('mysql.ft_boolean_syntax')->setName(pht('MySQL is Using the Default Boolean Syntax'))->setSummary($summary)->setMessage($message)->setDatabaseRef($ref)->addMySQLConfig('ft_boolean_syntax');
         }
     }
     $innodb_pool = $ref->loadRawMySQLConfigValue('innodb_buffer_pool_size');
     $innodb_bytes = phutil_parse_bytes($innodb_pool);
     $innodb_readable = phutil_format_bytes($innodb_bytes);
     // This is arbitrary and just trying to detect values that the user
     // probably didn't set themselves. The Mac OS X default is 128MB and
     // 40% of an AWS EC2 Micro instance is 245MB, so keeping it somewhere
     // between those two values seems like a reasonable approximation.
     $minimum_readable = '225MB';
     $minimum_bytes = phutil_parse_bytes($minimum_readable);
     if ($innodb_bytes < $minimum_bytes) {
         $summary = pht('MySQL (on host "%s") is configured with a very small ' . 'innodb_buffer_pool_size, which may impact performance.', $host_name);
         $message = pht("Database host \"%s\" is configured with a very small %s (%s). " . "This may cause poor database performance and lock exhaustion.\n\n" . "There are no hard-and-fast rules to setting an appropriate value, " . "but a reasonable starting point for a standard install is something " . "like 40%% of the total memory on the machine. For example, if you " . "have 4GB of RAM on the machine you have installed Phabricator on, " . "you might set this value to %s.\n\n" . "You can read more about this option in the MySQL documentation to " . "help you make a decision about how to configure it for your use " . "case. There are no concerns specific to Phabricator which make it " . "different from normal workloads with respect to this setting.\n\n" . "To adjust the setting, add something like this to your %s file (in " . "the %s section), replacing %s with an appropriate value for your " . "host and use case. Then restart %s:\n\n" . "%s\n" . "If you're satisfied with the current setting, you can safely " . "ignore this setup warning.", $host_name, phutil_tag('tt', array(), 'innodb_buffer_pool_size'), phutil_tag('tt', array(), $innodb_readable), phutil_tag('tt', array(), '1600M'), phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), '1600M'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'innodb_buffer_pool_size=1600M'));
         $this->newIssue('mysql.innodb_buffer_pool_size')->setName(pht('MySQL May Run Slowly'))->setSummary($summary)->setMessage($message)->setDatabaseRef($ref)->addMySQLConfig('innodb_buffer_pool_size');
     }
     $conn = $ref->newManagementConnection();
     $ok = PhabricatorStorageManagementAPI::isCharacterSetAvailableOnConnection('utf8mb4', $conn);
     if (!$ok) {
         $summary = pht('You are using an old version of MySQL (on host "%s"), and should ' . 'upgrade.', $host_name);
         $message = pht('You are using an old version of MySQL (on host "%s") which has poor ' . 'unicode support (it does not support the "utf8mb4" collation set). ' . 'You will encounter limitations when working with some unicode data.' . "\n\n" . 'We strongly recommend you upgrade to MySQL 5.5 or newer.', $host_name);
         $this->newIssue('mysql.utf8mb4')->setName(pht('Old MySQL Version'))->setSummary($summary)->setDatabaseRef($ref)->setMessage($message);
     }
     $info = queryfx_one($conn, 'SELECT UNIX_TIMESTAMP() epoch');
     $epoch = (int) $info['epoch'];
     $local = PhabricatorTime::getNow();
     $delta = (int) abs($local - $epoch);
     if ($delta > 60) {
         $this->newIssue('mysql.clock')->setName(pht('Major Web/Database Clock Skew'))->setSummary(pht('This web host ("%s") is set to a very different time than a ' . 'database host "%s".', php_uname('n'), $host_name))->setMessage(pht('A database host ("%s") and this web host ("%s") disagree on the ' . 'current time by more than 60 seconds (absolute skew is %s ' . 'seconds). Check that the current time is set correctly ' . 'everywhere.', $host_name, php_uname('n'), new PhutilNumber($delta)));
     }
 }
Пример #13
0
 private static function buildConfigurationSourceStack($config_optional)
 {
     self::dropConfigCache();
     $stack = new PhabricatorConfigStackSource();
     self::$sourceStack = $stack;
     $default_source = id(new PhabricatorConfigDefaultSource())->setName(pht('Global Default'));
     $stack->pushSource($default_source);
     $env = self::getSelectedEnvironmentName();
     if ($env) {
         $stack->pushSource(id(new PhabricatorConfigFileSource($env))->setName(pht("File '%s'", $env)));
     }
     $stack->pushSource(id(new PhabricatorConfigLocalSource())->setName(pht('Local Config')));
     // If the install overrides the database adapter, we might need to load
     // the database adapter class before we can push on the database config.
     // This config is locked and can't be edited from the web UI anyway.
     foreach (self::getEnvConfig('load-libraries') as $library) {
         phutil_load_library($library);
     }
     // Drop any class map caches, since they will have generated without
     // any classes from libraries. Without this, preflight setup checks can
     // cause generation of a setup check cache that omits checks defined in
     // libraries, for example.
     PhutilClassMapQuery::deleteCaches();
     // If custom libraries specify config options, they won't get default
     // values as the Default source has already been loaded, so we get it to
     // pull in all options from non-phabricator libraries now they are loaded.
     $default_source->loadExternalOptions();
     // If this install has site config sources, load them now.
     $site_sources = id(new PhutilClassMapQuery())->setAncestorClass('PhabricatorConfigSiteSource')->setSortMethod('getPriority')->execute();
     foreach ($site_sources as $site_source) {
         $stack->pushSource($site_source);
     }
     $masters = PhabricatorDatabaseRef::getMasterDatabaseRefs();
     if (!$masters) {
         self::setReadOnly(true, self::READONLY_MASTERLESS);
     } else {
         // If any master is severed, we drop to readonly mode. In theory we
         // could try to continue if we're only missing some applications, but
         // this is very complex and we're unlikely to get it right.
         foreach ($masters as $master) {
             // Give severed masters one last chance to get healthy.
             if ($master->isSevered()) {
                 $master->checkHealth();
             }
             if ($master->isSevered()) {
                 self::setReadOnly(true, self::READONLY_SEVERED);
                 break;
             }
         }
     }
     try {
         $stack->pushSource(id(new PhabricatorConfigDatabaseSource('default'))->setName(pht('Database')));
     } catch (AphrontSchemaQueryException $exception) {
         // If the database is not available, just skip this configuration
         // source. This happens during `bin/storage upgrade`, `bin/conf` before
         // schema setup, etc.
     } catch (AphrontConnectionQueryException $ex) {
         if (!$config_optional) {
             throw $ex;
         }
     } catch (AphrontInvalidCredentialsQueryException $ex) {
         if (!$config_optional) {
             throw $ex;
         }
     }
 }