From 5819ca94500d6515dc0c0fc4308ee824205365dc Mon Sep 17 00:00:00 2001 From: Ivan Dneprov Date: Tue, 7 Feb 2023 15:22:18 +0300 Subject: [PATCH] Update test Update getRetryingTarantoolClient in ReconnectIT and add test_should_retryDelete_ifRecordsNotFound test to use it in retrying client documentation Needed for #309 --- .../driver/integration/ReconnectIT.java | 97 +++++++++++++++---- .../cartridge/app/roles/api_router.lua | 13 +++ 2 files changed, 89 insertions(+), 21 deletions(-) diff --git a/src/test/java/io/tarantool/driver/integration/ReconnectIT.java b/src/test/java/io/tarantool/driver/integration/ReconnectIT.java index ca6565b37..cafa2132c 100644 --- a/src/test/java/io/tarantool/driver/integration/ReconnectIT.java +++ b/src/test/java/io/tarantool/driver/integration/ReconnectIT.java @@ -6,6 +6,8 @@ import io.tarantool.driver.api.TarantoolClusterAddressProvider; import io.tarantool.driver.api.TarantoolResult; import io.tarantool.driver.api.TarantoolServerAddress; +import io.tarantool.driver.api.conditions.Conditions; +import io.tarantool.driver.api.space.TarantoolSpaceOperations; import io.tarantool.driver.api.tuple.DefaultTarantoolTupleFactory; import io.tarantool.driver.api.tuple.TarantoolTuple; import io.tarantool.driver.api.tuple.TarantoolTupleFactory; @@ -46,6 +48,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +/** + * @author Oleg Kuznetsov + * @author Ivan Dneprov + *

