AliasProcessor
This commit is contained in:
parent
bb4618c101
commit
9cf0117c71
|
@ -6,6 +6,6 @@ class AliasIsIndexException extends \Exception
|
||||||
{
|
{
|
||||||
public function __construct($indexName)
|
public function __construct($indexName)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('Expected alias %s instead of index', $indexName));
|
parent::__construct(sprintf('Expected %s to be an alias but it is an index.', $indexName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,37 +54,47 @@ class AliasProcessor
|
||||||
$client = $index->getClient();
|
$client = $index->getClient();
|
||||||
|
|
||||||
$aliasName = $indexConfig->getElasticSearchName();
|
$aliasName = $indexConfig->getElasticSearchName();
|
||||||
$oldIndexName = false;
|
$oldIndexName = null;
|
||||||
$newIndexName = $index->getName();
|
$newIndexName = $index->getName();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$aliasedIndexes = $this->getAliasedIndexes($client, $aliasName);
|
$oldIndexName = $this->getAliasedIndex($client, $aliasName);
|
||||||
} catch (AliasIsIndexException $e) {
|
} catch (AliasIsIndexException $e) {
|
||||||
if (!$force) {
|
if (!$force) {
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->deleteIndex($client, $aliasName);
|
$this->deleteIndex($client, $aliasName);
|
||||||
$aliasedIndexes = array();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($aliasedIndexes) > 1) {
|
try {
|
||||||
throw new \RuntimeException(
|
$aliasUpdateRequest = $this->buildAliasUpdateRequest($oldIndexName, $aliasName, $newIndexName);
|
||||||
sprintf(
|
$client->request('_aliases', 'POST', $aliasUpdateRequest);
|
||||||
'Alias %s is used for multiple indexes: [%s].
|
} catch (ExceptionInterface $e) {
|
||||||
Make sure it\'s either not used or is assigned to one index only',
|
$this->cleanupRenameFailure($client, $newIndexName, $e);
|
||||||
$aliasName,
|
|
||||||
implode(', ', $aliasedIndexes)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete the old index after the alias has been switched
|
||||||
|
if (null !== $oldIndexName) {
|
||||||
|
$this->deleteIndex($client, $oldIndexName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an ElasticSearch request to rename or create an alias.
|
||||||
|
*
|
||||||
|
* @param string|null $aliasedIndex
|
||||||
|
* @param string $aliasName
|
||||||
|
* @param string $newIndexName
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function buildAliasUpdateRequest($aliasedIndex, $aliasName, $newIndexName)
|
||||||
|
{
|
||||||
$aliasUpdateRequest = array('actions' => array());
|
$aliasUpdateRequest = array('actions' => array());
|
||||||
if (count($aliasedIndexes) === 1) {
|
if (null !== $aliasedIndex) {
|
||||||
// if the alias is set - add an action to remove it
|
// if the alias is set - add an action to remove it
|
||||||
$oldIndexName = $aliasedIndexes[0];
|
|
||||||
$aliasUpdateRequest['actions'][] = array(
|
$aliasUpdateRequest['actions'][] = array(
|
||||||
'remove' => array('index' => $oldIndexName, 'alias' => $aliasName),
|
'remove' => array('index' => $aliasedIndex, 'alias' => $aliasName),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,58 +103,68 @@ class AliasProcessor
|
||||||
'add' => array('index' => $newIndexName, 'alias' => $aliasName),
|
'add' => array('index' => $newIndexName, 'alias' => $aliasName),
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
return $aliasUpdateRequest;
|
||||||
$client->request('_aliases', 'POST', $aliasUpdateRequest);
|
}
|
||||||
} catch (ExceptionInterface $renameAliasException) {
|
|
||||||
$additionalError = '';
|
|
||||||
// if we failed to move the alias, delete the newly built index
|
|
||||||
try {
|
|
||||||
$index->delete();
|
|
||||||
} catch (ExceptionInterface $deleteNewIndexException) {
|
|
||||||
$additionalError = sprintf(
|
|
||||||
'Tried to delete newly built index %s, but also failed: %s',
|
|
||||||
$newIndexName,
|
|
||||||
$deleteNewIndexException->getMessage()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new \RuntimeException(
|
/**
|
||||||
sprintf(
|
* Cleans up an index when we encounter a failure to rename the alias.
|
||||||
'Failed to updated index alias: %s. %s',
|
*
|
||||||
$renameAliasException->getMessage(),
|
* @param Client $client
|
||||||
$additionalError ?: sprintf('Newly built index %s was deleted', $newIndexName)
|
* @param $indexName
|
||||||
), 0, $renameAliasException
|
* @param \Exception $renameAliasException
|
||||||
|
*/
|
||||||
|
private function cleanupRenameFailure(Client $client, $indexName, \Exception $renameAliasException)
|
||||||
|
{
|
||||||
|
$additionalError = '';
|
||||||
|
try {
|
||||||
|
$this->deleteIndex($client, $indexName);
|
||||||
|
} catch (ExceptionInterface $deleteNewIndexException) {
|
||||||
|
$additionalError = sprintf(
|
||||||
|
'Tried to delete newly built index %s, but also failed: %s',
|
||||||
|
$indexName,
|
||||||
|
$deleteNewIndexException->getMessage()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the old index after the alias has been switched
|
throw new \RuntimeException(sprintf(
|
||||||
if ($oldIndexName) {
|
'Failed to updated index alias: %s. %s',
|
||||||
$oldIndex = new Index($client, $oldIndexName);
|
$renameAliasException->getMessage(),
|
||||||
try {
|
$additionalError ?: sprintf('Newly built index %s was deleted', $indexName)
|
||||||
$oldIndex->delete();
|
), 0, $renameAliasException);
|
||||||
} catch (ExceptionInterface $deleteOldIndexException) {
|
}
|
||||||
throw new \RuntimeException(
|
|
||||||
sprintf(
|
/**
|
||||||
'Failed to delete old index %s with message: %s',
|
* Delete an index.
|
||||||
$oldIndexName,
|
*
|
||||||
$deleteOldIndexException->getMessage()
|
* @param Client $client
|
||||||
), 0, $deleteOldIndexException
|
* @param string $indexName Index name to delete
|
||||||
);
|
*/
|
||||||
}
|
private function deleteIndex(Client $client, $indexName)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$path = sprintf("%s", $indexName);
|
||||||
|
$client->request($path, Request::DELETE);
|
||||||
|
} catch (ExceptionInterface $deleteOldIndexException) {
|
||||||
|
throw new \RuntimeException(sprintf(
|
||||||
|
'Failed to delete index %s with message: %s',
|
||||||
|
$indexName,
|
||||||
|
$deleteOldIndexException->getMessage()
|
||||||
|
), 0, $deleteOldIndexException);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns array of indexes which are mapped to given alias.
|
* Returns the name of a single index that an alias points to or throws
|
||||||
|
* an exception if there is more than one.
|
||||||
*
|
*
|
||||||
* @param Client $client
|
* @param Client $client
|
||||||
* @param string $aliasName Alias name
|
* @param string $aliasName Alias name
|
||||||
*
|
*
|
||||||
* @return array
|
* @return string|null
|
||||||
*
|
*
|
||||||
* @throws AliasIsIndexException
|
* @throws AliasIsIndexException
|
||||||
*/
|
*/
|
||||||
private function getAliasedIndexes(Client $client, $aliasName)
|
private function getAliasedIndex(Client $client, $aliasName)
|
||||||
{
|
{
|
||||||
$aliasesInfo = $client->request('_aliases', 'GET')->getData();
|
$aliasesInfo = $client->request('_aliases', 'GET')->getData();
|
||||||
$aliasedIndexes = array();
|
$aliasedIndexes = array();
|
||||||
|
@ -163,18 +183,15 @@ class AliasProcessor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $aliasedIndexes;
|
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,
|
||||||
|
implode(', ', $aliasedIndexes)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return array_shift($aliasedIndexes);
|
||||||
* Delete an index.
|
|
||||||
*
|
|
||||||
* @param Client $client
|
|
||||||
* @param string $indexName Index name to delete
|
|
||||||
*/
|
|
||||||
private function deleteIndex(Client $client, $indexName)
|
|
||||||
{
|
|
||||||
$path = sprintf("%s", $indexName);
|
|
||||||
$client->request($path, Request::DELETE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
223
Tests/Index/AliasProcessorTest.php
Normal file
223
Tests/Index/AliasProcessorTest.php
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is part of the FOSElasticaBundle project.
|
||||||
|
*
|
||||||
|
* (c) Tim Nagel <tim@nagel.com.au>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FOS\ElasticaBundle\Tests\Index;
|
||||||
|
|
||||||
|
use Elastica\Exception\ResponseException;
|
||||||
|
use Elastica\Request;
|
||||||
|
use Elastica\Response;
|
||||||
|
use FOS\ElasticaBundle\Configuration\IndexConfig;
|
||||||
|
use FOS\ElasticaBundle\Index\AliasProcessor;
|
||||||
|
|
||||||
|
class AliasProcessorTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var AliasProcessor
|
||||||
|
*/
|
||||||
|
private $processor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getSetRootNameData
|
||||||
|
* @param string $name
|
||||||
|
* @param array $configArray
|
||||||
|
* @param string $resultStartsWith
|
||||||
|
*/
|
||||||
|
public function testSetRootName($name, $configArray, $resultStartsWith)
|
||||||
|
{
|
||||||
|
$indexConfig = new IndexConfig($name, array(), $configArray);
|
||||||
|
$index = $this->getMockBuilder('FOS\\ElasticaBundle\\Elastica\\Index')
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
$index->expects($this->once())
|
||||||
|
->method('overrideName')
|
||||||
|
->with($this->stringStartsWith($resultStartsWith));
|
||||||
|
|
||||||
|
$this->processor->setRootName($indexConfig, $index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSwitchAliasNoAliasSet()
|
||||||
|
{
|
||||||
|
$indexConfig = new IndexConfig('name', array(), array());
|
||||||
|
list($index, $client) = $this->getMockedIndex('unique_name');
|
||||||
|
|
||||||
|
$client->expects($this->at(0))
|
||||||
|
->method('request')
|
||||||
|
->with('_aliases', 'GET')
|
||||||
|
->willReturn(new Response(array()));
|
||||||
|
$client->expects($this->at(1))
|
||||||
|
->method('request')
|
||||||
|
->with('_aliases', 'POST', array('actions' => array(
|
||||||
|
array('add' => array('index' => 'unique_name', 'alias' => 'name'))
|
||||||
|
)));
|
||||||
|
|
||||||
|
$this->processor->switchIndexAlias($indexConfig, $index, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSwitchAliasExistingAliasSet()
|
||||||
|
{
|
||||||
|
$indexConfig = new IndexConfig('name', array(), array());
|
||||||
|
list($index, $client) = $this->getMockedIndex('unique_name');
|
||||||
|
|
||||||
|
$client->expects($this->at(0))
|
||||||
|
->method('request')
|
||||||
|
->with('_aliases', 'GET')
|
||||||
|
->willReturn(new Response(array(
|
||||||
|
'old_unique_name' => array('aliases' => array('name'))
|
||||||
|
)));
|
||||||
|
$client->expects($this->at(1))
|
||||||
|
->method('request')
|
||||||
|
->with('_aliases', 'POST', array('actions' => array(
|
||||||
|
array('remove' => array('index' => 'old_unique_name', 'alias' => 'name')),
|
||||||
|
array('add' => array('index' => 'unique_name', 'alias' => 'name'))
|
||||||
|
)));
|
||||||
|
|
||||||
|
$this->processor->switchIndexAlias($indexConfig, $index, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \RuntimeException
|
||||||
|
*/
|
||||||
|
public function testSwitchAliasThrowsWhenMoreThanOneExists()
|
||||||
|
{
|
||||||
|
$indexConfig = new IndexConfig('name', array(), array());
|
||||||
|
list($index, $client) = $this->getMockedIndex('unique_name');
|
||||||
|
|
||||||
|
$client->expects($this->at(0))
|
||||||
|
->method('request')
|
||||||
|
->with('_aliases', 'GET')
|
||||||
|
->willReturn(new Response(array(
|
||||||
|
'old_unique_name' => array('aliases' => array('name')),
|
||||||
|
'another_old_unique_name' => array('aliases' => array('name'))
|
||||||
|
)));
|
||||||
|
|
||||||
|
$this->processor->switchIndexAlias($indexConfig, $index, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \FOS\ElasticaBundle\Exception\AliasIsIndexException
|
||||||
|
*/
|
||||||
|
public function testSwitchAliasThrowsWhenAliasIsAnIndex()
|
||||||
|
{
|
||||||
|
$indexConfig = new IndexConfig('name', array(), array());
|
||||||
|
list($index, $client) = $this->getMockedIndex('unique_name');
|
||||||
|
|
||||||
|
$client->expects($this->at(0))
|
||||||
|
->method('request')
|
||||||
|
->with('_aliases', 'GET')
|
||||||
|
->willReturn(new Response(array(
|
||||||
|
'name' => array(),
|
||||||
|
)));
|
||||||
|
|
||||||
|
$this->processor->switchIndexAlias($indexConfig, $index, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSwitchAliasDeletesIndexCollisionIfForced()
|
||||||
|
{
|
||||||
|
$indexConfig = new IndexConfig('name', array(), array());
|
||||||
|
list($index, $client) = $this->getMockedIndex('unique_name');
|
||||||
|
|
||||||
|
$client->expects($this->at(0))
|
||||||
|
->method('request')
|
||||||
|
->with('_aliases', 'GET')
|
||||||
|
->willReturn(new Response(array(
|
||||||
|
'name' => array(),
|
||||||
|
)));
|
||||||
|
$client->expects($this->at(1))
|
||||||
|
->method('request')
|
||||||
|
->with('name', 'DELETE');
|
||||||
|
|
||||||
|
$this->processor->switchIndexAlias($indexConfig, $index, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSwitchAliasDeletesOldIndex()
|
||||||
|
{
|
||||||
|
$indexConfig = new IndexConfig('name', array(), array());
|
||||||
|
list($index, $client) = $this->getMockedIndex('unique_name');
|
||||||
|
|
||||||
|
$client->expects($this->at(0))
|
||||||
|
->method('request')
|
||||||
|
->with('_aliases', 'GET')
|
||||||
|
->willReturn(new Response(array(
|
||||||
|
'old_unique_name' => array('aliases' => array('name')),
|
||||||
|
)));
|
||||||
|
$client->expects($this->at(1))
|
||||||
|
->method('request')
|
||||||
|
->with('_aliases', 'POST', array('actions' => array(
|
||||||
|
array('remove' => array('index' => 'old_unique_name', 'alias' => 'name')),
|
||||||
|
array('add' => array('index' => 'unique_name', 'alias' => 'name'))
|
||||||
|
)));
|
||||||
|
$client->expects($this->at(2))
|
||||||
|
->method('request')
|
||||||
|
->with('old_unique_name', 'DELETE');
|
||||||
|
|
||||||
|
$this->processor->switchIndexAlias($indexConfig, $index, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSwitchAliasCleansUpOnRenameFailure()
|
||||||
|
{
|
||||||
|
$indexConfig = new IndexConfig('name', array(), array());
|
||||||
|
list($index, $client) = $this->getMockedIndex('unique_name');
|
||||||
|
|
||||||
|
$client->expects($this->at(0))
|
||||||
|
->method('request')
|
||||||
|
->with('_aliases', 'GET')
|
||||||
|
->willReturn(new Response(array(
|
||||||
|
'old_unique_name' => array('aliases' => array('name')),
|
||||||
|
)));
|
||||||
|
$client->expects($this->at(1))
|
||||||
|
->method('request')
|
||||||
|
->with('_aliases', 'POST', array('actions' => array(
|
||||||
|
array('remove' => array('index' => 'old_unique_name', 'alias' => 'name')),
|
||||||
|
array('add' => array('index' => 'unique_name', 'alias' => 'name'))
|
||||||
|
)))
|
||||||
|
->will($this->throwException(new ResponseException(new Request(''), new Response(''))));
|
||||||
|
$client->expects($this->at(2))
|
||||||
|
->method('request')
|
||||||
|
->with('unique_name', 'DELETE');
|
||||||
|
// Not an annotation: we do not want a RuntimeException until now.
|
||||||
|
$this->setExpectedException('RuntimeException');
|
||||||
|
|
||||||
|
$this->processor->switchIndexAlias($indexConfig, $index, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSetRootNameData()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('name', array(), 'name_'),
|
||||||
|
array('name', array('elasticSearchName' => 'notname'), 'notname_')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
$this->processor = new AliasProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMockedIndex($name)
|
||||||
|
{
|
||||||
|
$index = $this->getMockBuilder('FOS\\ElasticaBundle\\Elastica\\Index')
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$client = $this->getMockBuilder('Elastica\\Client')
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
$index->expects($this->any())
|
||||||
|
->method('getClient')
|
||||||
|
->willReturn($client);
|
||||||
|
|
||||||
|
$index->expects($this->any())
|
||||||
|
->method('getName')
|
||||||
|
->willReturn($name);
|
||||||
|
|
||||||
|
return array($index, $client);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue