/* * Copyright (C) 2012-2015 DataStax Inc. * * 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.datastax.driver.core.policies; import com.datastax.driver.core.*; import com.datastax.driver.core.exceptions.DriverException; import com.datastax.driver.core.exceptions.OverloadedException; import com.datastax.driver.core.exceptions.ServerError; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import org.mockito.Mockito; import org.scassandra.Scassandra; import org.scassandra.http.client.ClosedConnectionConfig.CloseType; import org.scassandra.http.client.Config; import org.scassandra.http.client.PrimingRequest; import org.scassandra.http.client.PrimingRequest.PrimingRequestBuilder; import org.scassandra.http.client.Result; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import java.util.List; import java.util.Map; import static com.datastax.driver.core.TestUtils.nonQuietClusterCloseOptions; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.*; import static org.mockito.Mockito.times; import static org.scassandra.http.client.PrimingRequest.then; import static org.scassandra.http.client.Result.overloaded; import static org.scassandra.http.client.Result.server_error; /** * Base class for retry policy integration tests. * <p/> * We use SCassandra to easily simulate specific errors (unavailable, read timeout...) on nodes, * and SortingLoadBalancingPolicy to get a predictable order of the query plan (always host1, host2, host3). * <p/> * Note that SCassandra only allows a limited number of test cases, for instance it always returns errors * with receivedResponses = 0. If that becomes more finely tuneable in the future, we'll be able to add more * tests in child classes. */ public class AbstractRetryPolicyIntegrationTest { protected ScassandraCluster scassandras; protected Cluster cluster = null; protected Metrics.Errors errors; protected Host host1, host2, host3; protected Session session; protected RetryPolicy retryPolicy; protected AbstractRetryPolicyIntegrationTest() { } protected AbstractRetryPolicyIntegrationTest(RetryPolicy retryPolicy) { setRetryPolicy(retryPolicy); } protected final void setRetryPolicy(RetryPolicy retryPolicy) { this.retryPolicy = Mockito.spy(retryPolicy); } @BeforeMethod(groups = "short") public void beforeMethod() { scassandras = ScassandraCluster.builder().withNodes(3).build(); scassandras.init(); cluster = Cluster.builder() .addContactPoints(scassandras.address(1).getAddress()) .withPort(scassandras.getBinaryPort()) .withRetryPolicy(retryPolicy) .withLoadBalancingPolicy(new SortingLoadBalancingPolicy()) .withPoolingOptions(new PoolingOptions() .setCoreConnectionsPerHost(HostDistance.LOCAL, 1) .setMaxConnectionsPerHost(HostDistance.LOCAL, 1) .setHeartbeatIntervalSeconds(0)) .withNettyOptions(nonQuietClusterCloseOptions) // Mark everything as idempotent by default so RetryPolicy is exercised. .withQueryOptions(new QueryOptions().setDefaultIdempotence(true)) .build(); session = cluster.connect(); host1 = TestUtils.findHost(cluster, 1); host2 = TestUtils.findHost(cluster, 2); host3 = TestUtils.findHost(cluster, 3); errors = cluster.getMetrics().getErrorMetrics(); Mockito.reset(retryPolicy); for (Scassandra node : scassandras.nodes()) { node.activityClient().clearAllRecordedActivity(); } } protected void simulateError(int hostNumber, Result result) { simulateError(hostNumber, result, null); } protected void simulateError(int hostNumber, Result result, Config config) { PrimingRequest.Then.ThenBuilder then = then().withResult(result); PrimingRequestBuilder builder = PrimingRequest.queryBuilder().withQuery("mock query"); if (config != null) then = then.withConfig(config); builder = builder.withThen(then); scassandras.node(hostNumber).primingClient().prime(builder.build()); } protected void simulateNormalResponse(int hostNumber) { scassandras.node(hostNumber).primingClient().prime(PrimingRequest.queryBuilder() .withQuery("mock query") .withThen(then().withRows(row("result", "result1"))) .build()); } protected static List<Map<String, ?>> row(String key, String value) { return ImmutableList.<Map<String, ?>>of(ImmutableMap.of(key, value)); } protected ResultSet query() { return query(session); } protected ResultSet queryWithCL(ConsistencyLevel cl) { Statement statement = new SimpleStatement("mock query").setConsistencyLevel(cl); return session.execute(statement); } protected ResultSet query(Session session) { return session.execute("mock query"); } protected void assertOnReadTimeoutWasCalled(int times) { Mockito.verify(retryPolicy, times(times)).onReadTimeout( any(Statement.class), any(ConsistencyLevel.class), anyInt(), anyInt(), anyBoolean(), anyInt()); } protected void assertOnWriteTimeoutWasCalled(int times) { Mockito.verify(retryPolicy, times(times)).onWriteTimeout( any(Statement.class), any(ConsistencyLevel.class), any(WriteType.class), anyInt(), anyInt(), anyInt()); } protected void assertOnUnavailableWasCalled(int times) { Mockito.verify(retryPolicy, times(times)).onUnavailable( any(Statement.class), any(ConsistencyLevel.class), anyInt(), anyInt(), anyInt()); } protected void assertOnRequestErrorWasCalled(int times, Class<? extends DriverException> expected) { Mockito.verify(retryPolicy, times(times)).onRequestError( any(Statement.class), any(ConsistencyLevel.class), any(expected), anyInt()); } protected void assertQueried(int hostNumber, int times) { assertThat(scassandras.node(hostNumber).activityClient().retrieveQueries()).hasSize(times); } @AfterMethod(groups = "short", alwaysRun = true) public void afterMethod() { if (cluster != null) cluster.close(); if (scassandras != null) scassandras.stop(); } @DataProvider public static Object[][] serverSideErrors() { return new Object[][]{ {server_error, ServerError.class}, {overloaded, OverloadedException.class}, }; } @DataProvider public static Object[][] connectionErrors() { return new Object[][]{ {CloseType.CLOSE}, {CloseType.HALFCLOSE}, {CloseType.RESET} }; } }