Path: blob/master/src/infrastructure/cluster/search/PhabricatorSearchService.php
13441 views
<?php12class PhabricatorSearchService3extends Phobject {45const KEY_REFS = 'cluster.search.refs';67protected $config;8protected $disabled;9protected $engine;10protected $hosts = array();11protected $hostsConfig;12protected $hostType;13protected $roles = array();1415const STATUS_OKAY = 'okay';16const STATUS_FAIL = 'fail';1718const ROLE_WRITE = 'write';19const ROLE_READ = 'read';2021public function __construct(PhabricatorFulltextStorageEngine $engine) {22$this->engine = $engine;23$this->hostType = $engine->getHostType();24}2526/**27* @throws Exception28*/29public function newHost($config) {30$host = clone($this->hostType);31$host_config = $this->config + $config;32$host->setConfig($host_config);33$this->hosts[] = $host;34return $host;35}3637public function getEngine() {38return $this->engine;39}4041public function getDisplayName() {42return $this->hostType->getDisplayName();43}4445public function getStatusViewColumns() {46return $this->hostType->getStatusViewColumns();47}4849public function setConfig($config) {50$this->config = $config;5152if (!isset($config['hosts'])) {53$config['hosts'] = array(54array(55'host' => idx($config, 'host'),56'port' => idx($config, 'port'),57'protocol' => idx($config, 'protocol'),58'roles' => idx($config, 'roles'),59),60);61}62foreach ($config['hosts'] as $host) {63$this->newHost($host);64}6566}6768public function getConfig() {69return $this->config;70}7172public static function getConnectionStatusMap() {73return array(74self::STATUS_OKAY => array(75'icon' => 'fa-exchange',76'color' => 'green',77'label' => pht('Okay'),78),79self::STATUS_FAIL => array(80'icon' => 'fa-times',81'color' => 'red',82'label' => pht('Failed'),83),84);85}8687public function isWritable() {88return (bool)$this->getAllHostsForRole(self::ROLE_WRITE);89}9091public function isReadable() {92return (bool)$this->getAllHostsForRole(self::ROLE_READ);93}9495public function getPort() {96return idx($this->config, 'port');97}9899public function getProtocol() {100return idx($this->config, 'protocol');101}102103104public function getVersion() {105return idx($this->config, 'version');106}107108public function getHosts() {109return $this->hosts;110}111112113/**114* Get a random host reference with the specified role, skipping hosts which115* failed recent health checks.116* @throws PhabricatorClusterNoHostForRoleException if no healthy hosts match.117* @return PhabricatorSearchHost118*/119public function getAnyHostForRole($role) {120$hosts = $this->getAllHostsForRole($role);121shuffle($hosts);122foreach ($hosts as $host) {123$health = $host->getHealthRecord();124if ($health->getIsHealthy()) {125return $host;126}127}128throw new PhabricatorClusterNoHostForRoleException($role);129}130131132/**133* Get all configured hosts for this service which have the specified role.134* @return PhabricatorSearchHost[]135*/136public function getAllHostsForRole($role) {137// if the role is explicitly set to false at the top level, then all hosts138// have the role disabled.139if (idx($this->config, $role) === false) {140return array();141}142143$hosts = array();144foreach ($this->hosts as $host) {145if ($host->hasRole($role)) {146$hosts[] = $host;147}148}149return $hosts;150}151152/**153* Get a reference to all configured fulltext search cluster services154* @return PhabricatorSearchService[]155*/156public static function getAllServices() {157$cache = PhabricatorCaches::getRequestCache();158159$refs = $cache->getKey(self::KEY_REFS);160if (!$refs) {161$refs = self::newRefs();162$cache->setKey(self::KEY_REFS, $refs);163}164165return $refs;166}167168/**169* Load all valid PhabricatorFulltextStorageEngine subclasses170*/171public static function loadAllFulltextStorageEngines() {172return id(new PhutilClassMapQuery())173->setAncestorClass('PhabricatorFulltextStorageEngine')174->setUniqueMethod('getEngineIdentifier')175->execute();176}177178/**179* Create instances of PhabricatorSearchService based on configuration180* @return PhabricatorSearchService[]181*/182public static function newRefs() {183$services = PhabricatorEnv::getEnvConfig('cluster.search');184$engines = self::loadAllFulltextStorageEngines();185$refs = array();186187foreach ($services as $config) {188189// Normally, we've validated configuration before we get this far, but190// make sure we don't fatal if we end up here with a bogus configuration.191if (!isset($engines[$config['type']])) {192throw new Exception(193pht(194'Configured search engine type "%s" is unknown. Valid engines '.195'are: %s.',196$config['type'],197implode(', ', array_keys($engines))));198}199200$engine = clone($engines[$config['type']]);201$cluster = new self($engine);202$cluster->setConfig($config);203$engine->setService($cluster);204$refs[] = $cluster;205}206207return $refs;208}209210211/**212* (re)index the document: attempt to pass the document to all writable213* fulltext search hosts214*/215public static function reindexAbstractDocument(216PhabricatorSearchAbstractDocument $document) {217218$exceptions = array();219foreach (self::getAllServices() as $service) {220if (!$service->isWritable()) {221continue;222}223224$engine = $service->getEngine();225try {226$engine->reindexAbstractDocument($document);227} catch (Exception $ex) {228$exceptions[] = $ex;229}230}231232if ($exceptions) {233throw new PhutilAggregateException(234pht(235'Writes to search services failed while reindexing document "%s".',236$document->getPHID()),237$exceptions);238}239}240241/**242* Execute a full-text query and return a list of PHIDs of matching objects.243* @return string[]244* @throws PhutilAggregateException245*/246public static function executeSearch(PhabricatorSavedQuery $query) {247$result_set = self::newResultSet($query);248return $result_set->getPHIDs();249}250251public static function newResultSet(PhabricatorSavedQuery $query) {252$exceptions = array();253// try all services until one succeeds254foreach (self::getAllServices() as $service) {255if (!$service->isReadable()) {256continue;257}258259try {260$engine = $service->getEngine();261$phids = $engine->executeSearch($query);262263return id(new PhabricatorFulltextResultSet())264->setPHIDs($phids)265->setFulltextTokens($engine->getFulltextTokens());266} catch (PhutilSearchQueryCompilerSyntaxException $ex) {267// If there's a query compilation error, return it directly to the268// user: they issued a query with bad syntax.269throw $ex;270} catch (Exception $ex) {271$exceptions[] = $ex;272}273}274$msg = pht('All of the configured Fulltext Search services failed.');275throw new PhutilAggregateException($msg, $exceptions);276}277278}279280281