+ * WARNING: If you updated the code in this file, don't forget to update the MultiInstanceConnecting.md and + * docs/RetryingTarantoolClient.md permalinks! + */ @Testcontainers public class ReconnectIT extends SharedCartridgeContainer { @@ -53,6 +62,8 @@ public class ReconnectIT extends SharedCartridgeContainer { private static String USER_NAME; private static String PASSWORD; + private static final String SPACE_NAME = "test__profile"; + private static final String PK_FIELD_NAME = "profile_id"; private static final DefaultMessagePackMapperFactory mapperFactory = DefaultMessagePackMapperFactory.getInstance(); private static final TarantoolTupleFactory tupleFactory = @@ -66,6 +77,38 @@ public static void setUp() throws TimeoutException { PASSWORD = container.getPassword(); } + @Test + public void test_should_retryDelete_ifRecordsNotFound() { + // Creating multiple clients + TarantoolClient> crudClient = getCrudClient(); + TarantoolClient> retryingClient = + getRetryingTarantoolClient(crudClient); + + TarantoolSpaceOperations> space = + crudClient.space(SPACE_NAME); + space.truncate().join(); + + // Use TarantoolTupleFactory for instantiating new tuples + TarantoolTupleFactory tupleFactory = new DefaultTarantoolTupleFactory( + crudClient.getConfig().getMessagePackMapper()); + + // Start another thread that will insert value after 100 ms + new Thread(() -> { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + space.insert(tupleFactory.create(1, null, "FIO", 50, 100)).join(); + }).start(); + + // Call delete_with_error_if_not_found before record was inserted + Conditions conditions = Conditions.equals(PK_FIELD_NAME, 1); + assertDoesNotThrow(() -> retryingClient.space(SPACE_NAME).delete(conditions).join()); + TarantoolResult selectResult = space.select(conditions).join(); + assertEquals(0, selectResult.size()); + } + /** * Checking if this test is valid is here * {@link TarantoolErrorsIT#test_should_throwTarantoolNoSuchProcedureException_ifProcedureIsNil} @@ -73,14 +116,15 @@ public static void setUp() throws TimeoutException { @Test public void test_should_reconnect_ifCrudProcedureIsNotDefined() { //when - TarantoolClient> clusterClient = getClusterClient(); - TarantoolClient> retryingClient = getRetryingTarantoolClient(); + TarantoolClient> crudClient = getCrudClient(); + TarantoolClient> retryingClient = + getRetryingTarantoolClient(crudClient); try { //save procedure to tmp variable set it to nil and call - clusterClient.eval("rawset(_G, 'tmp_test_no_such_procedure', test_no_such_procedure)") - .thenAccept(c -> clusterClient.eval("rawset(_G, 'test_no_such_procedure', nil)")) - .thenApply(c -> clusterClient.call("test_no_such_procedure")) + crudClient.eval("rawset(_G, 'tmp_test_no_such_procedure', test_no_such_procedure)") + .thenAccept(c -> crudClient.eval("rawset(_G, 'test_no_such_procedure', nil)")) + .thenApply(c -> crudClient.call("test_no_such_procedure")) .join(); } catch (CompletionException exception) { assertTrue(exception.getCause() instanceof TarantoolNoSuchProcedureException); @@ -93,30 +137,36 @@ public void test_should_reconnect_ifCrudProcedureIsNotDefined() { } catch (InterruptedException e) { e.printStackTrace(); } - clusterClient.eval("rawset(_G, 'test_no_such_procedure', tmp_test_no_such_procedure)").join(); + crudClient.eval("rawset(_G, 'test_no_such_procedure', tmp_test_no_such_procedure)").join(); }).start(); assertDoesNotThrow(() -> retryingClient.call("test_no_such_procedure").join()); assertEquals("test_no_such_procedure", retryingClient.call("test_no_such_procedure").join().get(0)); } - private TarantoolClient> getClusterClient() { - return TarantoolClientFactory - .createClient() + private TarantoolClient> getCrudClient() { + return TarantoolClientFactory.createClient() + // You can connect to multiple routers .withAddresses( new TarantoolServerAddress(container.getRouterHost(), container.getMappedPort(3301)), new TarantoolServerAddress(container.getRouterHost(), container.getMappedPort(3302)), new TarantoolServerAddress(container.getRouterHost(), container.getMappedPort(3303)) ) + // For connecting to a Cartridge application, + // use the value of cluster_cookie parameter in the init.lua file .withCredentials(USER_NAME, PASSWORD) + // Number of connections per Tarantool instance .withConnections(10) + // Specify using the default CRUD proxy operations mapping configuration + .withProxyMethodMapping() .build(); } @Test public void test_should_reconnect_ifReconnectIsInvoked() throws Exception { //when - TarantoolClient> client = getRetryingTarantoolClient(); + TarantoolClient> client = + getRetryingTarantoolClient(getCrudClient()); // getting all routers uuids final Set routerUuids = getInstancesUuids(client); @@ -141,18 +191,23 @@ public void test_should_reconnect_ifReconnectIsInvoked() throws Exception { assertEquals(routerUuids.size(), uuidsAfterReconnect.size()); } - private TarantoolClient> getRetryingTarantoolClient() { - return TarantoolClientFactory.createClient() - .withAddresses( - new TarantoolServerAddress(container.getRouterHost(), container.getMappedPort(3301)), - new TarantoolServerAddress(container.getRouterHost(), container.getMappedPort(3302)), - new TarantoolServerAddress(container.getRouterHost(), container.getMappedPort(3303)) - ) - .withCredentials(USER_NAME, PASSWORD) - .withConnections(10) - .withProxyMethodMapping() + private TarantoolClient> getRetryingTarantoolClient( + TarantoolClient> client) { + + return TarantoolClientFactory.configureClient(client) + // Configure a custom delete function + .withProxyMethodMapping(builder -> builder.withDeleteFunctionName("delete_with_error_if_not_found")) + // Set retrying policy + // First parameter is number of attempts .withRetryingByNumberOfAttempts(5, - retryNetworkErrors().or(retryTarantoolNoSuchProcedureErrors()), + // You can use default predicates from TarantoolRequestRetryPolicies for checking errors + retryNetworkErrors() + // Also you can use your own predicates and combine them with each other + .or(e -> e.getMessage().contains("Unsuccessful attempt")) + .or(e -> e.getMessage().contains("Records not found")) + // Or with defaults + .or(retryTarantoolNoSuchProcedureErrors()), + // Also you can set delay in millisecond between attempts factory -> factory.withDelay(300) ) .build(); diff --git a/src/test/resources/cartridge/app/roles/api_router.lua b/src/test/resources/cartridge/app/roles/api_router.lua index 4ca735ede..9bbf25203 100644 --- a/src/test/resources/cartridge/app/roles/api_router.lua +++ b/src/test/resources/cartridge/app/roles/api_router.lua @@ -162,6 +162,19 @@ local function box_error_non_network_error() return nil, box.error.new(box.error.WAL_IO):unpack() end +function delete_with_error_if_not_found(space_name, key, opts) + local result, err = crud.delete(space_name, key, opts) + if err then + return nil, err + end + + if #result.rows == 0 then + return nil, "Records not found" + end + + return result +end + local function test_no_such_procedure() return 'test_no_such_procedure' end