Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 80 |
|
0.00% |
0 / 18 |
CRAP | |
0.00% |
0 / 1 |
Maintenance | |
0.00% |
0 / 79 |
|
0.00% |
0 / 18 |
1260 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
finalSetup | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
setupUserTest | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
runChild | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getConnection | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
20 | |||
getSearchConfig | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getMetaStore | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
decideCluster | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
loadSpecialVars | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
done | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
output | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
outputIndented | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
error | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
disablePoolCountersAndLogging | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
maybeCreateMetastore | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
requireCirrusReady | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getBackCompatOption | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
42 | |||
unwrap | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace CirrusSearch\Maintenance; |
4 | |
5 | use CirrusSearch\Connection; |
6 | use CirrusSearch\MetaStore\MetaStoreIndex; |
7 | use CirrusSearch\SearchConfig; |
8 | use CirrusSearch\UserTestingEngine; |
9 | use MediaWiki\MediaWikiServices; |
10 | use MediaWiki\Settings\SettingsBuilder; |
11 | use Status; |
12 | |
13 | // Maintenance class is loaded before autoload, so we need to pull the interface |
14 | require_once __DIR__ . '/Printer.php'; |
15 | |
16 | /** |
17 | * Cirrus helpful extensions to Maintenance. |
18 | * |
19 | * This program is free software; you can redistribute it and/or modify |
20 | * it under the terms of the GNU General Public License as published by |
21 | * the Free Software Foundation; either version 2 of the License, or |
22 | * (at your option) any later version. |
23 | * |
24 | * This program is distributed in the hope that it will be useful, |
25 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
26 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
27 | * GNU General Public License for more details. |
28 | * |
29 | * You should have received a copy of the GNU General Public License along |
30 | * with this program; if not, write to the Free Software Foundation, Inc., |
31 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
32 | * http://www.gnu.org/copyleft/gpl.html |
33 | */ |
34 | abstract class Maintenance extends \Maintenance implements Printer { |
35 | /** |
36 | * @var string The string to indent output with |
37 | */ |
38 | protected static $indent = null; |
39 | |
40 | /** |
41 | * @var Connection|null |
42 | */ |
43 | private $connection; |
44 | |
45 | /** |
46 | * @var SearchConfig |
47 | */ |
48 | private $searchConfig; |
49 | |
50 | public function __construct() { |
51 | parent::__construct(); |
52 | $this->addOption( 'cluster', 'Perform all actions on the specified elasticsearch cluster', |
53 | false, true ); |
54 | $this->addOption( 'userTestTrigger', 'Use config var and profiles set in the user testing ' . |
55 | 'framework, e.g. --userTestTrigger=trigger', false, true ); |
56 | $this->requireExtension( 'CirrusSearch' ); |
57 | } |
58 | |
59 | public function finalSetup( SettingsBuilder $settingsBuilder = null ) { |
60 | parent::finalSetup( $settingsBuilder ); |
61 | |
62 | if ( $this->hasOption( 'userTestTrigger' ) ) { |
63 | $this->setupUserTest(); |
64 | } |
65 | } |
66 | |
67 | /** |
68 | * Setup config vars with the UserTest framework |
69 | */ |
70 | private function setupUserTest() { |
71 | // Configure the UserTesting framework |
72 | // Useful in case an index needs to be built with a |
73 | // test config that is not meant to be the default. |
74 | // This is realistically only usefull to test across |
75 | // multiple clusters. |
76 | // Perhaps setting $wgCirrusSearchIndexBaseName to an |
77 | // alternate value would testing on the same cluster |
78 | // but this index would not receive updates. |
79 | $trigger = $this->getOption( 'userTestTrigger' ); |
80 | $engine = UserTestingEngine::fromConfig( $this->getConfig() ); |
81 | $status = $engine->decideTestByTrigger( $trigger ); |
82 | if ( !$status->isActive() ) { |
83 | $this->fatalError( "Unknown user test trigger: $trigger" ); |
84 | } |
85 | $engine->activateTest( $status ); |
86 | } |
87 | |
88 | /** |
89 | * @param string $maintClass |
90 | * @param string|null $classFile |
91 | * @return \Maintenance |
92 | */ |
93 | public function runChild( $maintClass, $classFile = null ) { |
94 | $child = parent::runChild( $maintClass, $classFile ); |
95 | if ( $child instanceof self ) { |
96 | $child->searchConfig = $this->searchConfig; |
97 | } |
98 | |
99 | return $child; |
100 | } |
101 | |
102 | /** |
103 | * @param string|null $cluster |
104 | * @return Connection |
105 | */ |
106 | public function getConnection( $cluster = null ) { |
107 | if ( $cluster ) { |
108 | if ( !$this->getSearchConfig() instanceof SearchConfig ) { |
109 | // We shouldn't ever get here ... but the makeConfig type signature returns the parent |
110 | // class of SearchConfig so just being extra careful... |
111 | throw new \RuntimeException( 'Expected instanceof CirrusSearch\SearchConfig, but received ' . |
112 | get_class( $this->getSearchConfig() ) ); |
113 | } |
114 | $connection = Connection::getPool( $this->getSearchConfig(), $cluster ); |
115 | } else { |
116 | if ( $this->connection === null ) { |
117 | $cluster = $this->decideCluster(); |
118 | $this->connection = Connection::getPool( $this->getSearchConfig(), $cluster ); |
119 | } |
120 | $connection = $this->connection; |
121 | } |
122 | |
123 | $connection->setTimeout( $this->getSearchConfig()->get( 'CirrusSearchMaintenanceTimeout' ) ); |
124 | |
125 | return $connection; |
126 | } |
127 | |
128 | public function getSearchConfig() { |
129 | if ( $this->searchConfig == null ) { |
130 | $this->searchConfig = MediaWikiServices::getInstance() |
131 | ->getConfigFactory() |
132 | ->makeConfig( 'CirrusSearch' ); |
133 | } |
134 | return $this->searchConfig; |
135 | } |
136 | |
137 | public function getMetaStore( Connection $conn = null ) { |
138 | return new MetaStoreIndex( $conn ?? $this->getConnection(), $this, $this->getSearchConfig() ); |
139 | } |
140 | |
141 | /** |
142 | * @return string|null |
143 | */ |
144 | private function decideCluster() { |
145 | $cluster = $this->getOption( 'cluster', null ); |
146 | if ( $cluster === null ) { |
147 | return null; |
148 | } |
149 | if ( $this->getSearchConfig()->has( 'CirrusSearchServers' ) ) { |
150 | $this->fatalError( 'Not configured for cluster operations.' ); |
151 | } |
152 | return $cluster; |
153 | } |
154 | |
155 | /** |
156 | * Execute a callback function at the end of initialisation |
157 | */ |
158 | public function loadSpecialVars() { |
159 | parent::loadSpecialVars(); |
160 | if ( self::$indent === null ) { |
161 | // First script gets no indentation |
162 | self::$indent = ''; |
163 | } else { |
164 | // Others get one tab beyond the last |
165 | self::$indent .= "\t"; |
166 | } |
167 | } |
168 | |
169 | /** |
170 | * Call to signal that execution of this maintenance script is complete so |
171 | * the next one gets the right indentation. |
172 | */ |
173 | public function done() { |
174 | self::$indent = substr( self::$indent, 1 ); |
175 | } |
176 | |
177 | /** |
178 | * @param string $message |
179 | * @param string|null $channel |
180 | */ |
181 | public function output( $message, $channel = null ) { |
182 | parent::output( $message ); |
183 | } |
184 | |
185 | public function outputIndented( $message ) { |
186 | $this->output( self::$indent . $message ); |
187 | } |
188 | |
189 | /** |
190 | * @param string $err |
191 | * @param int $die deprecated, do not use |
192 | */ |
193 | public function error( $err, $die = 0 ) { |
194 | parent::error( $err, $die ); |
195 | } |
196 | |
197 | /** |
198 | * Disable all pool counters and cirrus query logs. |
199 | * Only useful for maint scripts |
200 | * |
201 | * Ideally this method could be run in the constructor |
202 | * but apparently globals are reset just before the |
203 | * call to execute() |
204 | */ |
205 | protected function disablePoolCountersAndLogging() { |
206 | global $wgPoolCounterConf, $wgCirrusSearchLogElasticRequests; |
207 | |
208 | // Make sure we don't flood the pool counter |
209 | unset( $wgPoolCounterConf['CirrusSearch-Search'] ); |
210 | |
211 | // Don't skew the dashboards by logging these requests to |
212 | // the global request log. |
213 | $wgCirrusSearchLogElasticRequests = false; |
214 | // Disable statsd data collection. |
215 | $stats = MediaWikiServices::getInstance()->getStatsdDataFactory(); |
216 | $stats->setEnabled( false ); |
217 | } |
218 | |
219 | /** |
220 | * Create metastore only if the alias does not already exist |
221 | * @return MetaStoreIndex |
222 | */ |
223 | protected function maybeCreateMetastore() { |
224 | $metastore = new MetaStoreIndex( |
225 | $this->getConnection(), |
226 | $this, |
227 | $this->getSearchConfig() ); |
228 | $status = $metastore->createIfNecessary(); |
229 | $this->unwrap( $status ); |
230 | return $metastore; |
231 | } |
232 | |
233 | protected function requireCirrusReady() { |
234 | // If the version does not exist it's certainly because nothing has been indexed. |
235 | if ( !$this->getMetaStore()->cirrusReady() ) { |
236 | throw new \Exception( |
237 | "Cirrus meta store does not exist, you must index your data first" |
238 | ); |
239 | } |
240 | } |
241 | |
242 | /** |
243 | * Provides support for backward compatible CLI options |
244 | * |
245 | * Requires either one or neither of the two options to be provided. |
246 | * |
247 | * @param string $current The current option to request |
248 | * @param string $bc The old option to provide BC support for |
249 | * @param bool $required True if the option must be provided. When false and no option |
250 | * is provided null is returned. |
251 | * @return mixed |
252 | */ |
253 | protected function getBackCompatOption( string $current, string $bc, bool $required = true ) { |
254 | if ( $this->hasOption( $current ) && $this->hasOption( $bc ) ) { |
255 | $this->error( "\nERROR: --$current cannot be provided with --$bc" ); |
256 | $this->maybeHelp( true ); |
257 | } elseif ( $this->hasOption( $current ) ) { |
258 | return $this->getOption( $current ); |
259 | } elseif ( $this->hasOption( $bc ) ) { |
260 | return $this->getOption( $bc ); |
261 | } elseif ( $required ) { |
262 | $this->error( "\nERROR: Param $current is required" ); |
263 | $this->maybeHelp( true ); |
264 | } else { |
265 | return null; |
266 | } |
267 | } |
268 | |
269 | /** |
270 | * Helper method for Status returning methods, such as via ConfigUtils |
271 | * |
272 | * @param Status $status |
273 | * @return mixed |
274 | */ |
275 | protected function unwrap( Status $status ) { |
276 | if ( !$status->isGood() ) { |
277 | $this->fatalError( (string)$status ); |
278 | } |
279 | return $status->getValue(); |
280 | } |
281 | |
282 | } |