/**
* personium.io
* Copyright 2014 FUJITSU LIMITED
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fujitsu.dc.common.es.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.lang.reflect.Constructor;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.NoShardAvailableActionException;
import org.elasticsearch.action.support.broadcast.BroadcastShardOperationFailedException;
import org.elasticsearch.client.transport.NoNodeAvailableException;
import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.common.util.concurrent.UncategorizedExecutionException;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.index.engine.FlushNotAllowedEngineException;
import org.elasticsearch.transport.NodeDisconnectedException;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import com.fujitsu.dc.common.es.EsClient;
import com.fujitsu.dc.common.es.EsIndex;
import com.fujitsu.dc.common.es.impl.EsTranslogHandler.FlushTranslogRetryableRequest;
import com.fujitsu.dc.common.es.response.EsClientException;
import com.fujitsu.dc.common.es.test.util.EsTestNode;
/**
* AbstractRetryableEsRequestクラスのリトライテスト.
*/
@PrepareForTest(EsClientException.class)
public class AbstractRetryableEsRequestTest {
static final int RETRY_COUNT = 5;
static final long RETRY_INTERVAL = 500;
static final String SUCCESS_RESPONSE = "REQUEST_SUCCESS";
static final String ON_ERROR_RESPONSE = "REQUEST_SUCCESS_ON_PARTICULOR_ERROR";
private static final String TESTING_HOSTS = "localhost:9399";
private static final String TESTING_CLUSTER = "testingCluster";
private static final String INDEX_FOR_TEST = "index_for_test";
private static EsTestNode node;
private EsIndex index;
/**
* テストケース共通の初期化処理. テスト用のElasticsearchのNodeを初期化する
* @throws Exception 異常が発生した場合の例外
*/
@BeforeClass
public static void setUpBeforeClass() throws Exception {
node = new EsTestNode();
node.create();
}
/**
* テストケース共通のクリーンアップ処理. テスト用のElasticsearchのNodeをクローズする
* @throws Exception 異常が発生した場合の例外
*/
@AfterClass
public static void tearDownAfterClass() throws Exception {
node.close();
}
private EsClient esClientforTest;
/**
* 各テスト実行前の初期化処理.
* @throws Exception 異常が発生した場合の例外
*/
@Before
public void setUp() throws Exception {
esClientforTest = new EsClient(TESTING_CLUSTER, TESTING_HOSTS);
index = esClientforTest.idxAdmin(INDEX_FOR_TEST);
index.create();
}
/**
* 各テスト実行後のクリーンアップ処理.
* @throws Exception 異常が発生した場合の例外
*/
@After
public void tearDown() throws Exception {
try {
index.delete();
} catch (Exception ex) {
System.out.println("");
}
}
/**
* テスト用の例外.
*/
class EsExceptionForTest extends ElasticsearchException {
private static final long serialVersionUID = 1L;
public EsExceptionForTest(String msg) {
super(msg);
}
}
/**
* テスト用のリクエストクラス.
*/
class TestRequest extends AbstractRetryableEsRequest<String> {
@Override
public boolean isParticularError(ElasticsearchException e) {
return (e instanceof EsExceptionForTest || e instanceof SettingsException);
}
@Override
public String onParticularError(ElasticsearchException e) {
if (e instanceof EsExceptionForTest) {
return ON_ERROR_RESPONSE;
}
if (e instanceof SettingsException) {
throw new ContinueRetry();
}
throw e;
}
public TestRequest() {
super(RETRY_COUNT, RETRY_INTERVAL, "TestRequest");
}
@Override
String doProcess() {
return SUCCESS_RESPONSE;
}
@Override
EsTranslogHandler getEsTranslogHandler() {
InternalEsClient esClient = new InternalEsClient(TESTING_CLUSTER, TESTING_HOSTS);
return new EsIndexImpl(index.getName(), EsIndex.CATEGORY_AD, retryCount, 1, esClient);
}
}
/**
* 初回リクエストが成功した場合、適切な復帰値が返ること.
*/
@Test
public void 初回リクエストが成功した場合_適切な復帰値が返ること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
String result = requestMock.doRequest();
assertEquals(SUCCESS_RESPONSE, result);
Mockito.verify(requestMock, Mockito.times(1)).doProcess();
Mockito.verify(requestMock, Mockito.times(0)).onParticularError(Mockito.any(ElasticsearchException.class));
}
/**
* 初回リクエストでリトライ対象外の例外が発生した場合、リトライせずに初回例外を投げること.
*/
@Test(expected = EsClientException.class)
public void 初回リクエストでリトライ対象外の例外が発生した場合_リトライせずに初回例外を投げること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
Mockito.doThrow(new IndexNotFoundException("abc")) // なぜかモック例外だとうまく動かなかった.
.when(requestMock)
.doProcess();
try {
requestMock.doRequest();
fail("Should not return");
} finally {
Mockito.verify(requestMock, Mockito.times(1)).doProcess();
Mockito.verify(requestMock, Mockito.times(0)).onParticularError(Mockito.any(ElasticsearchException.class));
}
}
/**
* 初回リクエストでNodeDisconnectedException、リトライ1回目で成功した場合、適切な復帰値が返ること.
*/
@Test
public void 初回リクエストでNodeDisconnectedException_リトライ1回目で成功した場合_適切な復帰値が返ること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
NodeDisconnectedException toBeThrown = Mockito.mock(NodeDisconnectedException.class);
Mockito.doThrow(toBeThrown)
.doReturn(SUCCESS_RESPONSE)
.when(requestMock)
.doProcess();
String result = requestMock.doRequest();
assertEquals(SUCCESS_RESPONSE, result);
Mockito.verify(requestMock, Mockito.times(2)).doProcess();
Mockito.verify(requestMock, Mockito.times(0)).onParticularError(Mockito.any(ElasticsearchException.class));
}
/**
* 初回リクエストでNoNodeAvailableException、リトライ1回目で成功した場合、適切な復帰値が返ること.
*/
@Test
public void 初回リクエストでNoNodeAvailableException_リトライ1回目で成功した場合_適切な復帰値が返ること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
NoNodeAvailableException toBeThrown = Mockito.mock(NoNodeAvailableException.class);
Mockito.doThrow(toBeThrown)
.doReturn(SUCCESS_RESPONSE)
.when(requestMock)
.doProcess();
String result = requestMock.doRequest();
assertEquals(SUCCESS_RESPONSE, result);
Mockito.verify(requestMock, Mockito.times(2)).doProcess();
Mockito.verify(requestMock, Mockito.times(0)).onParticularError(Mockito.any(ElasticsearchException.class));
}
/**
* 初回リクエストでNoShardAvailableActionException、リトライ1回目で成功した場合、適切な復帰値が返ること.
*/
@Test
public void 初回リクエストでNoShardAvailableActionException_リトライ1回目で成功した場合_適切な復帰値が返ること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
NoShardAvailableActionException toBeThrown = Mockito.mock(NoShardAvailableActionException.class);
Mockito.doThrow(toBeThrown)
.doReturn(SUCCESS_RESPONSE)
.when(requestMock)
.doProcess();
String result = requestMock.doRequest();
assertEquals(SUCCESS_RESPONSE, result);
Mockito.verify(requestMock, Mockito.times(2)).doProcess();
Mockito.verify(requestMock, Mockito.times(0)).onParticularError(Mockito.any(ElasticsearchException.class));
}
/**
* 初回リクエストでClusterBlockException、リトライ1回目で成功した場合、適切な復帰値が返ること.
*/
@Test
public void 初回リクエストでClusterBlockException_リトライ1回目で成功した場合_適切な復帰値が返ること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
ClusterBlockException toBeThrown = Mockito.mock(ClusterBlockException.class);
Mockito.doThrow(toBeThrown)
.doReturn(SUCCESS_RESPONSE)
.when(requestMock)
.doProcess();
String result = requestMock.doRequest();
assertEquals(SUCCESS_RESPONSE, result);
Mockito.verify(requestMock, Mockito.times(2)).doProcess();
Mockito.verify(requestMock, Mockito.times(0)).onParticularError(Mockito.any(ElasticsearchException.class));
}
/**
* リトライ1回目でNodeDisconnectedException, リトライ2回目で成功した場合、適切な復帰値が返ること.
*/
@Test
public void リトライ1回目でNodeDisconnectedException_リトライ2回目で成功した場合_適切な復帰値が返ること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
NodeDisconnectedException toBeThrown = Mockito.mock(NodeDisconnectedException.class);
Mockito.doThrow(toBeThrown) // 初回
.doThrow(toBeThrown) // リトライ1回目
.doReturn(SUCCESS_RESPONSE) // リトライ2回目で正常復帰
.when(requestMock)
.doProcess();
String result = requestMock.doRequest();
assertEquals(SUCCESS_RESPONSE, result);
// doProcessが3回呼び出されるはず
Mockito.verify(requestMock, Mockito.times(3)).doProcess();
Mockito.verify(requestMock, Mockito.times(0)).onParticularError(Mockito.any(ElasticsearchException.class));
}
/**
* NoNodeAvailableExceptionが続き, リトライ3回目で成功した場合、適切な復帰値が返ること.
*/
@Test
public void NoNodeAvailableExceptionが続き_リトライ3回目で成功した場合_適切な復帰値が返ること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
NoNodeAvailableException toBeThrown = Mockito.mock(NoNodeAvailableException.class);
Mockito.doThrow(toBeThrown) // 初回
.doThrow(toBeThrown) // リトライ1回目
.doThrow(toBeThrown) // リトライ2回目
.doReturn(SUCCESS_RESPONSE) // リトライ3回目で正常復帰
.when(requestMock)
.doProcess();
String result = requestMock.doRequest();
assertEquals(SUCCESS_RESPONSE, result);
// doProcessが4回呼び出されるはず
Mockito.verify(requestMock, Mockito.times(4)).doProcess();
Mockito.verify(requestMock, Mockito.times(0)).onParticularError(Mockito.any(ElasticsearchException.class));
}
/**
* NoNodeAvailableExceptionが続き, リトライ3回目で成功した場合、適切な復帰値が返ること.
*/
@Test
public void NoShardAvailableActionExceptionが続き_リトライ4回目で成功した場合_適切な復帰値が返ること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
NoShardAvailableActionException toBeThrown = Mockito.mock(NoShardAvailableActionException.class);
Mockito.doThrow(toBeThrown) // 初回
.doThrow(toBeThrown) // リトライ1回目
.doThrow(toBeThrown) // リトライ2回目
.doThrow(toBeThrown) // リトライ3回目
.doReturn(SUCCESS_RESPONSE) // リトライ4回目で正常復帰
.when(requestMock)
.doProcess();
String result = requestMock.doRequest();
assertEquals(SUCCESS_RESPONSE, result);
// doProcessが5回呼び出されるはず
Mockito.verify(requestMock, Mockito.times(5)).doProcess();
Mockito.verify(requestMock, Mockito.times(0)).onParticularError(Mockito.any(ElasticsearchException.class));
}
/**
* ClusterBlockExceptionが続き_リトライ5回目で成功した場合_適切な復帰値が返ること.
*/
@Test
public void ClusterBlockExceptionが続き_リトライ5回目で成功した場合_適切な復帰値が返ること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
ClusterBlockException toBeThrown = Mockito.mock(ClusterBlockException.class);
Mockito.doThrow(toBeThrown) // 初回
.doThrow(toBeThrown) // リトライ1回目
.doThrow(toBeThrown) // リトライ2回目
.doThrow(toBeThrown) // リトライ3回目
.doThrow(toBeThrown) // リトライ4回目
.doReturn(SUCCESS_RESPONSE) // リトライ5回目で正常復帰
.when(requestMock)
.doProcess();
String result = requestMock.doRequest();
assertEquals(SUCCESS_RESPONSE, result);
// doProcessが6回呼び出されるはず
Mockito.verify(requestMock, Mockito.times(6)).doProcess();
Mockito.verify(requestMock, Mockito.times(0)).onParticularError(Mockito.any(ElasticsearchException.class));
}
/**
* リトライ対象例外が続き_リトライ5回目でもNGな場合_EsNoResponseExceptionが投げられること.
*/
@Test(expected = EsClientException.EsNoResponseException.class)
public void リトライ対象例外が続き_リトライ5回目でもNGな場合_EsNoResponseExceptionが投げられること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
ClusterBlockException toBeThrown = Mockito.mock(ClusterBlockException.class);
Mockito.doThrow(toBeThrown) // 初回
.doThrow(toBeThrown) // リトライ1回目
.doThrow(toBeThrown) // リトライ2回目
.doThrow(toBeThrown) // リトライ3回目
.doThrow(toBeThrown) // リトライ4回目
.doThrow(toBeThrown) // リトライ5回目
.when(requestMock)
.doProcess();
try {
requestMock.doRequest();
fail("Should not return");
} finally {
// doProcessが6回呼び出されるはず
Mockito.verify(requestMock, Mockito.times(6)).doProcess();
Mockito.verify(requestMock, Mockito.times(0)).onParticularError(Mockito.any(ElasticsearchException.class));
}
}
/**
* リトライ対象例外が続き_リトライ5回目でリトライ対象外の例外が発生した場合、EsClientExceptionが投げられること.
*/
@Test(expected = EsClientException.class)
public void リトライ対象例外が続き_リトライ5回目でリトライ対象外の例外が発生した場合_EsClientExceptionが投げられること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
ClusterBlockException toBeThrown = Mockito.mock(ClusterBlockException.class);
IndexNotFoundException toBeThrown2 = new IndexNotFoundException("abc");
Mockito.doThrow(toBeThrown) // 初回
.doThrow(toBeThrown) // リトライ1回目
.doThrow(toBeThrown) // リトライ2回目
.doThrow(toBeThrown) // リトライ3回目
.doThrow(toBeThrown) // リトライ4回目
.doThrow(toBeThrown2) // リトライ5回目
.when(requestMock)
.doProcess();
try {
requestMock.doRequest();
fail("Should not return");
} finally {
// doProcessが6回呼び出されるはず
Mockito.verify(requestMock, Mockito.times(6)).doProcess();
Mockito.verify(requestMock, Mockito.times(0)).onParticularError(Mockito.any(ElasticsearchException.class));
}
}
/**
* 初回リクエスト時に特定例外が発生した場合、特定例外用処理が呼び出されてレスポンスが返ること.
*/
@Test
public void 初回リクエスト時に特定例外が発生した場合_特定例外用処理が呼び出されてレスポンスが返ること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
EsExceptionForTest toBeThrown = Mockito.mock(EsExceptionForTest.class);
Mockito.doThrow(toBeThrown) // 初回
.when(requestMock)
.doProcess();
String result = requestMock.doRequest();
assertEquals(ON_ERROR_RESPONSE, result);
Mockito.verify(requestMock, Mockito.times(1)).doProcess();
Mockito.verify(requestMock, Mockito.times(1)).onParticularError(Mockito.any(ElasticsearchException.class));
}
/**
* リトライ中に特定例外が発生した場合、特定例外用処理が呼び出されてレスポンスが返ること.
*/
@Test
public void リトライ中にに特定例外が発生した場合_特定例外用処理が呼び出されてレスポンスが返ること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
NodeDisconnectedException toBeThrown = Mockito.mock(NodeDisconnectedException.class);
EsExceptionForTest toBeThrown2 = Mockito.mock(EsExceptionForTest.class);
Mockito.doThrow(toBeThrown) // 初回
.doThrow(toBeThrown) // リトライ1回目
.doThrow(toBeThrown) // リトライ2回目
.doThrow(toBeThrown2) // リトライ3回目 特定例外
.when(requestMock)
.doProcess();
String result = requestMock.doRequest();
assertEquals(ON_ERROR_RESPONSE, result);
Mockito.verify(requestMock, Mockito.times(4)).doProcess();
Mockito.verify(requestMock, Mockito.times(1)).onParticularError(Mockito.any(ElasticsearchException.class));
}
/**
* 初回リクエストの特定例外処理からContinueRetryが投げられた後、リトライ処理に移行すること.
*/
@Test
public void 初回リクエストの特定例外処理からContinueRetryが投げられた後_リトライ処理に移行すること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
NodeDisconnectedException toBeThrown = Mockito.mock(NodeDisconnectedException.class);
Mockito.doThrow(new SettingsException("foo")) // 初回リクエスト
.doThrow(toBeThrown) // リトライ1回目
.doReturn(SUCCESS_RESPONSE)
.when(requestMock)
.doProcess();
String result = requestMock.doRequest();
assertEquals(SUCCESS_RESPONSE, result);
Mockito.verify(requestMock, Mockito.times(3)).doProcess();
Mockito.verify(requestMock, Mockito.times(1)).onParticularError(Mockito.any(ElasticsearchException.class));
}
/**
* リトライ処理中の特定例外処理からContinueRetryが投げられた後、リトライ処理に移行すること.
*/
@Test
public void リトライ処理中の特定例外処理からContinueRetryが投げられた後_リトライ処理に移行すること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
NodeDisconnectedException toBeThrown = Mockito.mock(NodeDisconnectedException.class);
Mockito.doThrow(toBeThrown) // 初回リクエスト
.doThrow(toBeThrown) // リトライ1回目
.doThrow(new SettingsException("foo")) // リトライ2回目. この時は、 #onParticularError()でリトライ継続のために
// ContinueRetryが投げられる.
.doThrow(toBeThrown) // リトライ3回目
.doReturn(SUCCESS_RESPONSE)
.when(requestMock)
.doProcess();
String result = requestMock.doRequest();
assertEquals(SUCCESS_RESPONSE, result);
// 初回 + リトライ3回 + 処理成功で、5回呼ばれるはず.
Mockito.verify(requestMock, Mockito.times(5)).doProcess();
Mockito.verify(requestMock, Mockito.times(1)).onParticularError(Mockito.any(ElasticsearchException.class));
}
/**
* translog読み込み時にUncategorizedExecutionExceptionが発生した場合にflushが実行されること.
*/
@Test
public void translog読み込み時にUncategorizedExecutionExceptionが発生した場合にflushが実行されること() {
TestRequest requestMock = Mockito.spy(new TestRequest());
UncategorizedExecutionException toBeThrown = Mockito.mock(UncategorizedExecutionException.class);
Mockito.doThrow(toBeThrown) // 初回リクエスト
.doThrow(toBeThrown) // リトライ1回目
.doReturn(SUCCESS_RESPONSE)
.when(requestMock)
.doProcess();
requestMock.doRequest();
Mockito.verify(requestMock, Mockito.times(3)).doProcess();
Mockito.verify(requestMock, Mockito.times(2)).flushTransLog();
}
/**
* translogのflush時にNodeDisconnectedExceptionが発生した場合にflushのリトライをすること.
* @throws Exception 実行中の例外
*/
@Test
public void translogのflush時にNodeDisconnectedExceptionが発生した場合にflushのリトライをすること()
throws Exception {
Constructor<FlushTranslogRetryableRequest> constructor = FlushTranslogRetryableRequest.class
.getDeclaredConstructor(new Class[] {
EsTranslogHandler.class, Integer.TYPE, Long.TYPE });
constructor.setAccessible(true);
EsTranslogHandler handler = new EsTranslogHandler(RETRY_COUNT, 0, null, INDEX_FOR_TEST);
FlushTranslogRetryableRequest flushMock = Mockito.spy((FlushTranslogRetryableRequest) constructor.newInstance(
handler, 5, 0L));
NodeDisconnectedException toBeThrown2 = Mockito.mock(NodeDisconnectedException.class);
Mockito.doThrow(toBeThrown2) // 初回リクエストの例外投入
.doReturn(null)
.when(flushMock)
.doProcess();
flushMock.doRequest();
Mockito.verify(flushMock, Mockito.times(2)).doProcess(); // ParticularErroではないのでリトライしないこと
Mockito.verify(flushMock, Mockito.times(0)).flushTransLog();
}
/**
* translog読み込み時にUncategorizedExecutionExceptionが発生した場合にflushが実行されること.
* @throws Exception 実行時例外
*/
@Test
public void translogのflush時にBroadcastShardOperationFailedExceptionが発生した場合にflushのリトライをしないこと()
throws Exception {
Constructor<FlushTranslogRetryableRequest> constructor = FlushTranslogRetryableRequest.class
.getDeclaredConstructor(new Class[] {EsTranslogHandler.class, Integer.TYPE, Long.TYPE });
constructor.setAccessible(true);
EsTranslogHandler handler = new EsTranslogHandler(RETRY_COUNT, 0, null, INDEX_FOR_TEST);
FlushTranslogRetryableRequest flushMock = Mockito.spy((FlushTranslogRetryableRequest) constructor.newInstance(
handler, 5, 0L));
BroadcastShardOperationFailedException toBeThrown = Mockito.mock(BroadcastShardOperationFailedException.class);
Mockito.doThrow(toBeThrown) // 初回リクエストの例外投入
.doReturn(null)
.when(flushMock)
.doProcess();
flushMock.doRequest();
Mockito.verify(flushMock, Mockito.times(1)).doProcess(); // ParticularErrorのためリトライしないこと
Mockito.verify(flushMock, Mockito.times(0)).flushTransLog();
}
/**
* translog読み込み時にFlushNotAllowedEngineExceptionが発生した場合にflushが実行されること.
* @throws Exception 実行時例外
*/
@Test
public void translogのflush時にFlushNotAllowedEngineExceptionが発生した場合にflushのリトライをしないこと()
throws Exception {
Constructor<FlushTranslogRetryableRequest> constructor = FlushTranslogRetryableRequest.class
.getDeclaredConstructor(new Class[] {EsTranslogHandler.class, Integer.TYPE, Long.TYPE });
constructor.setAccessible(true);
EsTranslogHandler handler = new EsTranslogHandler(RETRY_COUNT, 0, null, INDEX_FOR_TEST);
FlushTranslogRetryableRequest flushMock = Mockito.spy((FlushTranslogRetryableRequest) constructor.newInstance(
handler, 5, 0L));
FlushNotAllowedEngineException toBeThrown = Mockito.mock(FlushNotAllowedEngineException.class);
Mockito.doThrow(toBeThrown) // 初回リクエストの例外投入
.doReturn(null)
.when(flushMock)
.doProcess();
flushMock.doRequest();
Mockito.verify(flushMock, Mockito.times(1)).doProcess(); // ParticularErrorのためリトライしないこと
Mockito.verify(flushMock, Mockito.times(0)).flushTransLog();
}
}