Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
MappingValidator
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 4
132
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 isNaturalSortConfigured
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 validate
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
56
 compareMappingToActual
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace CirrusSearch\Maintenance\Validators;
4
5use CirrusSearch\ElasticaErrorHandler;
6use CirrusSearch\Maintenance\Plugins;
7use CirrusSearch\Maintenance\Printer;
8use Elastica\Exception\ExceptionInterface;
9use Elastica\Index;
10use Elastica\Mapping;
11use MediaWiki\Language\RawMessage;
12use MediaWiki\Status\Status;
13use Wikimedia\Assert\Assert;
14
15class MappingValidator extends Validator {
16    /**
17     * @var Index
18     */
19    private $index;
20
21    /**
22     * @var string
23     */
24    private $masterTimeout;
25
26    /**
27     * @var bool
28     */
29    private $optimizeIndexForExperimentalHighlighter;
30
31    /**
32     * @var array
33     */
34    private $availablePlugins;
35
36    /**
37     * @var array
38     */
39    private $mappingConfig;
40
41    /**
42     * @todo this constructor takes way too much arguments - refactor
43     *
44     * @param Index $index
45     * @param string $masterTimeout
46     * @param bool $optimizeIndexForExperimentalHighlighter
47     * @param array $availablePlugins
48     * @param array $mappingConfig
49     * @param Printer|null $out
50     */
51    public function __construct(
52        Index $index,
53        $masterTimeout,
54        $optimizeIndexForExperimentalHighlighter,
55        array $availablePlugins,
56        array $mappingConfig,
57        ?Printer $out = null
58    ) {
59        parent::__construct( $out );
60
61        $this->index = $index;
62        $this->masterTimeout = $masterTimeout;
63        $this->optimizeIndexForExperimentalHighlighter = $optimizeIndexForExperimentalHighlighter;
64        $this->availablePlugins = $availablePlugins;
65        // Could be supported, but prefer consistency
66        Assert::parameter( isset( $mappingConfig['properties'] ), '$mappingConfig',
67            'Mapping types are no longer supported, properties must be top level' );
68        $this->mappingConfig = $mappingConfig;
69    }
70
71    private function isNaturalSortConfigured() {
72        // awkward much?
73        return isset( $this->mappingConfig['properties']['title']['fields']['natural_sort'] );
74    }
75
76    /**
77     * @return Status
78     */
79    public function validate() {
80        $this->outputIndented( "Validating mappings..." );
81        if ( $this->optimizeIndexForExperimentalHighlighter &&
82            !Plugins::contains( 'experimental-highlighter', $this->availablePlugins )
83        ) {
84            $this->output( "impossible!\n" );
85            return Status::newFatal( new RawMessage(
86                "wgCirrusSearchOptimizeIndexForExperimentalHighlighter is set to true but the " .
87                "'experimental-highlighter' plugin is not installed on all hosts." ) );
88        }
89        if ( $this->isNaturalSortConfigured() &&
90            !Plugins::contains( 'analysis-icu', $this->availablePlugins ) ) {
91            $this->output( "impossible!\n" );
92            return Status::newFatal( new RawMessage(
93                "wgCirrusSearchNaturalTitleSort is set to build but the " .
94                "'analysis-icu' plugin is not installed on all hosts." ) );
95        }
96
97        if ( !$this->compareMappingToActual() ) {
98            $action = new Mapping( $this->mappingConfig['properties'] );
99            $action->setParam( "dynamic", false );
100
101            try {
102                $action->send( $this->index, [
103                    'master_timeout' => $this->masterTimeout,
104                ] );
105                $this->output( "corrected\n" );
106            } catch ( ExceptionInterface $e ) {
107                $this->output( "failed!\n" );
108                $message = ElasticaErrorHandler::extractMessage( $e );
109                return Status::newFatal( new RawMessage(
110                    "Couldn't update existing mappings. You may need to reindex.\nHere is elasticsearch's error message: $message\n" ) );
111            }
112        }
113
114        return Status::newGood();
115    }
116
117    /**
118     * Check that the mapping returned from Elasticsearch is as we want it.
119     *
120     * @return bool is the mapping good enough for us?
121     */
122    private function compareMappingToActual() {
123        $actualMappings = $this->index->getMapping();
124        $this->output( "\n" );
125        $this->outputIndented( "\tValidating mapping..." );
126        if ( $this->checkConfig( $actualMappings, $this->mappingConfig ) ) {
127            $this->output( "ok\n" );
128            return true;
129        } else {
130            $this->output( "different..." );
131            return false;
132        }
133    }
134}