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
9.16
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
82.35% covered (warning)
82.35%
14 / 17
0.00% covered (danger)
0.00%
0 / 1
4.09
 buildArgs
88.89% covered (warning)
88.89%
16 / 18
0.00% covered (danger)
0.00%
0 / 1
4.02
1<?php
2
3namespace CirrusSearch\Maintenance;
4
5use Elastica\Index;
6use 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 bool $searchAllFields
69     * @param array $extraSettings
70     *
71     * @return Status
72     */
73    public function createIndex(
74        $rebuild,
75        $maxShardsPerNode,
76        $shardCount,
77        $replicaCount,
78        $refreshInterval,
79        array $mergeSettings,
80        $searchAllFields,
81        array $extraSettings
82    ) {
83        $args = $this->buildArgs(
84            $maxShardsPerNode,
85            $shardCount,
86            $replicaCount,
87            $refreshInterval,
88            $mergeSettings,
89            $searchAllFields,
90            $extraSettings
91        );
92
93        try {
94            $response = $this->index->create( $args, [ 'recreate' => $rebuild ] );
95
96            if ( $response->hasError() === true ) {
97                return Status::newFatal( $response->getError() );
98            }
99        } catch ( \Elastica\Exception\InvalidException | \Elastica\Exception\ResponseException $ex ) {
100            return Status::newFatal( $ex->getMessage() );
101        }
102
103        // On wikis with particularly large mappings, such as wikibase, sometimes we
104        // see a race where elastic says it created the index, but then a quick followup
105        // request 404's. Wait for green to ensure it's really ready.
106        if ( !$this->utils->waitForGreen( $this->index->getName(), $this->greenTimeout ) ) {
107            return Status::newFatal( 'Created index did not reach green state.' );
108        }
109
110        return Status::newGood();
111    }
112
113    /**
114     * @param int $maxShardsPerNode
115     * @param int $shardCount
116     * @param string $replicaCount
117     * @param int $refreshInterval
118     * @param array $mergeSettings
119     * @param bool $searchAllFields
120     * @param array $extraSettings
121     *
122     * @return array
123     */
124    private function buildArgs(
125        $maxShardsPerNode,
126        $shardCount,
127        $replicaCount,
128        $refreshInterval,
129        array $mergeSettings,
130        $searchAllFields,
131        array $extraSettings
132    ) {
133        $indexSettings = [
134            'number_of_shards' => $shardCount,
135            'auto_expand_replicas' => $replicaCount,
136            'refresh_interval' => $refreshInterval . 's',
137            'analysis' => $this->analysisConfig,
138            'routing' => [
139                'allocation.total_shards_per_node' => $maxShardsPerNode,
140            ]
141        ];
142
143        if ( $mergeSettings ) {
144            $indexSettings['merge.policy'] = $mergeSettings;
145        }
146
147        $similarity = $this->similarityConfig;
148        if ( $similarity ) {
149            $indexSettings['similarity'] = $similarity;
150        }
151
152        if ( $searchAllFields ) {
153            // Use our weighted all field as the default rather than _all which is disabled.
154            $indexSettings['query.default_field'] = 'all';
155        }
156
157        // ideally we should merge $extraSettings to $indexSettings
158        // but existing config might declare keys like "index.mapping.total_fields.limit"
159        // which would not work under the 'index' key.
160        $settings = [ 'index' => $indexSettings ] + $extraSettings;
161        return [ 'settings' => $settings ];
162    }
163
164}