Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
72.04% |
67 / 93 |
|
25.00% |
2 / 8 |
CRAP | |
0.00% |
0 / 1 |
ReindexTask | |
72.04% |
67 / 93 |
|
25.00% |
2 / 8 |
34.56 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isComplete | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
cancel | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
delete | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getResponse | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getStatus | |
66.67% |
14 / 21 |
|
0.00% |
0 / 1 |
8.81 | |||
mergeStatusWithChildren | |
85.96% |
49 / 57 |
|
0.00% |
0 / 1 |
8.18 |
1 | <?php |
2 | |
3 | namespace CirrusSearch\Elastica; |
4 | |
5 | use Elastica\Client; |
6 | use Elastica\Exception\ResponseException; |
7 | use Elastica\Exception\RuntimeException; |
8 | use Elastica\Request; |
9 | use MediaWiki\Logger\LoggerFactory; |
10 | |
11 | class ReindexTask { |
12 | /** @var Client */ |
13 | private $client; |
14 | /** @var string */ |
15 | private $taskId; |
16 | /** @var ReindexResponse|null */ |
17 | private $response; |
18 | /** @var \Psr\Log\LoggerInterface */ |
19 | private $log; |
20 | |
21 | /** |
22 | * @param Client $client |
23 | * @param string $taskId |
24 | */ |
25 | public function __construct( Client $client, $taskId ) { |
26 | $this->client = $client; |
27 | $this->taskId = $taskId; |
28 | $this->log = LoggerFactory::getInstance( 'CirrusSearch' ); |
29 | } |
30 | |
31 | /** |
32 | * @return string |
33 | */ |
34 | public function getId() { |
35 | return $this->taskId; |
36 | } |
37 | |
38 | /** |
39 | * @param bool $check When true queries the remote |
40 | * to see if the task is complete. Otherwise reports |
41 | * last requested status. |
42 | * @return bool True if the reindex task is complete |
43 | */ |
44 | public function isComplete( $check = false ) { |
45 | return $this->response !== null; |
46 | } |
47 | |
48 | /** |
49 | * Cancel the in-progress reindex task. |
50 | * @return bool True if cancel was succesful |
51 | */ |
52 | public function cancel() { |
53 | if ( $this->response ) { |
54 | throw new \Exception( 'Cannot cancel completed task' ); |
55 | } |
56 | |
57 | $response = $this->client->request( "_tasks/{$this->taskId}/_cancel", Request::POST ); |
58 | |
59 | return $response->isOK(); |
60 | } |
61 | |
62 | /** |
63 | * Delete the task |
64 | * @return bool True if delete was successful, false otherwise. |
65 | * Throws Elastica NotFoundException for unknown task (already |
66 | * deleted?) or HttpException for communication failures. |
67 | */ |
68 | public function delete() { |
69 | if ( !$this->response ) { |
70 | throw new \Exception( 'Cannot delete in-progress task' ); |
71 | } |
72 | $response = |
73 | $this->client->getIndex( '.tasks' )->deleteById( $this->taskId ); |
74 | |
75 | return $response->isOK(); |
76 | } |
77 | |
78 | /** |
79 | * Get the final result of the reindexing task. |
80 | * |
81 | * @return ReindexResponse|null The result of the reindex, or null |
82 | * if the reindex is still running. self::getStatus must be used |
83 | * to update the task completion status. |
84 | */ |
85 | public function getResponse() { |
86 | return $this->response; |
87 | } |
88 | |
89 | /** |
90 | * @return ReindexStatus|null The status of the reindex, or null |
91 | * on failure. Transport may also throw exceptions for network |
92 | * failures. |
93 | */ |
94 | public function getStatus() { |
95 | if ( $this->response ) { |
96 | // task complete |
97 | return $this->response; |
98 | } |
99 | |
100 | $response = $this->client->request( "_tasks/{$this->taskId}", Request::GET ); |
101 | if ( !$response->isOK() ) { |
102 | $lastRequest = $this->client->getLastRequest(); |
103 | if ( $lastRequest !== null ) { |
104 | throw new ResponseException( $lastRequest, $response ); |
105 | } else { |
106 | throw new RuntimeException( "Client::getLastRequest() should not be null" ); |
107 | } |
108 | } |
109 | $data = $response->getData(); |
110 | $status = $data['task']['status']; |
111 | |
112 | if ( isset( $data['response'] ) ) { |
113 | // task complete |
114 | $this->response = new ReindexResponse( $data['response'] ); |
115 | |
116 | return $this->response; |
117 | } |
118 | |
119 | /** |
120 | * the task.status.slices array contains null for each incomplete child |
121 | * task. This fetches the children and merges their status in. |
122 | */ |
123 | if ( isset( $data['task']['status']['slices'] ) ) { |
124 | $childResponse = $this->client->request( "_tasks", Request::GET, [], [ |
125 | 'parent_task_id' => $this->taskId, |
126 | 'detailed' => 'true', |
127 | ] ); |
128 | if ( $childResponse->isOK() ) { |
129 | $status = $this->mergeStatusWithChildren( $status, $childResponse->getData() ); |
130 | } |
131 | } |
132 | |
133 | return new ReindexStatus( $status ); |
134 | } |
135 | |
136 | private function mergeStatusWithChildren( array $status, array $childResponse ) { |
137 | foreach ( $childResponse['nodes'] as $nodeData ) { |
138 | foreach ( $nodeData['tasks'] as $taskId => $childData ) { |
139 | $sliceId = $childData['status']['slice_id']; |
140 | $status['slices'][$sliceId] = $childData['status']; |
141 | } |
142 | } |
143 | |
144 | // Below mimics org.elasticsearch.action.bulk.byscroll.BulkByScrollTask.Status::Status |
145 | // except that class doesn't have data about in-progress task's. |
146 | $status['total'] = 0; |
147 | $status['updated'] = 0; |
148 | $status['created'] = 0; |
149 | $status['deleted'] = 0; |
150 | $status['batches'] = 0; |
151 | $status['version_conflicts'] = 0; |
152 | $status['noops'] = 0; |
153 | $status['bulkRetries'] = 0; |
154 | $status['searchRetries'] = 0; |
155 | $status['throttled_millis'] = 0; |
156 | $status['requests_per_second'] = 0; |
157 | $status['throttled_until_millis'] = PHP_INT_MAX; |
158 | $sliceFields = [ |
159 | 'total', |
160 | 'updated', |
161 | 'created', |
162 | 'deleted', |
163 | 'batches', |
164 | 'version_conflicts', |
165 | 'noops', |
166 | 'retries', |
167 | 'throttled_millis', |
168 | 'requests_per_second', |
169 | 'throttled_until_millis', |
170 | ]; |
171 | foreach ( $status['slices'] as $slice ) { |
172 | if ( $slice === null ) { |
173 | // slice has failed catastrophically |
174 | continue; |
175 | } |
176 | $missing_status_fields = array_diff_key( array_flip( $sliceFields ), $slice ); |
177 | if ( $missing_status_fields !== [] ) { |
178 | // slice has missing key fields |
179 | $slice_to_json = json_encode( $slice ); |
180 | $this->log->warning( 'Missing key field(s) for reindex task status', [ |
181 | 'cirrus_reindex_task_slice' => $slice_to_json, |
182 | 'exact_missing_fields' => $missing_status_fields, |
183 | ] ); |
184 | continue; |
185 | } |
186 | $status['total'] += $slice['total']; |
187 | $status['updated'] += $slice['updated']; |
188 | $status['created'] += $slice['created']; |
189 | $status['deleted'] += $slice['deleted']; |
190 | $status['batches'] += $slice['batches']; |
191 | $status['version_conflicts'] += $slice['version_conflicts']; |
192 | $status['noops'] += $slice['noops']; |
193 | $status['retries']['bulk'] += $slice['retries']['bulk']; |
194 | $status['retries']['search'] += $slice['retries']['search']; |
195 | $status['throttled_millis'] += $slice['throttled_millis']; |
196 | $status['requests_per_second'] += $slice['requests_per_second'] === -1 ? INF |
197 | : $slice['requests_per_second']; |
198 | $status['throttled_until_millis'] += min( $status['throttled_until_millis'], |
199 | $slice['throttled_until_millis'] ); |
200 | } |
201 | |
202 | if ( $status['requests_per_second'] === INF ) { |
203 | $status['requests_per_second'] = -1; |
204 | } |
205 | |
206 | return $status; |
207 | } |
208 | } |