Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 278 |
|
0.00% |
0 / 28 |
CRAP | |
0.00% |
0 / 1 |
UpdateOneSearchIndexConfig | |
0.00% |
0 / 271 |
|
0.00% |
0 / 28 |
2550 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
addSharedOptions | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 69 |
|
0.00% |
0 / 1 |
90 | |||
updateVersions | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
2 | |||
validateIndex | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
createIndex | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
2 | |||
getIndexSettingsValidators | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
validateIndexSettings | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
validateAnalyzers | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
validateMapping | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
validateAlias | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
validateSpecificAlias | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
2 | |||
validateAllAlias | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
getShardAllocationValidator | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
validateShardAllocation | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
pickAnalyzer | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
initMappingConfigBuilder | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
30 | |||
getIndex | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getSpecificIndexName | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getIndexAliasName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getIndexName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getOldIndex | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getMergeSettings | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
getShardCount | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getReplicaCount | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getMaxShardsPerNode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
initAnalysisConfig | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
fatalError | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
42 |
1 | <?php |
2 | |
3 | namespace CirrusSearch\Maintenance; |
4 | |
5 | use CirrusSearch\Connection; |
6 | use CirrusSearch\ElasticaErrorHandler; |
7 | use CirrusSearch\Maintenance\Validators\MappingValidator; |
8 | use CirrusSearch\SearchConfig; |
9 | use CirrusSearch\Util; |
10 | use MediaWiki\Config\ConfigException; |
11 | |
12 | /** |
13 | * Update the search configuration on the search backend. |
14 | * |
15 | * This program is free software; you can redistribute it and/or modify |
16 | * it under the terms of the GNU General Public License as published by |
17 | * the Free Software Foundation; either version 2 of the License, or |
18 | * (at your option) any later version. |
19 | * |
20 | * This program is distributed in the hope that it will be useful, |
21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
23 | * GNU General Public License for more details. |
24 | * |
25 | * You should have received a copy of the GNU General Public License along |
26 | * with this program; if not, write to the Free Software Foundation, Inc., |
27 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
28 | * http://www.gnu.org/copyleft/gpl.html |
29 | */ |
30 | |
31 | $IP = getenv( 'MW_INSTALL_PATH' ); |
32 | if ( $IP === false ) { |
33 | $IP = __DIR__ . '/../../..'; |
34 | } |
35 | require_once "$IP/maintenance/Maintenance.php"; |
36 | require_once __DIR__ . '/../includes/Maintenance/Maintenance.php'; |
37 | |
38 | /** |
39 | * Update the elasticsearch configuration for this index. |
40 | */ |
41 | class UpdateOneSearchIndexConfig extends Maintenance { |
42 | /** |
43 | * @var string |
44 | */ |
45 | private $indexSuffix; |
46 | |
47 | /** |
48 | * @var bool Are we going to blow the index away and start from scratch? |
49 | */ |
50 | private $startOver; |
51 | |
52 | /** |
53 | * @var int |
54 | */ |
55 | private $reindexChunkSize; |
56 | |
57 | /** |
58 | * @var string |
59 | */ |
60 | private $indexBaseName; |
61 | |
62 | /** |
63 | * @var string |
64 | */ |
65 | private $indexIdentifier; |
66 | |
67 | /** |
68 | * @var bool |
69 | */ |
70 | private $reindexAndRemoveOk; |
71 | |
72 | /** |
73 | * @var int number of scan slices to use when reindexing |
74 | */ |
75 | private $reindexSlices; |
76 | |
77 | /** |
78 | * @var string language code we're building for |
79 | */ |
80 | private $langCode; |
81 | |
82 | /** |
83 | * @var bool prefix search on any term |
84 | */ |
85 | private $prefixSearchStartsWithAny; |
86 | |
87 | /** |
88 | * @var bool use suggestions on text fields |
89 | */ |
90 | private $phraseSuggestUseText; |
91 | |
92 | /** |
93 | * @var bool print config as it is being checked |
94 | */ |
95 | private $printDebugCheckConfig; |
96 | |
97 | /** |
98 | * @var float how much can the reindexed copy of an index is allowed to deviate from the current |
99 | * copy without triggering a reindex failure |
100 | */ |
101 | private $reindexAcceptableCountDeviation; |
102 | |
103 | /** |
104 | * @var array filtered analysis config |
105 | */ |
106 | private $analysisConfig; |
107 | |
108 | /** |
109 | * @var array list of available plugins |
110 | */ |
111 | private $availablePlugins; |
112 | |
113 | /** |
114 | * @var array |
115 | */ |
116 | protected $bannedPlugins; |
117 | |
118 | /** |
119 | * @var bool |
120 | */ |
121 | protected $optimizeIndexForExperimentalHighlighter; |
122 | |
123 | /** |
124 | * @var int |
125 | */ |
126 | protected $refreshInterval; |
127 | |
128 | /** |
129 | * @var string |
130 | */ |
131 | protected $masterTimeout; |
132 | |
133 | /** |
134 | * @var array |
135 | */ |
136 | private $mapping = []; |
137 | |
138 | /** |
139 | * @var array |
140 | */ |
141 | private $similarityConfig; |
142 | |
143 | /** |
144 | * @var bool true if the analysis config can be optimized |
145 | */ |
146 | private $safeToOptimizeAnalysisConfig; |
147 | |
148 | /** @var bool State flag indicating if we should attempt deleting the index we created */ |
149 | private $canCleanupCreatedIndex = false; |
150 | |
151 | public function __construct() { |
152 | parent::__construct(); |
153 | $this->addDescription( "Update the configuration or contents of one search index. This always " . |
154 | "operates on a single cluster." ); |
155 | $this->addOption( 'indexSuffix', 'Index to update. Either content or general.', false, true ); |
156 | $this->addOption( 'indexType', 'BC form of --indexSuffix', false, true ); |
157 | self::addSharedOptions( $this ); |
158 | } |
159 | |
160 | /** |
161 | * @param Maintenance $maintenance |
162 | */ |
163 | public static function addSharedOptions( $maintenance ) { |
164 | $maintenance->addOption( 'startOver', 'Blow away the identified index and rebuild it with ' . |
165 | 'no data.' ); |
166 | $maintenance->addOption( 'indexIdentifier', "Set the identifier of the index to work on. " . |
167 | "You'll need this if you have an index in production serving queries and you have " . |
168 | "to alter some portion of its configuration that cannot safely be done without " . |
169 | "rebuilding it. Once you specify a new indexIdentifier for this wiki you'll have to " . |
170 | "run this script with the same identifier each time. Defaults to 'current' which " . |
171 | "infers the currently in use identifier. You can also use 'now' to set the identifier " . |
172 | "to the current time in seconds which should give you a unique identifier.", false, true ); |
173 | $maintenance->addOption( 'reindexAndRemoveOk', "If the alias is held by another index then " . |
174 | "reindex all documents from that index (via the alias) to this one, swing the " . |
175 | "alias to this index, and then remove other index. Updates performed while this" . |
176 | "operation is in progress will be queued up in the job queue. Defaults to false." ); |
177 | $maintenance->addOption( 'reindexSlices', 'Number of slices to use in reindex. Roughly ' |
178 | . 'equivalent to the level of indexing parallelism. Defaults to number of shards.', false, true ); |
179 | $maintenance->addOption( 'reindexAcceptableCountDeviation', 'How much can the reindexed ' . |
180 | 'copy of an index is allowed to deviate from the current copy without triggering a ' . |
181 | 'reindex failure. Defaults to 5%.', false, true ); |
182 | $maintenance->addOption( 'reindexChunkSize', 'Documents per shard to reindex in a batch. ' . |
183 | 'Note when changing the number of shards that the old shard size is used, not the new ' . |
184 | 'one. If you see many errors submitting documents in bulk but the automatic retry as ' . |
185 | 'singles works then lower this number. Defaults to 100.', false, true ); |
186 | $maintenance->addOption( 'baseName', 'What basename to use for all indexes, ' . |
187 | 'defaults to wiki id', false, true ); |
188 | $maintenance->addOption( 'debugCheckConfig', 'Print the configuration as it is checked ' . |
189 | 'to help debug unexpected configuration mismatches.' ); |
190 | $maintenance->addOption( 'justAllocation', 'Just validate the shard allocation settings. Use ' . |
191 | "when you need to apply new cache warmers but want to be sure that you won't apply any other " . |
192 | 'changes at an inopportune time.' ); |
193 | $maintenance->addOption( 'fieldsToDelete', 'List of of comma separated field names to delete ' . |
194 | 'while reindexing documents (defaults to empty)', false, true ); |
195 | $maintenance->addOption( 'justMapping', 'Just try to update the mapping.' ); |
196 | } |
197 | |
198 | public function execute() { |
199 | global $wgLanguageCode, |
200 | $wgCirrusSearchPhraseSuggestUseText, |
201 | $wgCirrusSearchPrefixSearchStartsWithAnyWord, |
202 | $wgCirrusSearchBannedPlugins, |
203 | $wgCirrusSearchOptimizeIndexForExperimentalHighlighter, |
204 | $wgCirrusSearchRefreshInterval, |
205 | $wgCirrusSearchMasterTimeout; |
206 | |
207 | $this->disablePoolCountersAndLogging(); |
208 | |
209 | $utils = new ConfigUtils( $this->getConnection()->getClient(), $this ); |
210 | |
211 | $this->indexSuffix = $this->getBackCompatOption( 'indexSuffix', 'indexType' ); |
212 | $this->startOver = $this->getOption( 'startOver', false ); |
213 | $this->indexBaseName = $this->getOption( 'baseName', |
214 | $this->getSearchConfig()->get( SearchConfig::INDEX_BASE_NAME ) ); |
215 | $this->reindexAndRemoveOk = $this->getOption( 'reindexAndRemoveOk', false ); |
216 | $this->reindexSlices = $this->getOption( 'reindexSlices', null ); |
217 | $this->reindexAcceptableCountDeviation = Util::parsePotentialPercent( |
218 | $this->getOption( 'reindexAcceptableCountDeviation', '5%' ) ); |
219 | $this->reindexChunkSize = $this->getOption( 'reindexChunkSize', 100 ); |
220 | $this->printDebugCheckConfig = $this->getOption( 'debugCheckConfig', false ); |
221 | $this->langCode = $wgLanguageCode; |
222 | $this->prefixSearchStartsWithAny = $wgCirrusSearchPrefixSearchStartsWithAnyWord; |
223 | $this->phraseSuggestUseText = $wgCirrusSearchPhraseSuggestUseText; |
224 | $this->bannedPlugins = $wgCirrusSearchBannedPlugins; |
225 | $this->optimizeIndexForExperimentalHighlighter = $wgCirrusSearchOptimizeIndexForExperimentalHighlighter; |
226 | $this->masterTimeout = $wgCirrusSearchMasterTimeout; |
227 | $this->refreshInterval = $wgCirrusSearchRefreshInterval; |
228 | |
229 | if ( $this->indexSuffix === Connection::ARCHIVE_INDEX_SUFFIX ) { |
230 | if ( !$this->getSearchConfig()->get( 'CirrusSearchEnableArchive' ) ) { |
231 | $this->output( "Warning: Not allowing {$this->indexSuffix}, archives are disabled\n" ); |
232 | return true; |
233 | } |
234 | if ( !$this->getConnection()->getSettings()->isPrivateCluster() ) { |
235 | $this->output( "Warning: Not allowing {$this->indexSuffix} on a non-private cluster\n" ); |
236 | return true; |
237 | } |
238 | } |
239 | |
240 | $this->initMappingConfigBuilder(); |
241 | |
242 | try { |
243 | $indexSuffixes = $this->getConnection()->getAllIndexSuffixes( null ); |
244 | if ( !in_array( $this->indexSuffix, $indexSuffixes ) ) { |
245 | $this->fatalError( 'indexSuffix option must be one of ' . |
246 | implode( ', ', $indexSuffixes ) ); |
247 | } |
248 | |
249 | $this->unwrap( $utils->checkElasticsearchVersion() ); |
250 | $this->availablePlugins = $this->unwrap( $utils->scanAvailablePlugins( $this->bannedPlugins ) ); |
251 | |
252 | if ( $this->getOption( 'justAllocation', false ) ) { |
253 | $this->validateShardAllocation(); |
254 | return true; |
255 | } |
256 | |
257 | if ( $this->getOption( 'justMapping', false ) ) { |
258 | $this->validateMapping(); |
259 | return true; |
260 | } |
261 | |
262 | $this->initAnalysisConfig(); |
263 | $this->indexIdentifier = $this->unwrap( $utils->pickIndexIdentifierFromOption( |
264 | $this->getOption( 'indexIdentifier', 'current' ), $this->getIndexAliasName() ) ); |
265 | $this->validateIndex(); |
266 | $this->validateAnalyzers(); |
267 | $this->validateMapping(); |
268 | $this->validateAlias(); |
269 | $this->updateVersions(); |
270 | } catch ( \Elastica\Exception\Connection\HttpException $e ) { |
271 | $message = $e->getMessage(); |
272 | $this->output( "\nUnexpected Elasticsearch failure.\n" ); |
273 | $this->fatalError( "Http error communicating with Elasticsearch: $message.\n" ); |
274 | } catch ( \Elastica\Exception\ExceptionInterface $e ) { |
275 | $type = get_class( $e ); |
276 | $message = ElasticaErrorHandler::extractMessage( $e ); |
277 | /** @suppress PhanUndeclaredMethod ExceptionInterface has no methods */ |
278 | $trace = $e->getTraceAsString(); |
279 | $this->output( "\nUnexpected Elasticsearch failure.\n" ); |
280 | $this->fatalError( "Elasticsearch failed in an unexpected way. " . |
281 | "This is always a bug in CirrusSearch.\n" . |
282 | "Error type: $type\n" . |
283 | "Message: $message\n" . |
284 | "Trace:\n" . $trace ); |
285 | } |
286 | |
287 | return true; |
288 | } |
289 | |
290 | /** |
291 | * @suppress PhanUndeclaredMethod runChild technically returns a |
292 | * \Maintenance instance but only \CirrusSearch\Maintenance\Maintenance |
293 | * classes have the done method. Just allow it since we know what type of |
294 | * maint class is being created |
295 | */ |
296 | private function updateVersions() { |
297 | $child = $this->runChild( Metastore::class ); |
298 | $child->done(); |
299 | $child->loadParamsAndArgs( |
300 | null, |
301 | array_merge( $this->parameters->getOptions(), [ |
302 | 'index-version-basename' => $this->indexBaseName, |
303 | 'update-index-version' => true, |
304 | ] ), |
305 | $this->parameters->getArgs() |
306 | ); |
307 | $child->execute(); |
308 | $child->done(); |
309 | } |
310 | |
311 | private function validateIndex() { |
312 | if ( $this->startOver ) { |
313 | $this->createIndex( true, "Blowing away index to start over...\n" ); |
314 | } elseif ( !$this->getIndex()->exists() ) { |
315 | $this->createIndex( false, "Creating index...\n" ); |
316 | } |
317 | |
318 | $this->validateIndexSettings(); |
319 | } |
320 | |
321 | /** |
322 | * @param bool $rebuild |
323 | * @param string $msg |
324 | */ |
325 | private function createIndex( $rebuild, $msg ) { |
326 | global $wgCirrusSearchExtraIndexSettings; |
327 | |
328 | $this->canCleanupCreatedIndex = true; |
329 | $index = $this->getIndex(); |
330 | $indexCreator = new \CirrusSearch\Maintenance\IndexCreator( |
331 | $index, |
332 | new ConfigUtils( $index->getClient(), $this ), |
333 | $this->analysisConfig, |
334 | $this->similarityConfig |
335 | ); |
336 | |
337 | $this->outputIndented( $msg ); |
338 | |
339 | $this->unwrap( $indexCreator->createIndex( |
340 | $rebuild, |
341 | $this->getMaxShardsPerNode(), |
342 | $this->getShardCount(), |
343 | $this->getReplicaCount(), |
344 | $this->refreshInterval, |
345 | $this->getMergeSettings(), |
346 | $wgCirrusSearchExtraIndexSettings |
347 | ) ); |
348 | |
349 | $this->outputIndented( "Index created.\n" ); |
350 | } |
351 | |
352 | /** |
353 | * @return \CirrusSearch\Maintenance\Validators\Validator[] |
354 | */ |
355 | private function getIndexSettingsValidators() { |
356 | $validators = []; |
357 | $validators[] = new \CirrusSearch\Maintenance\Validators\NumberOfShardsValidator( |
358 | $this->getIndex(), $this->getShardCount(), $this ); |
359 | $validators[] = new \CirrusSearch\Maintenance\Validators\ReplicaRangeValidator( |
360 | $this->getIndex(), $this->getReplicaCount(), $this ); |
361 | $validators[] = $this->getShardAllocationValidator(); |
362 | $validators[] = new \CirrusSearch\Maintenance\Validators\MaxShardsPerNodeValidator( |
363 | $this->getIndex(), $this->getMaxShardsPerNode(), $this ); |
364 | return $validators; |
365 | } |
366 | |
367 | private function validateIndexSettings() { |
368 | $validators = $this->getIndexSettingsValidators(); |
369 | foreach ( $validators as $validator ) { |
370 | $this->unwrap( $validator->validate() ); |
371 | } |
372 | } |
373 | |
374 | private function validateAnalyzers() { |
375 | $validator = new \CirrusSearch\Maintenance\Validators\AnalyzersValidator( |
376 | $this->getIndex(), $this->analysisConfig, $this ); |
377 | $validator->printDebugCheckConfig( $this->printDebugCheckConfig ); |
378 | $this->unwrap( $validator->validate() ); |
379 | } |
380 | |
381 | private function validateMapping() { |
382 | $validator = new MappingValidator( |
383 | $this->getIndex(), |
384 | $this->masterTimeout, |
385 | $this->optimizeIndexForExperimentalHighlighter, |
386 | $this->availablePlugins, |
387 | $this->mapping, |
388 | $this |
389 | ); |
390 | $validator->printDebugCheckConfig( $this->printDebugCheckConfig ); |
391 | $this->unwrap( $validator->validate() ); |
392 | } |
393 | |
394 | private function validateAlias() { |
395 | $this->outputIndented( "Validating aliases...\n" ); |
396 | // Since validate the specific alias first as that can cause reindexing |
397 | // and we want the all index to stay with the old index during reindexing |
398 | $this->validateSpecificAlias(); |
399 | // At this point the index is live and under no circumstances should it be |
400 | // automatically deleted. |
401 | $this->canCleanupCreatedIndex = false; |
402 | |
403 | if ( $this->indexSuffix !== Connection::ARCHIVE_INDEX_SUFFIX ) { |
404 | // Do not add the archive index to the global alias |
405 | $this->validateAllAlias(); |
406 | } |
407 | } |
408 | |
409 | /** |
410 | * Validate the alias that is just for this index's type. |
411 | */ |
412 | private function validateSpecificAlias() { |
413 | $connection = $this->getConnection(); |
414 | |
415 | $reindexer = new Reindexer( |
416 | $this->getSearchConfig(), |
417 | $connection, |
418 | $connection, |
419 | $this->getIndex(), |
420 | $this->getOldIndex(), |
421 | $this, |
422 | array_filter( explode( ',', $this->getOption( 'fieldsToDelete', '' ) ) ) |
423 | ); |
424 | |
425 | $validator = new \CirrusSearch\Maintenance\Validators\SpecificAliasValidator( |
426 | $this->getConnection()->getClient(), |
427 | $this->getIndexAliasName(), |
428 | $this->getSpecificIndexName(), |
429 | $this->startOver, |
430 | $reindexer, |
431 | [ |
432 | $this->reindexSlices, |
433 | $this->reindexChunkSize, |
434 | $this->reindexAcceptableCountDeviation |
435 | ], |
436 | $this->getIndexSettingsValidators(), |
437 | $this->reindexAndRemoveOk, |
438 | $this |
439 | ); |
440 | $this->unwrap( $validator->validate() ); |
441 | } |
442 | |
443 | public function validateAllAlias() { |
444 | $validator = new \CirrusSearch\Maintenance\Validators\IndexAllAliasValidator( |
445 | $this->getConnection()->getClient(), |
446 | $this->getIndexName(), |
447 | $this->getSpecificIndexName(), |
448 | $this->startOver, |
449 | $this->getIndexAliasName(), |
450 | $this |
451 | ); |
452 | $this->unwrap( $validator->validate() ); |
453 | } |
454 | |
455 | /** |
456 | * @return \CirrusSearch\Maintenance\Validators\Validator |
457 | */ |
458 | private function getShardAllocationValidator() { |
459 | global $wgCirrusSearchIndexAllocation; |
460 | return new \CirrusSearch\Maintenance\Validators\ShardAllocationValidator( |
461 | $this->getIndex(), $wgCirrusSearchIndexAllocation, $this ); |
462 | } |
463 | |
464 | protected function validateShardAllocation() { |
465 | $this->unwrap( $this->getShardAllocationValidator()->validate() ); |
466 | } |
467 | |
468 | /** |
469 | * @param string $langCode |
470 | * @param array $availablePlugins |
471 | * @return AnalysisConfigBuilder |
472 | */ |
473 | private function pickAnalyzer( $langCode, array $availablePlugins = [] ) { |
474 | $analysisConfigBuilder = new \CirrusSearch\Maintenance\AnalysisConfigBuilder( |
475 | $langCode, $availablePlugins ); |
476 | $this->outputIndented( 'Picking analyzer...' . |
477 | $analysisConfigBuilder->getDefaultTextAnalyzerType( $langCode ) . |
478 | "\n" ); |
479 | return $analysisConfigBuilder; |
480 | } |
481 | |
482 | /** |
483 | * @throws ConfigException |
484 | */ |
485 | protected function initMappingConfigBuilder() { |
486 | $configFlags = 0; |
487 | if ( $this->prefixSearchStartsWithAny ) { |
488 | $configFlags |= MappingConfigBuilder::PREFIX_START_WITH_ANY; |
489 | } |
490 | if ( $this->phraseSuggestUseText ) { |
491 | $configFlags |= MappingConfigBuilder::PHRASE_SUGGEST_USE_TEXT; |
492 | } |
493 | switch ( $this->indexSuffix ) { |
494 | case Connection::ARCHIVE_DOC_TYPE: |
495 | $mappingConfigBuilder = new ArchiveMappingConfigBuilder( $this->optimizeIndexForExperimentalHighlighter, $configFlags ); |
496 | break; |
497 | default: |
498 | $mappingConfigBuilder = new MappingConfigBuilder( $this->optimizeIndexForExperimentalHighlighter, $configFlags ); |
499 | } |
500 | $this->mapping = $mappingConfigBuilder->buildConfig(); |
501 | $this->safeToOptimizeAnalysisConfig = $mappingConfigBuilder->canOptimizeAnalysisConfig(); |
502 | } |
503 | |
504 | /** |
505 | * @return \Elastica\Index being updated |
506 | */ |
507 | public function getIndex() { |
508 | return $this->getConnection()->getIndex( |
509 | $this->indexBaseName, $this->indexSuffix, $this->indexIdentifier ); |
510 | } |
511 | |
512 | /** |
513 | * @return string name of the index being updated |
514 | */ |
515 | protected function getSpecificIndexName() { |
516 | return $this->getConnection()->getIndexName( |
517 | $this->indexBaseName, $this->indexSuffix, $this->indexIdentifier ); |
518 | } |
519 | |
520 | /** |
521 | * @return string name of the index type being updated |
522 | */ |
523 | protected function getIndexAliasName() { |
524 | return $this->getConnection()->getIndexName( $this->indexBaseName, $this->indexSuffix ); |
525 | } |
526 | |
527 | /** |
528 | * @return string |
529 | */ |
530 | protected function getIndexName() { |
531 | return $this->getConnection()->getIndexName( $this->indexBaseName ); |
532 | } |
533 | |
534 | /** |
535 | * @return \Elastica\Index |
536 | */ |
537 | protected function getOldIndex() { |
538 | return $this->getConnection()->getIndex( $this->indexBaseName, $this->indexSuffix ); |
539 | } |
540 | |
541 | /** |
542 | * Get the merge settings for this index. |
543 | * @return array |
544 | */ |
545 | private function getMergeSettings() { |
546 | global $wgCirrusSearchMergeSettings; |
547 | |
548 | return $wgCirrusSearchMergeSettings[$this->indexSuffix] |
549 | // If there aren't configured merge settings for this index type |
550 | // default to the content type. |
551 | ?? $wgCirrusSearchMergeSettings['content'] |
552 | // It's also fine to not specify merge settings. |
553 | ?? []; |
554 | } |
555 | |
556 | /** |
557 | * @return int Number of shards this index should have |
558 | */ |
559 | private function getShardCount() { |
560 | return $this->getConnection()->getSettings()->getShardCount( $this->indexSuffix ); |
561 | } |
562 | |
563 | /** |
564 | * @return string Number of replicas this index should have. May be a range such as '0-2' |
565 | */ |
566 | private function getReplicaCount() { |
567 | return $this->getConnection()->getSettings()->getReplicaCount( $this->indexSuffix ); |
568 | } |
569 | |
570 | /** |
571 | * @return int Maximum number of shards that can be allocated on a single elasticsearch |
572 | * node. -1 for unlimited. |
573 | */ |
574 | private function getMaxShardsPerNode() { |
575 | return $this->getConnection()->getSettings()->getMaxShardsPerNode( $this->indexSuffix ); |
576 | } |
577 | |
578 | private function initAnalysisConfig() { |
579 | $analysisConfigBuilder = $this->pickAnalyzer( $this->langCode, $this->availablePlugins ); |
580 | |
581 | $this->analysisConfig = $analysisConfigBuilder->buildConfig(); |
582 | if ( $this->safeToOptimizeAnalysisConfig ) { |
583 | $filter = new AnalysisFilter(); |
584 | $deduplicate = $this->getSearchConfig()->get( 'CirrusSearchDeduplicateAnalysis' ); |
585 | // A bit adhoc, this is the list of analyzers that should not be renamed, because |
586 | // they are referenced at query time. |
587 | $protected = [ 'token_reverse' ]; |
588 | [ $this->analysisConfig, $this->mapping ] = $filter |
589 | ->filterAnalysis( $this->analysisConfig, $this->mapping, $deduplicate, $protected ); |
590 | } |
591 | $this->similarityConfig = $analysisConfigBuilder->buildSimilarityConfig(); |
592 | } |
593 | |
594 | /** |
595 | * Output a message and terminate the current script. |
596 | * |
597 | * @param string $msg Error Message |
598 | * @param int $exitCode PHP exit status. Should be in range 1-254 |
599 | * @return never |
600 | */ |
601 | protected function fatalError( $msg, $exitCode = 1 ) { |
602 | try { |
603 | if ( $this->canCleanupCreatedIndex && $this->getIndex()->exists() ) { |
604 | $utils = new ConfigUtils( $this->getConnection()->getClient(), $this ); |
605 | $indexName = $this->getSpecificIndexName(); |
606 | $status = $utils->isIndexLive( $indexName ); |
607 | if ( !$status->isGood() ) { |
608 | $this->output( (string)$status ); |
609 | } elseif ( $status->getValue() === false ) { |
610 | $this->output( "Cleaning up incomplete index {$indexName}\n" ); |
611 | $this->getIndex()->delete(); |
612 | } |
613 | } |
614 | } catch ( \Elastica\Exception\ExceptionInterface $e ) { |
615 | $this->output( "Exception thrown while cleaning up created index: $e\n" ); |
616 | } finally { |
617 | parent::fatalError( $msg, $exitCode ); |
618 | } |
619 | } |
620 | } |
621 | |
622 | $maintClass = UpdateOneSearchIndexConfig::class; |
623 | require_once RUN_MAINTENANCE_IF_MAIN; |