Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/config/application/PhabricatorConfigApplication.php b/src/applications/config/application/PhabricatorConfigApplication.php
index dda50a2b73..6b2704b0b4 100644
--- a/src/applications/config/application/PhabricatorConfigApplication.php
+++ b/src/applications/config/application/PhabricatorConfigApplication.php
@@ -1,76 +1,77 @@
<?php
final class PhabricatorConfigApplication extends PhabricatorApplication {
public function getBaseURI() {
return '/config/';
}
public function getIcon() {
return 'fa-sliders';
}
public function isPinnedByDefault(PhabricatorUser $viewer) {
return $viewer->getIsAdmin();
}
public function getTitleGlyph() {
return "\xE2\x98\xBA";
}
public function getApplicationGroup() {
return self::GROUP_ADMIN;
}
public function canUninstall() {
return false;
}
public function getName() {
return 'Config';
}
public function getShortDescription() {
return pht('Configure Phabricator');
}
public function getRoutes() {
return array(
'/config/' => array(
'' => 'PhabricatorConfigListController',
'application/' => 'PhabricatorConfigApplicationController',
'all/' => 'PhabricatorConfigAllController',
'history/' => 'PhabricatorConfigHistoryController',
'edit/(?P<key>[\w\.\-]+)/' => 'PhabricatorConfigEditController',
'group/(?P<key>[^/]+)/' => 'PhabricatorConfigGroupController',
'version/' => 'PhabricatorConfigVersionController',
'database/'.
+ '(?:(?P<ref>[^/]+)/'.
'(?:(?P<database>[^/]+)/'.
'(?:(?P<table>[^/]+)/'.
- '(?:(?:col/(?P<column>[^/]+)|key/(?P<key>[^/]+))/)?)?)?'
+ '(?:(?:col/(?P<column>[^/]+)|key/(?P<key>[^/]+))/)?)?)?)?'
=> 'PhabricatorConfigDatabaseStatusController',
'dbissue/' => 'PhabricatorConfigDatabaseIssueController',
'(?P<verb>ignore|unignore)/(?P<key>[^/]+)/'
=> 'PhabricatorConfigIgnoreController',
'issue/' => array(
'' => 'PhabricatorConfigIssueListController',
'panel/' => 'PhabricatorConfigIssuePanelController',
'(?P<key>[^/]+)/' => 'PhabricatorConfigIssueViewController',
),
'cache/' => array(
'' => 'PhabricatorConfigCacheController',
'purge/' => 'PhabricatorConfigPurgeCacheController',
),
'module/' => array(
'(?P<module>[^/]+)/' => 'PhabricatorConfigModuleController',
),
'cluster/' => array(
'databases/' => 'PhabricatorConfigClusterDatabasesController',
'notifications/' => 'PhabricatorConfigClusterNotificationsController',
'repositories/' => 'PhabricatorConfigClusterRepositoriesController',
),
),
);
}
}
diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseController.php b/src/applications/config/controller/PhabricatorConfigDatabaseController.php
index c9f2aa6a3c..53af9a6b92 100644
--- a/src/applications/config/controller/PhabricatorConfigDatabaseController.php
+++ b/src/applications/config/controller/PhabricatorConfigDatabaseController.php
@@ -1,63 +1,47 @@
<?php
abstract class PhabricatorConfigDatabaseController
extends PhabricatorConfigController {
- 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;
- }
-
protected function renderIcon($status) {
switch ($status) {
case PhabricatorConfigStorageSchema::STATUS_OKAY:
$icon = 'fa-check-circle green';
break;
case PhabricatorConfigStorageSchema::STATUS_WARN:
$icon = 'fa-exclamation-circle yellow';
break;
case PhabricatorConfigStorageSchema::STATUS_FAIL:
default:
$icon = 'fa-times-circle red';
break;
}
return id(new PHUIIconView())
->setIcon($icon);
}
protected function renderAttr($attr, $issue) {
if ($issue) {
return phutil_tag(
'span',
array(
'style' => 'color: #aa0000;',
),
$attr);
} else {
return $attr;
}
}
protected function renderBoolean($value) {
if ($value === null) {
return '';
} else if ($value === true) {
return pht('Yes');
} else {
return pht('No');
}
}
}
diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php
index f1a91d4d5b..7674d28f51 100644
--- a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php
+++ b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php
@@ -1,171 +1,181 @@
<?php
final class PhabricatorConfigDatabaseIssueController
extends PhabricatorConfigDatabaseController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
- $query = $this->buildSchemaQuery();
+ $query = new PhabricatorConfigSchemaQuery();
- $actual = $query->loadActualSchema();
- $expect = $query->loadExpectedSchema();
- $comp = $query->buildComparisonSchema($expect, $actual);
+ $actual = $query->loadActualSchemata();
+ $expect = $query->loadExpectedSchemata();
+ $comp_servers = $query->buildComparisonSchemata($expect, $actual);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Database Issues'));
$crumbs->setBorder(true);
// Collect all open issues.
$issues = array();
- foreach ($comp->getDatabases() as $database_name => $database) {
- foreach ($database->getLocalIssues() as $issue) {
- $issues[] = array(
- $database_name,
- null,
- null,
- null,
- $issue,
- );
- }
- foreach ($database->getTables() as $table_name => $table) {
- foreach ($table->getLocalIssues() as $issue) {
+ foreach ($comp_servers as $ref_name => $comp) {
+ foreach ($comp->getDatabases() as $database_name => $database) {
+ foreach ($database->getLocalIssues() as $issue) {
$issues[] = array(
+ $ref_name,
$database_name,
- $table_name,
+ null,
null,
null,
$issue,
);
}
- foreach ($table->getColumns() as $column_name => $column) {
- foreach ($column->getLocalIssues() as $issue) {
+ foreach ($database->getTables() as $table_name => $table) {
+ foreach ($table->getLocalIssues() as $issue) {
$issues[] = array(
+ $ref_name,
$database_name,
$table_name,
- 'column',
- $column_name,
+ null,
+ null,
$issue,
);
}
- }
- foreach ($table->getKeys() as $key_name => $key) {
- foreach ($key->getLocalIssues() as $issue) {
- $issues[] = array(
- $database_name,
- $table_name,
- 'key',
- $key_name,
- $issue,
- );
+ foreach ($table->getColumns() as $column_name => $column) {
+ foreach ($column->getLocalIssues() as $issue) {
+ $issues[] = array(
+ $ref_name,
+ $database_name,
+ $table_name,
+ 'column',
+ $column_name,
+ $issue,
+ );
+ }
+ }
+ foreach ($table->getKeys() as $key_name => $key) {
+ foreach ($key->getLocalIssues() as $issue) {
+ $issues[] = array(
+ $ref_name,
+ $database_name,
+ $table_name,
+ 'key',
+ $key_name,
+ $issue,
+ );
+ }
}
}
}
}
-
// Sort all open issues so that the most severe issues appear first.
$order = array();
$counts = array();
foreach ($issues as $key => $issue) {
- $const = $issue[4];
+ $const = $issue[5];
$status = PhabricatorConfigStorageSchema::getIssueStatus($const);
$severity = PhabricatorConfigStorageSchema::getStatusSeverity($status);
$order[$key] = sprintf(
'~%d~%s%s%s',
9 - $severity,
- $issue[0],
$issue[1],
- $issue[3]);
+ $issue[2],
+ $issue[4]);
if (empty($counts[$status])) {
$counts[$status] = 0;
}
$counts[$status]++;
}
asort($order);
$issues = array_select_keys($issues, array_keys($order));
// Render the issues.
$rows = array();
foreach ($issues as $issue) {
- $const = $issue[4];
+ $const = $issue[5];
+
+ $uri = $this->getApplicationURI('/database/'.$issue[0].'/'.$issue[1].'/');
$database_link = phutil_tag(
'a',
array(
- 'href' => $this->getApplicationURI('/database/'.$issue[0].'/'),
+ 'href' => $uri,
),
- $issue[0]);
+ $issue[1]);
$rows[] = array(
$this->renderIcon(
PhabricatorConfigStorageSchema::getIssueStatus($const)),
+ $issue[0],
$database_link,
- $issue[1],
$issue[2],
$issue[3],
+ $issue[4],
PhabricatorConfigStorageSchema::getIssueDescription($const),
);
}
$table = id(new AphrontTableView($rows))
->setNoDataString(
pht('No databases have any issues.'))
->setHeaders(
array(
null,
+ pht('Server'),
pht('Database'),
pht('Table'),
pht('Type'),
pht('Column/Key'),
pht('Issue'),
))
->setColumnClasses(
array(
null,
null,
null,
null,
null,
+ null,
'wide',
));
$errors = array();
if (isset($counts[PhabricatorConfigStorageSchema::STATUS_FAIL])) {
$errors[] = pht(
'Detected %s serious issue(s) with the schemata.',
new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_FAIL]));
}
if (isset($counts[PhabricatorConfigStorageSchema::STATUS_WARN])) {
$errors[] = pht(
'Detected %s warning(s) with the schemata.',
new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_WARN]));
}
$title = pht('Database Issues');
$header = id(new PHUIHeaderView())
->setHeader($title)
->setProfileHeader(true);
$nav = $this->buildSideNavView();
$nav->selectFilter('dbissue/');
$content = id(new PhabricatorConfigPageView())
->setHeader($header)
->setContent($table);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setNavigation($nav)
->appendChild($content)
->addClass('white-background');
}
}
diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php
index bdeb254437..d49a546387 100644
--- a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php
+++ b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php
@@ -1,748 +1,851 @@
<?php
final class PhabricatorConfigDatabaseStatusController
extends PhabricatorConfigDatabaseController {
private $database;
private $table;
private $column;
private $key;
+ private $ref;
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$this->database = $request->getURIData('database');
$this->table = $request->getURIData('table');
$this->column = $request->getURIData('column');
$this->key = $request->getURIData('key');
+ $this->ref = $request->getURIData('ref');
- $query = $this->buildSchemaQuery();
-
- $actual = $query->loadActualSchema();
- $expect = $query->loadExpectedSchema();
- $comp = $query->buildComparisonSchema($expect, $actual);
-
- if ($this->column) {
- return $this->renderColumn(
- $comp,
- $expect,
- $actual,
- $this->database,
- $this->table,
- $this->column);
- } else if ($this->key) {
- return $this->renderKey(
- $comp,
- $expect,
- $actual,
- $this->database,
- $this->table,
- $this->key);
- } else if ($this->table) {
- return $this->renderTable(
- $comp,
- $expect,
- $actual,
- $this->database,
- $this->table);
- } else if ($this->database) {
- return $this->renderDatabase(
- $comp,
- $expect,
- $actual,
- $this->database);
- } else {
- return $this->renderServer(
- $comp,
- $expect,
- $actual);
+ $query = new PhabricatorConfigSchemaQuery();
+
+ $actual = $query->loadActualSchemata();
+ $expect = $query->loadExpectedSchemata();
+ $comp = $query->buildComparisonSchemata($expect, $actual);
+
+ if ($this->ref !== null) {
+ $server_actual = idx($actual, $this->ref);
+ if (!$server_actual) {
+ return new Aphront404Response();
+ }
+
+ $server_comparison = $comp[$this->ref];
+ $server_expect = $expect[$this->ref];
+
+ if ($this->column) {
+ return $this->renderColumn(
+ $server_comparison,
+ $server_expect,
+ $server_actual,
+ $this->database,
+ $this->table,
+ $this->column);
+ } else if ($this->key) {
+ return $this->renderKey(
+ $server_comparison,
+ $server_expect,
+ $server_actual,
+ $this->database,
+ $this->table,
+ $this->key);
+ } else if ($this->table) {
+ return $this->renderTable(
+ $server_comparison,
+ $server_expect,
+ $server_actual,
+ $this->database,
+ $this->table);
+ } else if ($this->database) {
+ return $this->renderDatabase(
+ $server_comparison,
+ $server_expect,
+ $server_actual,
+ $this->database);
+ }
}
+
+ return $this->renderServers(
+ $comp,
+ $expect,
+ $actual);
}
private function buildResponse($title, $body) {
$nav = $this->buildSideNavView();
$nav->selectFilter('database/');
if (!$title) {
$title = pht('Database Status');
}
+ $ref = $this->ref;
+ $database = $this->database;
+ $table = $this->table;
+ $column = $this->column;
+ $key = $this->key;
+
+ $links = array();
+ $links[] = array(
+ pht('Database Status'),
+ 'database/',
+ );
+
+ if ($database) {
+ $links[] = array(
+ $database,
+ "database/{$ref}/{$database}/",
+ );
+ }
+
+ if ($table) {
+ $links[] = array(
+ $table,
+ "database/{$ref}/{$database}/{$table}/",
+ );
+ }
+
+ if ($column) {
+ $links[] = array(
+ $column,
+ "database/{$ref}/{$database}/{$table}/col/{$column}/",
+ );
+ }
+
+ if ($key) {
+ $links[] = array(
+ $key,
+ "database/{$ref}/{$database}/{$table}/key/{$key}/",
+ );
+ }
+
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setBorder(true);
- if ($this->database) {
- $crumbs->addTextCrumb(
- pht('Database Status'),
- $this->getApplicationURI('database/'));
- if ($this->table) {
- $crumbs->addTextCrumb(
- $this->database,
- $this->getApplicationURI('database/'.$this->database.'/'));
- if ($this->column || $this->key) {
- $crumbs->addTextCrumb(
- $this->table,
- $this->getApplicationURI(
- 'database/'.$this->database.'/'.$this->table.'/'));
- if ($this->column) {
- $crumbs->addTextCrumb($this->column);
- } else {
- $crumbs->addTextCrumb($this->key);
- }
- } else {
- $crumbs->addTextCrumb($this->table);
- }
+
+ $last_key = last_key($links);
+ foreach ($links as $link_key => $link) {
+ list($name, $href) = $link;
+ if ($link_key == $last_key) {
+ $crumbs->addTextCrumb($name);
} else {
- $crumbs->addTextCrumb($this->database);
+ $crumbs->addTextCrumb($name, $this->getApplicationURI($href));
}
- } else {
- $crumbs->addTextCrumb(pht('Database Status'));
}
$doc_link = PhabricatorEnv::getDoclink('Managing Storage Adjustments');
$header = id(new PHUIHeaderView())
->setHeader($title)
->setProfileHeader(true)
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-book')
->setHref($doc_link)
->setText(pht('Learn More')));
$content = id(new PhabricatorConfigPageView())
->setHeader($header)
->setContent($body);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setNavigation($nav)
->appendChild($content)
->addClass('white-background');
}
- private function renderServer(
- PhabricatorConfigServerSchema $comp,
- PhabricatorConfigServerSchema $expect,
- PhabricatorConfigServerSchema $actual) {
+ private function renderServers(
+ array $comp_servers,
+ array $expect_servers,
+ array $actual_servers) {
$charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
$collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
$rows = array();
- foreach ($comp->getDatabases() as $database_name => $database) {
- $actual_database = $actual->getDatabase($database_name);
- if ($actual_database) {
- $charset = $actual_database->getCharacterSet();
- $collation = $actual_database->getCollation();
- } else {
- $charset = null;
- $collation = null;
- }
+ foreach ($comp_servers as $ref_key => $comp) {
+ $actual = $actual_servers[$ref_key];
+ $expect = $expect_servers[$ref_key];
+ foreach ($comp->getDatabases() as $database_name => $database) {
+ $actual_database = $actual->getDatabase($database_name);
+ if ($actual_database) {
+ $charset = $actual_database->getCharacterSet();
+ $collation = $actual_database->getCollation();
+ } else {
+ $charset = null;
+ $collation = null;
+ }
- $status = $database->getStatus();
- $issues = $database->getIssues();
+ $status = $database->getStatus();
+ $issues = $database->getIssues();
- $rows[] = array(
- $this->renderIcon($status),
- phutil_tag(
- 'a',
+ $uri = $this->getURI(
array(
- 'href' => $this->getApplicationURI(
- '/database/'.$database_name.'/'),
- ),
- $database_name),
- $this->renderAttr($charset, $database->hasIssue($charset_issue)),
- $this->renderAttr($collation, $database->hasIssue($collation_issue)),
- );
+ 'ref' => $ref_key,
+ 'database' => $database_name,
+ ));
+
+ $rows[] = array(
+ $this->renderIcon($status),
+ $ref_key,
+ phutil_tag(
+ 'a',
+ array(
+ 'href' => $uri,
+ ),
+ $database_name),
+ $this->renderAttr($charset, $database->hasIssue($charset_issue)),
+ $this->renderAttr($collation, $database->hasIssue($collation_issue)),
+ );
+ }
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
+ pht('Server'),
pht('Database'),
pht('Charset'),
pht('Collation'),
))
->setColumnClasses(
array(
+ null,
null,
'wide pri',
null,
null,
));
$title = pht('Database Status');
$properties = $this->buildProperties(
array(
),
$comp->getIssues());
return $this->buildResponse($title, array($properties, $table));
}
private function renderDatabase(
PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual,
$database_name) {
$collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
$database = $comp->getDatabase($database_name);
if (!$database) {
return new Aphront404Response();
}
$rows = array();
foreach ($database->getTables() as $table_name => $table) {
$status = $table->getStatus();
+ $uri = $this->getURI(
+ array(
+ 'table' => $table_name,
+ ));
+
$rows[] = array(
$this->renderIcon($status),
phutil_tag(
'a',
array(
- 'href' => $this->getApplicationURI(
- '/database/'.$database_name.'/'.$table_name.'/'),
+ 'href' => $uri,
),
$table_name),
$this->renderAttr(
$table->getCollation(),
$table->hasIssue($collation_issue)),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Table'),
pht('Collation'),
))
->setColumnClasses(
array(
null,
'wide pri',
null,
));
$title = pht('Database: %s', $database_name);
$actual_database = $actual->getDatabase($database_name);
if ($actual_database) {
$actual_charset = $actual_database->getCharacterSet();
$actual_collation = $actual_database->getCollation();
} else {
$actual_charset = null;
$actual_collation = null;
}
$expect_database = $expect->getDatabase($database_name);
if ($expect_database) {
$expect_charset = $expect_database->getCharacterSet();
$expect_collation = $expect_database->getCollation();
} else {
$expect_charset = null;
$expect_collation = null;
}
$properties = $this->buildProperties(
array(
+ array(
+ pht('Server'),
+ $this->ref,
+ ),
array(
pht('Character Set'),
$actual_charset,
),
array(
pht('Expected Character Set'),
$expect_charset,
),
array(
pht('Collation'),
$actual_collation,
),
array(
pht('Expected Collation'),
$expect_collation,
),
),
$database->getIssues());
return $this->buildResponse($title, array($properties, $table));
}
private function renderTable(
PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual,
$database_name,
$table_name) {
$type_issue = PhabricatorConfigStorageSchema::ISSUE_COLUMNTYPE;
$charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
$collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
$nullable_issue = PhabricatorConfigStorageSchema::ISSUE_NULLABLE;
$unique_issue = PhabricatorConfigStorageSchema::ISSUE_UNIQUE;
$columns_issue = PhabricatorConfigStorageSchema::ISSUE_KEYCOLUMNS;
$longkey_issue = PhabricatorConfigStorageSchema::ISSUE_LONGKEY;
$auto_issue = PhabricatorConfigStorageSchema::ISSUE_AUTOINCREMENT;
$database = $comp->getDatabase($database_name);
if (!$database) {
return new Aphront404Response();
}
$table = $database->getTable($table_name);
if (!$table) {
return new Aphront404Response();
}
$actual_database = $actual->getDatabase($database_name);
$actual_table = null;
if ($actual_database) {
$actual_table = $actual_database->getTable($table_name);
}
$expect_database = $expect->getDatabase($database_name);
$expect_table = null;
if ($expect_database) {
$expect_table = $expect_database->getTable($table_name);
}
$rows = array();
foreach ($table->getColumns() as $column_name => $column) {
$expect_column = null;
if ($expect_table) {
$expect_column = $expect_table->getColumn($column_name);
}
$status = $column->getStatus();
$data_type = null;
if ($expect_column) {
$data_type = $expect_column->getDataType();
}
+ $uri = $this->getURI(
+ array(
+ 'column' => $column_name,
+ ));
+
$rows[] = array(
$this->renderIcon($status),
phutil_tag(
'a',
array(
- 'href' => $this->getApplicationURI(
- 'database/'.
- $database_name.'/'.
- $table_name.'/'.
- 'col/'.
- $column_name.'/'),
+ 'href' => $uri,
),
$column_name),
$data_type,
$this->renderAttr(
$column->getColumnType(),
$column->hasIssue($type_issue)),
$this->renderAttr(
$this->renderBoolean($column->getNullable()),
$column->hasIssue($nullable_issue)),
$this->renderAttr(
$this->renderBoolean($column->getAutoIncrement()),
$column->hasIssue($auto_issue)),
$this->renderAttr(
$column->getCharacterSet(),
$column->hasIssue($charset_issue)),
$this->renderAttr(
$column->getCollation(),
$column->hasIssue($collation_issue)),
);
}
$table_view = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Column'),
pht('Data Type'),
pht('Column Type'),
pht('Nullable'),
pht('Autoincrement'),
pht('Character Set'),
pht('Collation'),
))
->setColumnClasses(
array(
null,
'wide pri',
null,
null,
null,
null,
null,
));
$key_rows = array();
foreach ($table->getKeys() as $key_name => $key) {
$expect_key = null;
if ($expect_table) {
$expect_key = $expect_table->getKey($key_name);
}
$status = $key->getStatus();
$size = 0;
foreach ($key->getColumnNames() as $column_spec) {
list($column_name, $prefix) = $key->getKeyColumnAndPrefix($column_spec);
$column = $table->getColumn($column_name);
if (!$column) {
$size = 0;
break;
}
$size += $column->getKeyByteLength($prefix);
}
$size_formatted = null;
if ($size) {
$size_formatted = $this->renderAttr(
$size,
$key->hasIssue($longkey_issue));
}
+ $uri = $this->getURI(
+ array(
+ 'key' => $key_name,
+ ));
+
$key_rows[] = array(
$this->renderIcon($status),
phutil_tag(
'a',
array(
- 'href' => $this->getApplicationURI(
- 'database/'.
- $database_name.'/'.
- $table_name.'/'.
- 'key/'.
- $key_name.'/'),
+ 'href' => $uri,
),
$key_name),
$this->renderAttr(
implode(', ', $key->getColumnNames()),
$key->hasIssue($columns_issue)),
$this->renderAttr(
$this->renderBoolean($key->getUnique()),
$key->hasIssue($unique_issue)),
$size_formatted,
);
}
$keys_view = id(new AphrontTableView($key_rows))
->setHeaders(
array(
null,
pht('Key'),
pht('Columns'),
pht('Unique'),
pht('Size'),
))
->setColumnClasses(
array(
null,
'wide pri',
null,
null,
null,
));
$title = pht('Database: %s.%s', $database_name, $table_name);
if ($actual_table) {
$actual_collation = $actual_table->getCollation();
} else {
$actual_collation = null;
}
if ($expect_table) {
$expect_collation = $expect_table->getCollation();
} else {
$expect_collation = null;
}
$properties = $this->buildProperties(
array(
+ array(
+ pht('Server'),
+ $this->ref,
+ ),
array(
pht('Collation'),
$actual_collation,
),
array(
pht('Expected Collation'),
$expect_collation,
),
),
$table->getIssues());
return $this->buildResponse(
$title, array($properties, $table_view, $keys_view));
}
private function renderColumn(
PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual,
$database_name,
$table_name,
$column_name) {
$database = $comp->getDatabase($database_name);
if (!$database) {
return new Aphront404Response();
}
$table = $database->getTable($table_name);
if (!$table) {
return new Aphront404Response();
}
$column = $table->getColumn($column_name);
if (!$column) {
return new Aphront404Response();
}
$actual_database = $actual->getDatabase($database_name);
$actual_table = null;
$actual_column = null;
if ($actual_database) {
$actual_table = $actual_database->getTable($table_name);
if ($actual_table) {
$actual_column = $actual_table->getColumn($column_name);
}
}
$expect_database = $expect->getDatabase($database_name);
$expect_table = null;
$expect_column = null;
if ($expect_database) {
$expect_table = $expect_database->getTable($table_name);
if ($expect_table) {
$expect_column = $expect_table->getColumn($column_name);
}
}
if ($actual_column) {
$actual_coltype = $actual_column->getColumnType();
$actual_charset = $actual_column->getCharacterSet();
$actual_collation = $actual_column->getCollation();
$actual_nullable = $actual_column->getNullable();
$actual_auto = $actual_column->getAutoIncrement();
} else {
$actual_coltype = null;
$actual_charset = null;
$actual_collation = null;
$actual_nullable = null;
$actual_auto = null;
}
if ($expect_column) {
$data_type = $expect_column->getDataType();
$expect_coltype = $expect_column->getColumnType();
$expect_charset = $expect_column->getCharacterSet();
$expect_collation = $expect_column->getCollation();
$expect_nullable = $expect_column->getNullable();
$expect_auto = $expect_column->getAutoIncrement();
} else {
$data_type = null;
$expect_coltype = null;
$expect_charset = null;
$expect_collation = null;
$expect_nullable = null;
$expect_auto = null;
}
$title = pht(
'Database Status: %s.%s.%s',
$database_name,
$table_name,
$column_name);
$properties = $this->buildProperties(
array(
+ array(
+ pht('Server'),
+ $this->ref,
+ ),
array(
pht('Data Type'),
$data_type,
),
array(
pht('Column Type'),
$actual_coltype,
),
array(
pht('Expected Column Type'),
$expect_coltype,
),
array(
pht('Character Set'),
$actual_charset,
),
array(
pht('Expected Character Set'),
$expect_charset,
),
array(
pht('Collation'),
$actual_collation,
),
array(
pht('Expected Collation'),
$expect_collation,
),
array(
pht('Nullable'),
$this->renderBoolean($actual_nullable),
),
array(
pht('Expected Nullable'),
$this->renderBoolean($expect_nullable),
),
array(
pht('Autoincrement'),
$this->renderBoolean($actual_auto),
),
array(
pht('Expected Autoincrement'),
$this->renderBoolean($expect_auto),
),
),
$column->getIssues());
return $this->buildResponse($title, $properties);
}
private function renderKey(
PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual,
$database_name,
$table_name,
$key_name) {
$database = $comp->getDatabase($database_name);
if (!$database) {
return new Aphront404Response();
}
$table = $database->getTable($table_name);
if (!$table) {
return new Aphront404Response();
}
$key = $table->getKey($key_name);
if (!$key) {
return new Aphront404Response();
}
$actual_database = $actual->getDatabase($database_name);
$actual_table = null;
$actual_key = null;
if ($actual_database) {
$actual_table = $actual_database->getTable($table_name);
if ($actual_table) {
$actual_key = $actual_table->getKey($key_name);
}
}
$expect_database = $expect->getDatabase($database_name);
$expect_table = null;
$expect_key = null;
if ($expect_database) {
$expect_table = $expect_database->getTable($table_name);
if ($expect_table) {
$expect_key = $expect_table->getKey($key_name);
}
}
if ($actual_key) {
$actual_columns = $actual_key->getColumnNames();
$actual_unique = $actual_key->getUnique();
} else {
$actual_columns = array();
$actual_unique = null;
}
if ($expect_key) {
$expect_columns = $expect_key->getColumnNames();
$expect_unique = $expect_key->getUnique();
} else {
$expect_columns = array();
$expect_unique = null;
}
$title = pht(
'Database Status: %s.%s (%s)',
$database_name,
$table_name,
$key_name);
$properties = $this->buildProperties(
array(
+ array(
+ pht('Server'),
+ $this->ref,
+ ),
array(
pht('Unique'),
$this->renderBoolean($actual_unique),
),
array(
pht('Expected Unique'),
$this->renderBoolean($expect_unique),
),
array(
pht('Columns'),
implode(', ', $actual_columns),
),
array(
pht('Expected Columns'),
implode(', ', $expect_columns),
),
),
$key->getIssues());
return $this->buildResponse($title, $properties);
}
private function buildProperties(array $properties, array $issues) {
$view = id(new PHUIPropertyListView())
->setUser($this->getRequest()->getUser());
foreach ($properties as $property) {
list($key, $value) = $property;
$view->addProperty($key, $value);
}
$status_view = new PHUIStatusListView();
if (!$issues) {
$status_view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
->setTarget(pht('No Schema Issues')));
} else {
foreach ($issues as $issue) {
$note = PhabricatorConfigStorageSchema::getIssueDescription($issue);
$status = PhabricatorConfigStorageSchema::getIssueStatus($issue);
switch ($status) {
case PhabricatorConfigStorageSchema::STATUS_WARN:
$icon = PHUIStatusItemView::ICON_WARNING;
$color = 'yellow';
break;
case PhabricatorConfigStorageSchema::STATUS_FAIL:
default:
$icon = PHUIStatusItemView::ICON_REJECT;
$color = 'red';
break;
}
$item = id(new PHUIStatusItemView())
->setTarget(PhabricatorConfigStorageSchema::getIssueName($issue))
->setIcon($icon, $color)
->setNote($note);
$status_view->addItem($item);
}
}
$view->addProperty(pht('Schema Status'), $status_view);
return phutil_tag_div('config-page-property', $view);
}
+ private function getURI(array $properties) {
+ $defaults = array(
+ 'ref' => $this->ref,
+ 'database' => $this->database,
+ 'table' => $this->table,
+ 'column' => $this->column,
+ 'key' => $this->key,
+ );
+
+ $properties = $properties + $defaults;
+ $properties = array_select_keys($properties, array_keys($defaults));
+
+ $parts = array();
+ foreach ($properties as $key => $property) {
+ if (!strlen($property)) {
+ continue;
+ }
+
+ if ($key == 'column') {
+ $parts[] = 'col';
+ } else if ($key == 'key') {
+ $parts[] = 'key';
+ }
+
+ $parts[] = $property;
+ }
+
+ if ($parts) {
+ $parts = implode('/', $parts).'/';
+ } else {
+ $parts = null;
+ }
+
+ return $this->getApplicationURI('/database/'.$parts);
+ }
+
}
diff --git a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php
index ca9b44cc07..0ef5e484b1 100644
--- a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php
+++ b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php
@@ -1,314 +1,347 @@
<?php
final class PhabricatorConfigSchemaQuery extends Phobject {
- private $api;
+ private function getDatabaseNames(PhabricatorDatabaseRef $ref) {
+ $api = $this->getAPI($ref);
+ $patches = PhabricatorSQLPatchList::buildAllPatches();
+ return $api->getDatabaseList(
+ $patches,
+ $only_living = true);
+ }
- public function setAPI(PhabricatorStorageManagementAPI $api) {
- $this->api = $api;
- return $this;
+ private function getAPI(PhabricatorDatabaseRef $ref) {
+ return id(new PhabricatorStorageManagementAPI())
+ ->setUser($ref->getUser())
+ ->setHost($ref->getHost())
+ ->setPort($ref->getPort())
+ ->setNamespace(PhabricatorLiskDAO::getDefaultStorageNamespace())
+ ->setPassword($ref->getPass());
}
- protected function getAPI() {
- if (!$this->api) {
- throw new PhutilInvalidStateException('setAPI');
+ public function loadActualSchemata() {
+ $refs = PhabricatorDatabaseRef::getMasterDatabaseRefs();
+
+ $schemata = array();
+ foreach ($refs as $ref) {
+ $schema = $this->loadActualSchemaForServer($ref);
+ $schemata[$schema->getRef()->getRefKey()] = $schema;
}
- return $this->api;
- }
- protected function getConn() {
- return $this->getAPI()->getConn(null);
+ return $schemata;
}
- private function getDatabaseNames() {
- $api = $this->getAPI();
- $patches = PhabricatorSQLPatchList::buildAllPatches();
- return $api->getDatabaseList(
- $patches,
- $only_living = true);
- }
+ private function loadActualSchemaForServer(PhabricatorDatabaseRef $ref) {
+ $databases = $this->getDatabaseNames($ref);
- public function loadActualSchema() {
- $databases = $this->getDatabaseNames();
+ $conn = $ref->newManagementConnection();
- $conn = $this->getConn();
$tables = queryfx_all(
$conn,
'SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_COLLATION
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA IN (%Ls)',
$databases);
$database_info = queryfx_all(
$conn,
'SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM INFORMATION_SCHEMA.SCHEMATA
WHERE SCHEMA_NAME IN (%Ls)',
$databases);
$database_info = ipull($database_info, null, 'SCHEMA_NAME');
// Find databases which exist, but which the user does not have permission
// to see.
$invisible_databases = array();
foreach ($databases as $database_name) {
if (isset($database_info[$database_name])) {
continue;
}
try {
queryfx($conn, 'SHOW TABLES IN %T', $database_name);
} catch (AphrontAccessDeniedQueryException $ex) {
// This database exists, the user just doesn't have permission to
// see it.
$invisible_databases[] = $database_name;
} catch (AphrontSchemaQueryException $ex) {
// This database is legitimately missing.
}
}
$sql = array();
foreach ($tables as $table) {
$sql[] = qsprintf(
$conn,
'(TABLE_SCHEMA = %s AND TABLE_NAME = %s)',
$table['TABLE_SCHEMA'],
$table['TABLE_NAME']);
}
if ($sql) {
$column_info = queryfx_all(
$conn,
'SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME,
COLLATION_NAME, COLUMN_TYPE, IS_NULLABLE, EXTRA
FROM INFORMATION_SCHEMA.COLUMNS
WHERE (%Q)',
'('.implode(') OR (', $sql).')');
$column_info = igroup($column_info, 'TABLE_SCHEMA');
} else {
$column_info = array();
}
// NOTE: Tables like KEY_COLUMN_USAGE and TABLE_CONSTRAINTS only contain
// primary, unique, and foreign keys, so we can't use them here. We pull
// indexes later on using SHOW INDEXES.
- $server_schema = new PhabricatorConfigServerSchema();
+ $server_schema = id(new PhabricatorConfigServerSchema())
+ ->setRef($ref);
$tables = igroup($tables, 'TABLE_SCHEMA');
foreach ($tables as $database_name => $database_tables) {
$info = $database_info[$database_name];
$database_schema = id(new PhabricatorConfigDatabaseSchema())
->setName($database_name)
->setCharacterSet($info['DEFAULT_CHARACTER_SET_NAME'])
->setCollation($info['DEFAULT_COLLATION_NAME']);
$database_column_info = idx($column_info, $database_name, array());
$database_column_info = igroup($database_column_info, 'TABLE_NAME');
foreach ($database_tables as $table) {
$table_name = $table['TABLE_NAME'];
$table_schema = id(new PhabricatorConfigTableSchema())
->setName($table_name)
->setCollation($table['TABLE_COLLATION']);
$columns = idx($database_column_info, $table_name, array());
foreach ($columns as $column) {
if (strpos($column['EXTRA'], 'auto_increment') === false) {
$auto_increment = false;
} else {
$auto_increment = true;
}
$column_schema = id(new PhabricatorConfigColumnSchema())
->setName($column['COLUMN_NAME'])
->setCharacterSet($column['CHARACTER_SET_NAME'])
->setCollation($column['COLLATION_NAME'])
->setColumnType($column['COLUMN_TYPE'])
->setNullable($column['IS_NULLABLE'] == 'YES')
->setAutoIncrement($auto_increment);
$table_schema->addColumn($column_schema);
}
$key_parts = queryfx_all(
$conn,
'SHOW INDEXES FROM %T.%T',
$database_name,
$table_name);
$keys = igroup($key_parts, 'Key_name');
foreach ($keys as $key_name => $key_pieces) {
$key_pieces = isort($key_pieces, 'Seq_in_index');
$head = head($key_pieces);
// This handles string indexes which index only a prefix of a field.
$column_names = array();
foreach ($key_pieces as $piece) {
$name = $piece['Column_name'];
if ($piece['Sub_part']) {
$name = $name.'('.$piece['Sub_part'].')';
}
$column_names[] = $name;
}
$key_schema = id(new PhabricatorConfigKeySchema())
->setName($key_name)
->setColumnNames($column_names)
->setUnique(!$head['Non_unique'])
->setIndexType($head['Index_type']);
$table_schema->addKey($key_schema);
}
$database_schema->addTable($table_schema);
}
$server_schema->addDatabase($database_schema);
}
foreach ($invisible_databases as $database_name) {
$server_schema->addDatabase(
id(new PhabricatorConfigDatabaseSchema())
->setName($database_name)
->setAccessDenied(true));
}
return $server_schema;
}
- public function loadExpectedSchema() {
- $databases = $this->getDatabaseNames();
- $info = $this->getAPI()->getCharsetInfo();
+ public function loadExpectedSchemata() {
+ $refs = PhabricatorDatabaseRef::getMasterDatabaseRefs();
+
+ $schemata = array();
+ foreach ($refs as $ref) {
+ $schema = $this->loadExpectedSchemaForServer($ref);
+ $schemata[$schema->getRef()->getRefKey()] = $schema;
+ }
+
+ return $schemata;
+ }
+
+ public function loadExpectedSchemaForServer(PhabricatorDatabaseRef $ref) {
+ $databases = $this->getDatabaseNames($ref);
+ $info = $this->getAPI($ref)->getCharsetInfo();
$specs = id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorConfigSchemaSpec')
->execute();
- $server_schema = new PhabricatorConfigServerSchema();
+ $server_schema = id(new PhabricatorConfigServerSchema())
+ ->setRef($ref);
+
foreach ($specs as $spec) {
$spec
->setUTF8Charset(
$info[PhabricatorStorageManagementAPI::CHARSET_DEFAULT])
->setUTF8BinaryCollation(
$info[PhabricatorStorageManagementAPI::COLLATE_TEXT])
->setUTF8SortingCollation(
$info[PhabricatorStorageManagementAPI::COLLATE_SORT])
->setServer($server_schema)
->buildSchemata($server_schema);
}
return $server_schema;
}
- public function buildComparisonSchema(
+ public function buildComparisonSchemata(
+ array $expect_servers,
+ array $actual_servers) {
+
+ $schemata = array();
+ foreach ($actual_servers as $key => $actual_server) {
+ $schemata[$key] = $this->buildComparisonSchemaForServer(
+ $expect_servers[$key],
+ $actual_server);
+ }
+
+ return $schemata;
+ }
+
+ private function buildComparisonSchemaForServer(
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual) {
$comp_server = $actual->newEmptyClone();
$all_databases = $actual->getDatabases() + $expect->getDatabases();
foreach ($all_databases as $database_name => $database_template) {
$actual_database = $actual->getDatabase($database_name);
$expect_database = $expect->getDatabase($database_name);
$issues = $this->compareSchemata($expect_database, $actual_database);
$comp_database = $database_template->newEmptyClone()
->setIssues($issues);
if (!$actual_database) {
$actual_database = $expect_database->newEmptyClone();
}
if (!$expect_database) {
$expect_database = $actual_database->newEmptyClone();
}
$all_tables =
$actual_database->getTables() +
$expect_database->getTables();
foreach ($all_tables as $table_name => $table_template) {
$actual_table = $actual_database->getTable($table_name);
$expect_table = $expect_database->getTable($table_name);
$issues = $this->compareSchemata($expect_table, $actual_table);
$comp_table = $table_template->newEmptyClone()
->setIssues($issues);
if (!$actual_table) {
$actual_table = $expect_table->newEmptyClone();
}
if (!$expect_table) {
$expect_table = $actual_table->newEmptyClone();
}
$all_columns =
$actual_table->getColumns() +
$expect_table->getColumns();
foreach ($all_columns as $column_name => $column_template) {
$actual_column = $actual_table->getColumn($column_name);
$expect_column = $expect_table->getColumn($column_name);
$issues = $this->compareSchemata($expect_column, $actual_column);
$comp_column = $column_template->newEmptyClone()
->setIssues($issues);
$comp_table->addColumn($comp_column);
}
$all_keys =
$actual_table->getKeys() +
$expect_table->getKeys();
foreach ($all_keys as $key_name => $key_template) {
$actual_key = $actual_table->getKey($key_name);
$expect_key = $expect_table->getKey($key_name);
$issues = $this->compareSchemata($expect_key, $actual_key);
$comp_key = $key_template->newEmptyClone()
->setIssues($issues);
$comp_table->addKey($comp_key);
}
$comp_database->addTable($comp_table);
}
$comp_server->addDatabase($comp_database);
}
return $comp_server;
}
private function compareSchemata(
PhabricatorConfigStorageSchema $expect = null,
PhabricatorConfigStorageSchema $actual = null) {
$expect_is_key = ($expect instanceof PhabricatorConfigKeySchema);
$actual_is_key = ($actual instanceof PhabricatorConfigKeySchema);
if ($expect_is_key || $actual_is_key) {
$missing_issue = PhabricatorConfigStorageSchema::ISSUE_MISSINGKEY;
$surplus_issue = PhabricatorConfigStorageSchema::ISSUE_SURPLUSKEY;
} else {
$missing_issue = PhabricatorConfigStorageSchema::ISSUE_MISSING;
$surplus_issue = PhabricatorConfigStorageSchema::ISSUE_SURPLUS;
}
if (!$expect && !$actual) {
throw new Exception(pht('Can not compare two missing schemata!'));
} else if ($expect && !$actual) {
$issues = array($missing_issue);
} else if ($actual && !$expect) {
$issues = array($surplus_issue);
} else {
$issues = $actual->compareTo($expect);
}
return $issues;
}
}
diff --git a/src/applications/config/schema/PhabricatorConfigServerSchema.php b/src/applications/config/schema/PhabricatorConfigServerSchema.php
index b8b21fe919..f067534b23 100644
--- a/src/applications/config/schema/PhabricatorConfigServerSchema.php
+++ b/src/applications/config/schema/PhabricatorConfigServerSchema.php
@@ -1,41 +1,51 @@
<?php
final class PhabricatorConfigServerSchema
extends PhabricatorConfigStorageSchema {
+ private $ref;
private $databases = array();
+ public function setRef(PhabricatorDatabaseRef $ref) {
+ $this->ref = $ref;
+ return $this;
+ }
+
+ public function getRef() {
+ return $this->ref;
+ }
+
public function addDatabase(PhabricatorConfigDatabaseSchema $database) {
$key = $database->getName();
if (isset($this->databases[$key])) {
throw new Exception(
pht('Trying to add duplicate database "%s"!', $key));
}
$this->databases[$key] = $database;
return $this;
}
public function getDatabases() {
return $this->databases;
}
public function getDatabase($key) {
return idx($this->getDatabases(), $key);
}
protected function getSubschemata() {
return $this->getDatabases();
}
protected function compareToSimilarSchema(
PhabricatorConfigStorageSchema $expect) {
return array();
}
public function newEmptyClone() {
$clone = clone $this;
$clone->databases = array();
return $clone;
}
}
diff --git a/src/infrastructure/cluster/PhabricatorDatabaseRef.php b/src/infrastructure/cluster/PhabricatorDatabaseRef.php
index 2cab28104f..9033ba99ef 100644
--- a/src/infrastructure/cluster/PhabricatorDatabaseRef.php
+++ b/src/infrastructure/cluster/PhabricatorDatabaseRef.php
@@ -1,563 +1,574 @@
<?php
final class PhabricatorDatabaseRef
extends Phobject {
const STATUS_OKAY = 'okay';
const STATUS_FAIL = 'fail';
const STATUS_AUTH = 'auth';
const STATUS_REPLICATION_CLIENT = 'replication-client';
const REPLICATION_OKAY = 'okay';
const REPLICATION_MASTER_REPLICA = 'master-replica';
const REPLICATION_REPLICA_NONE = 'replica-none';
const REPLICATION_SLOW = 'replica-slow';
const REPLICATION_NOT_REPLICATING = 'not-replicating';
const KEY_REFS = 'cluster.db.refs';
const KEY_INDIVIDUAL = 'cluster.db.individual';
private $host;
private $port;
private $user;
private $pass;
private $disabled;
private $isMaster;
private $isIndividual;
private $connectionLatency;
private $connectionStatus;
private $connectionMessage;
private $replicaStatus;
private $replicaMessage;
private $replicaDelay;
private $healthRecord;
private $didFailToConnect;
public function setHost($host) {
$this->host = $host;
return $this;
}
public function getHost() {
return $this->host;
}
public function setPort($port) {
$this->port = $port;
return $this;
}
public function getPort() {
return $this->port;
}
public function setUser($user) {
$this->user = $user;
return $this;
}
public function getUser() {
return $this->user;
}
public function setPass(PhutilOpaqueEnvelope $pass) {
$this->pass = $pass;
return $this;
}
public function getPass() {
return $this->pass;
}
public function setIsMaster($is_master) {
$this->isMaster = $is_master;
return $this;
}
public function getIsMaster() {
return $this->isMaster;
}
public function setDisabled($disabled) {
$this->disabled = $disabled;
return $this;
}
public function getDisabled() {
return $this->disabled;
}
public function setConnectionLatency($connection_latency) {
$this->connectionLatency = $connection_latency;
return $this;
}
public function getConnectionLatency() {
return $this->connectionLatency;
}
public function setConnectionStatus($connection_status) {
$this->connectionStatus = $connection_status;
return $this;
}
public function getConnectionStatus() {
if ($this->connectionStatus === null) {
throw new PhutilInvalidStateException('queryAll');
}
return $this->connectionStatus;
}
public function setConnectionMessage($connection_message) {
$this->connectionMessage = $connection_message;
return $this;
}
public function getConnectionMessage() {
return $this->connectionMessage;
}
public function setReplicaStatus($replica_status) {
$this->replicaStatus = $replica_status;
return $this;
}
public function getReplicaStatus() {
return $this->replicaStatus;
}
public function setReplicaMessage($replica_message) {
$this->replicaMessage = $replica_message;
return $this;
}
public function getReplicaMessage() {
return $this->replicaMessage;
}
public function setReplicaDelay($replica_delay) {
$this->replicaDelay = $replica_delay;
return $this;
}
public function getReplicaDelay() {
return $this->replicaDelay;
}
public function setIsIndividual($is_individual) {
$this->isIndividual = $is_individual;
return $this;
}
public function getIsIndividual() {
return $this->isIndividual;
}
+ public function getRefKey() {
+ $host = $this->getHost();
+
+ $port = $this->getPort();
+ if (strlen($port)) {
+ return "{$host}:{$port}";
+ }
+
+ return $host;
+ }
+
public static function getConnectionStatusMap() {
return array(
self::STATUS_OKAY => array(
'icon' => 'fa-exchange',
'color' => 'green',
'label' => pht('Okay'),
),
self::STATUS_FAIL => array(
'icon' => 'fa-times',
'color' => 'red',
'label' => pht('Failed'),
),
self::STATUS_AUTH => array(
'icon' => 'fa-key',
'color' => 'red',
'label' => pht('Invalid Credentials'),
),
self::STATUS_REPLICATION_CLIENT => array(
'icon' => 'fa-eye-slash',
'color' => 'yellow',
'label' => pht('Missing Permission'),
),
);
}
public static function getReplicaStatusMap() {
return array(
self::REPLICATION_OKAY => array(
'icon' => 'fa-download',
'color' => 'green',
'label' => pht('Okay'),
),
self::REPLICATION_MASTER_REPLICA => array(
'icon' => 'fa-database',
'color' => 'red',
'label' => pht('Replicating Master'),
),
self::REPLICATION_REPLICA_NONE => array(
'icon' => 'fa-download',
'color' => 'red',
'label' => pht('Not A Replica'),
),
self::REPLICATION_SLOW => array(
'icon' => 'fa-hourglass',
'color' => 'red',
'label' => pht('Slow Replication'),
),
self::REPLICATION_NOT_REPLICATING => array(
'icon' => 'fa-exclamation-triangle',
'color' => 'red',
'label' => pht('Not Replicating'),
),
);
}
public static function getLiveRefs() {
$cache = PhabricatorCaches::getRequestCache();
$refs = $cache->getKey(self::KEY_REFS);
if (!$refs) {
$refs = self::newRefs();
$cache->setKey(self::KEY_REFS, $refs);
}
return $refs;
}
public static function getLiveIndividualRef() {
$cache = PhabricatorCaches::getRequestCache();
$ref = $cache->getKey(self::KEY_INDIVIDUAL);
if (!$ref) {
$ref = self::newIndividualRef();
$cache->setKey(self::KEY_INDIVIDUAL, $ref);
}
return $ref;
}
public static function newRefs() {
$refs = array();
$default_port = PhabricatorEnv::getEnvConfig('mysql.port');
$default_port = nonempty($default_port, 3306);
$default_user = PhabricatorEnv::getEnvConfig('mysql.user');
$default_pass = PhabricatorEnv::getEnvConfig('mysql.pass');
$default_pass = new PhutilOpaqueEnvelope($default_pass);
$config = PhabricatorEnv::getEnvConfig('cluster.databases');
foreach ($config as $server) {
$host = $server['host'];
$port = idx($server, 'port', $default_port);
$user = idx($server, 'user', $default_user);
$disabled = idx($server, 'disabled', false);
$pass = idx($server, 'pass');
if ($pass) {
$pass = new PhutilOpaqueEnvelope($pass);
} else {
$pass = clone $default_pass;
}
$role = $server['role'];
$ref = id(new self())
->setHost($host)
->setPort($port)
->setUser($user)
->setPass($pass)
->setDisabled($disabled)
->setIsMaster(($role == 'master'));
$refs[] = $ref;
}
return $refs;
}
public static function queryAll() {
$refs = self::newRefs();
foreach ($refs as $ref) {
if ($ref->getDisabled()) {
continue;
}
$conn = $ref->newManagementConnection();
$t_start = microtime(true);
$replica_status = false;
try {
$replica_status = queryfx_one($conn, 'SHOW SLAVE STATUS');
$ref->setConnectionStatus(self::STATUS_OKAY);
} catch (AphrontAccessDeniedQueryException $ex) {
$ref->setConnectionStatus(self::STATUS_REPLICATION_CLIENT);
$ref->setConnectionMessage(
pht(
'No permission to run "SHOW SLAVE STATUS". Grant this user '.
'"REPLICATION CLIENT" permission to allow Phabricator to '.
'monitor replica health.'));
} catch (AphrontInvalidCredentialsQueryException $ex) {
$ref->setConnectionStatus(self::STATUS_AUTH);
$ref->setConnectionMessage($ex->getMessage());
} catch (AphrontQueryException $ex) {
$ref->setConnectionStatus(self::STATUS_FAIL);
$class = get_class($ex);
$message = $ex->getMessage();
$ref->setConnectionMessage(
pht(
'%s: %s',
get_class($ex),
$ex->getMessage()));
}
$t_end = microtime(true);
$ref->setConnectionLatency($t_end - $t_start);
if ($replica_status !== false) {
$is_replica = (bool)$replica_status;
if ($ref->getIsMaster() && $is_replica) {
$ref->setReplicaStatus(self::REPLICATION_MASTER_REPLICA);
$ref->setReplicaMessage(
pht(
'This host has a "master" role, but is replicating data from '.
'another host ("%s")!',
idx($replica_status, 'Master_Host')));
} else if (!$ref->getIsMaster() && !$is_replica) {
$ref->setReplicaStatus(self::REPLICATION_REPLICA_NONE);
$ref->setReplicaMessage(
pht(
'This host has a "replica" role, but is not replicating data '.
'from a master (no output from "SHOW SLAVE STATUS").'));
} else {
$ref->setReplicaStatus(self::REPLICATION_OKAY);
}
if ($is_replica) {
$latency = idx($replica_status, 'Seconds_Behind_Master');
if (!strlen($latency)) {
$ref->setReplicaStatus(self::REPLICATION_NOT_REPLICATING);
} else {
$latency = (int)$latency;
$ref->setReplicaDelay($latency);
if ($latency > 30) {
$ref->setReplicaStatus(self::REPLICATION_SLOW);
$ref->setReplicaMessage(
pht(
'This replica is lagging far behind the master. Data is at '.
'risk!'));
}
}
}
}
}
return $refs;
}
public function newManagementConnection() {
return $this->newConnection(
array(
'retries' => 0,
'timeout' => 2,
));
}
public function newApplicationConnection($database) {
return $this->newConnection(
array(
'database' => $database,
));
}
public function isSevered() {
// If we only have an individual database, never sever our connection to
// it, at least for now. It's possible that using the same severing rules
// might eventually make sense to help alleviate load-related failures,
// but we should wait for all the cluster stuff to stabilize first.
if ($this->getIsIndividual()) {
return false;
}
if ($this->didFailToConnect) {
return true;
}
$record = $this->getHealthRecord();
$is_healthy = $record->getIsHealthy();
if (!$is_healthy) {
return true;
}
return false;
}
public function isReachable(AphrontDatabaseConnection $connection) {
$record = $this->getHealthRecord();
$should_check = $record->getShouldCheck();
if ($this->isSevered() && !$should_check) {
return false;
}
try {
$connection->openConnection();
$reachable = true;
} catch (AphrontSchemaQueryException $ex) {
// We get one of these if the database we're trying to select does not
// exist. In this case, just re-throw the exception. This is expected
// during first-time setup, when databases like "config" will not exist
// yet.
throw $ex;
} catch (Exception $ex) {
$reachable = false;
}
if ($should_check) {
$record->didHealthCheck($reachable);
}
if (!$reachable) {
$this->didFailToConnect = true;
}
return $reachable;
}
public function checkHealth() {
$health = $this->getHealthRecord();
$should_check = $health->getShouldCheck();
if ($should_check) {
// This does an implicit health update.
$connection = $this->newManagementConnection();
$this->isReachable($connection);
}
return $this;
}
public function getHealthRecord() {
if (!$this->healthRecord) {
$this->healthRecord = new PhabricatorDatabaseHealthRecord($this);
}
return $this->healthRecord;
}
public static function getMasterDatabaseRefs() {
$refs = self::getLiveRefs();
if (!$refs) {
return array(self::getLiveIndividualRef());
}
$masters = array();
foreach ($refs as $ref) {
if ($ref->getDisabled()) {
continue;
}
if ($ref->getIsMaster()) {
$masters[] = $ref;
}
}
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();
// TODO: Actually implement this.
return head($masters);
}
public static function newIndividualRef() {
$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())
->setIsIndividual(true)
->setIsMaster(true);
}
public static function getReplicaDatabaseRefs() {
$refs = self::getLiveRefs();
if (!$refs) {
return array();
}
$replicas = array();
foreach ($refs as $ref) {
if ($ref->getDisabled()) {
continue;
}
if ($ref->getIsMaster()) {
continue;
}
$replicas[] = $ref;
}
return $replicas;
}
public static function getReplicaDatabaseRefForDatabase($database) {
$replicas = self::getReplicaDatabaseRefs();
// TODO: Actually implement this.
// TODO: We may have multiple replicas to choose from, and could make
// more of an effort to pick the "best" one here instead of always
// picking the first one. Once we've picked one, we should try to use
// the same replica for the rest of the request, though.
return head($replicas);
}
private function newConnection(array $options) {
// If we believe the database is unhealthy, don't spend as much time
// trying to connect to it, since it's likely to continue to fail and
// hammering it can only make the problem worse.
$record = $this->getHealthRecord();
if ($record->getIsHealthy()) {
$default_retries = 3;
$default_timeout = 10;
} else {
$default_retries = 0;
$default_timeout = 2;
}
$spec = $options + array(
'user' => $this->getUser(),
'pass' => $this->getPass(),
'host' => $this->getHost(),
'port' => $this->getPort(),
'database' => null,
'retries' => $default_retries,
'timeout' => $default_timeout,
);
return PhabricatorEnv::newObjectFromConfig(
'mysql.implementation',
array(
$spec,
));
}
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Dec 2, 1:03 AM (22 h, 10 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
431530
Default Alt Text
(66 KB)

Event Timeline