/** * 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(); } }