Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.50% covered (warning)
87.50%
35 / 40
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
IndexCreator
87.50% covered (warning)
87.50%
35 / 40
33.33% covered (danger)
33.33%
1 / 3
8.12
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 createIndex
84.21% covered (warning)
84.21%
16 / 19
0.00% covered (danger)
0.00%
0 / 1
4.06
 buildSettings
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
3.02
1<?php
2
3namespace CirrusSearch\Maintenance;
4
5use CirrusSearch\ReplicaCount;
6use Elastica\Index;
7use MediaWiki\Status\Status;
8
9class IndexCreator {
10
11    /**
12     * @var Index
13     */
14    private $index;
15
16    /**
17     * @var array
18     */
19    private $analysisConfig;
20
21    /**
22     * @var array|null
23     */
24    private $similarityConfig;
25
26    /**
27     * @var array
28     */
29    private $mappings;
30
31    /**
32     * @var ConfigUtils
33     */
34    private $utils;
35
36    /**
37     * @var int How long to wait for index to become green, in seconds
38     */
39    private $greenTimeout;
40
41    /**
42     * @param Index $index
43     * @param ConfigUtils $utils
44     * @param array $analysisConfig
45     * @param array $mappings
46     * @param array|null $similarityConfig
47     * @param int $greenTimeout How long to wait for index to become green, in seconds
48     */
49    public function __construct(
50        Index $index,
51        ConfigUtils $utils,
52        array $analysisConfig,
53        array $mappings,
54        ?array $similarityConfig = null,
55        $greenTimeout = 120
56    ) {
57        $this->index = $index;
58        $this->utils = $utils;
59        $this->analysisConfig = $analysisConfig;
60        $this->similarityConfig = $similarityConfig;
61        $this->mappings = $mappings;
62        $this->greenTimeout = $greenTimeout;
63    }
64
65    /**
66     * @param bool $rebuild
67     * @param int $maxShardsPerNode
68     * @param int $shardCount
69     * @param ReplicaCount $replicaCount
70     * @param int $refreshInterval
71     * @param array $mergeSettings
72     * @param array $extraSettings
73     *
74     * @return Status
75     */
76    public function createIndex(
77        $rebuild,
78        $maxShardsPerNode,
79        $shardCount,
80        ReplicaCount $replicaCount,
81        $refreshInterval,
82        array $mergeSettings,
83        array $extraSettings
84    ) {
85        $args = [
86            'settings' => $this->buildSettings(
87                $maxShardsPerNode,
88                $shardCount,
89                $replicaCount,
90                $refreshInterval,
91                $mergeSettings,
92                $extraSettings
93            ),
94            'mappings' => $this->mappings,
95        ];
96
97        try {
98            $response = $this->index->create( $args, [ 'recreate' => $rebuild ] );
99
100            if ( $response->hasError() === true ) {
101                return Status::newFatal( $response->getError() );
102            }
103        } catch ( \Elastica\Exception\InvalidException | \Elastica\Exception\ResponseException $ex ) {
104            return Status::newFatal( $ex->getMessage() );
105        }
106
107        // On wikis with particularly large mappings, such as wikibase, sometimes we
108        // see a race where the search engine says it created the index, but then a
109        // quick followup request 404's. Wait for green to ensure it's really ready.
110        if ( !$this->utils->waitForGreen( $this->index->getName(), $this->greenTimeout ) ) {
111            return Status::newFatal( 'Created index did not reach green state.' );
112        }
113
114        return Status::newGood();
115    }
116
117    /**
118     * @param int $maxShardsPerNode
119     * @param int $shardCount
120     * @param ReplicaCount $replicaCount
121     * @param int $refreshInterval
122     * @param array $mergeSettings
123     * @param array $extraSettings
124     *
125     * @return array
126     */
127    private function buildSettings(
128        $maxShardsPerNode,
129        $shardCount,
130        ReplicaCount $replicaCount,
131        $refreshInterval,
132        array $mergeSettings,
133        array $extraSettings
134    ) {
135        $indexSettings = [
136            'number_of_shards' => $shardCount,
137            'refresh_interval' => $refreshInterval . 's',
138            'analysis' => $this->analysisConfig,
139            'routing' => [
140                'allocation.total_shards_per_node' => $maxShardsPerNode,
141            ]
142        ] + $replicaCount->toCreateSettings();
143
144        if ( $mergeSettings ) {
145            $indexSettings['merge.policy'] = $mergeSettings;
146        }
147
148        $similarity = $this->similarityConfig;
149        if ( $similarity ) {
150            $indexSettings['similarity'] = $similarity;
151        }
152
153        // Use our weighted all field as the default rather than _all which is disabled.
154        $indexSettings['query.default_field'] = 'all';
155
156        // ideally we should merge $extraSettings to $indexSettings
157        // but existing config might declare keys like "index.mapping.total_fields.limit"
158        // which would not work under the 'index' key.
159        return [ 'index' => $indexSettings ] + $extraSettings;
160    }
161
162}