Completed
Pull Request — master (#1093)
by Oleg
06:31 queued 02:17
created

AliasProcessor::switchIndexAlias()   C

Complexity

Conditions 7
Paths 28

Size

Total Lines 38
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7.0422

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 38
ccs 19
cts 21
cp 0.9048
rs 6.7272
cc 7
eloc 24
nc 28
nop 4
crap 7.0422
1
<?php
2
3
/**
4
 * This file is part of the FOSElasticaBundle project.
5
 *
6
 * (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace FOS\ElasticaBundle\Index;
13
14
use Elastica\Client;
15
use Elastica\Exception\ExceptionInterface;
16
use Elastica\Request;
17
use FOS\ElasticaBundle\Configuration\IndexConfig;
18
use FOS\ElasticaBundle\Elastica\Index;
19
use FOS\ElasticaBundle\Exception\AliasIsIndexException;
20
21
class AliasProcessor
22
{
23
    /**
24
     * Sets the randomised root name for an index.
25
     *
26
     * @param IndexConfig $indexConfig
27
     * @param Index       $index
28
     */
29 2
    public function setRootName(IndexConfig $indexConfig, Index $index)
30
    {
31 2
        $index->overrideName(
32 2
            sprintf('%s_%s',
33 2
                $indexConfig->getElasticSearchName(),
34 2
                date('Y-m-d-His')
35
            )
36
        );
37 2
    }
38
39
    /**
40
     * Switches an index to become the new target for an alias. Only applies for
41
     * indexes that are set to use aliases.
42
     *
43
     * $force will delete an index encountered where an alias is expected.
44
     *
45
     * @param IndexConfig $indexConfig
46
     * @param Index       $index
47
     * @param bool        $force
48
     * @param bool        $delete
49
     *
50
     * @throws AliasIsIndexException
51
     */
52 7
    public function switchIndexAlias(IndexConfig $indexConfig, Index $index, $force = false, $delete = true)
53
    {
54 7
        $client = $index->getClient();
55
56 7
        $aliasName = $indexConfig->getElasticSearchName();
57 7
        $oldIndexName = null;
58 7
        $newIndexName = $index->getName();
59
60
        try {
61 7
            $oldIndexName = $this->getAliasedIndex($client, $aliasName);
62 3
        } catch (AliasIsIndexException $e) {
63 2
            if (!$force) {
64 1
                throw $e;
65
            }
66
67 1
            if ($delete) {
68 1
                $this->deleteIndex($client, $aliasName);
69
            } else {
70
                $this->closeIndex($client, $aliasName);
71
            }
72
        }
73
74
        try {
75 5
            $aliasUpdateRequest = $this->buildAliasUpdateRequest($oldIndexName, $aliasName, $newIndexName);
76 5
            $client->request('_aliases', 'POST', $aliasUpdateRequest);
77 1
        } catch (ExceptionInterface $e) {
78 1
            $this->cleanupRenameFailure($client, $newIndexName, $e);
79
        }
80
81
        // Delete the old index after the alias has been switched
82 4
        if (null !== $oldIndexName) {
83 2
            if ($delete) {
84 2
                $this->deleteIndex($client, $oldIndexName);
85
            } else {
86
                $this->closeIndex($client, $oldIndexName);
87
            }
88
        }
89 4
    }
90
91
    /**
92
     * Builds an ElasticSearch request to rename or create an alias.
93
     *
94
     * @param string|null $aliasedIndex
95
     * @param string $aliasName
96
     * @param string $newIndexName
97
     * @return array
98
     */
99 5
    private function buildAliasUpdateRequest($aliasedIndex, $aliasName, $newIndexName)
100
    {
101 5
        $aliasUpdateRequest = array('actions' => array());
102 5
        if (null !== $aliasedIndex) {
103
            // if the alias is set - add an action to remove it
104 3
            $aliasUpdateRequest['actions'][] = array(
105 3
                'remove' => array('index' => $aliasedIndex, 'alias' => $aliasName),
106
            );
107
        }
108
109
        // add an action to point the alias to the new index
110 5
        $aliasUpdateRequest['actions'][] = array(
111 5
            'add' => array('index' => $newIndexName, 'alias' => $aliasName),
112
        );
113
114 5
        return $aliasUpdateRequest;
115
    }
116
117
    /**
118
     * Cleans up an index when we encounter a failure to rename the alias.
119
     *
120
     * @param Client $client
121
     * @param string $indexName
122
     * @param \Exception $renameAliasException
123
     */
124 1
    private function cleanupRenameFailure(Client $client, $indexName, \Exception $renameAliasException)
125
    {
126 1
        $additionalError = '';
127
        try {
128 1
            $this->deleteIndex($client, $indexName);
129
        } catch (ExceptionInterface $deleteNewIndexException) {
130
            $additionalError = sprintf(
131
                'Tried to delete newly built index %s, but also failed: %s',
132
                $indexName,
133
                $deleteNewIndexException->getMessage()
134
            );
135
        }
136
137 1
        throw new \RuntimeException(sprintf(
138 1
            'Failed to updated index alias: %s. %s',
139 1
            $renameAliasException->getMessage(),
140 1
            $additionalError ?: sprintf('Newly built index %s was deleted', $indexName)
141 1
        ), 0, $renameAliasException);
142
    }
143
144
    /**
145
     * Delete an index.
146
     *
147
     * @param Client $client
148
     * @param string $indexName Index name to delete
149
     */
150 4 View Code Duplication
    private function deleteIndex(Client $client, $indexName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
151
    {
152
        try {
153 4
            $path = sprintf("%s", $indexName);
154 4
            $client->request($path, Request::DELETE);
155
        } catch (ExceptionInterface $deleteOldIndexException) {
156
            throw new \RuntimeException(sprintf(
157
                'Failed to delete index %s with message: %s',
158
                $indexName,
159
                $deleteOldIndexException->getMessage()
160
            ), 0, $deleteOldIndexException);
161
        }
162 4
    }
163
164
    /**
165
     * Close an index
166
     *
167
     * @param Client $client
168
     * @param string $indexName
169
     */
170 View Code Duplication
    private function closeIndex(Client $client, $indexName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
171
    {
172
        try {
173
            $path = sprintf("%s/_close", $indexName);
174
            $client->request($path, Request::POST);
175
        } catch (ExceptionInterface $e) {
176
            throw new \RuntimeException(
177
                sprintf(
178
                    'Failed to close index %s with message: %s',
179
                    $indexName,
180
                    $e->getMessage()
181
                ),
182
                0,
183
                $e
184
            );
185
        }
186
    }
187
188
    /**
189
     * Returns the name of a single index that an alias points to or throws
190
     * an exception if there is more than one.
191
     *
192
     * @param Client $client
193
     * @param string $aliasName Alias name
194
     *
195
     * @return string|null
196
     *
197
     * @throws AliasIsIndexException
198
     */
199 7
    private function getAliasedIndex(Client $client, $aliasName)
200
    {
201 7
        $aliasesInfo = $client->request('_aliases', 'GET')->getData();
202 7
        $aliasedIndexes = array();
203
204 7
        foreach ($aliasesInfo as $indexName => $indexInfo) {
0 ignored issues
show
Bug introduced by
The expression $aliasesInfo of type array|object<Elastica\Response> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
205 6
            if ($indexName === $aliasName) {
206 2
                throw new AliasIsIndexException($indexName);
207
            }
208 4
            if (!isset($indexInfo['aliases'])) {
209
                continue;
210
            }
211
212 4
            $aliases = array_keys($indexInfo['aliases']);
213 4
            if (in_array($aliasName, $aliases)) {
214 4
                $aliasedIndexes[] = $indexName;
215
            }
216
        }
217
218 5
        if (count($aliasedIndexes) > 1) {
219 1
            throw new \RuntimeException(sprintf(
220
                'Alias %s is used for multiple indexes: [%s]. Make sure it\'s'.
221 1
                'either not used or is assigned to one index only',
222
                $aliasName,
223 1
                implode(', ', $aliasedIndexes)
224
            ));
225
        }
226
227 4
        return array_shift($aliasedIndexes);
228
    }
229
}
230