Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 277
0.00% covered (danger)
0.00%
0 / 28
CRAP
0.00% covered (danger)
0.00%
0 / 1
UpdateOneSearchIndexConfig
0.00% covered (danger)
0.00%
0 / 270
0.00% covered (danger)
0.00%
0 / 28
2550
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 addSharedOptions
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 1
90
 updateVersions
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 validateIndex
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 createIndex
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
2
 getIndexSettingsValidators
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 validateIndexSettings
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 validateAnalyzers
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 validateMapping
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 validateAlias
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 validateSpecificAlias
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
2
 validateAllAlias
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getShardAllocationValidator
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 validateShardAllocation
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 pickAnalyzer
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 initMappingConfigBuilder
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 getIndex
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getSpecificIndexName
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getIndexAliasName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIndexName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOldIndex
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMergeSettings
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getShardCount
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getReplicaCount
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMaxShardsPerNode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 initAnalysisConfig
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 fatalError
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2
3namespace CirrusSearch\Maintenance;
4
5use CirrusSearch\Connection;
6use CirrusSearch\ElasticaErrorHandler;
7use CirrusSearch\Maintenance\Validators\MappingValidator;
8use CirrusSearch\SearchConfig;
9use CirrusSearch\Util;
10
11/**
12 * Update the search configuration on the search backend.
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 * http://www.gnu.org/copyleft/gpl.html
28 */
29
30$IP = getenv( 'MW_INSTALL_PATH' );
31if ( $IP === false ) {
32    $IP = __DIR__ . '/../../..';
33}
34require_once "$IP/maintenance/Maintenance.php";
35require_once __DIR__ . '/../includes/Maintenance/Maintenance.php';
36
37/**
38 * Update the elasticsearch configuration for this index.
39 */
40class UpdateOneSearchIndexConfig extends Maintenance {
41    /**
42     * @var string
43     */
44    private $indexSuffix;
45
46    /**
47     * @var bool Are we going to blow the index away and start from scratch?
48     */
49    private $startOver;
50
51    /**
52     * @var int
53     */
54    private $reindexChunkSize;
55
56    /**
57     * @var string
58     */
59    private $indexBaseName;
60
61    /**
62     * @var string
63     */
64    private $indexIdentifier;
65
66    /**
67     * @var bool
68     */
69    private $reindexAndRemoveOk;
70
71    /**
72     * @var int number of scan slices to use when reindexing
73     */
74    private $reindexSlices;
75
76    /**
77     * @var string language code we're building for
78     */
79    private $langCode;
80
81    /**
82     * @var bool prefix search on any term
83     */
84    private $prefixSearchStartsWithAny;
85
86    /**
87     * @var bool use suggestions on text fields
88     */
89    private $phraseSuggestUseText;
90
91    /**
92     * @var bool print config as it is being checked
93     */
94    private $printDebugCheckConfig;
95
96    /**
97     * @var float how much can the reindexed copy of an index is allowed to deviate from the current
98     * copy without triggering a reindex failure
99     */
100    private $reindexAcceptableCountDeviation;
101
102    /**
103     * @var array filtered analysis config
104     */
105    private $analysisConfig;
106
107    /**
108     * @var array list of available plugins
109     */
110    private $availablePlugins;
111
112    /**
113     * @var array
114     */
115    protected $bannedPlugins;
116
117    /**
118     * @var bool
119     */
120    protected $optimizeIndexForExperimentalHighlighter;
121
122    /**
123     * @var int
124     */
125    protected $refreshInterval;
126
127    /**
128     * @var string
129     */
130    protected $masterTimeout;
131
132    /**
133     * @var array
134     */
135    private $mapping = [];
136
137    /**
138     * @var array
139     */
140    private $similarityConfig;
141
142    /**
143     * @var bool true if the analysis config can be optimized
144     */
145    private $safeToOptimizeAnalysisConfig;
146
147    /** @var bool State flag indicating if we should attempt deleting the index we created */
148    private $canCleanupCreatedIndex = false;
149
150    public function __construct() {
151        parent::__construct();
152        $this->addDescription( "Update the configuration or contents of one search index. This always " .
153            "operates on a single cluster." );
154        $this->addOption( 'indexSuffix', 'Index to update.  Either content or general.', false, true );
155        $this->addOption( 'indexType', 'BC form of --indexSuffix', false, true );
156        self::addSharedOptions( $this );
157    }
158
159    /**
160     * @param Maintenance $maintenance
161     */
162    public static function addSharedOptions( $maintenance ) {
163        $maintenance->addOption( 'startOver', 'Blow away the identified index and rebuild it with ' .
164            'no data.' );
165        $maintenance->addOption( 'indexIdentifier', "Set the identifier of the index to work on.  " .
166            "You'll need this if you have an index in production serving queries and you have " .
167            "to alter some portion of its configuration that cannot safely be done without " .
168            "rebuilding it.  Once you specify a new indexIdentifier for this wiki you'll have to " .
169            "run this script with the same identifier each time.  Defaults to 'current' which " .
170            "infers the currently in use identifier.  You can also use 'now' to set the identifier " .
171            "to the current time in seconds which should give you a unique identifier.", false, true );
172        $maintenance->addOption( 'reindexAndRemoveOk', "If the alias is held by another index then " .
173            "reindex all documents from that index (via the alias) to this one, swing the " .
174            "alias to this index, and then remove other index.  Updates performed while this" .
175            "operation is in progress will be queued up in the job queue.  Defaults to false." );
176        $maintenance->addOption( 'reindexSlices', 'Number of slices to use in reindex. Roughly '
177            . 'equivalent to the level of indexing parallelism. Defaults to number of shards.', false, true );
178        $maintenance->addOption( 'reindexAcceptableCountDeviation', 'How much can the reindexed ' .
179            'copy of an index is allowed to deviate from the current copy without triggering a ' .
180            'reindex failure.  Defaults to 5%.', false, true );
181        $maintenance->addOption( 'reindexChunkSize', 'Documents per shard to reindex in a batch.   ' .
182            'Note when changing the number of shards that the old shard size is used, not the new ' .
183            'one.  If you see many errors submitting documents in bulk but the automatic retry as ' .
184            'singles works then lower this number.  Defaults to 100.', false, true );
185        $maintenance->addOption( 'baseName', 'What basename to use for all indexes, ' .
186            'defaults to wiki id', false, true );
187        $maintenance->addOption( 'debugCheckConfig', 'Print the configuration as it is checked ' .
188            'to help debug unexpected configuration mismatches.' );
189        $maintenance->addOption( 'justAllocation', 'Just validate the shard allocation settings.  Use ' .
190            "when you need to apply new cache warmers but want to be sure that you won't apply any other " .
191            'changes at an inopportune time.' );
192        $maintenance->addOption( 'fieldsToDelete', 'List of of comma separated field names to delete ' .
193            'while reindexing documents (defaults to empty)', false, true );
194        $maintenance->addOption( 'justMapping', 'Just try to update the mapping.' );
195    }
196
197    public function execute() {
198        global $wgLanguageCode,
199            $wgCirrusSearchPhraseSuggestUseText,
200            $wgCirrusSearchPrefixSearchStartsWithAnyWord,
201            $wgCirrusSearchBannedPlugins,
202            $wgCirrusSearchOptimizeIndexForExperimentalHighlighter,
203            $wgCirrusSearchRefreshInterval,
204            $wgCirrusSearchMasterTimeout;
205
206        $this->disablePoolCountersAndLogging();
207
208        $utils = new ConfigUtils( $this->getConnection()->getClient(), $this );
209
210        $this->indexSuffix = $this->getBackCompatOption( 'indexSuffix', 'indexType' );
211        $this->startOver = $this->getOption( 'startOver', false );
212        $this->indexBaseName = $this->getOption( 'baseName',
213            $this->getSearchConfig()->get( SearchConfig::INDEX_BASE_NAME ) );
214        $this->reindexAndRemoveOk = $this->getOption( 'reindexAndRemoveOk', false );
215        $this->reindexSlices = $this->getOption( 'reindexSlices', null );
216        $this->reindexAcceptableCountDeviation = Util::parsePotentialPercent(
217            $this->getOption( 'reindexAcceptableCountDeviation', '5%' ) );
218        $this->reindexChunkSize = $this->getOption( 'reindexChunkSize', 100 );
219        $this->printDebugCheckConfig = $this->getOption( 'debugCheckConfig', false );
220        $this->langCode = $wgLanguageCode;
221        $this->prefixSearchStartsWithAny = $wgCirrusSearchPrefixSearchStartsWithAnyWord;
222        $this->phraseSuggestUseText = $wgCirrusSearchPhraseSuggestUseText;
223        $this->bannedPlugins = $wgCirrusSearchBannedPlugins;
224        $this->optimizeIndexForExperimentalHighlighter = $wgCirrusSearchOptimizeIndexForExperimentalHighlighter;
225        $this->masterTimeout = $wgCirrusSearchMasterTimeout;
226        $this->refreshInterval = $wgCirrusSearchRefreshInterval;
227
228        if ( $this->indexSuffix === Connection::ARCHIVE_INDEX_SUFFIX ) {
229            if ( !$this->getSearchConfig()->get( 'CirrusSearchEnableArchive' ) ) {
230                $this->output( "Warning: Not allowing {$this->indexSuffix}, archives are disabled\n" );
231                return true;
232            }
233            if ( !$this->getConnection()->getSettings()->isPrivateCluster() ) {
234                $this->output( "Warning: Not allowing {$this->indexSuffix} on a non-private cluster\n" );
235                return true;
236            }
237        }
238
239        $this->initMappingConfigBuilder();
240
241        try{
242            $indexSuffixes = $this->getConnection()->getAllIndexSuffixes( null );
243            if ( !in_array( $this->indexSuffix, $indexSuffixes ) ) {
244                $this->fatalError( 'indexSuffix option must be one of ' .
245                    implode( ', ', $indexSuffixes ) );
246            }
247
248            $this->unwrap( $utils->checkElasticsearchVersion() );
249            $this->availablePlugins = $this->unwrap( $utils->scanAvailablePlugins( $this->bannedPlugins ) );
250
251            if ( $this->getOption( 'justAllocation', false ) ) {
252                $this->validateShardAllocation();
253                return true;
254            }
255
256            if ( $this->getOption( 'justMapping', false ) ) {
257                $this->validateMapping();
258                return true;
259            }
260
261            $this->initAnalysisConfig();
262            $this->indexIdentifier = $this->unwrap( $utils->pickIndexIdentifierFromOption(
263                $this->getOption( 'indexIdentifier', 'current' ), $this->getIndexAliasName() ) );
264            $this->validateIndex();
265            $this->validateAnalyzers();
266            $this->validateMapping();
267            $this->validateAlias();
268            $this->updateVersions();
269        } catch ( \Elastica\Exception\Connection\HttpException $e ) {
270            $message = $e->getMessage();
271            $this->output( "\nUnexpected Elasticsearch failure.\n" );
272            $this->fatalError( "Http error communicating with Elasticsearch:  $message.\n" );
273        } catch ( \Elastica\Exception\ExceptionInterface $e ) {
274            $type = get_class( $e );
275            $message = ElasticaErrorHandler::extractMessage( $e );
276            /** @suppress PhanUndeclaredMethod ExceptionInterface has no methods */
277            $trace = $e->getTraceAsString();
278            $this->output( "\nUnexpected Elasticsearch failure.\n" );
279            $this->fatalError( "Elasticsearch failed in an unexpected way. " .
280                "This is always a bug in CirrusSearch.\n" .
281                "Error type: $type\n" .
282                "Message: $message\n" .
283                "Trace:\n" . $trace );
284        }
285
286        return true;
287    }
288
289    /**
290     * @suppress PhanUndeclaredMethod runChild technically returns a
291     *  \Maintenance instance but only \CirrusSearch\Maintenance\Maintenance
292     *  classes have the done method. Just allow it since we know what type of
293     *  maint class is being created
294     */
295    private function updateVersions() {
296        $child = $this->runChild( Metastore::class );
297        $child->done();
298        $child->loadParamsAndArgs(
299            null,
300            array_merge( $this->parameters->getOptions(), [
301                'index-version-basename' => $this->indexBaseName,
302                'update-index-version' => true,
303            ] ),
304            $this->parameters->getArgs()
305        );
306        $child->execute();
307        $child->done();
308    }
309
310    private function validateIndex() {
311        if ( $this->startOver ) {
312            $this->createIndex( true, "Blowing away index to start over...\n" );
313        } elseif ( !$this->getIndex()->exists() ) {
314            $this->createIndex( false, "Creating index...\n" );
315        }
316
317        $this->validateIndexSettings();
318    }
319
320    /**
321     * @param bool $rebuild
322     * @param string $msg
323     */
324    private function createIndex( $rebuild, $msg ) {
325        global $wgCirrusSearchAllFields, $wgCirrusSearchExtraIndexSettings;
326
327        $this->canCleanupCreatedIndex = true;
328        $index = $this->getIndex();
329        $indexCreator = new \CirrusSearch\Maintenance\IndexCreator(
330            $index,
331            new ConfigUtils( $index->getClient(), $this ),
332            $this->analysisConfig,
333            $this->similarityConfig
334        );
335
336        $this->outputIndented( $msg );
337
338        $this->unwrap( $indexCreator->createIndex(
339            $rebuild,
340            $this->getMaxShardsPerNode(),
341            $this->getShardCount(),
342            $this->getReplicaCount(),
343            $this->refreshInterval,
344            $this->getMergeSettings(),
345            $wgCirrusSearchAllFields['build'],
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            list( $this->analysisConfig, $this->mapping ) = $filter
585                ->filterAnalysis( $this->analysisConfig, $this->mapping );
586        }
587        $this->similarityConfig = $analysisConfigBuilder->buildSimilarityConfig();
588    }
589
590    /**
591     * Output a message and terminate the current script.
592     *
593     * @param string $msg Error Message
594     * @param int $exitCode PHP exit status. Should be in range 1-254
595     * @return never
596     */
597    protected function fatalError( $msg, $exitCode = 1 ) {
598        try {
599            if ( $this->canCleanupCreatedIndex && $this->getIndex()->exists() ) {
600                $utils = new ConfigUtils( $this->getConnection()->getClient(), $this );
601                $indexName = $this->getSpecificIndexName();
602                $status = $utils->isIndexLive( $indexName );
603                if ( !$status->isGood() ) {
604                    $this->output( (string)$status );
605                } elseif ( $status->getValue() === false ) {
606                    $this->output( "Cleaning up incomplete index {$indexName}\n" );
607                    $this->getIndex()->delete();
608                }
609            }
610        } catch ( \Elastica\Exception\ExceptionInterface $e ) {
611            $this->output( "Exception thrown while cleaning up created index: $e\n" );
612        } finally {
613            parent::fatalError( $msg, $exitCode );
614        }
615    }
616}
617
618$maintClass = UpdateOneSearchIndexConfig::class;
619require_once RUN_MAINTENANCE_IF_MAIN;