diff --git a/Client.php b/Client.php index 7719095..679317c 100644 --- a/Client.php +++ b/Client.php @@ -32,4 +32,9 @@ class Client extends ElasticaClient return $response; } + + public function getIndex($name) + { + return new DynamicIndex($this, $name); + } } diff --git a/Command/PopulateCommand.php b/Command/PopulateCommand.php index a67df94..98834c7 100644 --- a/Command/PopulateCommand.php +++ b/Command/PopulateCommand.php @@ -126,6 +126,7 @@ class PopulateCommand extends ContainerAwareCommand } $output->writeln(sprintf('Refreshing %s', $index)); + $this->resetter->postPopulate($index); $this->indexManager->getIndex($index)->refresh(); } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 0f868af..e72d85c 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -140,6 +140,7 @@ class Configuration implements ConfigurationInterface ->prototype('array') ->children() ->scalarNode('index_name')->end() + ->booleanNode('use_alias')->defaultValue(false)->end() ->scalarNode('client')->end() ->scalarNode('finder') ->treatNullLike(true) diff --git a/DependencyInjection/FOSElasticaExtension.php b/DependencyInjection/FOSElasticaExtension.php index 41c8c1f..553af14 100644 --- a/DependencyInjection/FOSElasticaExtension.php +++ b/DependencyInjection/FOSElasticaExtension.php @@ -125,6 +125,7 @@ class FOSElasticaExtension extends Extension $indexIds[$name] = $indexId; $this->indexConfigs[$name] = array( 'index' => new Reference($indexId), + 'name_or_alias' => $indexName, 'config' => array( 'mappings' => array() ) @@ -135,6 +136,10 @@ class FOSElasticaExtension extends Extension if (!empty($index['settings'])) { $this->indexConfigs[$name]['config']['settings'] = $index['settings']; } + if ($index['use_alias']) { + $this->indexConfigs[$name]['use_alias'] = true; + } + $this->loadTypes(isset($index['types']) ? $index['types'] : array(), $container, $name, $indexId, $typePrototypeConfig); } diff --git a/DynamicIndex.php b/DynamicIndex.php new file mode 100644 index 0000000..cbec8e9 --- /dev/null +++ b/DynamicIndex.php @@ -0,0 +1,28 @@ + + */ +class DynamicIndex extends Index +{ + /** + * Reassign index name + * + * While it's technically a regular setter for name property, it's specifically named overrideName, but not setName + * since it's used for a very specific case and normally should not be used + * + * @param string $name Index name + * + * @return void + */ + public function overrideName($name) + { + $this->_name = $name; + } +} diff --git a/Resetter.php b/Resetter.php index 863524b..c7f4638 100644 --- a/Resetter.php +++ b/Resetter.php @@ -2,6 +2,8 @@ namespace FOS\ElasticaBundle; +use Elastica\Exception\ExceptionInterface; +use Elastica\Index; use Elastica\Exception\ResponseException; use Elastica\Type\Mapping; @@ -27,8 +29,8 @@ class Resetter */ public function resetAllIndexes() { - foreach ($this->indexConfigsByName as $indexConfig) { - $indexConfig['index']->create($indexConfig['config'], true); + foreach (array_keys($this->indexConfigsByName) as $name) { + $this->resetIndex($name); } } @@ -41,7 +43,17 @@ class Resetter public function resetIndex($indexName) { $indexConfig = $this->getIndexConfig($indexName); - $indexConfig['index']->create($indexConfig['config'], true); + $esIndex = $indexConfig['index']; + if (isset($indexConfig['use_alias']) && $indexConfig['use_alias']) { + $name = $indexConfig['name_or_alias']; + $name .= uniqid(); + $esIndex->overrideName($name); + $esIndex->create($indexConfig['config']); + + return; + } + + $esIndex->create($indexConfig['config'], true); } /** @@ -112,4 +124,122 @@ class Resetter return $this->indexConfigsByName[$indexName]; } + + public function postPopulate($indexName) + { + $indexConfig = $this->getIndexConfig($indexName); + if (isset($indexConfig['use_alias']) && $indexConfig['use_alias']) { + $this->switchIndexAlias($indexName); + } + } + + /** + * Switches the alias for given index (by key) to the newly populated index + * and deletes the old index + * + * @param string $indexName Index name + * + * @throws \RuntimeException + */ + private function switchIndexAlias($indexName) + { + $indexConfig = $this->getIndexConfig($indexName); + $esIndex = $indexConfig['index']; + $aliasName = $indexConfig['name_or_alias']; + $oldIndexName = false; + $newIndexName = $esIndex->getName(); + + $aliasedIndexes = $this->getAliasedIndexes($esIndex, $aliasName); + + if (count($aliasedIndexes) > 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, + join(', ', $aliasedIndexes) + ) + ); + } + + // Change the alias to point to the new index + // Elastica's addAlias can't be used directly, because in current (0.19.x) version it's not atomic + // In 0.20.x it's atomic, but it doesn't return the old index name + $aliasUpdateRequest = array('actions' => array()); + if (count($aliasedIndexes) == 1) { + // if the alias is set - add an action to remove it + $oldIndexName = $aliasedIndexes[0]; + $aliasUpdateRequest['actions'][] = array( + 'remove' => array('index' => $oldIndexName, 'alias' => $aliasName) + ); + } + + // add an action to point the alias to the new index + $aliasUpdateRequest['actions'][] = array( + 'add' => array('index' => $newIndexName, 'alias' => $aliasName) + ); + + try { + $esIndex->getClient()->request('_aliases', 'POST', $aliasUpdateRequest); + } catch (ExceptionInterface $renameAliasException) { + $additionalError = ''; + // if we failed to move the alias, delete the newly built index + try { + $esIndex->delete(); + } catch (ExceptionInterface $deleteNewIndexException) { + $additionalError = sprintf( + 'Tried to delete newly built index %s, but also failed: %s', + $newIndexName, + $deleteNewIndexException->getError() + ); + } + + throw new \RuntimeException( + sprintf( + 'Failed to updated index alias: %s. %s', + $renameAliasException->getMessage(), + $additionalError ?: sprintf('Newly built index %s was deleted', $newIndexName) + ) + ); + } + + // Delete the old index after the alias has been switched + if ($oldIndexName) { + $oldIndex = new Index($esIndex->getClient(), $oldIndexName); + try { + $oldIndex->delete(); + } catch (ExceptionInterface $deleteOldIndexException) { + throw new \RuntimeException( + sprintf( + 'Failed to delete old index %s with message: %s', + $oldIndexName, + $deleteOldIndexException->getMessage() + ) + ); + } + } + } + + /** + * Returns array of indexes which are mapped to given alias + * + * @param Index $esIndex ES Index + * @param string $aliasName Alias name + * + * @return array + */ + private function getAliasedIndexes(Index $esIndex, $aliasName) + { + $aliasesInfo = $esIndex->getClient()->request('_aliases', 'GET')->getData(); + $aliasedIndexes = array(); + + foreach ($aliasesInfo as $indexName => $indexInfo) { + $aliases = array_keys($indexInfo['aliases']); + if (in_array($aliasName, $aliases)) { + $aliasedIndexes[] = $indexName; + } + } + + return $aliasedIndexes; + } } diff --git a/Resources/config/config.xml b/Resources/config/config.xml index 85cfbed..4419b4a 100644 --- a/Resources/config/config.xml +++ b/Resources/config/config.xml @@ -6,7 +6,7 @@ FOS\ElasticaBundle\Client - Elastica\Index + FOS\ElasticaBundle\DynamicIndex Elastica\Type FOS\ElasticaBundle\Logger\ElasticaLogger FOS\ElasticaBundle\DataCollector\ElasticaDataCollector