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