Completed
Pull Request — master (#917)
by Dmitry
08:52
created

AliasProcessor::buildAliasUpdateRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2
Metric Value
dl 0
loc 17
ccs 8
cts 8
cp 1
rs 9.4285
cc 2
eloc 8
nc 2
nop 3
crap 2
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
     *
49
     * @throws AliasIsIndexException
50
     * @throws \RuntimeException
51
     */
52 7
    public function switchIndexAlias(IndexConfig $indexConfig, Index $index, $force = false)
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
            $this->deleteIndex($client, $aliasName);
68
        }
69
70
        try {
71 5
            $aliasUpdateRequest = $this->buildAliasUpdateRequest($oldIndexName, $aliasName, $newIndexName);
72 5
            $client->request('_aliases', 'POST', $aliasUpdateRequest);
73 1
        } catch (ExceptionInterface $e) {
74 1
            $this->cleanupRenameFailure($client, $newIndexName, $e);
75
        }
76
77
        // Delete the old index after the alias has been switched
78 4
        if (null !== $oldIndexName) {
79 2
            $this->deleteIndex($client, $oldIndexName);
80
        }
81 4
    }
82
83
    /**
84
     * Builds an ElasticSearch request to rename or create an alias.
85
     *
86
     * @param string|null $aliasedIndex
87
     * @param string $aliasName
88
     * @param string $newIndexName
89
     * @return array
90
     */
91 5
    private function buildAliasUpdateRequest($aliasedIndex, $aliasName, $newIndexName)
92
    {
93 5
        $aliasUpdateRequest = array('actions' => array());
94 5
        if (null !== $aliasedIndex) {
95
            // if the alias is set - add an action to remove it
96 3
            $aliasUpdateRequest['actions'][] = array(
97 3
                'remove' => array('index' => $aliasedIndex, 'alias' => $aliasName),
98
            );
99
        }
100
101
        // add an action to point the alias to the new index
102 5
        $aliasUpdateRequest['actions'][] = array(
103 5
            'add' => array('index' => $newIndexName, 'alias' => $aliasName),
104
        );
105
106 5
        return $aliasUpdateRequest;
107
    }
108
109
    /**
110
     * Cleans up an index when we encounter a failure to rename the alias.
111
     *
112
     * @param Client $client
113
     * @param string $indexName
114
     * @param \Exception $renameAliasException
115
     */
116 1
    private function cleanupRenameFailure(Client $client, $indexName, \Exception $renameAliasException)
117
    {
118 1
        $additionalError = '';
119
        try {
120 1
            $this->deleteIndex($client, $indexName);
121
        } catch (ExceptionInterface $deleteNewIndexException) {
122
            $additionalError = sprintf(
123
                'Tried to delete newly built index %s, but also failed: %s',
124
                $indexName,
125
                $deleteNewIndexException->getMessage()
126
            );
127
        }
128
129 1
        throw new \RuntimeException(sprintf(
130 1
            'Failed to updated index alias: %s. %s',
131 1
            $renameAliasException->getMessage(),
132 1
            $additionalError ?: sprintf('Newly built index %s was deleted', $indexName)
133 1
        ), 0, $renameAliasException);
134
    }
135
136
    /**
137
     * Delete an index.
138
     *
139
     * @param Client $client
140
     * @param string $indexName Index name to delete
141
     */
142 4
    private function deleteIndex(Client $client, $indexName)
143
    {
144
        try {
145 4
            $path = sprintf("%s", $indexName);
146 4
            $client->request($path, Request::DELETE);
147
        } catch (ExceptionInterface $deleteOldIndexException) {
148
            throw new \RuntimeException(sprintf(
149
                'Failed to delete index %s with message: %s',
150
                $indexName,
151
                $deleteOldIndexException->getMessage()
152
            ), 0, $deleteOldIndexException);
153
        }
154 4
    }
155
156
    /**
157
     * Returns the name of a single index that an alias points to or throws
158
     * an exception if there is more than one.
159
     *
160
     * @param Client $client
161
     * @param string $aliasName Alias name
162
     *
163
     * @return string|null
164
     *
165
     * @throws AliasIsIndexException
166
     */
167 7
    private function getAliasedIndex(Client $client, $aliasName)
168
    {
169 7
        $aliasesInfo = $client->request('_aliases', 'GET')->getData();
170 7
        $aliasedIndexes = array();
171
172 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...
173 6
            if ($indexName === $aliasName) {
174 2
                throw new AliasIsIndexException($indexName);
175
            }
176 4
            if (!isset($indexInfo['aliases'])) {
177
                continue;
178
            }
179
180 4
            $aliases = array_keys($indexInfo['aliases']);
181 4
            if (in_array($aliasName, $aliases)) {
182 4
                $aliasedIndexes[] = $indexName;
183
            }
184
        }
185
186 5
        if (count($aliasedIndexes) > 1) {
187 1
            throw new \RuntimeException(sprintf(
188
                'Alias %s is used for multiple indexes: [%s]. Make sure it\'s'.
189 1
                'either not used or is assigned to one index only',
190
                $aliasName,
191 1
                implode(', ', $aliasedIndexes)
192
            ));
193
        }
194
195 4
        return array_shift($aliasedIndexes);
196
    }
197
}
198