Completed
Pull Request — master (#1646)
by Karel
04:11
created

AliasProcessor::getAliasedIndex()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 6.0131

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 13
cts 14
cp 0.9286
rs 8.8977
c 0
b 0
f 0
cc 6
nc 9
nop 2
crap 6.0131
1
<?php
2
3
/*
4
 * This file is part of the FOSElasticaBundle package.
5
 *
6
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
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 2
    public function setRootName(IndexConfig $indexConfig, Index $index)
27
    {
28 2
        $index->overrideName(
29 2
            sprintf('%s_%s',
30 2
                $indexConfig->getElasticSearchName(),
31 2
                date('Y-m-d-His')
32
            )
33
        );
34 2
    }
35
36
    /**
37
     * Switches an index to become the new target for an alias. Only applies for
38
     * indexes that are set to use aliases.
39
     *
40
     * $force will delete an index encountered where an alias is expected.
41
     *
42
     * @param bool $force
43
     * @param bool $delete
44
     *
45
     * @throws AliasIsIndexException
46
     */
47 7
    public function switchIndexAlias(IndexConfig $indexConfig, Index $index, $force = false, $delete = true)
48
    {
49 7
        $client = $index->getClient();
50
51 7
        $aliasName = $indexConfig->getElasticSearchName();
52 7
        $oldIndexName = null;
53 7
        $newIndexName = $index->getName();
54
55
        try {
56 7
            $oldIndexName = $this->getAliasedIndex($client, $aliasName);
57 3
        } catch (AliasIsIndexException $e) {
58 2
            if (!$force) {
59 1
                throw $e;
60
            }
61
62 1
            if ($delete) {
63 1
                $this->deleteIndex($client, $aliasName);
64
            } else {
65
                $this->closeIndex($client, $aliasName);
66
            }
67
        }
68
69
        try {
70 5
            $aliasUpdateRequest = $this->buildAliasUpdateRequest($oldIndexName, $aliasName, $newIndexName);
71 5
            $client->request('_aliases', 'POST', $aliasUpdateRequest);
72 1
        } catch (ExceptionInterface $e) {
73 1
            $this->cleanupRenameFailure($client, $newIndexName, $e);
74
        }
75
76
        // Delete the old index after the alias has been switched
77 4
        if (null !== $oldIndexName) {
78 2
            if ($delete) {
79 2
                $this->deleteIndex($client, $oldIndexName);
80
            } else {
81
                $this->closeIndex($client, $oldIndexName);
82
            }
83
        }
84 4
    }
85
86
    /**
87
     * Builds an ElasticSearch request to rename or create an alias.
88
     *
89
     * @param string|null $aliasedIndex
90
     * @param string      $aliasName
91
     * @param string      $newIndexName
92
     *
93
     * @return array
94
     */
95 5
    private function buildAliasUpdateRequest($aliasedIndex, $aliasName, $newIndexName)
96
    {
97 5
        $aliasUpdateRequest = ['actions' => []];
98 5
        if (null !== $aliasedIndex) {
99
            // if the alias is set - add an action to remove it
100 3
            $aliasUpdateRequest['actions'][] = [
101 3
                'remove' => ['index' => $aliasedIndex, 'alias' => $aliasName],
102
            ];
103
        }
104
105
        // add an action to point the alias to the new index
106 5
        $aliasUpdateRequest['actions'][] = [
107 5
            'add' => ['index' => $newIndexName, 'alias' => $aliasName],
108
        ];
109
110 5
        return $aliasUpdateRequest;
111
    }
112
113
    /**
114
     * Cleans up an index when we encounter a failure to rename the alias.
115
     *
116
     * @param string $indexName
117
     */
118 1
    private function cleanupRenameFailure(Client $client, $indexName, \Exception $renameAliasException)
119
    {
120 1
        $additionalError = '';
121
        try {
122 1
            $this->deleteIndex($client, $indexName);
123
        } catch (ExceptionInterface $deleteNewIndexException) {
124
            $additionalError = sprintf(
125
                'Tried to delete newly built index %s, but also failed: %s',
126
                $indexName,
127
                $deleteNewIndexException->getMessage()
128
            );
129
        }
130
131 1
        throw new \RuntimeException(sprintf('Failed to updated index alias: %s. %s', $renameAliasException->getMessage(), $additionalError ?: sprintf('Newly built index %s was deleted', $indexName)), 0, $renameAliasException);
132
    }
133
134
    /**
135
     * Delete an index.
136
     *
137
     * @param string $indexName Index name to delete
138
     */
139 4 View Code Duplication
    private function deleteIndex(Client $client, $indexName)
140
    {
141
        try {
142 4
            $path = sprintf('%s', $indexName);
143 4
            $client->request($path, Request::DELETE);
144
        } catch (ExceptionInterface $deleteOldIndexException) {
145
            throw new \RuntimeException(sprintf('Failed to delete index %s with message: %s', $indexName, $deleteOldIndexException->getMessage()), 0, $deleteOldIndexException);
146
        }
147 4
    }
148
149
    /**
150
     * Close an index.
151
     *
152
     * @param string $indexName
153
     */
154 View Code Duplication
    private function closeIndex(Client $client, $indexName)
155
    {
156
        try {
157
            $path = sprintf('%s/_close', $indexName);
158
            $client->request($path, Request::POST);
159
        } catch (ExceptionInterface $e) {
160
            throw new \RuntimeException(sprintf('Failed to close index %s with message: %s', $indexName, $e->getMessage()), 0, $e);
161
        }
162
    }
163
164
    /**
165
     * Returns the name of a single index that an alias points to or throws
166
     * an exception if there is more than one.
167
     *
168
     * @param string $aliasName Alias name
169
     *
170
     * @return string|null
171
     *
172
     * @throws AliasIsIndexException
173
     */
174 7
    private function getAliasedIndex(Client $client, $aliasName)
175
    {
176 7
        $aliasesInfo = $client->request('_aliases', 'GET')->getData();
177 7
        $aliasedIndexes = [];
178
179 7
        foreach ($aliasesInfo as $indexName => $indexInfo) {
180 6
            if ($indexName === $aliasName) {
181 2
                throw new AliasIsIndexException($indexName);
182
            }
183 4
            if (!isset($indexInfo['aliases'])) {
184
                continue;
185
            }
186
187 4
            $aliases = array_keys($indexInfo['aliases']);
188 4
            if (in_array($aliasName, $aliases, true)) {
189 4
                $aliasedIndexes[] = $indexName;
190
            }
191
        }
192
193 5
        if (count($aliasedIndexes) > 1) {
194 1
            throw new \RuntimeException(sprintf('Alias %s is used for multiple indexes: [%s]. Make sure it\'s'.'either not used or is assigned to one index only', $aliasName, implode(', ', $aliasedIndexes)));
195
        }
196
197 4
        return array_shift($aliasedIndexes);
198
    }
199
}
200