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