AliasProcessor::switchIndexAlias()   B
last analyzed

Complexity

Conditions 7
Paths 28

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 7.0052

Importance

Changes 0
Metric Value
dl 0
loc 38
ccs 20
cts 21
cp 0.9524
rs 8.3786
c 0
b 0
f 0
cc 7
nc 28
nop 4
crap 7.0052
1
<?php
2
3
/*
4
 * This file is part of the FOSElasticaBundle package.
5
 *
6
 * (c) FriendsOfSymfony <https://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
     * @throws AliasIsIndexException
43
     */
44 8
    public function switchIndexAlias(IndexConfig $indexConfig, Index $index, bool $force = false, bool $delete = true)
45
    {
46 8
        $client = $index->getClient();
47
48 8
        $aliasName = $indexConfig->getElasticSearchName();
49 8
        $oldIndexName = null;
50 8
        $newIndexName = $index->getName();
51
52
        try {
53 8
            $oldIndexName = $this->getAliasedIndex($client, $aliasName);
54 3
        } catch (AliasIsIndexException $e) {
55 2
            if (!$force) {
56 1
                throw $e;
57
            }
58
59 1
            if ($delete) {
60 1
                $this->deleteIndex($client, $aliasName);
61
            } else {
62
                $this->closeIndex($client, $aliasName);
63
            }
64
        }
65
66
        try {
67 6
            $aliasUpdateRequest = $this->buildAliasUpdateRequest($oldIndexName, $aliasName, $newIndexName);
68 6
            $client->request('_aliases', 'POST', $aliasUpdateRequest);
69 1
        } catch (ExceptionInterface $e) {
70 1
            $this->cleanupRenameFailure($client, $newIndexName, $e);
71
        }
72
73
        // Delete the old index after the alias has been switched
74 5
        if (null !== $oldIndexName) {
75 3
            if ($delete) {
76 2
                $this->deleteIndex($client, $oldIndexName);
77
            } else {
78 1
                $this->closeIndex($client, $oldIndexName);
79
            }
80
        }
81 5
    }
82
83
    /**
84
     * Builds an ElasticSearch request to rename or create an alias.
85
     */
86 6
    private function buildAliasUpdateRequest(?string $aliasedIndex, string $aliasName, string $newIndexName): array
87
    {
88 6
        $aliasUpdateRequest = ['actions' => []];
89 6
        if (null !== $aliasedIndex) {
90
            // if the alias is set - add an action to remove it
91 4
            $aliasUpdateRequest['actions'][] = [
92 4
                'remove' => ['index' => $aliasedIndex, 'alias' => $aliasName],
93
            ];
94
        }
95
96
        // add an action to point the alias to the new index
97 6
        $aliasUpdateRequest['actions'][] = [
98 6
            'add' => ['index' => $newIndexName, 'alias' => $aliasName],
99
        ];
100
101 6
        return $aliasUpdateRequest;
102
    }
103
104
    /**
105
     * Cleans up an index when we encounter a failure to rename the alias.
106
     */
107 1
    private function cleanupRenameFailure(Client $client, string $indexName, \Exception $renameAliasException): void
108
    {
109 1
        $additionalError = '';
110
        try {
111 1
            $this->deleteIndex($client, $indexName);
112
        } catch (ExceptionInterface $deleteNewIndexException) {
113
            $additionalError = \sprintf(
114
                'Tried to delete newly built index %s, but also failed: %s',
115
                $indexName,
116
                $deleteNewIndexException->getMessage()
117
            );
118
        }
119
120 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);
121
    }
122
123
    /**
124
     * Delete an index.
125
     */
126 4
    private function deleteIndex(Client $client, string $indexName): void
127
    {
128
        try {
129 4
            $path = $indexName;
130 4
            $client->request($path, Request::DELETE);
131
        } catch (ExceptionInterface $deleteOldIndexException) {
132
            throw new \RuntimeException(\sprintf('Failed to delete index "%s" with message: "%s"', $indexName, $deleteOldIndexException->getMessage()), 0, $deleteOldIndexException);
133
        }
134 4
    }
135
136
    /**
137
     * Close an index.
138
     */
139 1
    private function closeIndex(Client $client, string $indexName): void
140
    {
141
        try {
142 1
            $path = $indexName.'/_close';
143 1
            $client->request($path, Request::POST);
144
        } catch (ExceptionInterface $e) {
145
            throw new \RuntimeException(\sprintf('Failed to close index "%s" with message: "%s"', $indexName, $e->getMessage()), 0, $e);
146
        }
147 1
    }
148
149
    /**
150
     * Returns the name of a single index that an alias points to or throws
151
     * an exception if there is more than one.
152
     *
153
     * @throws AliasIsIndexException
154
     */
155 8
    private function getAliasedIndex(Client $client, string $aliasName): ?string
156
    {
157 8
        $aliasesInfo = $client->request('_aliases', 'GET')->getData();
158 8
        $aliasedIndexes = [];
159
160 8
        foreach ($aliasesInfo as $indexName => $indexInfo) {
161 7
            if ($indexName === $aliasName) {
162 2
                throw new AliasIsIndexException($indexName);
163
            }
164 5
            if (!isset($indexInfo['aliases'])) {
165
                continue;
166
            }
167
168 5
            $aliases = \array_keys($indexInfo['aliases']);
169 5
            if (\in_array($aliasName, $aliases, true)) {
170 5
                $aliasedIndexes[] = $indexName;
171
            }
172
        }
173
174 6
        if (\count($aliasedIndexes) > 1) {
175 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)));
176
        }
177
178 5
        return \array_shift($aliasedIndexes);
179
    }
180
}
181