Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/cluster/search/PhabricatorSearchService.php
13441 views
1
<?php
2
3
class PhabricatorSearchService
4
extends Phobject {
5
6
const KEY_REFS = 'cluster.search.refs';
7
8
protected $config;
9
protected $disabled;
10
protected $engine;
11
protected $hosts = array();
12
protected $hostsConfig;
13
protected $hostType;
14
protected $roles = array();
15
16
const STATUS_OKAY = 'okay';
17
const STATUS_FAIL = 'fail';
18
19
const ROLE_WRITE = 'write';
20
const ROLE_READ = 'read';
21
22
public function __construct(PhabricatorFulltextStorageEngine $engine) {
23
$this->engine = $engine;
24
$this->hostType = $engine->getHostType();
25
}
26
27
/**
28
* @throws Exception
29
*/
30
public function newHost($config) {
31
$host = clone($this->hostType);
32
$host_config = $this->config + $config;
33
$host->setConfig($host_config);
34
$this->hosts[] = $host;
35
return $host;
36
}
37
38
public function getEngine() {
39
return $this->engine;
40
}
41
42
public function getDisplayName() {
43
return $this->hostType->getDisplayName();
44
}
45
46
public function getStatusViewColumns() {
47
return $this->hostType->getStatusViewColumns();
48
}
49
50
public function setConfig($config) {
51
$this->config = $config;
52
53
if (!isset($config['hosts'])) {
54
$config['hosts'] = array(
55
array(
56
'host' => idx($config, 'host'),
57
'port' => idx($config, 'port'),
58
'protocol' => idx($config, 'protocol'),
59
'roles' => idx($config, 'roles'),
60
),
61
);
62
}
63
foreach ($config['hosts'] as $host) {
64
$this->newHost($host);
65
}
66
67
}
68
69
public function getConfig() {
70
return $this->config;
71
}
72
73
public static function getConnectionStatusMap() {
74
return array(
75
self::STATUS_OKAY => array(
76
'icon' => 'fa-exchange',
77
'color' => 'green',
78
'label' => pht('Okay'),
79
),
80
self::STATUS_FAIL => array(
81
'icon' => 'fa-times',
82
'color' => 'red',
83
'label' => pht('Failed'),
84
),
85
);
86
}
87
88
public function isWritable() {
89
return (bool)$this->getAllHostsForRole(self::ROLE_WRITE);
90
}
91
92
public function isReadable() {
93
return (bool)$this->getAllHostsForRole(self::ROLE_READ);
94
}
95
96
public function getPort() {
97
return idx($this->config, 'port');
98
}
99
100
public function getProtocol() {
101
return idx($this->config, 'protocol');
102
}
103
104
105
public function getVersion() {
106
return idx($this->config, 'version');
107
}
108
109
public function getHosts() {
110
return $this->hosts;
111
}
112
113
114
/**
115
* Get a random host reference with the specified role, skipping hosts which
116
* failed recent health checks.
117
* @throws PhabricatorClusterNoHostForRoleException if no healthy hosts match.
118
* @return PhabricatorSearchHost
119
*/
120
public function getAnyHostForRole($role) {
121
$hosts = $this->getAllHostsForRole($role);
122
shuffle($hosts);
123
foreach ($hosts as $host) {
124
$health = $host->getHealthRecord();
125
if ($health->getIsHealthy()) {
126
return $host;
127
}
128
}
129
throw new PhabricatorClusterNoHostForRoleException($role);
130
}
131
132
133
/**
134
* Get all configured hosts for this service which have the specified role.
135
* @return PhabricatorSearchHost[]
136
*/
137
public function getAllHostsForRole($role) {
138
// if the role is explicitly set to false at the top level, then all hosts
139
// have the role disabled.
140
if (idx($this->config, $role) === false) {
141
return array();
142
}
143
144
$hosts = array();
145
foreach ($this->hosts as $host) {
146
if ($host->hasRole($role)) {
147
$hosts[] = $host;
148
}
149
}
150
return $hosts;
151
}
152
153
/**
154
* Get a reference to all configured fulltext search cluster services
155
* @return PhabricatorSearchService[]
156
*/
157
public static function getAllServices() {
158
$cache = PhabricatorCaches::getRequestCache();
159
160
$refs = $cache->getKey(self::KEY_REFS);
161
if (!$refs) {
162
$refs = self::newRefs();
163
$cache->setKey(self::KEY_REFS, $refs);
164
}
165
166
return $refs;
167
}
168
169
/**
170
* Load all valid PhabricatorFulltextStorageEngine subclasses
171
*/
172
public static function loadAllFulltextStorageEngines() {
173
return id(new PhutilClassMapQuery())
174
->setAncestorClass('PhabricatorFulltextStorageEngine')
175
->setUniqueMethod('getEngineIdentifier')
176
->execute();
177
}
178
179
/**
180
* Create instances of PhabricatorSearchService based on configuration
181
* @return PhabricatorSearchService[]
182
*/
183
public static function newRefs() {
184
$services = PhabricatorEnv::getEnvConfig('cluster.search');
185
$engines = self::loadAllFulltextStorageEngines();
186
$refs = array();
187
188
foreach ($services as $config) {
189
190
// Normally, we've validated configuration before we get this far, but
191
// make sure we don't fatal if we end up here with a bogus configuration.
192
if (!isset($engines[$config['type']])) {
193
throw new Exception(
194
pht(
195
'Configured search engine type "%s" is unknown. Valid engines '.
196
'are: %s.',
197
$config['type'],
198
implode(', ', array_keys($engines))));
199
}
200
201
$engine = clone($engines[$config['type']]);
202
$cluster = new self($engine);
203
$cluster->setConfig($config);
204
$engine->setService($cluster);
205
$refs[] = $cluster;
206
}
207
208
return $refs;
209
}
210
211
212
/**
213
* (re)index the document: attempt to pass the document to all writable
214
* fulltext search hosts
215
*/
216
public static function reindexAbstractDocument(
217
PhabricatorSearchAbstractDocument $document) {
218
219
$exceptions = array();
220
foreach (self::getAllServices() as $service) {
221
if (!$service->isWritable()) {
222
continue;
223
}
224
225
$engine = $service->getEngine();
226
try {
227
$engine->reindexAbstractDocument($document);
228
} catch (Exception $ex) {
229
$exceptions[] = $ex;
230
}
231
}
232
233
if ($exceptions) {
234
throw new PhutilAggregateException(
235
pht(
236
'Writes to search services failed while reindexing document "%s".',
237
$document->getPHID()),
238
$exceptions);
239
}
240
}
241
242
/**
243
* Execute a full-text query and return a list of PHIDs of matching objects.
244
* @return string[]
245
* @throws PhutilAggregateException
246
*/
247
public static function executeSearch(PhabricatorSavedQuery $query) {
248
$result_set = self::newResultSet($query);
249
return $result_set->getPHIDs();
250
}
251
252
public static function newResultSet(PhabricatorSavedQuery $query) {
253
$exceptions = array();
254
// try all services until one succeeds
255
foreach (self::getAllServices() as $service) {
256
if (!$service->isReadable()) {
257
continue;
258
}
259
260
try {
261
$engine = $service->getEngine();
262
$phids = $engine->executeSearch($query);
263
264
return id(new PhabricatorFulltextResultSet())
265
->setPHIDs($phids)
266
->setFulltextTokens($engine->getFulltextTokens());
267
} catch (PhutilSearchQueryCompilerSyntaxException $ex) {
268
// If there's a query compilation error, return it directly to the
269
// user: they issued a query with bad syntax.
270
throw $ex;
271
} catch (Exception $ex) {
272
$exceptions[] = $ex;
273
}
274
}
275
$msg = pht('All of the configured Fulltext Search services failed.');
276
throw new PhutilAggregateException($msg, $exceptions);
277
}
278
279
}
280
281