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