Page MenuHomePhabricator

D17384.id41877.diff
No OneTemporary

D17384.id41877.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2250,12 +2250,16 @@
'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php',
'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php',
'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php',
- 'PhabricatorClusterDatabasesConfigOptionType' => 'infrastructure/cluster/PhabricatorClusterDatabasesConfigOptionType.php',
- 'PhabricatorClusterException' => 'infrastructure/cluster/PhabricatorClusterException.php',
- 'PhabricatorClusterExceptionHandler' => 'infrastructure/cluster/PhabricatorClusterExceptionHandler.php',
- 'PhabricatorClusterImpossibleWriteException' => 'infrastructure/cluster/PhabricatorClusterImpossibleWriteException.php',
- 'PhabricatorClusterImproperWriteException' => 'infrastructure/cluster/PhabricatorClusterImproperWriteException.php',
- 'PhabricatorClusterStrandedException' => 'infrastructure/cluster/PhabricatorClusterStrandedException.php',
+ 'PhabricatorClusterDatabasesConfigOptionType' => 'infrastructure/cluster/config/PhabricatorClusterDatabasesConfigOptionType.php',
+ 'PhabricatorClusterException' => 'infrastructure/cluster/exception/PhabricatorClusterException.php',
+ 'PhabricatorClusterExceptionHandler' => 'infrastructure/cluster/exception/PhabricatorClusterExceptionHandler.php',
+ 'PhabricatorClusterImpossibleWriteException' => 'infrastructure/cluster/exception/PhabricatorClusterImpossibleWriteException.php',
+ 'PhabricatorClusterImproperWriteException' => 'infrastructure/cluster/exception/PhabricatorClusterImproperWriteException.php',
+ 'PhabricatorClusterSearchConfigOptionType' => 'infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php',
+ 'PhabricatorClusterSearchRef' => 'infrastructure/cluster/PhabricatorClusterSearchRef.php',
+ 'PhabricatorClusterServiceHealthRecord' => 'infrastructure/cluster/PhabricatorClusterServiceHealthRecord.php',
+ 'PhabricatorClusterServiceInterface' => 'infrastructure/cluster/PhabricatorClusterServiceInterface.php',
+ 'PhabricatorClusterStrandedException' => 'infrastructure/cluster/exception/PhabricatorClusterStrandedException.php',
'PhabricatorColumnProxyInterface' => 'applications/project/interface/PhabricatorColumnProxyInterface.php',
'PhabricatorColumnsEditField' => 'applications/transactions/editfield/PhabricatorColumnsEditField.php',
'PhabricatorCommentEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php',
@@ -2301,6 +2305,7 @@
'PhabricatorConfigClusterDatabasesController' => 'applications/config/controller/PhabricatorConfigClusterDatabasesController.php',
'PhabricatorConfigClusterNotificationsController' => 'applications/config/controller/PhabricatorConfigClusterNotificationsController.php',
'PhabricatorConfigClusterRepositoriesController' => 'applications/config/controller/PhabricatorConfigClusterRepositoriesController.php',
+ 'PhabricatorConfigClusterSearchController' => 'applications/config/controller/PhabricatorConfigClusterSearchController.php',
'PhabricatorConfigCollectorsModule' => 'applications/config/module/PhabricatorConfigCollectorsModule.php',
'PhabricatorConfigColumnSchema' => 'applications/config/schema/PhabricatorConfigColumnSchema.php',
'PhabricatorConfigConfigPHIDType' => 'applications/config/phid/PhabricatorConfigConfigPHIDType.php',
@@ -2532,7 +2537,6 @@
'PhabricatorDashboardViewController' => 'applications/dashboard/controller/PhabricatorDashboardViewController.php',
'PhabricatorDataCacheSpec' => 'applications/cache/spec/PhabricatorDataCacheSpec.php',
'PhabricatorDataNotAttachedException' => 'infrastructure/storage/lisk/PhabricatorDataNotAttachedException.php',
- 'PhabricatorDatabaseHealthRecord' => 'infrastructure/cluster/PhabricatorDatabaseHealthRecord.php',
'PhabricatorDatabaseRef' => 'infrastructure/cluster/PhabricatorDatabaseRef.php',
'PhabricatorDatabaseRefParser' => 'infrastructure/cluster/PhabricatorDatabaseRefParser.php',
'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php',
@@ -2630,6 +2634,7 @@
'PhabricatorEditorMultipleSetting' => 'applications/settings/setting/PhabricatorEditorMultipleSetting.php',
'PhabricatorEditorSetting' => 'applications/settings/setting/PhabricatorEditorSetting.php',
'PhabricatorElasticFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php',
+ 'PhabricatorElasticSearchServerRef' => 'infrastructure/cluster/PhabricatorElasticSearchServerRef.php',
'PhabricatorElasticSearchSetupCheck' => 'applications/config/check/PhabricatorElasticSearchSetupCheck.php',
'PhabricatorEmailAddressesSettingsPanel' => 'applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php',
'PhabricatorEmailContentSource' => 'applications/metamta/contentsource/PhabricatorEmailContentSource.php',
@@ -3051,6 +3056,7 @@
'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/PhabricatorMySQLFileStorageEngine.php',
'PhabricatorMySQLFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php',
'PhabricatorMySQLSetupCheck' => 'applications/config/check/PhabricatorMySQLSetupCheck.php',
+ 'PhabricatorMysqlSearchServerRef' => 'infrastructure/cluster/PhabricatorMysqlSearchServerRef.php',
'PhabricatorNamedQuery' => 'applications/search/storage/PhabricatorNamedQuery.php',
'PhabricatorNamedQueryQuery' => 'applications/search/query/PhabricatorNamedQueryQuery.php',
'PhabricatorNavigationRemarkupRule' => 'infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php',
@@ -7255,6 +7261,9 @@
'PhabricatorClusterExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorClusterImpossibleWriteException' => 'PhabricatorClusterException',
'PhabricatorClusterImproperWriteException' => 'PhabricatorClusterException',
+ 'PhabricatorClusterSearchConfigOptionType' => 'PhabricatorConfigJSONOptionType',
+ 'PhabricatorClusterSearchRef' => 'Phobject',
+ 'PhabricatorClusterServiceHealthRecord' => 'Phobject',
'PhabricatorClusterStrandedException' => 'PhabricatorClusterException',
'PhabricatorColumnsEditField' => 'PhabricatorPHIDListEditField',
'PhabricatorCommentEditEngineExtension' => 'PhabricatorEditEngineExtension',
@@ -7306,6 +7315,7 @@
'PhabricatorConfigClusterDatabasesController' => 'PhabricatorConfigController',
'PhabricatorConfigClusterNotificationsController' => 'PhabricatorConfigController',
'PhabricatorConfigClusterRepositoriesController' => 'PhabricatorConfigController',
+ 'PhabricatorConfigClusterSearchController' => 'PhabricatorConfigController',
'PhabricatorConfigCollectorsModule' => 'PhabricatorConfigModule',
'PhabricatorConfigColumnSchema' => 'PhabricatorConfigStorageSchema',
'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType',
@@ -7574,7 +7584,6 @@
'PhabricatorDashboardViewController' => 'PhabricatorDashboardProfileController',
'PhabricatorDataCacheSpec' => 'PhabricatorCacheSpec',
'PhabricatorDataNotAttachedException' => 'Exception',
- 'PhabricatorDatabaseHealthRecord' => 'Phobject',
'PhabricatorDatabaseRef' => 'Phobject',
'PhabricatorDatabaseRefParser' => 'Phobject',
'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck',
@@ -7677,6 +7686,7 @@
'PhabricatorEditorMultipleSetting' => 'PhabricatorSelectSetting',
'PhabricatorEditorSetting' => 'PhabricatorStringSetting',
'PhabricatorElasticFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine',
+ 'PhabricatorElasticSearchServerRef' => 'PhabricatorClusterSearchRef',
'PhabricatorElasticSearchSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorEmailAddressesSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorEmailContentSource' => 'PhabricatorContentSource',
@@ -8146,6 +8156,7 @@
'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorMySQLFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine',
'PhabricatorMySQLSetupCheck' => 'PhabricatorSetupCheck',
+ 'PhabricatorMysqlSearchServerRef' => 'PhabricatorClusterSearchRef',
'PhabricatorNamedQuery' => array(
'PhabricatorSearchDAO',
'PhabricatorPolicyInterface',
diff --git a/src/applications/config/application/PhabricatorConfigApplication.php b/src/applications/config/application/PhabricatorConfigApplication.php
--- a/src/applications/config/application/PhabricatorConfigApplication.php
+++ b/src/applications/config/application/PhabricatorConfigApplication.php
@@ -69,6 +69,7 @@
'databases/' => 'PhabricatorConfigClusterDatabasesController',
'notifications/' => 'PhabricatorConfigClusterNotificationsController',
'repositories/' => 'PhabricatorConfigClusterRepositoriesController',
+ 'search/' => 'PhabricatorConfigClusterSearchController',
),
),
);
diff --git a/src/applications/config/check/PhabricatorElasticSearchSetupCheck.php b/src/applications/config/check/PhabricatorElasticSearchSetupCheck.php
--- a/src/applications/config/check/PhabricatorElasticSearchSetupCheck.php
+++ b/src/applications/config/check/PhabricatorElasticSearchSetupCheck.php
@@ -70,8 +70,14 @@
}
protected function shouldUseElasticSearchEngine() {
- $search_engine = PhabricatorFulltextStorageEngine::loadEngine();
- return ($search_engine instanceof PhabricatorElasticFulltextStorageEngine);
+ $engines = PhabricatorFulltextStorageEngine::loadAllEngines();
+ foreach ($engines as $engine) {
+ if ($engine instanceof PhabricatorElasticFulltextStorageEngine
+ && $engine->isEnabled()) {
+ return true;
+ }
+ }
+ return false;
}
}
diff --git a/src/applications/config/controller/PhabricatorConfigClusterSearchController.php b/src/applications/config/controller/PhabricatorConfigClusterSearchController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/config/controller/PhabricatorConfigClusterSearchController.php
@@ -0,0 +1,152 @@
+<?php
+
+final class PhabricatorConfigClusterSearchController
+ extends PhabricatorConfigController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $nav = $this->buildSideNavView();
+ $nav->selectFilter('cluster/search/');
+
+ $title = pht('Cluster Search');
+ $doc_href = PhabricatorEnv::getDoclink('Cluster: Search');
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader($title)
+ ->setProfileHeader(true)
+ ->addActionLink(
+ id(new PHUIButtonView())
+ ->setIcon('fa-book')
+ ->setHref($doc_href)
+ ->setTag('a')
+ ->setText(pht('Documentation')));
+
+ $crumbs = $this
+ ->buildApplicationCrumbs($nav)
+ ->addTextCrumb($title)
+ ->setBorder(true);
+
+ $search_status = $this->buildClusterSearchStatus();
+
+ $content = id(new PhabricatorConfigPageView())
+ ->setHeader($header)
+ ->setContent($search_status);
+
+ return $this->newPage()
+ ->setTitle($title)
+ ->setCrumbs($crumbs)
+ ->setNavigation($nav)
+ ->appendChild($content)
+ ->addClass('white-background');
+ }
+
+ private function buildClusterSearchStatus() {
+ $viewer = $this->getViewer();
+
+ $servers = PhabricatorClusterSearchRef::newRefs();
+ Javelin::initBehavior('phabricator-tooltips');
+ $status_map = PhabricatorDatabaseRef::getConnectionStatusMap();
+
+ $rows = array();
+ foreach ($servers as $server) {
+
+ $reachable = false;
+ try {
+ $engine = $server->getEngine();
+ $reachable = $engine->indexExists();
+ } catch (Exception $ex) {
+ phlog($ex);
+ // ignore
+ }
+ $server->didHealthCheck($reachable);
+
+ try {
+ $status = $server->loadServerStatus();
+ $status = idx($status_map, $status, array());
+ } catch (Exception $ex) {
+ $status['icon'] = 'fa-times';
+ $status['label'] = pht('Connection Error');
+ $status['color'] = 'red';
+ }
+
+ $type_icon = 'fa-search sky';
+ $type_tip = $server->getDisplayName();
+
+ $type_icon = id(new PHUIIconView())
+ ->setIcon($type_icon)
+ ->addSigil('has-tooltip')
+ ->setMetadata(
+ array(
+ 'tip' => $type_tip,
+ ));
+
+
+ $status_view = array(
+ id(new PHUIIconView())->setIcon("{$status['icon']} {$status['color']}"),
+ ' ',
+ $status['label'],
+ );
+
+ $rows[] = array(
+ array($type_icon, ' '.$type_tip),
+ $server->getProtocol(),
+ $server->getHost(),
+ $server->getPort(),
+ $status_view,
+ get_class($engine),
+ $this->checkicon($engine->isEnabled()),
+ $this->checkicon($server->isWritable()),
+ $engine->getEnginePriority(),
+ );
+ }
+
+ $table = id(new AphrontTableView($rows))
+ ->setNoDataString(
+ pht('No search servers are configured.'))
+ ->setHeaders(
+ array(
+ pht('Type'),
+ pht('Protocol'),
+ pht('Host'),
+ pht('Port'),
+ pht('Status'),
+ pht('Engine'),
+ pht('Enabled'),
+ pht('Writable'),
+ pht('Priority'),
+ null,
+ ))
+ ->setColumnClasses(
+ array(
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 'wide',
+ ));
+
+ return $table;
+ }
+
+ private function checkicon($check) {
+ $icon = $check
+ ? 'fa-check green'
+ : 'fa-times red';
+ $label = $check
+ ? pht('Yes')
+ : pht('No');
+ $view = id(new PHUIIconView())
+ ->setIcon($icon)
+ ->addSigil('has-tooltip')
+ ->setMetadata(
+ array(
+ 'tip' => $label,
+ ));
+
+ return $view;
+ }
+}
diff --git a/src/applications/config/controller/PhabricatorConfigController.php b/src/applications/config/controller/PhabricatorConfigController.php
--- a/src/applications/config/controller/PhabricatorConfigController.php
+++ b/src/applications/config/controller/PhabricatorConfigController.php
@@ -42,8 +42,11 @@
pht('Notification Servers'), null, 'fa-bell-o');
$nav->addFilter('cluster/repositories/',
pht('Repository Servers'), null, 'fa-code');
+ $nav->addFilter('cluster/search/',
+ pht('Search Servers'), null, 'fa-search');
$nav->addLabel(pht('Modules'));
+
$modules = PhabricatorConfigModule::getAllModules();
foreach ($modules as $key => $module) {
$nav->addFilter('module/'.$key.'/',
diff --git a/src/applications/config/option/PhabricatorClusterConfigOptions.php b/src/applications/config/option/PhabricatorClusterConfigOptions.php
--- a/src/applications/config/option/PhabricatorClusterConfigOptions.php
+++ b/src/applications/config/option/PhabricatorClusterConfigOptions.php
@@ -38,6 +38,9 @@
$intro_href = PhabricatorEnv::getDoclink('Clustering Introduction');
$intro_name = pht('Clustering Introduction');
+ $search_type = 'custom:PhabricatorClusterSearchConfigOptionType';
+ $search_help = 'TODO';
+
return array(
$this->newOption('cluster.addresses', 'list<string>', array())
->setLocked(true)
@@ -114,6 +117,21 @@
->setSummary(
pht('Configure database read replicas.'))
->setDescription($databases_help),
+ $this->newOption('cluster.search', $search_type, array())
+ ->setLocked(true)
+ ->setSummary(
+ pht('Configure full-text search services.'))
+ ->setDescription($search_help)
+ ->setDefault(
+ array(
+ array(
+ 'type' => 'mysql',
+ 'roles' => array(
+ 'read' => true,
+ 'write' => true,
+ ),
+ ),
+ )),
);
}
diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php
--- a/src/applications/maniphest/query/ManiphestTaskQuery.php
+++ b/src/applications/maniphest/query/ManiphestTaskQuery.php
@@ -506,8 +506,8 @@
$fulltext_query->setParameter('types',
array(ManiphestTaskPHIDType::TYPECONST));
- $engine = PhabricatorFulltextStorageEngine::loadEngine();
- $fulltext_results = $engine->executeSearch($fulltext_query);
+ $fulltext_results = PhabricatorClusterSearchRef::executeSearch(
+ $fulltext_query);
if (empty($fulltext_results)) {
$fulltext_results = array(null);
diff --git a/src/applications/search/config/PhabricatorSearchConfigOptions.php b/src/applications/search/config/PhabricatorSearchConfigOptions.php
--- a/src/applications/search/config/PhabricatorSearchConfigOptions.php
+++ b/src/applications/search/config/PhabricatorSearchConfigOptions.php
@@ -20,7 +20,15 @@
}
public function getOptions() {
+ $servers_type = 'custom:PhabricatorClusterSearchConfigOptionType';
+ $servers_help = 'TODO';
+
return array(
+ $this->newOption('search.servers', $servers_type, array())
+ ->setLocked(true)
+ ->setSummary(
+ pht('Configure full-text search servers.'))
+ ->setDescription($servers_help),
$this->newOption('search.elastic.host', 'string', null)
->setLocked(true)
->setDescription(pht('Elastic Search host.'))
diff --git a/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php
--- a/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php
+++ b/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php
@@ -3,35 +3,35 @@
final class PhabricatorElasticFulltextStorageEngine
extends PhabricatorFulltextStorageEngine {
- private $uri;
+ private $ref;
private $index;
private $timeout;
- public function __construct() {
- $this->uri = PhabricatorEnv::getEnvConfig('search.elastic.host');
- $this->index = PhabricatorEnv::getEnvConfig('search.elastic.namespace');
- }
+ public function setRef(PhabricatorElasticSearchServerRef $ref) {
+ $this->ref = $ref;
+ $this->index = str_replace('/', '', $ref->getPath());
+ $this->version = (int)$ref->getVersion();
- public function getEngineIdentifier() {
- return 'elasticsearch';
- }
+ $this->timestampFieldKey = $this->version < 2
+ ? '_timestamp'
+ : 'lastModified';
- public function getEnginePriority() {
- return 10;
+ $this->textFieldType = $this->version >= 5
+ ? 'text'
+ : 'string';
+ return $this;
}
public function isEnabled() {
- return (bool)$this->uri;
+ return true;
}
- public function setURI($uri) {
- $this->uri = $uri;
- return $this;
+ public function getEngineIdentifier() {
+ return 'elasticsearch';
}
- public function setIndex($index) {
- $this->index = $index;
- return $this;
+ public function getEnginePriority() {
+ return 10;
}
public function setTimeout($timeout) {
@@ -39,8 +39,8 @@
return $this;
}
- public function getURI() {
- return $this->uri;
+ public function getURI($path = '') {
+ return $this->ref->getURI($path);
}
public function getIndex() {
@@ -284,8 +284,18 @@
public function indexExists() {
try {
- return (bool)$this->executeRequest('/_status/', array());
+ if ($this->version >= 5) {
+ $uri = '/_stats/';
+ $res = $this->executeRequest($uri, array());
+ return isset($res['indices']['phabricator']);
+ } else if ($this->version >= 2) {
+ $uri = '';
+ } else {
+ $uri = '/_status/';
+ }
+ return (bool)$this->executeRequest($uri, array());
} catch (HTTPFutureHTTPResponseStatus $e) {
+ phlog($e);
if ($e->getStatusCode() == 404) {
return false;
}
@@ -336,6 +346,7 @@
}
public function indexIsSane() {
+
if (!$this->indexExists()) {
return false;
}
@@ -345,7 +356,8 @@
$actual = array_merge($cur_settings[$this->index],
$cur_mapping[$this->index]);
- return $this->check($actual, $this->getIndexConfiguration());
+ $res = $this->check($actual, $this->getIndexConfiguration());
+ return $res;
}
/**
@@ -410,10 +422,19 @@
$this->executeRequest('/', $data, 'PUT');
}
+ public function didHealthCheck($reachable) {
+ static $cache=null;
+ if ($cache !== null) {
+ return;
+ }
+
+ $cache = $reachable;
+ $this->ref->didHealthCheck($reachable);
+ return $this;
+ }
+
private function executeRequest($path, array $data, $method = 'GET') {
- $uri = new PhutilURI($this->uri);
- $uri->setPath($this->index);
- $uri->appendPath($path);
+ $uri = $this->ref->getURI($path);
$data = json_encode($data);
$future = new HTTPSFuture($uri, $data);
@@ -423,19 +444,30 @@
if ($this->getTimeout()) {
$future->setTimeout($this->getTimeout());
}
- list($body) = $future->resolvex();
+ try {
+ list($body) = $future->resolvex();
+ } catch (HTTPFutureResponseStatus $ex) {
+ if ($ex->isTimeout() || (int)$ex->getStatusCode() > 499) {
+ $this->didHealthCheck(false);
+ }
+ throw $ex;
+ }
if ($method != 'GET') {
return null;
}
try {
- return phutil_json_decode($body);
+ $data = phutil_json_decode($body);
+ $this->didHealthCheck(true);
+ return $data;
} catch (PhutilJSONParserException $ex) {
+ $this->didHealthCheck(false);
throw new PhutilProxyException(
pht('ElasticSearch server returned invalid JSON!'),
$ex);
}
+
}
}
diff --git a/src/applications/search/index/PhabricatorFulltextEngine.php b/src/applications/search/index/PhabricatorFulltextEngine.php
--- a/src/applications/search/index/PhabricatorFulltextEngine.php
+++ b/src/applications/search/index/PhabricatorFulltextEngine.php
@@ -40,8 +40,7 @@
$extension->indexFulltextObject($object, $document);
}
- $storage_engine = PhabricatorFulltextStorageEngine::loadEngine();
- $storage_engine->reindexAbstractDocument($document);
+ PhabricatorClusterSearchRef::reindexAbstractDocument($document);
}
protected function newAbstractDocument($object) {
diff --git a/src/applications/search/query/PhabricatorSearchDocumentQuery.php b/src/applications/search/query/PhabricatorSearchDocumentQuery.php
--- a/src/applications/search/query/PhabricatorSearchDocumentQuery.php
+++ b/src/applications/search/query/PhabricatorSearchDocumentQuery.php
@@ -73,10 +73,7 @@
$query = id(clone($this->savedQuery))
->setParameter('offset', $this->getOffset())
->setParameter('limit', $this->getRawResultLimit());
-
- $engine = PhabricatorFulltextStorageEngine::loadEngine();
-
- return $engine->executeSearch($query);
+ return PhabricatorClusterSearchRef::executeSearch($query);
}
public function getQueryApplicationClass() {
diff --git a/src/infrastructure/cluster/PhabricatorClusterSearchRef.php b/src/infrastructure/cluster/PhabricatorClusterSearchRef.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/cluster/PhabricatorClusterSearchRef.php
@@ -0,0 +1,234 @@
+<?php
+
+abstract class PhabricatorClusterSearchRef
+ extends Phobject {
+
+ const KEY_REFS = 'cluster.search.refs';
+ const KEY_HEALTH = 'cluster.search.health';
+
+ protected $healthRecord;
+ protected $roles = array();
+
+ protected $disabled;
+ protected $host;
+ protected $port;
+ protected $hostRefs;
+
+
+ const STATUS_OKAY = 'okay';
+ const STATUS_FAIL = 'fail';
+
+ public function setDisabled($disabled) {
+ $this->disabled = $disabled;
+ return $this;
+ }
+
+ public function getDisabled() {
+ return $this->disabled;
+ }
+
+ 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'),
+ ),
+ );
+ }
+
+ public function isWritable() {
+ return $this->hasRole('write');
+ }
+
+ public function isReadable() {
+ return $this->hasRole('read');
+ }
+
+ public function hasRole($role) {
+ return isset($this->roles[$role]) && $this->roles[$role] === true;
+ }
+
+ public function setRoles(array $roles) {
+ foreach ($roles as $role => $val) {
+ $this->roles[$role] = $val;
+ }
+ return $this;
+ }
+
+ public function getRoles() {
+ return $this->roles;
+ }
+
+ public function setPort($value) {
+ $this->port = $value;
+ return $this;
+ }
+
+ public function getPort() {
+ return $this->port;
+ }
+
+ public function setHost($value) {
+ $this->host = $value;
+ return $this;
+ }
+
+ public function getHost() {
+ return $this->host;
+ }
+
+ public function setHostRefs(array $refs) {
+ $this->hostRefs = $refs;
+ return $this;
+ }
+
+ /** @return PhabricatorClusterSearchRef */
+ public function getHostForRole($role) {
+ $hosts = $this->getAllHostsForRole($role);
+ $random = array_rand($hosts);
+ return $hosts[$random];
+ }
+
+ public function getAllHostsForRole($role) {
+ $hosts = array();
+ foreach ($this->hostRefs as $host) {
+ if ($host->hasRole($role)) {
+ $hosts[] = $host;
+ }
+ }
+ return $hosts;
+ }
+
+ public function getHealthRecordCacheKey() {
+ $host = $this->getHost();
+ $port = $this->getPort();
+ $key = self::KEY_HEALTH;
+
+ return "{$key}({$host}, {$port})";
+ }
+
+/**
+ * @return PhabricatorClusterServiceHealthRecord
+ */
+ public function getHealthRecord() {
+ if (!$this->healthRecord) {
+ $this->healthRecord = new PhabricatorClusterServiceHealthRecord(
+ $this->getHealthRecordCacheKey());
+ }
+ return $this->healthRecord;
+ }
+
+ public function didHealthCheck($reachable) {
+ $record = $this->getHealthRecord();
+ $should_check = $record->getShouldCheck();
+
+ if ($should_check) {
+ $record->didHealthCheck($reachable);
+ }
+ }
+
+ /** @return PhabricatorClusterSearchRef[] */
+ public static function getLiveServers() {
+ $cache = PhabricatorCaches::getRequestCache();
+
+ $refs = $cache->getKey(self::KEY_REFS);
+ if (!$refs) {
+ $refs = self::newRefs();
+ $cache->setKey(self::KEY_REFS, $refs);
+ }
+
+ return $refs;
+ }
+
+ /**
+ * @return PhabricatorClusterSearchRef[]
+ */
+ public static function newRefs() {
+ $services = PhabricatorEnv::getEnvConfig('cluster.search');
+ $types = id(new PhutilClassMapQuery())
+ ->setAncestorClass(__CLASS__)
+ ->setUniqueMethod('getEngineIdentifier')
+ ->execute();
+ $refs = array();
+
+ foreach ($services as $config) {
+ if (!isset($types[$config['type']])) {
+ throw new Exception(pht('Configured search server type is invalid: %s',
+ $config['type']));
+ }
+ $type = $types[$config['type']];
+ $service = clone($type);
+ $service->setConfig($config);
+ if (is_array($config['hosts']) && count($config['hosts'])) {
+ $hosts = array();
+ foreach ($config['hosts'] as $host) {
+ $hostref = clone($service);
+ $hostref->setPort(idx($host, 'port'))
+ ->setHost(idx($host, 'host'))
+ ->setRoles(idx($host, 'roles', array()));
+ $hosts[] = $hostref;
+ }
+ $service->setHostRefs($hosts);
+ }
+ $refs[] = $service;
+ }
+
+ return $refs;
+ }
+
+ /**
+ * @return PhabricatorFulltextStorageEngine
+ */
+ abstract public function getEngine();
+
+ abstract public function loadServerStatus();
+
+ public static function reindexAbstractDocument(
+ PhabricatorSearchAbstractDocument $doc) {
+
+ $services = self::getLiveServers();
+ $indexed = 0;
+ foreach ($services as $service) {
+ $host = $service->getHostForRole('write');
+ if ($host) {
+ $host->getEngine()->reindexAbstractDocument($doc);
+ $indexed++;
+ }
+ }
+ if ($indexed == 0) {
+ throw new Exception(
+ pht('No hosts were available to index fulltext document: %s',
+ $doc->getDocumentTitle()));
+ }
+ }
+
+ public static function executeSearch(PhabricatorSavedQuery $query) {
+ $services = self::getLiveServers();
+ foreach ($services as $service) {
+ $hosts = $service->getAllHostsForRole('read');
+ // try all hosts until one succeeds
+ foreach ($hosts as $host) {
+ $last_exception = null;
+ try {
+ $res = $host->getEngine()->executeSearch($query);
+ // return immediately if we get results without an exception
+ return $res;
+ } catch (Exception $ex) {
+ // try each server in turn, only throw if none succeed
+ $last_exception = $ex;
+ }
+ }
+ }
+ if ($last_exception) {
+ throw $last_exception;
+ }
+ return $res;
+ }
+
+}
diff --git a/src/infrastructure/cluster/PhabricatorDatabaseHealthRecord.php b/src/infrastructure/cluster/PhabricatorClusterServiceHealthRecord.php
rename from src/infrastructure/cluster/PhabricatorDatabaseHealthRecord.php
rename to src/infrastructure/cluster/PhabricatorClusterServiceHealthRecord.php
--- a/src/infrastructure/cluster/PhabricatorDatabaseHealthRecord.php
+++ b/src/infrastructure/cluster/PhabricatorClusterServiceHealthRecord.php
@@ -1,20 +1,19 @@
<?php
-final class PhabricatorDatabaseHealthRecord
+class PhabricatorClusterServiceHealthRecord
extends Phobject {
- private $ref;
+ private $cacheKey;
private $shouldCheck;
private $isHealthy;
private $upEventCount;
private $downEventCount;
- public function __construct(PhabricatorDatabaseRef $ref) {
- $this->ref = $ref;
+ public function __construct($cache_key) {
+ $this->cacheKey = $cache_key;
$this->readState();
}
-
/**
* Is the database currently healthy?
*/
@@ -153,18 +152,13 @@
}
}
- private function getHealthRecordCacheKey() {
- $ref = $this->ref;
-
- $host = $ref->getHost();
- $port = $ref->getPort();
-
- return "cluster.db.health({$host}, {$port})";
+ public function getCacheKey() {
+ return $this->cacheKey;
}
private function readHealthRecord() {
$cache = PhabricatorCaches::getSetupCache();
- $cache_key = $this->getHealthRecordCacheKey();
+ $cache_key = $this->getCacheKey();
$health_record = $cache->getKey($cache_key);
if (!is_array($health_record)) {
@@ -180,7 +174,7 @@
private function writeHealthRecord(array $record) {
$cache = PhabricatorCaches::getSetupCache();
- $cache_key = $this->getHealthRecordCacheKey();
+ $cache_key = $this->getCacheKey();
$cache->setKey($cache_key, $record);
}
diff --git a/src/infrastructure/cluster/PhabricatorClusterServiceInterface.php b/src/infrastructure/cluster/PhabricatorClusterServiceInterface.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/cluster/PhabricatorClusterServiceInterface.php
@@ -0,0 +1,3 @@
+<?php
+
+interface PhabricatorClusterServiceInterface {}
diff --git a/src/infrastructure/cluster/PhabricatorDatabaseRef.php b/src/infrastructure/cluster/PhabricatorDatabaseRef.php
--- a/src/infrastructure/cluster/PhabricatorDatabaseRef.php
+++ b/src/infrastructure/cluster/PhabricatorDatabaseRef.php
@@ -14,6 +14,7 @@
const REPLICATION_SLOW = 'replica-slow';
const REPLICATION_NOT_REPLICATING = 'not-replicating';
+ const KEY_HEALTH = 'cluster.db.health';
const KEY_REFS = 'cluster.db.refs';
const KEY_INDIVIDUAL = 'cluster.db.individual';
@@ -489,9 +490,18 @@
return $this;
}
+ private function getHealthRecordCacheKey() {
+ $host = $this->getHost();
+ $port = $this->getPort();
+ $key = self::KEY_HEALTH;
+
+ return "{$key}({$host}, {$port})";
+ }
+
public function getHealthRecord() {
if (!$this->healthRecord) {
- $this->healthRecord = new PhabricatorDatabaseHealthRecord($this);
+ $this->healthRecord = new PhabricatorClusterServiceHealthRecord(
+ $this->getHealthRecordCacheKey());
}
return $this->healthRecord;
}
diff --git a/src/infrastructure/cluster/PhabricatorElasticSearchServerRef.php b/src/infrastructure/cluster/PhabricatorElasticSearchServerRef.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/cluster/PhabricatorElasticSearchServerRef.php
@@ -0,0 +1,96 @@
+<?php
+
+final class PhabricatorElasticSearchServerRef
+ extends PhabricatorClusterSearchRef {
+
+ private $engine;
+ private $version;
+ private $path;
+ private $protocol;
+
+ const KEY_REFS = 'search.elastic.refs';
+
+ public function setConfig($config) {
+ $this->setRoles(idx($config, 'roles', array('read', 'write')))
+ ->setHosts(idx($config, 'hosts', array()))
+ ->setPort(idx($config, 'port'))
+ ->setProtocol(idx($config, 'protocol'))
+ ->setPath(idx($config, 'path'))
+ ->setVersion(idx($config, 'version'));
+ return $this;
+ }
+
+ public function getDisplayName() {
+ return 'ElasticSearch';
+ }
+
+ public function getEngineIdentifier() {
+ return 'elasticsearch';
+ }
+
+ public function setProtocol($protocol) {
+ $this->protocol = $protocol;
+ return $this;
+ }
+
+ public function getProtocol() {
+ return $this->protocol;
+ }
+
+ public function setPath($path) {
+ $this->path = $path;
+ return $this;
+ }
+
+ public function getPath() {
+ return $this->path;
+ }
+
+ public function setVersion($version) {
+ $this->version = $version;
+ return $this;
+ }
+
+ public function getVersion() {
+ return $this->version;
+ }
+
+
+
+
+ public function getURI($to_path = null) {
+ $uri = id(new PhutilURI('http://'.$this->getHost()))
+ ->setProtocol($this->getProtocol())
+ ->setPort($this->getPort())
+ ->setPath($this->getPath());
+
+ if ($to_path) {
+ $uri->appendPath($to_path);
+ }
+
+ return $uri;
+ }
+
+ /**
+ * @return PhabricatorElasticFulltextStorageEngine
+ */
+ public function getEngine() {
+ if (!$this->engine) {
+ $engine = new PhabricatorElasticFulltextStorageEngine();
+ $this->engine = $engine->setRef($this);
+ }
+ return $this->engine;
+ }
+
+ public function loadServerStatus() {
+ $status = $this->getEngine()->indexIsSane()
+ ? 'okay'
+ : 'fail';
+ $engine = $this->getEngine();
+ $status = $engine->indexIsSane()
+ ? 'okay'
+ : 'fail';
+ return $status;
+ }
+
+}
diff --git a/src/infrastructure/cluster/PhabricatorMysqlSearchServerRef.php b/src/infrastructure/cluster/PhabricatorMysqlSearchServerRef.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/cluster/PhabricatorMysqlSearchServerRef.php
@@ -0,0 +1,40 @@
+<?php
+
+final class PhabricatorMysqlSearchServerRef
+ extends PhabricatorClusterSearchRef {
+
+ private $engine;
+
+ public function __construct() {
+ $this->engine = new PhabricatorMySQLFulltextStorageEngine();
+ }
+
+ public function setConfig($config) {
+ $this->setRoles(idx($config, 'roles', array('read', 'write')));
+ return $this;
+ }
+
+ public function getDisplayName() {
+ return 'MySQL';
+ }
+
+ public function getEngineIdentifier() {
+ return 'mysql';
+ }
+
+ public function getEngine() {
+ return $this->engine;
+ }
+
+ public function getProtocol() {
+ return 'mysql';
+ }
+
+ public function loadServerStatus() {
+ PhabricatorDatabaseRef::queryAll();
+ $ref = PhabricatorDatabaseRef::getMasterDatabaseRefForApplication('search');
+ $status = $ref->getConnectionStatus();
+ return $status;
+ }
+
+}
diff --git a/src/infrastructure/cluster/PhabricatorClusterDatabasesConfigOptionType.php b/src/infrastructure/cluster/config/PhabricatorClusterDatabasesConfigOptionType.php
rename from src/infrastructure/cluster/PhabricatorClusterDatabasesConfigOptionType.php
rename to src/infrastructure/cluster/config/PhabricatorClusterDatabasesConfigOptionType.php
diff --git a/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php b/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php
@@ -0,0 +1,54 @@
+<?php
+
+final class PhabricatorClusterSearchConfigOptionType
+ extends PhabricatorConfigJSONOptionType {
+
+ public function validateOption(PhabricatorConfigOption $option, $value) {
+ if (!is_array($value)) {
+ throw new Exception(
+ pht(
+ 'Search cluster configuration is not valid: value must be a '.
+ 'list of search hosts.'));
+ }
+
+ foreach ($value as $index => $spec) {
+ if (!is_array($spec)) {
+ throw new Exception(
+ pht(
+ 'Search cluster configuration is not valid: each entry in the '.
+ 'list must be a dictionary describing a search service, but '.
+ 'the value with index "%s" is not a dictionary.',
+ $index));
+
+ try {
+ PhutilTypeSpec::checkMap(
+ $spec,
+ array(
+ 'type' => 'string',
+ 'hosts' => 'optional list<map<string, wild>>',
+ 'roles' => 'optional list<string>',
+ 'port' => 'optional int',
+ 'protocol' => 'optional string',
+ 'path' => 'optional string',
+ 'version' => 'optional int',
+ ));
+ } catch (Exception $ex) {
+ throw new Exception(
+ pht(
+ 'Search cluster configuration has an invalid service '.
+ 'specification (at index "%s"): %s.',
+ $index,
+ $ex->getMessage()));
+ }
+ if (isset($spec['hosts']) && !is_array($spec['hosts'])) {
+ throw new Exception(
+ pht(
+ 'Search cluster configuration has invalid "hosts" specification '.
+ 'for the service at index "%s": Hosts must be a list with at '.
+ 'least one dictionary describing a host for this service.'),
+ $index);
+ }
+ }
+ }
+ }
+}
diff --git a/src/infrastructure/cluster/PhabricatorClusterException.php b/src/infrastructure/cluster/exception/PhabricatorClusterException.php
rename from src/infrastructure/cluster/PhabricatorClusterException.php
rename to src/infrastructure/cluster/exception/PhabricatorClusterException.php
diff --git a/src/infrastructure/cluster/PhabricatorClusterExceptionHandler.php b/src/infrastructure/cluster/exception/PhabricatorClusterExceptionHandler.php
rename from src/infrastructure/cluster/PhabricatorClusterExceptionHandler.php
rename to src/infrastructure/cluster/exception/PhabricatorClusterExceptionHandler.php
diff --git a/src/infrastructure/cluster/PhabricatorClusterImpossibleWriteException.php b/src/infrastructure/cluster/exception/PhabricatorClusterImpossibleWriteException.php
rename from src/infrastructure/cluster/PhabricatorClusterImpossibleWriteException.php
rename to src/infrastructure/cluster/exception/PhabricatorClusterImpossibleWriteException.php
diff --git a/src/infrastructure/cluster/PhabricatorClusterImproperWriteException.php b/src/infrastructure/cluster/exception/PhabricatorClusterImproperWriteException.php
rename from src/infrastructure/cluster/PhabricatorClusterImproperWriteException.php
rename to src/infrastructure/cluster/exception/PhabricatorClusterImproperWriteException.php
diff --git a/src/infrastructure/cluster/PhabricatorClusterStrandedException.php b/src/infrastructure/cluster/exception/PhabricatorClusterStrandedException.php
rename from src/infrastructure/cluster/PhabricatorClusterStrandedException.php
rename to src/infrastructure/cluster/exception/PhabricatorClusterStrandedException.php

File Metadata

Mime Type
text/plain
Expires
Mon, Apr 28, 10:12 AM (1 w, 4 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/5o/4j/2foolgiwtzofabtx
Default Alt Text
D17384.id41877.diff (39 KB)

Event Timeline