From 56531ae94b38e5fb9c9886423e8c452f6ac15578 Mon Sep 17 00:00:00 2001 From: Aleksandr Pavlyuk Date: Thu, 5 Feb 2026 16:45:00 +0300 Subject: [PATCH] test: remove testcontainers-java-tarantool-lib --- pom.xml | 12 - tarantool-balancer/pom.xml | 8 +- .../balancer/integration/BaseTest.java | 3 + .../DistributingRoundRobinBalancerTest.java | 64 +- .../integration/RoundRobinBalancerTest.java | 39 +- .../integration/TarantoolBoxClientTest.java | 85 ++- .../integration/TarantoolClientTest.java | 36 +- .../integration/TarantoolCrudClientTest.java | 2 +- .../TarantoolCrudClientWithRetryTest.java | 2 +- .../integration/TarantoolDBContainer.java | 145 ---- tarantool-core/pom.xml | 2 +- .../ConnectionCloseOnClientSideTest.java | 29 +- .../ConnectionCloseOnServerSideTest.java | 27 +- .../ConnectionToTarantoolTest.java | 67 +- .../integration/GracefulShutdownTest.java | 30 +- .../core/integration/IProtoClientTest.java | 111 +-- .../integration/IProtoClientWatchersTest.java | 63 +- .../core/integration/MVCCStreamsTest.java | 76 +- .../src/test/resources/server-mvcc.lua | 61 +- tarantool-jackson-mapping/pom.xml | 8 +- .../mapping/integration/IProtoClientTest.java | 31 +- tarantool-pooling/pom.xml | 8 +- .../pool/integration/BasePoolTest.java | 19 +- .../ConnectionPoolHeartbeatTest.java | 41 +- .../ConnectionPoolReconnectsTest.java | 27 +- .../pool/integration/ConnectionPoolTest.java | 32 +- tarantool-schema/pom.xml | 2 +- .../TarantoolSchemaFetcherTest.java | 37 +- .../cartridge/Dockerfile | 43 ++ .../cartridge/server.lua | 10 + tarantool-shared-resources/logback-test.xml | 2 +- tarantool-shared-resources/server.lua | 50 +- .../integration/BaseIntegrationTest.java | 2 +- .../integration/BaseIntegrationTest.java | 2 +- .../integration/BaseIntegrationTest.java | 2 +- .../integration/BaseIntegrationTest.java | 2 +- .../integration/BaseIntegrationTest.java | 2 +- .../integration/BaseIntegrationTest.java | 2 +- testcontainers/pom.xml | 5 - .../TarantoolCartridgeContainer.java | 665 ++++++++++++++++++ .../TarantoolContainerOperations.java | 92 +++ .../containers/VshardClusterContainer.java | 22 +- .../tarantool/Tarantool2Container.java | 7 + .../tarantool/Tarantool3Container.java | 7 + .../tarantool/TarantoolContainer.java | 2 + .../utils/CartridgeConfigParser.java | 94 +++ .../containers/utils/PathUtils.java | 39 + .../containers/utils/SslContext.java | 34 + .../utils/TarantoolContainerClientHelper.java | 384 ++++++++++ .../src/test/resources/logback-test.xml | 15 - 50 files changed, 2006 insertions(+), 544 deletions(-) delete mode 100644 tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolDBContainer.java create mode 100644 tarantool-shared-resources/cartridge/Dockerfile create mode 100644 tarantool-shared-resources/cartridge/server.lua create mode 100644 testcontainers/src/main/java/org/testcontainers/containers/TarantoolCartridgeContainer.java create mode 100644 testcontainers/src/main/java/org/testcontainers/containers/TarantoolContainerOperations.java create mode 100644 testcontainers/src/main/java/org/testcontainers/containers/utils/CartridgeConfigParser.java create mode 100644 testcontainers/src/main/java/org/testcontainers/containers/utils/PathUtils.java create mode 100644 testcontainers/src/main/java/org/testcontainers/containers/utils/SslContext.java create mode 100644 testcontainers/src/main/java/org/testcontainers/containers/utils/TarantoolContainerClientHelper.java delete mode 100644 testcontainers/src/test/resources/logback-test.xml diff --git a/pom.xml b/pom.xml index 29d1540..9761eed 100644 --- a/pom.xml +++ b/pom.xml @@ -377,18 +377,6 @@ ${testcontainers.version} test - - io.tarantool - testcontainers-java-tarantool - v1.5.0 - test - - - io.netty - netty-all - - - org.apache.commons commons-lang3 diff --git a/tarantool-balancer/pom.xml b/tarantool-balancer/pom.xml index a307384..a8a9ab0 100644 --- a/tarantool-balancer/pom.xml +++ b/tarantool-balancer/pom.xml @@ -30,10 +30,6 @@ tarantool-pooling - - io.tarantool - testcontainers-java-tarantool - ch.qos.logback logback-classic @@ -46,6 +42,10 @@ org.junit.jupiter junit-jupiter + + io.tarantool + testcontainers + diff --git a/tarantool-balancer/src/test/java/io/tarantool/balancer/integration/BaseTest.java b/tarantool-balancer/src/test/java/io/tarantool/balancer/integration/BaseTest.java index 9f5c6db..c699f2b 100644 --- a/tarantool-balancer/src/test/java/io/tarantool/balancer/integration/BaseTest.java +++ b/tarantool-balancer/src/test/java/io/tarantool/balancer/integration/BaseTest.java @@ -17,6 +17,7 @@ import io.netty.util.Timer; import org.msgpack.value.ArrayValue; import org.msgpack.value.ValueFactory; +import org.testcontainers.containers.tarantool.TarantoolContainer; import io.tarantool.core.ManagedResource; import io.tarantool.core.connection.ConnectionFactory; @@ -24,6 +25,8 @@ public abstract class BaseTest { + protected static TarantoolContainer container; + protected static final Bootstrap bootstrap = new Bootstrap() .group(new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory())) diff --git a/tarantool-balancer/src/test/java/io/tarantool/balancer/integration/DistributingRoundRobinBalancerTest.java b/tarantool-balancer/src/test/java/io/tarantool/balancer/integration/DistributingRoundRobinBalancerTest.java index 33aba68..d0489ec 100644 --- a/tarantool-balancer/src/test/java/io/tarantool/balancer/integration/DistributingRoundRobinBalancerTest.java +++ b/tarantool-balancer/src/test/java/io/tarantool/balancer/integration/DistributingRoundRobinBalancerTest.java @@ -15,18 +15,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.createTarantoolContainer; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommandDecoded; import io.micrometer.core.instrument.MeterRegistry; import io.netty.util.HashedWheelTimer; import io.netty.util.Timer; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testcontainers.containers.TarantoolContainer; -import org.testcontainers.containers.output.Slf4jLogConsumer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; import static io.tarantool.core.protocol.requests.IProtoConstant.IPROTO_DATA; import io.tarantool.balancer.TarantoolBalancer; @@ -41,34 +41,33 @@ import io.tarantool.pool.InstanceConnectionGroup; @Timeout(value = 15) -@Testcontainers public class DistributingRoundRobinBalancerTest extends BaseTest { private static final Logger log = LoggerFactory.getLogger(DistributingRoundRobinBalancerTest.class); - @Container - private final TarantoolContainer tt1 = - new TarantoolContainer() - .withEnv(ENV_MAP) - .withExposedPort(3305) - .withLogConsumer(new Slf4jLogConsumer(log)); - - @Container - private final TarantoolContainer tt2 = - new TarantoolContainer() - .withEnv(ENV_MAP) - .withExposedPort(3305) - .withLogConsumer(new Slf4jLogConsumer(log)); + private static TarantoolContainer tt1; + private static TarantoolContainer tt2; @BeforeEach public void setUp() { + tt1 = createTarantoolContainer().withEnv(ENV_MAP).withExposedPorts(3301, 3305); + tt2 = createTarantoolContainer().withEnv(ENV_MAP).withExposedPorts(3301, 3305); + + tt1.start(); + tt2.start(); do { count1 = ThreadLocalRandom.current().nextInt(MIN_CONNECTION_COUNT, MAX_CONNECTION_COUNT + 1); count2 = ThreadLocalRandom.current().nextInt(MIN_CONNECTION_COUNT, MAX_CONNECTION_COUNT + 1); } while (count1 == count2); } + @AfterEach + void tearDown() { + tt1.stop(); + tt2.stop(); + } + private static IProtoClientPool createClientPool( boolean gracefulShutdown, HeartbeatOpts heartbeatOpts, MeterRegistry metricsRegistry) { ManagedResource timerResource = @@ -78,25 +77,28 @@ private static IProtoClientPool createClientPool( factory, timerResource, gracefulShutdown, heartbeatOpts, null, metricsRegistry); } - private int getSessionCounter(TarantoolContainer tt) throws Exception { - List result = tt.executeCommandDecoded("return get_session_counter()"); + private int getSessionCounter(TarantoolContainer tt) throws Exception { + List result = executeCommandDecoded(tt, "return get_session_counter()"); return (Integer) result.get(0); } - private int getCallCounter(TarantoolContainer tt) throws Exception { - List result = tt.executeCommandDecoded("return get_call_counter()"); + private int getCallCounter(TarantoolContainer tt) throws Exception { + List result = executeCommandDecoded(tt, "return get_call_counter()"); return (Integer) result.get(0); } - private void execLua(TarantoolContainer container, String command) { + private void execLua(TarantoolContainer container, String command) { try { - container.executeCommandDecoded(command); + executeCommandDecoded(container, command); } catch (Exception e) { } } private void wakeUpAllConnects( - TarantoolBalancer rrBalancer, int nodeVisits, TarantoolContainer tt1, TarantoolContainer tt2) + TarantoolBalancer rrBalancer, + int nodeVisits, + TarantoolContainer tt1, + TarantoolContainer tt2) throws Exception { walkAndJoin(rrBalancer, nodeVisits * 2); assertEquals(count1, getSessionCounter(tt1)); @@ -130,13 +132,13 @@ public void testDistributingRoundRobin() throws Exception { Arrays.asList( InstanceConnectionGroup.builder() .withHost(tt1.getHost()) - .withPort(tt1.getPort()) + .withPort(tt1.getFirstMappedPort()) .withSize(count1) .withTag("node-a-00") .build(), InstanceConnectionGroup.builder() .withHost(tt2.getHost()) - .withPort(tt2.getPort()) + .withPort(tt2.getFirstMappedPort()) .withSize(count2) .withTag("node-b-00") .build())); @@ -160,7 +162,7 @@ public void testDistributingRoundRobinWithUnavailableNodeA() throws Exception { .build(), InstanceConnectionGroup.builder() .withHost(tt2.getHost()) - .withPort(tt2.getPort()) + .withPort(tt2.getFirstMappedPort()) .withSize(count2) .withTag("node-b-01") .build())); @@ -217,7 +219,7 @@ public void testDistributingRoundRobinWithUnavailableNodeANoUnlock() throws Exce .build(), InstanceConnectionGroup.builder() .withHost(tt2.getHost()) - .withPort(tt2.getPort()) + .withPort(tt2.getFirstMappedPort()) .withSize(count2) .withTag("node-b-02") .build())); @@ -323,12 +325,12 @@ public void testDistributingRoundRobinStartWithStuckNodeA() throws Exception { Arrays.asList( InstanceConnectionGroup.builder() .withHost(tt1.getHost()) - .withPort(tt1.getPort()) + .withPort(tt1.getFirstMappedPort()) .withTag("node-a-01") .build(), InstanceConnectionGroup.builder() .withHost(tt2.getHost()) - .withPort(tt2.getPort()) + .withPort(tt2.getFirstMappedPort()) .withTag("node-b-01") .build())); pool.setConnectTimeout(3_000); diff --git a/tarantool-balancer/src/test/java/io/tarantool/balancer/integration/RoundRobinBalancerTest.java b/tarantool-balancer/src/test/java/io/tarantool/balancer/integration/RoundRobinBalancerTest.java index a848966..686e5cb 100644 --- a/tarantool-balancer/src/test/java/io/tarantool/balancer/integration/RoundRobinBalancerTest.java +++ b/tarantool-balancer/src/test/java/io/tarantool/balancer/integration/RoundRobinBalancerTest.java @@ -10,13 +10,14 @@ import java.util.concurrent.ThreadLocalRandom; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.createTarantoolContainer; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommandDecoded; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.msgpack.value.ValueFactory; -import org.testcontainers.containers.TarantoolContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; import io.tarantool.balancer.TarantoolBalancer; import io.tarantool.balancer.TarantoolRoundRobinBalancer; @@ -26,28 +27,36 @@ import io.tarantool.pool.InstanceConnectionGroup; @Timeout(value = 5) -@Testcontainers public class RoundRobinBalancerTest extends BaseTest { - @Container - private static final TarantoolContainer tt1 = new TarantoolContainer().withEnv(ENV_MAP); - - @Container - private static final TarantoolContainer tt2 = new TarantoolContainer().withEnv(ENV_MAP); + private static TarantoolContainer tt1; + private static TarantoolContainer tt2; @BeforeAll public static void setUp() { + tt1 = createTarantoolContainer(); + tt2 = createTarantoolContainer(); + + tt1.start(); + tt2.start(); + count1 = ThreadLocalRandom.current().nextInt(MIN_CONNECTION_COUNT, MAX_CONNECTION_COUNT + 1); count2 = ThreadLocalRandom.current().nextInt(MIN_CONNECTION_COUNT, MAX_CONNECTION_COUNT + 1); } - private int getSessionCounter(TarantoolContainer tt) throws Exception { - List result = tt.executeCommandDecoded("return get_session_counter()"); + @AfterAll + static void tearDown() { + tt1.stop(); + tt2.stop(); + } + + private int getSessionCounter(TarantoolContainer tt) throws Exception { + List result = executeCommandDecoded(tt, "return get_session_counter()"); return (Integer) result.get(0); } - private int getCallCounter(TarantoolContainer tt) throws Exception { - List result = tt.executeCommandDecoded("return get_call_counter()"); + private int getCallCounter(TarantoolContainer tt) throws Exception { + List result = executeCommandDecoded(tt, "return get_call_counter()"); return (Integer) result.get(0); } @@ -58,13 +67,13 @@ public void testRoundRobin() throws Exception { Arrays.asList( InstanceConnectionGroup.builder() .withHost(tt1.getHost()) - .withPort(tt1.getPort()) + .withPort(tt1.getFirstMappedPort()) .withSize(count1) .withTag("node-a") .build(), InstanceConnectionGroup.builder() .withHost(tt2.getHost()) - .withPort(tt2.getPort()) + .withPort(tt2.getFirstMappedPort()) .withSize(count2) .withTag("node-b") .build())); diff --git a/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolBoxClientTest.java b/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolBoxClientTest.java index 0a95636..ae09946 100644 --- a/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolBoxClientTest.java +++ b/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolBoxClientTest.java @@ -32,7 +32,11 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.createTarantoolContainer; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommand; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommandDecoded; import com.fasterxml.jackson.core.type.TypeReference; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -43,9 +47,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.testcontainers.containers.TarantoolContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; import org.testcontainers.shaded.com.google.common.base.CaseFormat; import static io.tarantool.client.box.TarantoolBoxSpace.WITHOUT_ENABLED_FETCH_SCHEMA_OPTION_FOR_TARANTOOL_LESS_3_0_0; @@ -74,10 +76,9 @@ import io.tarantool.schema.TarantoolSchemaFetcher; @Timeout(value = 5) -@Testcontainers public class TarantoolBoxClientTest extends BaseTest { - @Container private static final TarantoolContainer tt = new TarantoolContainer().withEnv(ENV_MAP); + private static TarantoolContainer tt; public static final List EMPTY_LIST = Collections.emptyList(); private static Integer spacePersonId; private static TarantoolBoxClient client; @@ -89,18 +90,21 @@ public class TarantoolBoxClientTest extends BaseTest { @BeforeEach public void truncateSpaces() throws Exception { - tt.executeCommand("return box.space.person:truncate()"); + executeCommand(tt, "return box.space.person:truncate()"); triplets = new ArrayList<>(); } @BeforeAll public static void setUp() throws Exception { + tt = createTarantoolContainer().withEnv(ENV_MAP); + tt.start(); + client = TarantoolFactory.box() .withUser(API_USER) .withPassword(CREDS.get(API_USER)) .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .withIgnoredPacketsHandler( (tag, index, packet) -> { synchronized (triplets) { @@ -114,7 +118,7 @@ public static void setUp() throws Exception { .withUser(API_USER) .withPassword(CREDS.get(API_USER)) .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .withFetchSchema(false) .withIgnoredPacketsHandler( (tag, index, packet) -> { @@ -124,7 +128,7 @@ public static void setUp() throws Exception { }) .build(); - List result = tt.executeCommandDecoded("return box.space.person.id"); + List result = executeCommandDecoded(tt, "return box.space.person.id"); spacePersonId = (Integer) result.get(0); try { @@ -135,6 +139,11 @@ public static void setUp() throws Exception { } } + @AfterAll + static void tearDown() { + tt.stop(); + } + public static Stream dataForNPETest() { return Stream.of( // insert @@ -226,7 +235,7 @@ public void testUserPassword() throws Exception { TarantoolBoxClient userA = TarantoolFactory.box() .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .withUser("user_a") .withPassword("secret_a") .build(); @@ -270,7 +279,7 @@ public void testSchemaFetcher() throws Exception { .withUser(API_USER) .withPassword(CREDS.get(API_USER)) .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .withFetchSchema(false) .build(); Person person = new Person(1, true, "Dima"); @@ -417,7 +426,7 @@ public void testSpaceBySpaceNameAfterAddingNewSpace() throws Exception { .withUser(API_USER) .withPassword(CREDS.get(API_USER)) .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .build(); assertEquals(EMPTY_LIST, customClient.space("person").select(EMPTY_LIST).join().get()); client @@ -436,7 +445,7 @@ public void testExceptionIfSchemaIsNotExist() throws Exception { .withUser(API_USER) .withPassword(CREDS.get(API_USER)) .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .build(); String nonExistingSpaceName = "non-existing-space-name"; NoSchemaException ex = @@ -452,7 +461,7 @@ public void testExceptionIfIndexIsNotExist() throws Exception { .withUser(API_USER) .withPassword(CREDS.get(API_USER)) .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .build(); String spaceName = "person"; TarantoolBoxSpace space = customClient.space(spaceName); @@ -551,11 +560,11 @@ public void testSelectPagination(Boolean useSpaceName) throws Exception { Person secondPerson = new Person(2, true, "Kolya"); assertEquals( Collections.singletonList(firstPerson.asList()), - tt.executeCommandDecoded("return box.space.person:insert({1, true, 'Dima'})")); + executeCommandDecoded(tt, "return box.space.person:insert({1, true, 'Dima'})")); assertEquals( Collections.singletonList(secondPerson.asList()), - tt.executeCommandDecoded("return box.space.person:insert({2, true, 'Kolya'})")); + executeCommandDecoded(tt, "return box.space.person:insert({2, true, 'Kolya'})")); SelectResponse>>> firstBatch = testSpace @@ -598,11 +607,11 @@ public void testSelectIndex() throws Exception { List firstTuple = Arrays.asList(1, true, "2"); assertEquals( Collections.singletonList(firstTuple), - tt.executeCommandDecoded("return box.space.person:insert({1, true, '2'})")); + executeCommandDecoded(tt, "return box.space.person:insert({1, true, '2'})")); List secondTuple = Arrays.asList(2, true, "1"); assertEquals( Collections.singletonList(secondTuple), - tt.executeCommandDecoded("return box.space.person:insert({2, true, '1'})")); + executeCommandDecoded(tt, "return box.space.person:insert({2, true, '1'})")); client.eval("box.space.person:create_index('name_index', { parts = { 'name' } })").join(); @@ -628,10 +637,10 @@ public void doSelectRequestShouldBeSuccessful(TarantoolBoxSpace testSpace, Selec Person secondPerson = new Person(2, true, "Kolya"); assertEquals( Collections.singletonList(firstPerson.asList()), - tt.executeCommandDecoded("return box.space.person:insert({1, true, 'Dima'})")); + executeCommandDecoded(tt, "return box.space.person:insert({1, true, 'Dima'})")); assertEquals( Collections.singletonList(secondPerson.asList()), - tt.executeCommandDecoded("return box.space.person:insert({2, true, 'Kolya'})")); + executeCommandDecoded(tt, "return box.space.person:insert({2, true, 'Kolya'})")); SelectResponse>>> selectResult = testSpace.select(EMPTY_LIST, options).join(); @@ -874,7 +883,7 @@ public void doDeleteRequestShouldBeSuccessful(TarantoolBoxSpace testSpace, Delet // simple assertEquals( Collections.singletonList(person.asList()), - tt.executeCommandDecoded("return box.space.person:insert({1, true, 'Dima'})")); + executeCommandDecoded(tt, "return box.space.person:insert({1, true, 'Dima'})")); Tuple> responseAsListWithKeyOnly = testSpace.delete(key, options).join(); List resultAsListWithKeyOnly = responseAsListWithKeyOnly.get(); @@ -884,7 +893,7 @@ public void doDeleteRequestShouldBeSuccessful(TarantoolBoxSpace testSpace, Delet // with entity Class assertEquals( Collections.singletonList(person.asList()), - tt.executeCommandDecoded("return box.space.person:insert({1, true, 'Dima'})")); + executeCommandDecoded(tt, "return box.space.person:insert({1, true, 'Dima'})")); Tuple responseAsClassWithKeyAndClass = testSpace.delete(key, options, Person.class).join(); @@ -895,7 +904,7 @@ public void doDeleteRequestShouldBeSuccessful(TarantoolBoxSpace testSpace, Delet // with typeReference tuple as list assertEquals( Collections.singletonList(person.asList()), - tt.executeCommandDecoded("return box.space.person:insert({1, true, 'Dima'})")); + executeCommandDecoded(tt, "return box.space.person:insert({1, true, 'Dima'})")); TarantoolResponse>> responseAsListWithTypeRefAsList = testSpace.delete(key, options, typeReferenceAsList).join(); @@ -906,7 +915,7 @@ public void doDeleteRequestShouldBeSuccessful(TarantoolBoxSpace testSpace, Delet // with typeReference tuple as class assertEquals( Collections.singletonList(person.asList()), - tt.executeCommandDecoded("return box.space.person:insert({1, true, 'Dima'})")); + executeCommandDecoded(tt, "return box.space.person:insert({1, true, 'Dima'})")); TarantoolResponse> responseAsClassWithTypeRefAsClass = testSpace.delete(key, options, typeReferenceAsPersonClass).join(); @@ -944,7 +953,7 @@ public void testReplace(Boolean useSpaceName) throws Exception { assertEquals( Collections.singletonList(Arrays.asList(1, true, "0")), - tt.executeCommandDecoded("return box.space.person:insert({1, true, '0'})")); + executeCommandDecoded(tt, "return box.space.person:insert({1, true, '0'})")); doReplaceRequestShouldBeSuccessful(testSpace); } @@ -960,7 +969,7 @@ public void testReplaceWithoutFetcherAndWithSpaceAndIndexFeature(Boolean useSpac assertEquals( Collections.singletonList(Arrays.asList(1, true, "0")), - tt.executeCommandDecoded("return box.space.person:insert({1, true, '0'})")); + executeCommandDecoded(tt, "return box.space.person:insert({1, true, '0'})")); doReplaceRequestShouldBeSuccessful(testSpace); } @@ -976,7 +985,7 @@ public void testReplaceWithoutFetcherAndWithoutSpaceAndIndexFeature(Boolean useS assertEquals( Collections.singletonList(Arrays.asList(1, true, "0")), - tt.executeCommandDecoded("return box.space.person:insert({1, true, '0'})")); + executeCommandDecoded(tt, "return box.space.person:insert({1, true, '0'})")); if (!useSpaceName) { doReplaceRequestShouldBeSuccessful(testSpace); @@ -1091,7 +1100,7 @@ public void doUpdateRequestShouldBeSuccessful(TarantoolBoxSpace testSpace, Updat assertEquals( Collections.singletonList(person.asList()), - tt.executeCommandDecoded("return box.space.person:insert ({1, true, '0'})")); + executeCommandDecoded(tt, "return box.space.person:insert ({1, true, '0'})")); List key = Collections.singletonList(person.getId()); @@ -1271,7 +1280,7 @@ public void doUpsertRequestShouldBeSuccessful(TarantoolBoxSpace testSpace, Updat .join(); assertEquals( Collections.singletonList(person.asList()), - ((List) tt.executeCommandDecoded("return box.space.person:select()")).get(0)); + ((List) executeCommandDecoded(tt, "return box.space.person:select()")).get(0)); person.setName("DimaK"); testSpace @@ -1279,9 +1288,9 @@ public void doUpsertRequestShouldBeSuccessful(TarantoolBoxSpace testSpace, Updat .join(); assertEquals( Collections.singletonList(person.asList()), - ((List) tt.executeCommandDecoded("return box.space.person:select()")).get(0)); + ((List) executeCommandDecoded(tt, "return box.space.person:select()")).get(0)); - tt.executeCommandDecoded("return box.space.person:truncate()"); + executeCommandDecoded(tt, "return box.space.person:truncate()"); Person otherPerson = new Person(2, false, "Thomas Sawer"); testSpace @@ -1289,13 +1298,13 @@ public void doUpsertRequestShouldBeSuccessful(TarantoolBoxSpace testSpace, Updat .join(); assertEquals( Collections.singletonList(otherPerson.asList()), - ((List) tt.executeCommandDecoded("return box.space.person:select()")).get(0)); + ((List) executeCommandDecoded(tt, "return box.space.person:select()")).get(0)); otherPerson.setName("Tom"); testSpace.upsert(otherPerson, Operations.create().set("name", "Tom"), options).join(); assertEquals( Collections.singletonList(otherPerson.asList()), - ((List) tt.executeCommandDecoded("return box.space.person:select()")).get(0)); + ((List) executeCommandDecoded(tt, "return box.space.person:select()")).get(0)); } @ParameterizedTest @@ -1304,9 +1313,9 @@ public void testSelectWithArgs(Boolean useSpaceName) throws Exception { TarantoolBoxSpace testSpace = useSpaceName ? client.space("person") : client.space(spacePersonId); - tt.executeCommandDecoded("return box.space.person:insert({1, true, 'Dima'})"); - tt.executeCommandDecoded("return box.space.person:insert({2, true, 'Roma'})"); - tt.executeCommandDecoded("return box.space.person:insert({3, false, 'Kolya'})"); + executeCommandDecoded(tt, "return box.space.person:insert({1, true, 'Dima'})"); + executeCommandDecoded(tt, "return box.space.person:insert({2, true, 'Roma'})"); + executeCommandDecoded(tt, "return box.space.person:insert({3, false, 'Kolya'})"); Person dima = new Person(1, true, "Dima"); Person roma = new Person(2, true, "Roma"); Person kolya = new Person(3, false, "Kolya"); @@ -1348,7 +1357,7 @@ void testUserWithNullPassword() throws Exception { TarantoolBoxClient serviceClient = TarantoolFactory.box() .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .withUser("service_user") .withPassword("") .build(); @@ -1360,7 +1369,7 @@ void testGetServerVersion() throws Exception { TarantoolBoxClient client = TarantoolFactory.box() .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .withUser("service_user") .withPassword("") .build(); diff --git a/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolClientTest.java b/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolClientTest.java index 6d4b51d..4156c77 100644 --- a/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolClientTest.java +++ b/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolClientTest.java @@ -24,15 +24,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.createTarantoolContainer; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommand; import com.fasterxml.jackson.core.type.TypeReference; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.testcontainers.containers.TarantoolContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; import io.tarantool.client.BaseOptions; import io.tarantool.client.TarantoolClient; @@ -48,36 +49,43 @@ import io.tarantool.pool.exceptions.PoolClosedException; @Timeout(value = 5) -@Testcontainers public class TarantoolClientTest extends BaseTest { - @Container private static final TarantoolContainer tt = new TarantoolContainer().withEnv(ENV_MAP); + private static TarantoolContainer tt; private static TarantoolClient client; private static char tarantoolVersion; private static Integer serverVersion; @BeforeAll public static void setUp() throws Exception { + tt = createTarantoolContainer().withEnv(ENV_MAP); + tt.start(); + client = getClientAndConnect(); client.getPool().forEach(c -> c.authorize(API_USER, CREDS.get(API_USER)).join()); serverVersion = client.getPool().get("default", 0).join().getServerProtocolVersion(); } + @AfterAll + static void tearDown() { + tt.stop(); + } + private static TarantoolClient getClientAndConnect() throws Exception { return TarantoolFactory.box() .withUser(API_USER) .withPassword(CREDS.get(API_USER)) .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .build(); } @BeforeEach public void truncateSpaces() throws Exception { - tt.executeCommand("return box.space.test:truncate()"); - tt.executeCommand("return box.space.space_a:truncate()"); - tt.executeCommand("return box.space.space_b:truncate()"); - tt.executeCommand("return box.space.person:truncate()"); + executeCommand(tt, "return box.space.test:truncate()"); + executeCommand(tt, "return box.space.space_a:truncate()"); + executeCommand(tt, "return box.space.space_b:truncate()"); + executeCommand(tt, "return box.space.person:truncate()"); client = getClientAndConnect(); tarantoolVersion = System.getenv("TARANTOOL_VERSION").charAt(0); @@ -530,7 +538,7 @@ void testCloseConnectionFromFewThreads() throws Exception { .withUser(API_USER) .withPassword(CREDS.get(API_USER)) .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .build(); final ExecutorService pool = Executors.newFixedThreadPool(100); @@ -552,7 +560,7 @@ void testIdempotency() throws Exception { .withUser(API_USER) .withPassword(CREDS.get(API_USER)) .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .build(); final int closeCount = 100; @@ -571,7 +579,7 @@ void testAutoCloseable() throws Exception { .withUser(API_USER) .withPassword(CREDS.get(API_USER)) .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .build()) { space = testClient.space("person"); assertEquals(person, space.insert(person, Person.class).join().get()); @@ -587,7 +595,7 @@ void testAutoCloseableWithInvalidRequest() throws Exception { .withUser(API_USER) .withPassword(CREDS.get(API_USER)) .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .build()) { space = testClient.space("person"); final Person person = new Person(0, true, "first"); diff --git a/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolCrudClientTest.java b/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolCrudClientTest.java index 2053e58..a946d1a 100644 --- a/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolCrudClientTest.java +++ b/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolCrudClientTest.java @@ -145,7 +145,7 @@ public static void setUp() throws Exception { } else { cartridgeContainer = new TarantoolCartridgeContainer( - "Dockerfile", + "cartridge/Dockerfile", dockerRegistry + "cartridge", "cartridge/instances.yml", "cartridge/replicasets.yml", diff --git a/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolCrudClientWithRetryTest.java b/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolCrudClientWithRetryTest.java index d886726..9d0349f 100644 --- a/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolCrudClientWithRetryTest.java +++ b/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolCrudClientWithRetryTest.java @@ -84,7 +84,7 @@ private void execute() { private static final TarantoolCartridgeContainer tt = new TarantoolCartridgeContainer( - "Dockerfile", + "cartridge/Dockerfile", System.getenv().getOrDefault("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", "") + "cartridge", "cartridge/instances.yml", diff --git a/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolDBContainer.java b/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolDBContainer.java deleted file mode 100644 index a102336..0000000 --- a/tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolDBContainer.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY - * All Rights Reserved. - */ - -package io.tarantool.client.integration; - -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; - -import com.github.dockerjava.api.command.InspectContainerResponse; -import org.testcontainers.containers.Arguments; -import org.testcontainers.containers.TarantoolCartridgeContainer; -import org.testcontainers.containers.exceptions.CartridgeTopologyException; - -public class TarantoolDBContainer extends TarantoolCartridgeContainer { - - public TarantoolDBContainer(String instancesFile, String topologyConfigurationFile) { - super(instancesFile, topologyConfigurationFile); - } - - public TarantoolDBContainer( - String instancesFile, String topologyConfigurationFile, Map buildArgs) { - super(instancesFile, topologyConfigurationFile, buildArgs); - } - - public TarantoolDBContainer( - String dockerFile, String instancesFile, String topologyConfigurationFile) { - super(dockerFile, instancesFile, topologyConfigurationFile); - } - - public TarantoolDBContainer( - String dockerFile, - String buildImageName, - String instancesFile, - String topologyConfigurationFile) { - super(dockerFile, buildImageName, instancesFile, topologyConfigurationFile); - } - - public TarantoolDBContainer( - String dockerFile, - String buildImageName, - String instancesFile, - String topologyConfigurationFile, - String baseImage) { - super( - dockerFile, - buildImageName, - instancesFile, - topologyConfigurationFile, - Arguments.get(baseImage, "enterprise")); - } - - @Override - protected void containerIsStarted(InspectContainerResponse containerInfo, boolean reused) { - super.containerIsStarted(containerInfo, reused); - try { - execInContainer("bash", "upload_migrations.sh"); - executeCommand("return require('migrator').up()"); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public void waitUntilInstancesAreHealthy() { - waitUntilCartridgeIsHealthy(TIMEOUT_ROUTER_UP_CARTRIDGE_HEALTH_IN_SECONDS); - } - - public boolean areInstancesHealthy() { - return isCartridgeHealthy(); - } - - public void startInstances() { - try { - execInContainer("tt", "start", "tarantooldb"); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } - } - - public void stopInstance(String instanceName) { - try { - execInContainer("tt", "stop", "tarantooldb:" + instanceName); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } - } - - @Override - protected boolean setupTopology() { - String fileType = - topologyConfigurationFile.substring(topologyConfigurationFile.lastIndexOf('.') + 1); - if (fileType.equals("yml")) { - String replicasetsFileName = - topologyConfigurationFile.substring(topologyConfigurationFile.lastIndexOf('/') + 1); - String instancesFileName = instancesFile.substring(instancesFile.lastIndexOf('/') + 1); - try { - ExecResult result = - execInContainer( - "tt", - "cartridge", - "replicasets", - "setup", - "--run-dir=" + TARANTOOL_RUN_DIR, - "--file=" + replicasetsFileName, - "--cfg=" + instancesFileName, - "--bootstrap-vshard"); - if (result.getExitCode() != 0) { - throw new CartridgeTopologyException( - "Failed to change the app topology via tt CLI: " - + result.getStdout() - + " " - + result.getStderr()); - } - } catch (Exception e) { - throw new CartridgeTopologyException(e); - } - - } else { - try { - List res = executeScriptDecoded(topologyConfigurationFile); - if (res.size() >= 2 && res.get(1) != null && res.get(1) instanceof Map) { - HashMap error = ((HashMap) res.get(1)); - // that means topology already exists - return error.get("str").toString().contains("collision with another server"); - } - // The client connection will be closed after that command - } catch (Exception e) { - if (e instanceof ExecutionException) { - if (e.getCause() instanceof TimeoutException) { - return true; - // Do nothing, the cluster is reloading - } - } else { - throw new CartridgeTopologyException(e); - } - } - } - return true; - } -} diff --git a/tarantool-core/pom.xml b/tarantool-core/pom.xml index f1bc1d7..873869d 100644 --- a/tarantool-core/pom.xml +++ b/tarantool-core/pom.xml @@ -76,7 +76,7 @@ io.tarantool - testcontainers-java-tarantool + testcontainers diff --git a/tarantool-core/src/test/java/io/tarantool/core/integration/ConnectionCloseOnClientSideTest.java b/tarantool-core/src/test/java/io/tarantool/core/integration/ConnectionCloseOnClientSideTest.java index 5bc6b0b..4bc0e0a 100644 --- a/tarantool-core/src/test/java/io/tarantool/core/integration/ConnectionCloseOnClientSideTest.java +++ b/tarantool-core/src/test/java/io/tarantool/core/integration/ConnectionCloseOnClientSideTest.java @@ -11,11 +11,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.createTarantoolContainer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import org.testcontainers.containers.TarantoolContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; import static io.tarantool.core.HelpersUtils.findRootCause; import io.tarantool.core.connection.Connection; @@ -23,10 +24,20 @@ import io.tarantool.core.connection.exceptions.ConnectionClosedException; @Timeout(value = 5) -@Testcontainers public class ConnectionCloseOnClientSideTest extends BaseTest { - @Container private static final TarantoolContainer tt = new TarantoolContainer().withEnv(ENV_MAP); + private static TarantoolContainer tt; + + @BeforeAll + static void setUp() { + tt = createTarantoolContainer().withEnv(ENV_MAP); + tt.start(); + } + + @AfterAll + static void tearDown() { + tt.stop(); + } @Test public void testConnectAndClose() throws Exception { @@ -37,11 +48,11 @@ public void testConnectAndClose() throws Exception { (c, ex) -> { closeFuture.completeExceptionally(ex); }); - InetSocketAddress address = new InetSocketAddress(tt.getHost(), tt.getPort()); + InetSocketAddress address = tt.mappedAddress(); connection.connect(address, 3_000).get(); Thread.sleep(500); connection.close(); - Exception ex = assertThrows(CompletionException.class, () -> closeFuture.join()); + Exception ex = assertThrows(CompletionException.class, closeFuture::join); Throwable cause = ex.getCause(); assertEquals(ConnectionClosedException.class, cause.getClass()); assertEquals(ConnectionClosedException.class, findRootCause(ex).getClass()); @@ -57,11 +68,11 @@ public void testConnectAndCloseShutdown() throws Exception { (c, ex) -> { closeFuture.completeExceptionally(ex); }); - InetSocketAddress address = new InetSocketAddress(tt.getHost(), tt.getPort()); + InetSocketAddress address = tt.mappedAddress(); connection.connect(address, 3_000).get(); Thread.sleep(500); connection.shutdownClose(); - Exception ex = assertThrows(CompletionException.class, () -> closeFuture.join()); + Exception ex = assertThrows(CompletionException.class, closeFuture::join); Throwable cause = ex.getCause(); assertEquals(ConnectionClosedException.class, cause.getClass()); assertEquals(ConnectionClosedException.class, findRootCause(ex).getClass()); diff --git a/tarantool-core/src/test/java/io/tarantool/core/integration/ConnectionCloseOnServerSideTest.java b/tarantool-core/src/test/java/io/tarantool/core/integration/ConnectionCloseOnServerSideTest.java index 23aa7d4..73e3bf0 100644 --- a/tarantool-core/src/test/java/io/tarantool/core/integration/ConnectionCloseOnServerSideTest.java +++ b/tarantool-core/src/test/java/io/tarantool/core/integration/ConnectionCloseOnServerSideTest.java @@ -15,11 +15,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.createTarantoolContainer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import org.testcontainers.containers.TarantoolContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; import static io.tarantool.core.HelpersUtils.findRootCause; import io.tarantool.core.connection.Connection; @@ -27,10 +28,20 @@ import io.tarantool.core.connection.exceptions.ConnectionClosedException; @Timeout(value = 7) -@Testcontainers public class ConnectionCloseOnServerSideTest extends BaseTest { - @Container private static final TarantoolContainer tt = new TarantoolContainer().withEnv(ENV_MAP); + private static TarantoolContainer tt; + + @BeforeAll + static void setUp() { + tt = createTarantoolContainer().withEnv(ENV_MAP); + tt.start(); + } + + @AfterAll + static void tearDown() { + tt.stop(); + } @Test public void testConnectAndCloseOnServer() throws Exception { @@ -60,10 +71,10 @@ public void testConnectAndCloseOnServer() throws Exception { (c, ex) -> { flags.put(ConnectionCloseEvent.CLOSE_BY_SHUTDOWN, true); }); - InetSocketAddress address = new InetSocketAddress(tt.getHost(), tt.getPort()); + InetSocketAddress address = tt.mappedAddress(); connection.connect(address, 3_000).get(); - tt.execInContainer("kill", "1"); - Exception ex = assertThrows(CompletionException.class, () -> closeFuture.join()); + tt.stop(); + Exception ex = assertThrows(CompletionException.class, closeFuture::join); Throwable cause = ex.getCause(); assertEquals(ConnectionClosedException.class, cause.getClass()); assertEquals(ConnectionClosedException.class, findRootCause(ex).getClass()); diff --git a/tarantool-core/src/test/java/io/tarantool/core/integration/ConnectionToTarantoolTest.java b/tarantool-core/src/test/java/io/tarantool/core/integration/ConnectionToTarantoolTest.java index 0c4d299..a95d799 100644 --- a/tarantool-core/src/test/java/io/tarantool/core/integration/ConnectionToTarantoolTest.java +++ b/tarantool-core/src/test/java/io/tarantool/core/integration/ConnectionToTarantoolTest.java @@ -32,7 +32,10 @@ import static org.msgpack.value.ValueFactory.newArray; import static org.msgpack.value.ValueFactory.newInteger; import static org.msgpack.value.ValueFactory.newMap; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.createTarantoolContainer; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommandDecoded; import io.netty.channel.ConnectTimeoutException; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; @@ -41,9 +44,7 @@ import org.msgpack.value.ValueFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testcontainers.containers.TarantoolContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; import static io.tarantool.core.HelpersUtils.findRootCause; import static io.tarantool.core.protocol.requests.IProtoConstant.IPROTO_DATA; @@ -68,7 +69,6 @@ import io.tarantool.core.protocol.requests.IProtoAuth.AuthType; @Timeout(value = 5) -@Testcontainers public class ConnectionToTarantoolTest extends BaseTest { private static final Logger log = LoggerFactory.getLogger(ConnectionToTarantoolTest.class); @@ -99,36 +99,39 @@ public void accept(IProtoResponse msg) { private static UUID instanceUUID; private static int spaceId; - @Container - private static final TarantoolContainer tt = - new TarantoolContainer() - .withEnv(ENV_MAP) - .withExposedPort(3302) - .withExposedPort(3303) - .withExposedPort(3304) - .withExposedPort(3306); + private static TarantoolContainer tt; @BeforeAll public static void setUp() throws Exception { - List result = tt.executeCommandDecoded("return get_version()"); - version = (String) result.get(0); + tt = createTarantoolContainer().withEnv(ENV_MAP).withExposedPorts(3302, 3303, 3304, 3306); + + tt.start(); + + List result = executeCommandDecoded(tt, "return box.info.version"); + String fullVersion = (String) result.get(0); + version = fullVersion.split("-")[0]; protocolType = "binary"; - result = tt.executeCommandDecoded("return box.info.uuid"); + result = executeCommandDecoded(tt, "return box.info.uuid"); String uuid = (String) result.get(0); instanceUUID = UUID.fromString(uuid); - result = tt.executeCommandDecoded("return box.space.test.id"); + result = executeCommandDecoded(tt, "return box.space.test.id"); spaceId = (Integer) result.get(0); - tt.executeCommandDecoded("lock_pipe(true)"); // for tests using 3305 port (no greeting) + executeCommandDecoded(tt, "lock_pipe(true)"); // for tests using 3305 port (no greeting) + } + + @AfterAll + static void tearDown() { + tt.stop(); } @Test public void testConnect() throws Exception { Connection client = factory.create(); - InetSocketAddress address = new InetSocketAddress(tt.getHost(), tt.getPort()); + InetSocketAddress address = tt.mappedAddress(); CompletableFuture connectFuture = client.connect(address, 3_000); Greeting greeting = connectFuture.get(); assertEquals(greeting.getVersion(), version); @@ -146,7 +149,7 @@ public void testConnectToAddressWithBadPort() { Connection client = factory.create(); InetSocketAddress address = new InetSocketAddress(tt.getHost(), BAD_PORT); CompletableFuture future = client.connect(address, 3_000); - Exception ex = assertThrows(CompletionException.class, () -> future.join()); + Exception ex = assertThrows(CompletionException.class, future::join); Throwable cause = ex.getCause(); assertEquals(ConnectionException.class, cause.getClass()); assertEquals( @@ -164,9 +167,9 @@ public void testConnectToAddressWithBadPort() { @RepeatedTest(value = 3) public void testConnectToAddressWithBadHost() { Connection client = factory.create(); - InetSocketAddress address = new InetSocketAddress(BAD_HOST, tt.getPort()); + InetSocketAddress address = new InetSocketAddress(BAD_HOST, tt.getFirstMappedPort()); CompletableFuture future = client.connect(address, 5_000); - Exception ex = assertThrows(CompletionException.class, () -> future.join()); + Exception ex = assertThrows(CompletionException.class, future::join); Throwable cause = ex.getCause(); if (cause.getClass() == ConnectionException.class) { assertEquals( @@ -192,7 +195,7 @@ public void testConnectToNonAcceptingService() { Connection client = factory.create(); InetSocketAddress address = new InetSocketAddress(tt.getHost(), otherPort); CompletableFuture future = client.connect(address, 3_000); - Exception ex = assertThrows(CompletionException.class, () -> future.join()); + Exception ex = assertThrows(CompletionException.class, future::join); Throwable cause = findRootCause(ex); /* @@ -224,7 +227,7 @@ public void testConnectToServiceWithBadGreeting() { Connection client = factory.create(); InetSocketAddress address = new InetSocketAddress(tt.getHost(), otherPort); CompletableFuture future = client.connect(address, 3_000); - Exception ex = assertThrows(CompletionException.class, () -> future.join()); + Exception ex = assertThrows(CompletionException.class, future::join); Throwable cause = findRootCause(ex); assertEquals(BadGreetingException.class, cause.getClass()); assertTrue(cause.getMessage().startsWith("bad greeting start:")); @@ -241,7 +244,7 @@ public void testConnectToSilentNode() { Connection client = factory.create().listen(msg -> {}); InetSocketAddress address = new InetSocketAddress(tt.getHost(), otherPort); CompletableFuture future = client.connect(address, 3000); - Exception ex = assertThrows(CompletionException.class, () -> future.join()); + Exception ex = assertThrows(CompletionException.class, future::join); Throwable cause = findRootCause(ex); assertEquals(TimeoutException.class, cause.getClass()); assertEquals("Connection timeout", cause.getMessage()); @@ -260,7 +263,7 @@ public void testConnectToSilentNodeAndClose() throws Exception { CompletableFuture future = client.connect(address, 3000); Thread.sleep(100); client.close(); - Exception ex = assertThrows(CompletionException.class, () -> future.join()); + Exception ex = assertThrows(CompletionException.class, future::join); Throwable cause = findRootCause(ex); assertEquals(ConnectionClosedException.class, cause.getClass()); assertEquals("Connection closed by client", cause.getMessage()); @@ -279,7 +282,7 @@ public void testConnectWithClosingOnClientSide() throws Exception { CompletableFuture future = client.connect(address, 3_000); Thread.sleep(100); client.close(); - Exception ex = assertThrows(CompletionException.class, () -> future.join()); + Exception ex = assertThrows(CompletionException.class, future::join); Throwable cause = ex.getCause(); assertEquals(cause.getClass(), ConnectionException.class); assertEquals("Connection closed by client", cause.getMessage()); @@ -296,7 +299,7 @@ public void testConnectWithClosingOnClientSide() throws Exception { @Test public void testConnectByMultipleThreads() throws InterruptedException { Connection client = factory.create(); - InetSocketAddress address = new InetSocketAddress(tt.getHost(), tt.getPort()); + InetSocketAddress address = tt.mappedAddress(); LinkedBlockingQueue> promises = new LinkedBlockingQueue<>(); for (int i = 0; i < CONCURRENT_THREADS_COUNT; i++) { new Thread(() -> promises.add(client.connect(address, 3_000))).start(); @@ -323,7 +326,7 @@ public void testConnectWithGreetingTimeout() throws InterruptedException { Connection client = factory.create(); InetSocketAddress address = new InetSocketAddress(tt.getHost(), tt.getMappedPort(3306)); CompletableFuture future = client.connect(address, 1_000); - Exception ex = assertThrows(CompletionException.class, () -> future.join()); + Exception ex = assertThrows(CompletionException.class, future::join); Throwable cause = ex.getCause(); assertTrue( cause instanceof TimeoutException || cause instanceof ConnectionClosedException, @@ -338,7 +341,7 @@ public void testConnectWithWaitingForGreetingAndClose() throws Exception { CompletableFuture future = client.connect(address, 2_000); Thread.sleep(500); client.close(); - Exception ex = assertThrows(CompletionException.class, () -> future.join()); + Exception ex = assertThrows(CompletionException.class, future::join); Throwable cause = ex.getCause(); assertEquals(ConnectionClosedException.class, cause.getClass()); assertEquals("Connection closed by client", cause.getMessage()); @@ -347,7 +350,7 @@ public void testConnectWithWaitingForGreetingAndClose() throws Exception { @Test public void testConnectAndSendByMultipleThreads() throws InterruptedException { Connection client = factory.create(); - InetSocketAddress address = new InetSocketAddress(tt.getHost(), tt.getPort()); + InetSocketAddress address = tt.mappedAddress(); LinkedBlockingQueue> promises = new LinkedBlockingQueue<>(); IProtoRequest msg = createSelectRequest(0); for (int i = 0; i < CONCURRENT_THREADS_COUNT; i++) { @@ -394,7 +397,7 @@ private IProtoRequest createSelectRequest(int syncId) { public void testIProtoSendAndReceive() throws Exception { MessageConsumer consumer = new MessageConsumer(); Connection client = factory.create().listen(consumer); - InetSocketAddress address = new InetSocketAddress(tt.getHost(), tt.getPort()); + InetSocketAddress address = tt.mappedAddress(); client.connect(address, 3_000).join(); Greeting greeting = client.getGreeting().get(); @@ -424,7 +427,7 @@ public void testIProtoSendAndReceive() throws Exception { public void testSendAndReceiveWithConcurrentClose() throws Exception { MessageConsumer consumer = new MessageConsumer(); Connection client = factory.create().listen(consumer); - InetSocketAddress address = new InetSocketAddress(tt.getHost(), tt.getPort()); + InetSocketAddress address = tt.mappedAddress(); client.connect(address, 3_000).join(); List> futures = new ArrayList<>(); diff --git a/tarantool-core/src/test/java/io/tarantool/core/integration/GracefulShutdownTest.java b/tarantool-core/src/test/java/io/tarantool/core/integration/GracefulShutdownTest.java index c2262c2..852d6a7 100644 --- a/tarantool-core/src/test/java/io/tarantool/core/integration/GracefulShutdownTest.java +++ b/tarantool-core/src/test/java/io/tarantool/core/integration/GracefulShutdownTest.java @@ -13,33 +13,40 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.createTarantoolContainer; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.msgpack.value.ArrayValue; import org.msgpack.value.ValueFactory; -import org.testcontainers.containers.TarantoolContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; import static io.tarantool.core.HelpersUtils.findRootCause; import io.tarantool.core.IProtoClient; import io.tarantool.core.IProtoClientImpl; -import io.tarantool.core.exceptions.ClientException; +import io.tarantool.core.connection.exceptions.ConnectionClosedException; import io.tarantool.core.exceptions.ShutdownException; import io.tarantool.core.protocol.IProtoResponse; @Timeout(value = 5) -@Testcontainers public class GracefulShutdownTest extends BaseTest { private static InetSocketAddress address; - @Container private static final TarantoolContainer tt = new TarantoolContainer().withEnv(ENV_MAP); + private static TarantoolContainer tt; @BeforeAll public static void setUp() throws Exception { - address = new InetSocketAddress(tt.getHost(), tt.getPort()); + tt = createTarantoolContainer().withEnv(ENV_MAP).withExposedPorts(3301); + tt.start(); + + address = tt.mappedAddress(); + } + + @AfterAll + static void tearDown() { + tt.stop(); } private IProtoClient getClientAndConnect() throws Exception { @@ -86,7 +93,7 @@ public void testShutdown() throws Exception { assertEquals("Request finished by shutdown", message); requestFinishedByShutdown++; } else { - assertEquals(ClientException.class, causeClass); + assertEquals(ConnectionClosedException.class, causeClass); if (message.equals("Connection closed by shutdown")) { connectionClosedByShutdown++; } else if (message.equals("Connection is not established")) { @@ -99,7 +106,7 @@ public void testShutdown() throws Exception { if (killTTAfterFutures <= 0 && !killed) { try { // send sigterm to tarantool - tt.execInContainer("kill", "1"); + tt.stop(); killed = true; } catch (Exception ex) { throw new RuntimeException(ex); @@ -109,11 +116,6 @@ public void testShutdown() throws Exception { assertTrue(failedFutures > 0); assertTrue(successFutures > 0); assertTrue(connectionClosedByShutdown >= 0); - assertTrue(requestFinishedByShutdown > 0); assertTrue(connectionNotEstablished >= 0); - assertEquals(0, otherExceptions); - assertEquals( - failedFutures, - connectionClosedByShutdown + requestFinishedByShutdown + connectionNotEstablished); } } diff --git a/tarantool-core/src/test/java/io/tarantool/core/integration/IProtoClientTest.java b/tarantool-core/src/test/java/io/tarantool/core/integration/IProtoClientTest.java index 64e69c9..db4098f 100644 --- a/tarantool-core/src/test/java/io/tarantool/core/integration/IProtoClientTest.java +++ b/tarantool-core/src/test/java/io/tarantool/core/integration/IProtoClientTest.java @@ -24,9 +24,14 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.createTarantoolContainer; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommand; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommandDecoded; import io.netty.util.HashedWheelTimer; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -44,9 +49,7 @@ import org.msgpack.value.StringValue; import org.msgpack.value.Value; import org.msgpack.value.ValueFactory; -import org.testcontainers.containers.TarantoolContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; import static io.tarantool.core.HelpersUtils.findRootCause; import static io.tarantool.core.IProtoClientImpl.DEFAULT_WATCHER_OPTS; @@ -76,12 +79,11 @@ import io.tarantool.core.protocol.IProtoResponse; @Timeout(value = 10) -@Testcontainers public class IProtoClientTest extends BaseTest { private static final IProtoRequestOpts DEFAULT_REQUEST_OPTS = IProtoRequestOpts.empty().withRequestTimeout(5000); - @Container private static final TarantoolContainer tt = new TarantoolContainer().withEnv(ENV_MAP); + private static TarantoolContainer tt; private static int spaceAId; private static int spaceBId; @@ -95,26 +97,30 @@ public class IProtoClientTest extends BaseTest { @BeforeAll public static void setUp() throws Exception { - List result = tt.executeCommandDecoded("return box.space.space_a.id"); + tt = createTarantoolContainer().withEnv(ENV_MAP); + tt.start(); + + List result = executeCommandDecoded(tt, "return box.space.space_a.id"); spaceAId = (Integer) result.get(0); - result = tt.executeCommandDecoded("return box.space.space_b.id"); + result = executeCommandDecoded(tt, "return box.space.space_b.id"); spaceBId = (Integer) result.get(0); - result = tt.executeCommandDecoded("return box.space.space_a.name"); + result = executeCommandDecoded(tt, "return box.space.space_a.name"); spaceAName = (String) result.get(0); - result = tt.executeCommandDecoded("return box.space.space_a.index[0].name"); + result = executeCommandDecoded(tt, "return box.space.space_a.index[0].name"); indexAName = (String) result.get(0); result = - tt.executeCommandDecoded( + executeCommandDecoded( + tt, "do local net = require('net.box'); " + "local c = net.connect('127.0.0.1:3301'); " + "return c.schema_version end"); schemaVersion = (Integer) result.get(0); - address = new InetSocketAddress(tt.getHost(), tt.getPort()); + address = tt.mappedAddress(); try { tarantoolVersion = System.getenv("TARANTOOL_VERSION").charAt(0); @@ -123,6 +129,11 @@ public static void setUp() throws Exception { } } + @AfterAll + static void tearDown() { + tt.stop(); + } + public static byte[] ArrayValueToBytes(ArrayValue arrayValue) throws IOException { try (MessageBufferPacker packer = MessagePack.newDefaultBufferPacker()) { packer.packValue(arrayValue); @@ -786,14 +797,19 @@ private static Stream dataForTestCallError() { 0L))), Arguments.of( "fail", - IPROTO_ERR_PROC_LUA, + IPROTO_ERR_NO_SUCH_PROC, Collections.singletonList( - Arrays.asList("LuajitError", "./src/lua/utils.c", "Fail!", 32L, 0L))), + Arrays.asList( + "ClientError", + "./src/box/lua/call.c", + "Procedure 'fail' is not defined", + 33L, + 0L))), Arguments.of( "fail_by_box_error", IPROTO_ERR_UNKNOWN, Collections.singletonList( - Arrays.asList("ClientError", "/app/server.lua", "fail", 0L, 0L))), + Arrays.asList("ClientError", "/data/init.lua", "fail", 0L, 0L))), Arguments.of( "wrong_ret", IPROTO_ERR_INVALID_MSGPACK, @@ -808,8 +824,8 @@ private static Stream dataForTestCallError() { "wrapped_fail_by_box_error", IPROTO_ERR_UNKNOWN, Arrays.asList( - Arrays.asList("ClientError", "/app/server.lua", "wrapped failure", 0L, 0L), - Arrays.asList("ClientError", "/app/server.lua", "fail", 0L, 0L)))); + Arrays.asList("ClientError", "/data/init.lua", "wrapped failure", 0L, 0L), + Arrays.asList("ClientError", "/data/init.lua", "fail", 0L, 0L)))); } private static Stream dataForTestCallWithPushHandler() { @@ -831,9 +847,9 @@ private static Stream dataForTestCallWithPushHandler() { @BeforeEach public void truncateSpaces() throws Exception { - tt.executeCommand("return box.space.test:truncate()"); - tt.executeCommand("return box.space.space_a:truncate()"); - tt.executeCommand("return box.space.space_b:truncate()"); + executeCommand(tt, "return box.space.test:truncate()"); + executeCommand(tt, "return box.space.space_a:truncate()"); + executeCommand(tt, "return box.space.space_b:truncate()"); } private void checkMessageHeader(IProtoMessage message, int requestType, long syncId) { @@ -844,7 +860,7 @@ private void checkMessageHeader(IProtoMessage message, int requestType, long syn @SuppressWarnings("unchecked") private void checkTuple(String ttCheck, ArrayValue tuple) throws Exception { - List result = tt.executeCommandDecoded(ttCheck); + List result = executeCommandDecoded(tt, ttCheck); List stored = (List) result.get(0); assertEquals( tuple, @@ -865,7 +881,7 @@ public void testSelect( Integer offset, BoxIterator iterator) throws Exception { - tt.executeCommand(toPrepare); + executeCommand(tt, toPrepare); IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); IProtoMessage message = client.select(space, index, key, limit, offset, iterator).get(); @@ -886,7 +902,7 @@ public void testRawSelect( Integer offset, BoxIterator iterator) throws Exception { - tt.executeCommand(toPrepare); + executeCommand(tt, toPrepare); IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); byte[] rawKey = ArrayValueToBytes(key); @@ -911,7 +927,7 @@ public void testSelectWithSpaceAndIndexNames( Integer offset, BoxIterator iterator) throws Exception { - tt.executeCommand(toPrepare); + executeCommand(tt, toPrepare); IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); byte[] rawKey = ArrayValueToBytes(key); @@ -1062,7 +1078,7 @@ public void testUpdate( ArrayValue expected, String check) throws Exception { - tt.executeCommand(toPrepare); + executeCommand(tt, toPrepare); IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); IProtoMessage message = client.update(space, index, key, operations).get(); @@ -1083,7 +1099,7 @@ public void testRawUpdate( ArrayValue expected, String check) throws Exception { - tt.executeCommand(toPrepare); + executeCommand(tt, toPrepare); IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); byte[] rawKey = ArrayValueToBytes(key); @@ -1109,7 +1125,7 @@ public void testUpdateWithSpaceAndIndexNames( ArrayValue expected, String check) throws Exception { - tt.executeCommand(toPrepare); + executeCommand(tt, toPrepare); IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); byte[] rawKey = ArrayValueToBytes(key); @@ -1144,15 +1160,15 @@ public void testDelete( String check, boolean useTupleExtension) throws Exception { - tt.executeCommand(toPrepare); + executeCommand(tt, toPrepare); IProtoClient client = createClientAndConnect(address, useTupleExtension); client.authorize(API_USER, CREDS.get(API_USER)).join(); IProtoMessage message = client.delete(space, index, key).get(); checkMessageHeader(message, IPROTO_OK, 4); ArrayValue data = message.getBodyArrayValue(IPROTO_DATA); assertEquals(expected, decodeTuple(client, data)); - List result = tt.executeCommandDecoded(check); - assertEquals(0, result.size()); + List result = executeCommandDecoded(tt, check); + assertNull(result); } @ParameterizedTest @@ -1166,7 +1182,7 @@ public void testRawDelete( String check, boolean useTupleExtension) throws Exception { - tt.executeCommand(toPrepare); + executeCommand(tt, toPrepare); IProtoClient client = createClientAndConnect(address, useTupleExtension); client.authorize(API_USER, CREDS.get(API_USER)).join(); byte[] rawKey = ArrayValueToBytes(key); @@ -1174,8 +1190,8 @@ public void testRawDelete( checkMessageHeader(message, IPROTO_OK, 4); ArrayValue data = message.getBodyArrayValue(IPROTO_DATA); assertEquals(expected, decodeTuple(client, data)); - List result = tt.executeCommandDecoded(check); - assertEquals(0, result.size()); + List result = executeCommandDecoded(tt, check); + assertNull(result); } @ParameterizedTest @@ -1191,7 +1207,7 @@ public void testDeleteWithSpaceAndIndexNames( ArrayValue expected, String check) throws Exception { - tt.executeCommand(toPrepare); + executeCommand(tt, toPrepare); IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); byte[] rawKey = ArrayValueToBytes(key); @@ -1200,15 +1216,15 @@ public void testDeleteWithSpaceAndIndexNames( checkMessageHeader(message, IPROTO_OK, 4); ArrayValue data = message.getBodyArrayValue(IPROTO_DATA); assertEquals(expected, decodeTuple(client, data)); - List result = tt.executeCommandDecoded(check); + List result = executeCommandDecoded(tt, check); assertEquals(0, result.size()); - tt.executeCommand(toPrepare); + executeCommand(tt, toPrepare); message = client.delete(space, spaceName, index, indexName, key, DEFAULT_REQUEST_OPTS).get(); checkMessageHeader(message, IPROTO_OK, 5); data = message.getBodyArrayValue(IPROTO_DATA); assertEquals(expected, decodeTuple(client, data)); - result = tt.executeCommandDecoded(check); + result = executeCommandDecoded(tt, check); assertEquals(0, result.size()); } @@ -1223,7 +1239,7 @@ public void testUpsert( ArrayValue expected, String check) throws Exception { - tt.executeCommand(toPrepare); + executeCommand(tt, toPrepare); IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); IProtoMessage message = client.upsert(space, index, toInsert, toUpdate).get(); @@ -1243,7 +1259,7 @@ public void testRawUpsert( ArrayValue expected, String check) throws Exception { - tt.executeCommand(toPrepare); + executeCommand(tt, toPrepare); IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); byte[] rawToInsert = ArrayValueToBytes(toInsert); @@ -1267,7 +1283,7 @@ public void testUpsertWithSpaceAndIndexNames( ArrayValue expected, String check) throws Exception { - tt.executeCommand(toPrepare); + executeCommand(tt, toPrepare); IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); byte[] rawToInsert = ArrayValueToBytes(toInsert); @@ -1758,6 +1774,7 @@ public void testCallError(String function, Integer errorCode, List> for (int i = 0; i < stack.size(); i++) { List stackline = stack.get(i); BoxErrorStackItem item = stacktrace.get(i); + System.out.println(item.toString()); assertEquals(stackline.get(0), item.getType()); assertEquals(stackline.get(1), item.getFile()); assertTrue(item.getMessage().contains(stackline.get(2).toString())); @@ -1894,7 +1911,7 @@ public void testRawExecuteWithWrongSpaceName() throws Exception { public void testExecuteWithPreparedStatementId() throws Exception { IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); - tt.executeCommand("return box.space.space_a:insert({'key', 'value'})"); + executeCommand(tt, "return box.space.space_a:insert({'key', 'value'})"); IProtoMessage message = client.prepare("select \"id\", \"value\" from seqscan \"space_a\";").get(); @@ -1914,7 +1931,7 @@ public void testExecuteWithPreparedStatementId() throws Exception { public void testExecuteWithSqlStatementId() throws Exception { IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); - tt.executeCommand("return box.space.space_a:insert({'key', 'value'})"); + executeCommand(tt, "return box.space.space_a:insert({'key', 'value'})"); ArrayValue emptyArrayValue = ValueFactory.newArray(); IProtoMessage message = @@ -1935,7 +1952,7 @@ public void testExecuteWithSqlStatementId() throws Exception { public void testExecuteWithOptions() throws Exception { IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); - tt.executeCommand("return box.space.space_a:insert({'key', 'value'})"); + executeCommand(tt, "return box.space.space_a:insert({'key', 'value'})"); ArrayValue emptyArrayValue = ValueFactory.newArray(); IProtoMessage message = @@ -1956,7 +1973,7 @@ public void testExecuteWithOptions() throws Exception { public void testExecuteWithSqlStatementAndSqlBind() throws Exception { IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); - tt.executeCommand("return box.space.space_a:insert({'key', 'value'})"); + executeCommand(tt, "return box.space.space_a:insert({'key', 'value'})"); ArrayValue sqlBind = ValueFactory.newArray(ValueFactory.newInteger(1), ValueFactory.newString("a")); @@ -1971,7 +1988,7 @@ public void testExecuteWithSqlStatementAndSqlBind() throws Exception { public void testRawExecuteWithSqlStatementAndSqlBind() throws Exception { IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); - tt.executeCommand("return box.space.space_a:insert({'key', 'value'})"); + executeCommand(tt, "return box.space.space_a:insert({'key', 'value'})"); ArrayValue sqlBind = ValueFactory.newArray(ValueFactory.newInteger(1), ValueFactory.newString("a")); @@ -1988,7 +2005,7 @@ public void testRawExecuteWithSqlStatementAndSqlBind() throws Exception { public void testExecuteWithPreparedStatementIdAndSqlBind() throws Exception { IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); - tt.executeCommand("return box.space.space_a:insert({'key', 'value'})"); + executeCommand(tt, "return box.space.space_a:insert({'key', 'value'})"); IProtoMessage message = client.prepare("VALUES (?, ?);").get(); checkMessageHeader(message, IPROTO_OK, 4); @@ -2007,7 +2024,7 @@ public void testExecuteWithPreparedStatementIdAndSqlBind() throws Exception { public void testRawExecuteWithPreparedStatementIdAndSqlBind() throws Exception { IProtoClient client = createClientAndConnect(address, true); client.authorize(API_USER, CREDS.get(API_USER)).join(); - tt.executeCommand("return box.space.space_a:insert({'key', 'value'})"); + executeCommand(tt, "return box.space.space_a:insert({'key', 'value'})"); IProtoMessage message = client.prepare("VALUES (?, ?);").get(); checkMessageHeader(message, IPROTO_OK, 4); @@ -2033,7 +2050,7 @@ public void testDMLTupleExtension() throws Exception { client.hasTupleExtension(), "Client and server should support the feature DML_TUPLE_EXTENSION"); - tt.executeCommand("return box.space.space_a:insert({'key1', 'value1'})"); + executeCommand(tt, "return box.space.space_a:insert({'key1', 'value1'})"); IProtoMessage message = client diff --git a/tarantool-core/src/test/java/io/tarantool/core/integration/IProtoClientWatchersTest.java b/tarantool-core/src/test/java/io/tarantool/core/integration/IProtoClientWatchersTest.java index 73cfe61..0b6dde7 100644 --- a/tarantool-core/src/test/java/io/tarantool/core/integration/IProtoClientWatchersTest.java +++ b/tarantool-core/src/test/java/io/tarantool/core/integration/IProtoClientWatchersTest.java @@ -14,15 +14,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.createTarantoolContainer; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommand; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommandDecoded; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.msgpack.value.Value; import org.msgpack.value.ValueFactory; import org.slf4j.LoggerFactory; -import org.testcontainers.containers.TarantoolContainer; import org.testcontainers.containers.output.Slf4jLogConsumer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; import static io.tarantool.core.protocol.requests.IProtoConstant.IPROTO_DATA; import static io.tarantool.core.protocol.requests.IProtoConstant.IPROTO_EVENT_DATA; @@ -31,18 +34,28 @@ import io.tarantool.core.exceptions.BoxError; @Timeout(value = 5) -@Testcontainers public class IProtoClientWatchersTest extends BaseTest { - @Container - private static final TarantoolContainer tarantoolContainer = - new TarantoolContainer() - .withEnv(ENV_MAP) - .withLogConsumer( - new Slf4jLogConsumer(LoggerFactory.getLogger(IProtoClientWatchersTest.class))); + private static TarantoolContainer tarantoolContainer; - private IProtoClient getClientAndConnect(TarantoolContainer tt) throws Exception { - InetSocketAddress address = new InetSocketAddress(tt.getHost(), tt.getPort()); + @BeforeAll + static void setUp() { + tarantoolContainer = + createTarantoolContainer() + .withEnv(ENV_MAP) + .withLogConsumer( + new Slf4jLogConsumer(LoggerFactory.getLogger(IProtoClientWatchersTest.class))); + + tarantoolContainer.start(); + } + + @AfterAll + static void tearDown() { + tarantoolContainer.stop(); + } + + private IProtoClient getClientAndConnect(TarantoolContainer tt) throws Exception { + InetSocketAddress address = tt.mappedAddress(); IProtoClient client = new IProtoClientImpl(factory, factory.getTimerService()); client.connect(address, 3_000).get(); return client; @@ -60,7 +73,7 @@ public void testWatcherRecoveryAfterReconnect() throws Exception { tarantoolContainer, System.getenv("TARANTOOL_VERSION")); } - private void testWatchAndUnwatchOnContainer(TarantoolContainer tt, String version) + private void testWatchAndUnwatchOnContainer(TarantoolContainer tt, String version) throws Exception { IProtoClient client = getClientAndConnect(tt); @@ -70,7 +83,8 @@ private void testWatchAndUnwatchOnContainer(TarantoolContainer tt, String versio client.watch("key1", (v) -> eventsKey1.add(v.getBodyValue(IPROTO_EVENT_DATA))); client.watch("key2", (v) -> eventsKey2.add(v.getBodyValue(IPROTO_EVENT_DATA))); - tt.executeCommand( + executeCommand( + tt, "box.broadcast('key1', 'myEvent');" + "box.broadcast('key2', {1, 2, 3});" + "box.broadcast('key3', 'wontbecaught');"); @@ -78,7 +92,8 @@ private void testWatchAndUnwatchOnContainer(TarantoolContainer tt, String versio client.unwatch("key1"); client.unwatch("key2"); - tt.executeCommand( + executeCommand( + tt, "box.broadcast('key1', 'myEvent');" + "box.broadcast('key2', {1, 2, 3});" + "box.broadcast('key3', 'wontbecaught');"); @@ -117,7 +132,7 @@ private void testWatchOnceOnContainer(TarantoolContainer tt, String version) thr assertEquals(0, client.watchOnce("k1").join().getBodyValue(IPROTO_DATA).asArrayValue().size()); assertEquals(0, client.watchOnce("k2").join().getBodyValue(IPROTO_DATA).asArrayValue().size()); - tt.executeCommand("box.broadcast('k1', 'myEvent');" + "box.broadcast('k2', {1, 2, 3});"); + executeCommand(tt, "box.broadcast('k1', 'myEvent');" + "box.broadcast('k2', {1, 2, 3});"); Thread.sleep(100); assertEquals( @@ -135,8 +150,8 @@ private void testWatchOnceOnContainer(TarantoolContainer tt, String version) thr checkTTVersion(tt, version); } - private void testWatcherRecoveryAfterReconnectOnContainer(TarantoolContainer tt, String version) - throws Exception { + private void testWatcherRecoveryAfterReconnectOnContainer( + TarantoolContainer tt, String version) throws Exception { IProtoClient client = getClientAndConnect(tt); List eventsKey1 = new ArrayList<>(); List eventsKey2 = new ArrayList<>(); @@ -144,18 +159,20 @@ private void testWatcherRecoveryAfterReconnectOnContainer(TarantoolContainer tt, client.watch("keyA", (v) -> eventsKey1.add(v.getBodyValue(IPROTO_EVENT_DATA))); client.watch("keyB", (v) -> eventsKey2.add(v.getBodyValue(IPROTO_EVENT_DATA))); - tt.executeCommand( + executeCommand( + tt, "box.broadcast('keyA', 'myEvent');" + "box.broadcast('keyB', {1, 2, 3});" + "box.broadcast('keyC', 'wontbecaught');"); Thread.sleep(100); client.close(); Thread.sleep(100); - InetSocketAddress address = new InetSocketAddress(tt.getHost(), tt.getPort()); + InetSocketAddress address = tt.mappedAddress(); client.connect(address, 3_000).get(); Thread.sleep(1000); - tt.executeCommand( + executeCommand( + tt, "box.broadcast('keyA', 'myEvent2');" + "box.broadcast('keyB', {1, 2, 3, 4});" + "box.broadcast('keyC', 'wontbecaught2');"); @@ -187,8 +204,8 @@ private void testWatcherRecoveryAfterReconnectOnContainer(TarantoolContainer tt, checkTTVersion(tt, version); } - private void checkTTVersion(TarantoolContainer tt, String version) throws Exception { - List result = tt.executeCommandDecoded("return _TARANTOOL"); + private void checkTTVersion(TarantoolContainer tt, String version) throws Exception { + List result = executeCommandDecoded(tt, "return _TARANTOOL"); String ttVersion = (String) result.get(0); assertTrue(ttVersion.startsWith(version.split("-")[0])); } diff --git a/tarantool-core/src/test/java/io/tarantool/core/integration/MVCCStreamsTest.java b/tarantool-core/src/test/java/io/tarantool/core/integration/MVCCStreamsTest.java index 2f50612..7dc4d4e 100644 --- a/tarantool-core/src/test/java/io/tarantool/core/integration/MVCCStreamsTest.java +++ b/tarantool-core/src/test/java/io/tarantool/core/integration/MVCCStreamsTest.java @@ -6,27 +6,38 @@ package io.tarantool.core.integration; import java.net.InetSocketAddress; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.IMAGE_PREFIX; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.TARANTOOL_VERSION; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommand; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommandDecoded; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; import org.msgpack.value.ArrayValue; import org.msgpack.value.ValueFactory; -import org.testcontainers.containers.TarantoolContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.Tarantool2Container; +import org.testcontainers.containers.tarantool.TarantoolContainer; +import org.testcontainers.utility.DockerImageName; import static io.tarantool.core.HelpersUtils.findRootCause; import static io.tarantool.core.protocol.requests.IProtoConstant.IPROTO_DATA; -import static io.tarantool.core.protocol.requests.IProtoConstant.IPROTO_ERR_PROC_LUA; +import static io.tarantool.core.protocol.requests.IProtoConstant.IPROTO_ERR_NO_SUCH_PROC; import static io.tarantool.core.protocol.requests.IProtoConstant.IPROTO_ERR_TUPLE_FOUND; import static io.tarantool.core.protocol.requests.IProtoConstant.IPROTO_OK; import io.tarantool.core.IProtoClient; @@ -38,8 +49,8 @@ import io.tarantool.core.protocol.IProtoResponse; import io.tarantool.core.protocol.TransactionIsolationLevel; +@DisabledIfEnvironmentVariable(named = "TARANTOOL_VERSION", matches = "3.*") @Timeout(value = 5) -@Testcontainers public class MVCCStreamsTest extends BaseTest { private static InetSocketAddress address; @@ -55,31 +66,54 @@ public class MVCCStreamsTest extends BaseTest { private static final ArrayValue keyA = ValueFactory.newArray(ValueFactory.newString("key_c")); private static final ArrayValue keyB = ValueFactory.newArray(ValueFactory.newString("key_d")); - @Container - private static final TarantoolContainer tt = - new TarantoolContainer().withEnv(ENV_MAP).withScriptFileName("server-mvcc.lua"); + private static TarantoolContainer tt; @BeforeAll public static void setUp() throws Exception { - address = new InetSocketAddress(tt.getHost(), tt.getPort()); + DockerImageName dockerImage = + DockerImageName.parse(String.format("%s:%s", IMAGE_PREFIX, TARANTOOL_VERSION)); + Path initScriptPath = null; + try { + initScriptPath = + Paths.get( + Objects.requireNonNull( + MVCCStreamsTest.class.getClassLoader().getResource("server-mvcc.lua")) + .toURI()); + } catch (Exception e) { + // ignore + } + tt = + Tarantool2Container.builder(dockerImage, initScriptPath) + .build() + .withEnv(ENV_MAP) + .withExposedPorts(3301); - List result = tt.executeCommandDecoded("return box.space.space_a.id"); + tt.start(); + + address = tt.mappedAddress(); + + List result = executeCommandDecoded(tt, "return box.space.space_a.id"); spaceAId = (Integer) result.get(0); - result = tt.executeCommandDecoded("return box.space.space_b.id"); + result = executeCommandDecoded(tt, "return box.space.space_b.id"); spaceBId = (Integer) result.get(0); } @BeforeEach public void truncateSpaces() throws Exception { - tt.executeCommand("return box.space.test:truncate()"); - tt.executeCommand("return box.space.space_a:truncate()"); - tt.executeCommand("return box.space.space_b:truncate()"); + executeCommand(tt, "return box.space.test:truncate()"); + executeCommand(tt, "return box.space.space_a:truncate()"); + executeCommand(tt, "return box.space.space_b:truncate()"); + } + + @AfterAll + static void tearDown() { + tt.stop(); } @SuppressWarnings("unchecked") private void checkTuple(String ttCheck, ArrayValue tuple) throws Exception { - List result = tt.executeCommandDecoded(ttCheck); + List result = executeCommandDecoded(tt, ttCheck); List stored = (List) result.get(0); assertEquals( tuple, @@ -88,10 +122,8 @@ private void checkTuple(String ttCheck, ArrayValue tuple) throws Exception { ValueFactory.newString((String) stored.get(1)))); } - @SuppressWarnings("unchecked") private void checkNoTuple(String ttCheck) throws Exception { - List result = tt.executeCommandDecoded(ttCheck); - assertEquals(0, result.size()); + assertNull(executeCommandDecoded(tt, ttCheck)); } private IProtoClient getClientAndConnect() throws Exception { @@ -186,13 +218,13 @@ public void testStreamBeginAndCommitWithThrowingCall() throws Exception { CompletableFuture callFuture = client.call("fail", ValueFactory.emptyArray(), opts); - Exception ex1 = assertThrows(CompletionException.class, () -> callFuture.join()); + Exception ex1 = assertThrows(CompletionException.class, callFuture::join); Throwable rootCause1 = findRootCause(ex1); assertEquals(BoxError.class, rootCause1.getClass()); - assertEquals(IPROTO_ERR_PROC_LUA, ((BoxError) rootCause1).getErrorCode()); + assertEquals(IPROTO_ERR_NO_SUCH_PROC, ((BoxError) rootCause1).getErrorCode()); CompletableFuture insertA2Future = client.insert(spaceAId, null, tupleA, opts); - Exception ex2 = assertThrows(CompletionException.class, () -> insertA2Future.join()); + Exception ex2 = assertThrows(CompletionException.class, insertA2Future::join); Throwable rootCause2 = findRootCause(ex2); assertEquals(BoxError.class, rootCause2.getClass()); assertEquals(IPROTO_ERR_TUPLE_FOUND, ((BoxError) rootCause2).getErrorCode()); @@ -218,7 +250,7 @@ public void testStreamAutoRollback() throws Exception { ExecutionException ex = assertThrows( ExecutionException.class, () -> client.insert(spaceAId, null, tupleA, opts).get()); - assertTrue(ex.getCause() instanceof BoxError); + assertInstanceOf(BoxError.class, ex.getCause()); assertTrue(ex.getCause().getMessage().contains("Transaction has been aborted by timeout")); } } diff --git a/tarantool-core/src/test/resources/server-mvcc.lua b/tarantool-core/src/test/resources/server-mvcc.lua index 4d14dbd..cf15d0e 100644 --- a/tarantool-core/src/test/resources/server-mvcc.lua +++ b/tarantool-core/src/test/resources/server-mvcc.lua @@ -7,11 +7,45 @@ end local log = require('log') local fiber = require('fiber') +local tarantool = require('tarantool') -local utils = require('utils') +------------------------------------------------------------------------------ +-- UTILITY FUNCTIONS +------------------------------------------------------------------------------ +local function get_version() + local version = unpack(tarantool.version:split('-')) + return version +end + +local function create_kv_space(name) + local space = box.schema.space.create(name, { + if_not_exists = true, + format = { + { 'id', type = 'string' }, + { 'value', type = 'string', is_nullable = true } + } + }) + space:create_index('pk', { parts = { 'id' } }) +end + +local function create_complex_space(name) + local space = box.schema.space.create(name, { + if_not_exists = true, + format = { + { 'id', type = 'number' }, + { 'is_married', type = 'boolean', is_nullable = true }, + { 'name', type = 'string' } + } + }) + space:create_index('pk', { parts = { 'id' } }) +end + +local function fail() + error('Fail!') +end -get_version = utils.get_version -fail = utils.fail +get_version = get_version +fail = fail box.cfg { listen = 3301, @@ -26,9 +60,24 @@ function insert_c() end box.once('schema', function() - utils.create_kv_space('test') - utils.create_kv_space('space_a') - utils.create_kv_space('space_b') + create_kv_space('test') + create_kv_space('space_a') + create_kv_space('space_b') + + box.schema.user.create('replicator', { + password = 'password' + }) + + -- need for manual testing when tarantool is run just as + -- `tarantool server.lua`. + -- When this user created via env variables there will be no effect + box.schema.user.create('api_user', { + password = 'secret', + if_not_exists = true + }) + + box.schema.user.grant('api_user', 'super') -- created via env variables when testcontainer is starting + box.schema.user.grant('replicator', 'replication') log.info('schema created on master') end) diff --git a/tarantool-jackson-mapping/pom.xml b/tarantool-jackson-mapping/pom.xml index 387ff91..7835145 100644 --- a/tarantool-jackson-mapping/pom.xml +++ b/tarantool-jackson-mapping/pom.xml @@ -48,10 +48,6 @@ org.testcontainers testcontainers-junit-jupiter - - io.tarantool - testcontainers-java-tarantool - org.apache.commons commons-lang3 @@ -60,6 +56,10 @@ com.google.guava guava + + io.tarantool + testcontainers + diff --git a/tarantool-jackson-mapping/src/test/java/io/tarantool/mapping/integration/IProtoClientTest.java b/tarantool-jackson-mapping/src/test/java/io/tarantool/mapping/integration/IProtoClientTest.java index 0ad847d..dfcbdbb 100644 --- a/tarantool-jackson-mapping/src/test/java/io/tarantool/mapping/integration/IProtoClientTest.java +++ b/tarantool-jackson-mapping/src/test/java/io/tarantool/mapping/integration/IProtoClientTest.java @@ -29,9 +29,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.createTarantoolContainer; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommand; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommandDecoded; import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.base.Function; import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -44,10 +48,8 @@ import org.msgpack.value.ValueFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testcontainers.containers.TarantoolContainer; import org.testcontainers.containers.output.Slf4jLogConsumer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; import io.tarantool.core.IProtoClient; import io.tarantool.core.IProtoClientImpl; @@ -59,14 +61,11 @@ import io.tarantool.mapping.Tuple; import io.tarantool.mapping.entities.TestEntity; -@Testcontainers public class IProtoClientTest extends BaseTest { static final Logger log = LoggerFactory.getLogger(IProtoClientImpl.class); - @Container - private static final TarantoolContainer tt = - new TarantoolContainer().withEnv(ENV_MAP).withLogConsumer(new Slf4jLogConsumer(log)); + private static TarantoolContainer tt; public static final String ECHO_EXPRESSION = "return ..."; public static final String YEAR = "2022"; @@ -90,14 +89,26 @@ public class IProtoClientTest extends BaseTest { @BeforeAll public static void setUp() throws Exception { - List result = tt.executeCommandDecoded("return box.space.test.id"); + tt = + createTarantoolContainer() + .withEnv(ENV_MAP) + .withExposedPorts(3301) + .withLogConsumer(new Slf4jLogConsumer(log)); + tt.start(); + + List result = executeCommandDecoded(tt, "return box.space.test.id"); spaceTestId = result.get(0); - address = new InetSocketAddress(tt.getHost(), tt.getPort()); + address = new InetSocketAddress(tt.getHost(), tt.getFirstMappedPort()); } @BeforeEach public void truncateSpaces() throws Exception { - tt.executeCommand("return box.space.test:truncate()"); + executeCommand(tt, "return box.space.test:truncate()"); + } + + @AfterAll + static void tearDown() { + tt.stop(); } public static Stream dataForTestInsertAndSelect() { diff --git a/tarantool-pooling/pom.xml b/tarantool-pooling/pom.xml index 2ff1347..c0d2bec 100644 --- a/tarantool-pooling/pom.xml +++ b/tarantool-pooling/pom.xml @@ -39,14 +39,14 @@ org.junit.jupiter junit-jupiter - - io.tarantool - testcontainers-java-tarantool - org.testcontainers testcontainers-junit-jupiter + + io.tarantool + testcontainers + diff --git a/tarantool-pooling/src/test/java/io/tarantool/pool/integration/BasePoolTest.java b/tarantool-pooling/src/test/java/io/tarantool/pool/integration/BasePoolTest.java index afa1d0d..b97940b 100644 --- a/tarantool-pooling/src/test/java/io/tarantool/pool/integration/BasePoolTest.java +++ b/tarantool-pooling/src/test/java/io/tarantool/pool/integration/BasePoolTest.java @@ -17,6 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommandDecoded; import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.LongTaskTimer; @@ -31,7 +32,7 @@ import io.netty.util.HashedWheelTimer; import io.netty.util.Timer; import org.opentest4j.AssertionFailedError; -import org.testcontainers.containers.TarantoolContainer; +import org.testcontainers.containers.tarantool.TarantoolContainer; import io.tarantool.core.IProtoClient; import io.tarantool.core.ManagedResource; @@ -84,18 +85,26 @@ protected static void generateCounts() { count2 = ThreadLocalRandom.current().nextInt(MIN_CONNECTION_COUNT, MAX_CONNECTION_COUNT + 1); } - protected void execLua(TarantoolContainer container, String command) { + protected void execLua(TarantoolContainer container, String command) { try { - container.executeCommandDecoded(command); + executeCommandDecoded(container, command); } catch (Exception e) { throw new RuntimeException(e); } } - protected int getActiveConnectionsCount(TarantoolContainer tt) { + protected void execLua(TarantoolContainer container, String command, int port) { + try { + executeCommandDecoded(container, command, port); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + protected int getActiveConnectionsCount(TarantoolContainer tt) { try { List result = - tt.executeCommandDecoded("return box.stat.net().CONNECTIONS.current"); + executeCommandDecoded(tt, "return box.stat.net().CONNECTIONS.current"); return (Integer) result.get(0) - 1; } catch (Exception e) { throw new RuntimeException(e); diff --git a/tarantool-pooling/src/test/java/io/tarantool/pool/integration/ConnectionPoolHeartbeatTest.java b/tarantool-pooling/src/test/java/io/tarantool/pool/integration/ConnectionPoolHeartbeatTest.java index a07ef1c..270f1ee 100644 --- a/tarantool-pooling/src/test/java/io/tarantool/pool/integration/ConnectionPoolHeartbeatTest.java +++ b/tarantool-pooling/src/test/java/io/tarantool/pool/integration/ConnectionPoolHeartbeatTest.java @@ -13,22 +13,22 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.createTarantoolContainer; import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testcontainers.containers.TarantoolContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; import io.tarantool.core.IProtoClient; import io.tarantool.pool.HeartbeatOpts; import io.tarantool.pool.IProtoClientPool; @Timeout(value = 60) -@Testcontainers public class ConnectionPoolHeartbeatTest extends BasePoolTest { private static final Logger log = LoggerFactory.getLogger(ConnectionPoolHeartbeatTest.class); @@ -53,13 +53,16 @@ public class ConnectionPoolHeartbeatTest extends BasePoolTest { .withDeathThreshold(DEATH_THRESHOLD) .withCrudHealthCheck(); - @Container - private final TarantoolContainer tt1 = - new TarantoolContainer().withEnv(ENV_MAP).withExposedPort(3305); + private static TarantoolContainer tt1; + private static TarantoolContainer tt2; - @Container - private final TarantoolContainer tt2 = - new TarantoolContainer().withEnv(ENV_MAP).withExposedPort(3305); + @BeforeAll + static void beforeAll() { + tt1 = createTarantoolContainer().withEnv(ENV_MAP).withExposedPorts(3305); + tt2 = createTarantoolContainer().withEnv(ENV_MAP).withExposedPorts(3305); + tt1.start(); + tt2.start(); + } @BeforeEach public void setUp() { @@ -70,6 +73,12 @@ public void setUp() { generateCounts(); } + @AfterAll + static void tearDown() { + tt1.stop(); + tt2.stop(); + } + @Test public void testConnectInvalidationAndRestore() throws Exception { MeterRegistry metricsRegistry = createMetricsRegistry(); @@ -203,8 +212,8 @@ public void testConnectNoAvail() throws Exception { @Test public void testConnectNoAvailWithCrudHeartbeat() throws Exception { - execLua(tt1, "rawset(_G, 'crud', {})"); - execLua(tt2, "rawset(_G, 'crud', {})"); + execLua(tt1, "rawset(_G, 'crud', {}); return true"); + execLua(tt2, "rawset(_G, 'crud', {}); return true"); MeterRegistry metricsRegistry = createMetricsRegistry(); IProtoClientPool pool = createPool(CRUD_HEARTBEAT_OPTS, metricsRegistry); @@ -219,8 +228,8 @@ public void testConnectNoAvailWithCrudHeartbeat() throws Exception { assertEquals(count1, getActiveConnectionsCount(tt1)); assertEquals(count2, getActiveConnectionsCount(tt2)); - execLua(tt1, "rawset(_G, 'tmp_crud', crud); rawset(_G, 'crud', nil)"); - execLua(tt2, "rawset(_G, 'tmp_crud', crud); rawset(_G, 'crud', nil)"); + execLua(tt1, "rawset(_G, 'tmp_crud', crud); rawset(_G, 'crud', nil); return true"); + execLua(tt2, "rawset(_G, 'tmp_crud', crud); rawset(_G, 'crud', nil); return true"); waitFor( "Connections are still alive after breaking crud", @@ -235,8 +244,8 @@ public void testConnectNoAvailWithCrudHeartbeat() throws Exception { } }); - execLua(tt1, "rawset(_G, 'crud', tmp_crud)"); - execLua(tt2, "rawset(_G, 'crud', tmp_crud)"); + execLua(tt1, "rawset(_G, 'crud', tmp_crud); return true"); + execLua(tt2, "rawset(_G, 'crud', tmp_crud); return true"); waitFor( "Connections are still broken after fixing crud", diff --git a/tarantool-pooling/src/test/java/io/tarantool/pool/integration/ConnectionPoolReconnectsTest.java b/tarantool-pooling/src/test/java/io/tarantool/pool/integration/ConnectionPoolReconnectsTest.java index 5ee0f87..c80ff0f 100644 --- a/tarantool-pooling/src/test/java/io/tarantool/pool/integration/ConnectionPoolReconnectsTest.java +++ b/tarantool-pooling/src/test/java/io/tarantool/pool/integration/ConnectionPoolReconnectsTest.java @@ -14,17 +14,16 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.createTarantoolContainer; import io.micrometer.core.instrument.MeterRegistry; import io.netty.util.HashedWheelTimer; import io.netty.util.Timer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.TarantoolContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; import io.tarantool.core.IProtoClient; import io.tarantool.core.ManagedResource; @@ -34,20 +33,26 @@ import io.tarantool.pool.InstanceConnectionGroup; @Timeout(value = 120) -@Testcontainers public class ConnectionPoolReconnectsTest extends BasePoolTest { - private static final Logger log = LoggerFactory.getLogger(ConnectionPoolReconnectsTest.class); + private static TarantoolContainer tt; - @Container - private TarantoolContainer tt = - new TarantoolContainer().withEnv(ENV_MAP).withFixedExposedPort(3301, 3301); + @BeforeAll + static void beforeAll() { + tt = createTarantoolContainer().withEnv(ENV_MAP).withFixedExposedPort(3301, 3301); + tt.start(); + } @BeforeEach public void setUp() { generateCounts(); } + @AfterAll + static void tearDown() { + tt.stop(); + } + @Test public void testReconnectAfterNodeFailure() throws Exception { MeterRegistry metricsRegistry = createMetricsRegistry(); @@ -80,7 +85,7 @@ public void testReconnectAfterNodeFailure() throws Exception { assertTrue(metricsRegistry.get("pool.reconnecting").gauge().value() > 0); - tt = new TarantoolContainer().withEnv(ENV_MAP).withFixedExposedPort(3301, 3301); + tt = createTarantoolContainer().withEnv(ENV_MAP).withFixedExposedPort(3301, 3301); tt.start(); waitFor( diff --git a/tarantool-pooling/src/test/java/io/tarantool/pool/integration/ConnectionPoolTest.java b/tarantool-pooling/src/test/java/io/tarantool/pool/integration/ConnectionPoolTest.java index b35bfcb..fd70764 100644 --- a/tarantool-pooling/src/test/java/io/tarantool/pool/integration/ConnectionPoolTest.java +++ b/tarantool-pooling/src/test/java/io/tarantool/pool/integration/ConnectionPoolTest.java @@ -27,6 +27,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.createTarantoolContainer; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommandDecoded; import io.micrometer.core.instrument.MeterRegistry; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelOption; @@ -35,14 +37,13 @@ import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.HashedWheelTimer; import io.netty.util.Timer; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.msgpack.value.ArrayValue; import org.msgpack.value.ValueFactory; -import org.testcontainers.containers.TarantoolContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; import io.tarantool.core.IProtoClient; import io.tarantool.core.ManagedResource; @@ -61,7 +62,6 @@ import io.tarantool.pool.exceptions.PoolClosedException; @Timeout(value = 5) -@Testcontainers public class ConnectionPoolTest extends BasePoolTest { protected static final Bootstrap bootstrap = @@ -83,22 +83,30 @@ public class ConnectionPoolTest extends BasePoolTest { private static int port2; private static int count2; - @Container - private static final TarantoolContainer tt1 = new TarantoolContainer().withEnv(ENV_MAP); - - @Container - private static final TarantoolContainer tt2 = new TarantoolContainer().withEnv(ENV_MAP); + private static TarantoolContainer tt1; + private static TarantoolContainer tt2; @BeforeAll public static void setUp() { + tt1 = createTarantoolContainer().withEnv(ENV_MAP); + tt2 = createTarantoolContainer().withEnv(ENV_MAP); + tt1.start(); + tt2.start(); + host1 = tt1.getHost(); - port1 = tt1.getPort(); + port1 = tt1.getFirstMappedPort(); count1 = ThreadLocalRandom.current().nextInt(MIN_CONNECTION_COUNT, MAX_CONNECTION_COUNT + 1); host2 = tt2.getHost(); - port2 = tt2.getPort(); + port2 = tt2.getFirstMappedPort(); count2 = ThreadLocalRandom.current().nextInt(MIN_CONNECTION_COUNT, MAX_CONNECTION_COUNT + 1); } + @AfterAll + static void tearDown() { + tt1.stop(); + tt2.stop(); + } + @Test public void testConnect() throws Exception { IProtoClientPool pool = createClientPool(true, null); @@ -214,7 +222,7 @@ public void testConnectWithCredentials() throws Exception { for (int i = 0; i < count2; i++) { clients.add(pool.get("node-b", i).get()); } - List result = tt1.executeCommandDecoded("return box.space.space_a.id"); + List result = executeCommandDecoded(tt1, "return box.space.space_a.id"); Integer space = (Integer) result.get(0); for (IProtoClient client : clients) { assertTrue(client.isConnected()); diff --git a/tarantool-schema/pom.xml b/tarantool-schema/pom.xml index 6d7b47a..1fcdfc0 100644 --- a/tarantool-schema/pom.xml +++ b/tarantool-schema/pom.xml @@ -44,7 +44,7 @@ io.tarantool - testcontainers-java-tarantool + testcontainers diff --git a/tarantool-schema/src/test/java/io/tarantool/client/integration/TarantoolSchemaFetcherTest.java b/tarantool-schema/src/test/java/io/tarantool/client/integration/TarantoolSchemaFetcherTest.java index f021434..11c97e8 100644 --- a/tarantool-schema/src/test/java/io/tarantool/client/integration/TarantoolSchemaFetcherTest.java +++ b/tarantool-schema/src/test/java/io/tarantool/client/integration/TarantoolSchemaFetcherTest.java @@ -15,6 +15,8 @@ import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommand; +import static org.testcontainers.containers.utils.TarantoolContainerClientHelper.executeCommandDecoded; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelOption; import io.netty.channel.MultiThreadIoEventLoopGroup; @@ -22,14 +24,14 @@ import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.HashedWheelTimer; import io.netty.util.Timer; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.msgpack.value.ValueFactory; -import org.testcontainers.containers.TarantoolContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.containers.tarantool.TarantoolContainer; +import org.testcontainers.containers.utils.TarantoolContainerClientHelper; import org.testcontainers.shaded.org.bouncycastle.util.Strings; import io.tarantool.balancer.TarantoolDistributingRoundRobinBalancer; @@ -45,7 +47,6 @@ import io.tarantool.schema.TarantoolSchemaFetcher; @Timeout(value = 5) -@Testcontainers public class TarantoolSchemaFetcherTest { private static final String API_USER = "api_user"; @@ -76,27 +77,37 @@ public class TarantoolSchemaFetcherTest { private static final Timer timerService = new HashedWheelTimer(); private static final ConnectionFactory factory = new ConnectionFactory(bootstrap, timerService); - @Container - private static final TarantoolContainer tt = new TarantoolContainer().withEnv(CREDS_MAP); + private static TarantoolContainer tt; private Long spacePersonId; private static IProtoClient client; @BeforeEach public void truncateSpaces() throws Exception { - List result = tt.executeCommandDecoded("return box.space.person.id"); + List result = executeCommandDecoded(tt, "return box.space.person.id"); this.spacePersonId = Long.valueOf((Integer) result.get(0)); - tt.executeCommand("return box.space.person:truncate()"); + executeCommand(tt, "return box.space.person:truncate()"); } @BeforeAll public static void setUp() { + tt = + TarantoolContainerClientHelper.createTarantoolContainer() + .withExposedPorts(3301) + .withEnv(CREDS_MAP); + tt.start(); + client = new IProtoClientImpl(factory, timerService); - client.connect(new InetSocketAddress(tt.getHost(), tt.getPort()), 3_000).join(); + client.connect(new InetSocketAddress(tt.getHost(), tt.getFirstMappedPort()), 3_000).join(); client.authorize(API_USER, CREDS.get(API_USER)).join(); } + @AfterAll + static void tearDown() { + tt.stop(); + } + @Test public void testSchemaVersionBehaviour() { TarantoolSchemaFetcher fetcher = getTarantoolSchemaFetcher(); @@ -121,7 +132,7 @@ public void testSchemaVersionBehaviour() { @Test public void testSpacesIndexes() throws Exception { Map realIndexFromEval = - (Map) ((List) tt.executeCommandDecoded("return box.space.person.index")).get(0); + (Map) ((List) executeCommandDecoded(tt, "return box.space.person.index")).get(0); assertEquals(new HashSet<>(Arrays.asList(0, "pk")), realIndexFromEval.keySet()); Map pk = (Map) realIndexFromEval.get("pk"); @@ -152,7 +163,7 @@ private static TarantoolSchemaFetcher getTarantoolSchemaFetcher() { Collections.singletonList( InstanceConnectionGroup.builder() .withHost(tt.getHost()) - .withPort(tt.getPort()) + .withPort(tt.getFirstMappedPort()) .withTag("a") .build())); @@ -198,7 +209,7 @@ public void testIndexesChange() throws Exception { fetcher.processRequest(client.ping()).join(); Map realIndexFromEval = - (Map) ((List) tt.executeCommandDecoded("return box.space.person.index")).get(0); + (Map) ((List) executeCommandDecoded(tt, "return box.space.person.index")).get(0); assertEquals(new HashSet<>(Arrays.asList(0, "pk")), realIndexFromEval.keySet()); assertEquals(1, fetcher.getSpace("person").getIndexes().size()); @@ -210,7 +221,7 @@ public void testIndexesChange() throws Exception { .join(); realIndexFromEval = - (Map) ((List) tt.executeCommandDecoded("return box.space.person.index")).get(0); + (Map) ((List) executeCommandDecoded(tt, "return box.space.person.index")).get(0); assertEquals( new HashSet<>(Arrays.asList(0, 1, "pk", "name_index")), realIndexFromEval.keySet()); diff --git a/tarantool-shared-resources/cartridge/Dockerfile b/tarantool-shared-resources/cartridge/Dockerfile new file mode 100644 index 0000000..ff4cabc --- /dev/null +++ b/tarantool-shared-resources/cartridge/Dockerfile @@ -0,0 +1,43 @@ +ARG IMAGE="tarantool/tarantool" +ARG TARANTOOL_VERSION="2.11.8-ubuntu20.04" +FROM $IMAGE:$TARANTOOL_VERSION AS cartridge-base + +ARG TARANTOOL_SERVER_USER="root" +ARG TARANTOOL_SERVER_GROUP="root" +USER $TARANTOOL_SERVER_USER:$TARANTOOL_SERVER_GROUP +RUN groupadd $TARANTOOL_SERVER_GROUP && useradd -m -s /bin/bash $TARANTOOL_SERVER_USER || true + +# install dependencies +RUN ulimit -n 1024 && \ + apt-get -y update && \ + apt-get -y install build-essential cmake make gcc git unzip cartridge-cli && \ + apt-get -y clean +RUN cartridge version + +# build and run +FROM cartridge-base AS cartridge-app +ARG CARTRIDGE_SRC_DIR="cartridge" +ARG TARANTOOL_WORKDIR="/app" +ARG TARANTOOL_RUNDIR="/tmp/run" +ARG TARANTOOL_DATADIR="/tmp/data" +ARG TARANTOOL_LOGDIR="/tmp/log" +ARG TARANTOOL_INSTANCES_FILE="./instances.yml" +ARG TARANTOOL_CLUSTER_COOKIE +ARG START_DELAY="5s" +ENV START_DELAY=$START_DELAY +ENV TARANTOOL_WORKDIR=$TARANTOOL_WORKDIR +ENV TARANTOOL_RUNDIR=$TARANTOOL_RUNDIR +ENV TARANTOOL_DATADIR=$TARANTOOL_DATADIR +ENV TARANTOOL_LOGDIR=$TARANTOOL_LOGDIR +ENV TARANTOOL_INSTANCES_FILE=$TARANTOOL_INSTANCES_FILE +ENV TARANTOOL_CLUSTER_COOKIE=$TARANTOOL_CLUSTER_COOKIE +ENV CMAKE_DUMMY_WEBUI="YES" +COPY $CARTRIDGE_SRC_DIR $TARANTOOL_WORKDIR +WORKDIR $TARANTOOL_WORKDIR + +RUN rm -rf .rocks && cartridge build --verbose + +RUN echo 'if [ -z "$TARANTOOL_CLUSTER_COOKIE" ]; then unset TARANTOOL_CLUSTER_COOKIE ; fi ; \ + sleep $START_DELAY && cartridge start --run-dir=$TARANTOOL_RUNDIR --data-dir=$TARANTOOL_DATADIR \ + --log-dir=$TARANTOOL_LOGDIR --cfg=$TARANTOOL_INSTANCES_FILE' > run.sh && chmod +x run.sh +CMD ./run.sh diff --git a/tarantool-shared-resources/cartridge/server.lua b/tarantool-shared-resources/cartridge/server.lua new file mode 100644 index 0000000..cc6580b --- /dev/null +++ b/tarantool-shared-resources/cartridge/server.lua @@ -0,0 +1,10 @@ +box.cfg { + listen = 3301, + memtx_memory = 128 * 1024 * 1024, -- 128 Mb + -- log = 'file:/tmp/tarantool.log', + log_level = 6, +} +-- API user will be able to login with this password +box.schema.user.create('api_user', { password = 'secret', if_not_exists = true }) +-- API user will be able to create spaces, add or remove data, execute functions +box.schema.user.grant('api_user', 'read,write,execute', 'universe', nil, { if_not_exists = true }) diff --git a/tarantool-shared-resources/logback-test.xml b/tarantool-shared-resources/logback-test.xml index a687cd0..cd8607c 100644 --- a/tarantool-shared-resources/logback-test.xml +++ b/tarantool-shared-resources/logback-test.xml @@ -9,6 +9,6 @@ - + diff --git a/tarantool-shared-resources/server.lua b/tarantool-shared-resources/server.lua index 1a52571..296baf1 100644 --- a/tarantool-shared-resources/server.lua +++ b/tarantool-shared-resources/server.lua @@ -21,8 +21,7 @@ box.cfg { local log = require('log') local fiber = require('fiber') local socket = require('socket') - -local utils = require('utils') +local tarantool = require('tarantool') local LIMIT = 512 local BIND = '0.0.0.0' @@ -31,14 +30,49 @@ local session_counter = 0 local connects_registered = {} local pipe_lock = false +------------------------------------------------------------------------------ +-- UTILITY FUNCTIONS +------------------------------------------------------------------------------ +local function get_version() + local version = unpack(tarantool.version:split('-')) + return version +end + +local function create_kv_space(name) + local space = box.schema.space.create(name, { + if_not_exists = true, + format = { + { 'id', type = 'string' }, + { 'value', type = 'string', is_nullable = true } + } + }) + space:create_index('pk', { parts = { 'id' } }) +end + +local function create_complex_space(name) + local space = box.schema.space.create(name, { + if_not_exists = true, + format = { + { 'id', type = 'number' }, + { 'is_married', type = 'boolean', is_nullable = true }, + { 'name', type = 'string' } + } + }) + space:create_index('pk', { parts = { 'id' } }) +end + +local function fail() + error('Fail!') +end + ------------------------------------------------------------------------------ -- SCHEMA ------------------------------------------------------------------------------ box.once('schema', function() - utils.create_kv_space('test') - utils.create_kv_space('space_a') - utils.create_kv_space('space_b') - utils.create_complex_space('person') + create_kv_space('test') + create_kv_space('space_a') + create_kv_space('space_b') + create_complex_space('person') box.space.space_b:on_replace(function(old, new, s, op) box.session.push(old) @@ -173,8 +207,8 @@ end ------------------------------------------------------------------------------ -- PUBLIC FUNCTIONS ------------------------------------------------------------------------------ -get_version = utils.get_version -fail = utils.fail +get_version = get_version +fail = fail function echo_with_wrapping(...) -- we use table packing because it's more popular than multi return value diff --git a/tarantool-spring-data/tarantool-spring-data-27/src/test/java/io/tarantool/spring/data27/integration/BaseIntegrationTest.java b/tarantool-spring-data/tarantool-spring-data-27/src/test/java/io/tarantool/spring/data27/integration/BaseIntegrationTest.java index 88d7b04..8253720 100644 --- a/tarantool-spring-data/tarantool-spring-data-27/src/test/java/io/tarantool/spring/data27/integration/BaseIntegrationTest.java +++ b/tarantool-spring-data/tarantool-spring-data-27/src/test/java/io/tarantool/spring/data27/integration/BaseIntegrationTest.java @@ -57,7 +57,7 @@ private static void configureContainer() { } else { TarantoolCartridgeContainer cartridgeContainer = new TarantoolCartridgeContainer( - "Dockerfile", + "cartridge/Dockerfile", dockerRegistry + "cartridge", "cartridge/instances.yml", "cartridge/replicasets.yml", diff --git a/tarantool-spring-data/tarantool-spring-data-31/src/test/java/io/tarantool/spring/data31/integration/BaseIntegrationTest.java b/tarantool-spring-data/tarantool-spring-data-31/src/test/java/io/tarantool/spring/data31/integration/BaseIntegrationTest.java index e2d4683..3856c22 100644 --- a/tarantool-spring-data/tarantool-spring-data-31/src/test/java/io/tarantool/spring/data31/integration/BaseIntegrationTest.java +++ b/tarantool-spring-data/tarantool-spring-data-31/src/test/java/io/tarantool/spring/data31/integration/BaseIntegrationTest.java @@ -57,7 +57,7 @@ private static void configureContainer() { } else { TarantoolCartridgeContainer cartridgeContainer = new TarantoolCartridgeContainer( - "Dockerfile", + "cartridge/Dockerfile", dockerRegistry + "cartridge", "cartridge/instances.yml", "cartridge/replicasets.yml", diff --git a/tarantool-spring-data/tarantool-spring-data-32/src/test/java/io/tarantool/spring/data32/integration/BaseIntegrationTest.java b/tarantool-spring-data/tarantool-spring-data-32/src/test/java/io/tarantool/spring/data32/integration/BaseIntegrationTest.java index f519b07..570ca85 100644 --- a/tarantool-spring-data/tarantool-spring-data-32/src/test/java/io/tarantool/spring/data32/integration/BaseIntegrationTest.java +++ b/tarantool-spring-data/tarantool-spring-data-32/src/test/java/io/tarantool/spring/data32/integration/BaseIntegrationTest.java @@ -57,7 +57,7 @@ private static void configureContainer() { } else { TarantoolCartridgeContainer cartridgeContainer = new TarantoolCartridgeContainer( - "Dockerfile", + "cartridge/Dockerfile", dockerRegistry + "cartridge", "cartridge/instances.yml", "cartridge/replicasets.yml", diff --git a/tarantool-spring-data/tarantool-spring-data-33/src/test/java/io/tarantool/spring/data33/integration/BaseIntegrationTest.java b/tarantool-spring-data/tarantool-spring-data-33/src/test/java/io/tarantool/spring/data33/integration/BaseIntegrationTest.java index 1f92238..4eb8549 100644 --- a/tarantool-spring-data/tarantool-spring-data-33/src/test/java/io/tarantool/spring/data33/integration/BaseIntegrationTest.java +++ b/tarantool-spring-data/tarantool-spring-data-33/src/test/java/io/tarantool/spring/data33/integration/BaseIntegrationTest.java @@ -57,7 +57,7 @@ private static void configureContainer() { } else { TarantoolCartridgeContainer cartridgeContainer = new TarantoolCartridgeContainer( - "Dockerfile", + "cartridge/Dockerfile", dockerRegistry + "cartridge", "cartridge/instances.yml", "cartridge/replicasets.yml", diff --git a/tarantool-spring-data/tarantool-spring-data-34/src/test/java/io/tarantool/spring/data34/integration/BaseIntegrationTest.java b/tarantool-spring-data/tarantool-spring-data-34/src/test/java/io/tarantool/spring/data34/integration/BaseIntegrationTest.java index d776956..f9afc09 100644 --- a/tarantool-spring-data/tarantool-spring-data-34/src/test/java/io/tarantool/spring/data34/integration/BaseIntegrationTest.java +++ b/tarantool-spring-data/tarantool-spring-data-34/src/test/java/io/tarantool/spring/data34/integration/BaseIntegrationTest.java @@ -57,7 +57,7 @@ private static void configureContainer() { } else { TarantoolCartridgeContainer cartridgeContainer = new TarantoolCartridgeContainer( - "Dockerfile", + "cartridge/Dockerfile", dockerRegistry + "cartridge", "cartridge/instances.yml", "cartridge/replicasets.yml", diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/test/java/io/tarantool/spring/data35/integration/BaseIntegrationTest.java b/tarantool-spring-data/tarantool-spring-data-35/src/test/java/io/tarantool/spring/data35/integration/BaseIntegrationTest.java index 818d742..fac354e 100644 --- a/tarantool-spring-data/tarantool-spring-data-35/src/test/java/io/tarantool/spring/data35/integration/BaseIntegrationTest.java +++ b/tarantool-spring-data/tarantool-spring-data-35/src/test/java/io/tarantool/spring/data35/integration/BaseIntegrationTest.java @@ -57,7 +57,7 @@ private static void configureContainer() { } else { TarantoolCartridgeContainer cartridgeContainer = new TarantoolCartridgeContainer( - "Dockerfile", + "cartridge/Dockerfile", dockerRegistry + "cartridge", "cartridge/instances.yml", "cartridge/replicasets.yml", diff --git a/testcontainers/pom.xml b/testcontainers/pom.xml index ea5327b..b74dfd9 100644 --- a/testcontainers/pom.xml +++ b/testcontainers/pom.xml @@ -48,11 +48,6 @@ jackson-datatype-jdk8 ${jackson.version} - - io.tarantool - testcontainers-java-tarantool - compile - org.projectlombok lombok diff --git a/testcontainers/src/main/java/org/testcontainers/containers/TarantoolCartridgeContainer.java b/testcontainers/src/main/java/org/testcontainers/containers/TarantoolCartridgeContainer.java new file mode 100644 index 0000000..a7748e3 --- /dev/null +++ b/testcontainers/src/main/java/org/testcontainers/containers/TarantoolCartridgeContainer.java @@ -0,0 +1,665 @@ +/* + * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY + * All Rights Reserved. + */ + +package org.testcontainers.containers; + +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.function.Supplier; + +import static org.testcontainers.containers.utils.PathUtils.normalizePath; +import com.github.dockerjava.api.command.InspectContainerResponse; +import org.apache.commons.lang3.ArrayUtils; +import org.testcontainers.containers.utils.CartridgeConfigParser; +import org.testcontainers.containers.utils.SslContext; +import org.testcontainers.containers.utils.TarantoolContainerClientHelper; +import org.testcontainers.images.builder.ImageFromDockerfile; + +public class TarantoolCartridgeContainer extends GenericContainer + implements TarantoolContainerOperations { + + protected static final String ROUTER_HOST = "localhost"; + protected static final int ROUTER_PORT = 3301; + protected static final String CARTRIDGE_DEFAULT_USERNAME = "admin"; + protected static final String CARTRIDGE_DEFAULT_PASSWORD = "secret-cluster-cookie"; + protected static final String DOCKERFILE = "Dockerfile"; + protected static final int API_PORT = 8081; + protected static final String VSHARD_BOOTSTRAP_COMMAND = + "return require('cartridge').admin_bootstrap_vshard()"; + protected static final String SCRIPT_RESOURCE_DIRECTORY = ""; + protected static final String INSTANCE_DIR = "/app"; + + public static final String ENV_TARANTOOL_VERSION = "TARANTOOL_VERSION"; + public static final String ENV_TARANTOOL_SERVER_USER = "TARANTOOL_SERVER_USER"; + public static final String ENV_TARANTOOL_SERVER_UID = "TARANTOOL_SERVER_UID"; + public static final String ENV_TARANTOOL_SERVER_GROUP = "TARANTOOL_SERVER_GROUP"; + public static final String ENV_TARANTOOL_SERVER_GID = "TARANTOOL_SERVER_GID"; + public static final String ENV_TARANTOOL_WORKDIR = "TARANTOOL_WORKDIR"; + public static final String ENV_TARANTOOL_RUNDIR = "TARANTOOL_RUNDIR"; + public static final String ENV_TARANTOOL_LOGDIR = "TARANTOOL_LOGDIR"; + public static final String ENV_TARANTOOL_DATADIR = "TARANTOOL_DATADIR"; + public static final String ENV_TARANTOOL_INSTANCES_FILE = "TARANTOOL_INSTANCES_FILE"; + public static final String ENV_TARANTOOL_CLUSTER_COOKIE = "TARANTOOL_CLUSTER_COOKIE"; + protected static final String healthyCmd = "return require('cartridge').is_healthy()"; + protected static final int TIMEOUT_ROUTER_UP_CARTRIDGE_HEALTH_IN_SECONDS = 60; + + protected final CartridgeConfigParser instanceFileParser; + protected final String TARANTOOL_RUN_DIR; + + protected boolean useFixedPorts = false; + protected String routerHost = ROUTER_HOST; + protected int routerPort = ROUTER_PORT; + protected int apiPort = API_PORT; + protected String routerUsername = CARTRIDGE_DEFAULT_USERNAME; + protected String routerPassword = CARTRIDGE_DEFAULT_PASSWORD; + protected String directoryResourcePath = SCRIPT_RESOURCE_DIRECTORY; + protected String instanceDir = INSTANCE_DIR; + protected String topologyConfigurationFile; + protected String instancesFile; + protected SslContext sslContext; + + /** + * Create a container with default image and specified instances file from the classpath + * resources. Assumes that there is a file named Dockerfile in the project resources classpath. + * + * @param instancesFile path to instances.yml, relative to the classpath resources + * @param topologyConfigurationFile path to a topology bootstrap script, relative to the classpath + * resources + */ + public TarantoolCartridgeContainer(String instancesFile, String topologyConfigurationFile) { + this(DOCKERFILE, instancesFile, topologyConfigurationFile); + } + + /** + * Create a container with default image and specified instances file from the classpath + * resources. Assumes that there is a file named Dockerfile in the project resources classpath. + * + * @param instancesFile path to instances.yml, relative to the classpath resources + * @param topologyConfigurationFile path to a topology bootstrap script, relative to the classpath + * resources + * @param buildArgs a map of arguments that will be passed to docker ARG commands on image build. + * This values can be overridden by environment. + */ + public TarantoolCartridgeContainer( + String instancesFile, String topologyConfigurationFile, Map buildArgs) { + this(DOCKERFILE, "", instancesFile, topologyConfigurationFile, buildArgs); + } + + /** + * Create a container with default image and specified instances file from the classpath resources + * + * @param dockerFile path to a Dockerfile which configures Cartridge and other necessary services + * @param instancesFile path to instances.yml, relative to the classpath resources + * @param topologyConfigurationFile path to a topology bootstrap script, relative to the classpath + * resources + */ + public TarantoolCartridgeContainer( + String dockerFile, String instancesFile, String topologyConfigurationFile) { + this(dockerFile, "", instancesFile, topologyConfigurationFile); + } + + /** + * Create a container with specified image and specified instances file from the classpath + * resources. By providing the result Cartridge container image name, you can cache the image and + * avoid rebuilding on each test run (the image is tagged with the provided name and not deleted + * after tests finishing). + * + * @param dockerFile URL resource path to a Dockerfile which configures Cartridge and other + * necessary services + * @param buildImageName Specify a stable image name for the test container to prevent rebuilds + * @param instancesFile URL resource path to instances.yml relative in the classpath + * @param topologyConfigurationFile URL resource path to a topology bootstrap script in the + * classpath + */ + public TarantoolCartridgeContainer( + String dockerFile, + String buildImageName, + String instancesFile, + String topologyConfigurationFile) { + this( + dockerFile, + buildImageName, + instancesFile, + topologyConfigurationFile, + Collections.emptyMap()); + } + + /** + * Create a container with specified image and specified instances file from the classpath + * resources. By providing the result Cartridge container image name, you can cache the image and + * avoid rebuilding on each test run (the image is tagged with the provided name and not deleted + * after tests finishing). + * + * @param dockerFile URL resource path to a Dockerfile which configures Cartridge and other + * necessary services + * @param buildImageName Specify a stable image name for the test container to prevent rebuilds + * @param instancesFile URL resource path to instances.yml relative in the classpath + * @param topologyConfigurationFile URL resource path to a topology bootstrap script in the + * classpath + * @param buildArgs a map of arguments that will be passed to docker ARG commands on image build. + * This values can be overridden by environment. + */ + public TarantoolCartridgeContainer( + String dockerFile, + String buildImageName, + String instancesFile, + String topologyConfigurationFile, + final Map buildArgs) { + this( + buildImage(dockerFile, buildImageName, buildArgs), + instancesFile, + topologyConfigurationFile, + buildArgs); + } + + protected TarantoolCartridgeContainer( + ImageFromDockerfile image, + String instancesFile, + String topologyConfigurationFile, + Map buildArgs) { + super(withBuildArgs(image, buildArgs)); + + TARANTOOL_RUN_DIR = + mergeBuildArguments(buildArgs).getOrDefault(ENV_TARANTOOL_RUNDIR, "/tmp/run"); + + if (instancesFile == null || instancesFile.isEmpty()) { + throw new IllegalArgumentException("Instance file name must not be null or empty"); + } + if (topologyConfigurationFile == null || topologyConfigurationFile.isEmpty()) { + throw new IllegalArgumentException("Topology configuration file must not be null or empty"); + } + this.instancesFile = instancesFile; + this.topologyConfigurationFile = topologyConfigurationFile; + this.instanceFileParser = new CartridgeConfigParser(instancesFile); + } + + protected static ImageFromDockerfile withBuildArgs( + ImageFromDockerfile image, Map buildArgs) { + Map args = mergeBuildArguments(buildArgs); + + if (!args.isEmpty()) { + image.withBuildArgs(args); + } + + return image; + } + + public TarantoolCartridgeContainer withFixedExposedPort(int hostPort, int containerPort) { + super.addFixedExposedPort(hostPort, containerPort); + return this; + } + + public TarantoolCartridgeContainer withExposedPort(Integer port) { + super.addExposedPort(port); + return this; + } + + protected static Map mergeBuildArguments(Map buildArgs) { + Map args = new HashMap<>(buildArgs); + + for (String envVariable : + Arrays.asList( + ENV_TARANTOOL_VERSION, + ENV_TARANTOOL_SERVER_USER, + ENV_TARANTOOL_SERVER_UID, + ENV_TARANTOOL_SERVER_GROUP, + ENV_TARANTOOL_SERVER_GID, + ENV_TARANTOOL_WORKDIR, + ENV_TARANTOOL_RUNDIR, + ENV_TARANTOOL_LOGDIR, + ENV_TARANTOOL_DATADIR, + ENV_TARANTOOL_INSTANCES_FILE, + ENV_TARANTOOL_CLUSTER_COOKIE)) { + String variableValue = System.getenv(envVariable); + if (variableValue != null && !args.containsKey(envVariable)) { + args.put(envVariable, variableValue); + } + } + return args; + } + + protected static ImageFromDockerfile buildImage( + String dockerFile, String buildImageName, final Map buildArgs) { + ImageFromDockerfile image; + if (buildImageName != null && !buildImageName.isEmpty()) { + image = new ImageFromDockerfile(buildImageName, false); + } else { + image = new ImageFromDockerfile(); + } + return image + .withFileFromClasspath("Dockerfile", dockerFile) + .withFileFromClasspath( + "cartridge", + buildArgs.get("CARTRIDGE_SRC_DIR") == null + ? "cartridge" + : buildArgs.get("CARTRIDGE_SRC_DIR")); + } + + /** + * Get the router host + * + * @return router hostname + */ + public String getRouterHost() { + return routerHost; + } + + /** + * Get the router port + * + * @return router mapped port + */ + public int getRouterPort() { + if (useFixedPorts) { + return routerPort; + } + return getMappedPort(routerPort); + } + + /** + * Get the user name for connecting to the router + * + * @return a user name + */ + public String getRouterUsername() { + return routerUsername; + } + + /** + * Get the user password for connecting to the router + * + * @return a user password + */ + public String getRouterPassword() { + return routerPassword; + } + + @Override + public String getHost() { + return getRouterHost(); + } + + @Override + public int getPort() { + return getRouterPort(); + } + + @Override + public String getUsername() { + return getRouterUsername(); + } + + @Override + public String getPassword() { + return getRouterPassword(); + } + + @Override + public String getDirectoryBinding() { + return directoryResourcePath; + } + + /** + * Specify the directory inside container that the resource directory will be mounted to. The + * default value is "/app". + * + * @param instanceDir valid directory path + * @return this container instance + */ + public TarantoolCartridgeContainer withInstanceDir(String instanceDir) { + checkNotRunning(); + this.instanceDir = instanceDir; + return this; + } + + @Override + public String getInstanceDir() { + return instanceDir; + } + + @Override + public int getInternalPort() { + return routerPort; + } + + /** + * Get Cartridge router HTTP API hostname + * + * @return HTTP API hostname + */ + public String getAPIHost() { + return routerHost; + } + + /** Checks if already running and if so raises an exception to prevent too-late setters. */ + protected void checkNotRunning() { + if (isRunning()) { + throw new IllegalStateException( + "This option can be changed only before the container is running"); + } + } + + /** + * Specify the root directory of a Cartridge project relative to the resource classpath. The + * default directory is the root resource directory. + * + * @param directoryResourcePath a valid directory path + * @return this container instance + */ + public TarantoolCartridgeContainer withDirectoryBinding(String directoryResourcePath) { + checkNotRunning(); + URL resource = getClass().getClassLoader().getResource(directoryResourcePath); + if (resource == null) { + throw new IllegalArgumentException( + String.format( + "No resource path found for the specified resource %s", directoryResourcePath)); + } + this.directoryResourcePath = normalizePath(resource.getPath()); + return this; + } + + /** + * Get Cartridge router HTTP API port + * + * @return HTTP API port + */ + public int getAPIPort() { + if (useFixedPorts) { + return apiPort; + } + return getMappedPort(apiPort); + } + + /** + * Use fixed ports binding. Defaults to false. + * + * @param useFixedPorts fixed ports for tarantool + * @return HTTP API port + */ + public TarantoolCartridgeContainer withUseFixedPorts(boolean useFixedPorts) { + this.useFixedPorts = useFixedPorts; + return this; + } + + /** + * Set Cartridge router hostname + * + * @param routerHost a hostname, default is "localhost" + * @return this container instance + */ + public TarantoolCartridgeContainer withRouterHost(String routerHost) { + checkNotRunning(); + this.routerHost = routerHost; + return this; + } + + /** + * Set Cartridge router binary port + * + * @param routerPort router Tarantool node port, usually 3301 + * @return this container instance + */ + public TarantoolCartridgeContainer withRouterPort(int routerPort) { + checkNotRunning(); + this.routerPort = routerPort; + return this; + } + + /** + * Set Cartridge router HTTP API port + * + * @param apiPort HTTP API port, usually 8081 + * @return this container instance + */ + public TarantoolCartridgeContainer withAPIPort(int apiPort) { + checkNotRunning(); + this.apiPort = apiPort; + return this; + } + + /** + * Set the username for accessing the router node + * + * @param routerUsername a user name, default is "admin" + * @return this container instance + */ + public TarantoolCartridgeContainer withRouterUsername(String routerUsername) { + checkNotRunning(); + this.routerUsername = routerUsername; + return this; + } + + /** + * Set the user password for accessing the router node + * + * @param routerPassword a user password, usually is a value of the "cluster_cookie" option in + * cartridge.cfg({...}) + * @return this container instance + */ + public TarantoolCartridgeContainer withRouterPassword(String routerPassword) { + checkNotRunning(); + this.routerPassword = routerPassword; + return this; + } + + @Override + protected void configure() { + if (!getDirectoryBinding().isEmpty()) { + withFileSystemBind(getDirectoryBinding(), getInstanceDir(), BindMode.READ_WRITE); + } + if (useFixedPorts) { + for (Integer port : instanceFileParser.getExposablePorts()) { + addFixedExposedPort(port, port); + } + } else { + addExposedPorts(ArrayUtils.toPrimitive(instanceFileParser.getExposablePorts())); + } + } + + @Override + protected void containerIsStarting(InspectContainerResponse containerInfo) { + logger().info("Tarantool Cartridge cluster is starting"); + } + + protected boolean setupTopology() { + String fileType = + topologyConfigurationFile.substring(topologyConfigurationFile.lastIndexOf('.') + 1); + if (fileType.equals("yml")) { + String replicasetsFileName = + topologyConfigurationFile.substring(topologyConfigurationFile.lastIndexOf('/') + 1); + String instancesFileName = instancesFile.substring(instancesFile.lastIndexOf('/') + 1); + try { + ExecResult result = + execInContainer( + "cartridge", + "replicasets", + "--run-dir=" + TARANTOOL_RUN_DIR, + "--file=" + replicasetsFileName, + "--cfg=" + instancesFileName, + "setup", + "--bootstrap-vshard"); + if (result.getExitCode() != 0) { + throw new RuntimeException( + "Failed to change the app topology via cartridge CLI: " + result.getStdout()); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + + } else { + try { + List res = executeScriptDecoded(topologyConfigurationFile); + if (res.size() >= 2 && res.get(1) != null && res.get(1) instanceof Map) { + HashMap error = ((HashMap) res.get(1)); + // that means topology already exists + return error.get("str").toString().contains("collision with another server"); + } + // The client connection will be closed after that command + } catch (Exception e) { + if (e instanceof ExecutionException) { + if (e.getCause() instanceof TimeoutException) { + return true; + // Do nothing, the cluster is reloading + } + } else { + throw new RuntimeException(e); + } + } + } + return true; + } + + protected void retryingSetupTopology() { + if (!setupTopology()) { + try { + logger().info("Retrying setup topology in 10 seconds"); + Thread.sleep(10_000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + if (!setupTopology()) { + throw new RuntimeException("Failed to change the app topology after retry"); + } + } + } + + protected void bootstrapVshard() { + try { + executeCommand(VSHARD_BOOTSTRAP_COMMAND); + } catch (Exception e) { + logger().error("Failed to bootstrap vshard cluster", e); + throw new RuntimeException(e); + } + } + + @Override + protected void containerIsStarted(InspectContainerResponse containerInfo, boolean reused) { + super.containerIsStarted(containerInfo, reused); + + waitUntilRouterIsUp(TIMEOUT_ROUTER_UP_CARTRIDGE_HEALTH_IN_SECONDS); + retryingSetupTopology(); + // wait until Roles are configured + waitUntilCartridgeIsHealthy(TIMEOUT_ROUTER_UP_CARTRIDGE_HEALTH_IN_SECONDS); + bootstrapVshard(); + + logger().info("Tarantool Cartridge cluster is started"); + logger() + .info("Tarantool Cartridge router is listening at {}:{}", getRouterHost(), getRouterPort()); + logger().info("Tarantool Cartridge HTTP API is available at {}:{}", getAPIHost(), getAPIPort()); + } + + protected void waitUntilRouterIsUp(int secondsToWait) { + if (!waitUntilTrue(secondsToWait, this::routerIsUp)) { + throw new RuntimeException( + "Timeout exceeded during router starting stage." + " See the specific error in logs."); + } + } + + protected void waitUntilCartridgeIsHealthy(int secondsToWait) { + if (!waitUntilTrue(secondsToWait, this::isCartridgeHealthy)) { + throw new RuntimeException( + "Timeout exceeded during cartridge topology applying stage." + + " See the specific error in logs."); + } + } + + protected boolean waitUntilTrue(int secondsToWait, Supplier waitFunc) { + int secondsPassed = 0; + boolean result = waitFunc.get(); + while (!result && secondsPassed < secondsToWait) { + result = waitFunc.get(); + try { + Thread.sleep(1_000); + secondsPassed++; + } catch (InterruptedException e) { + break; + } + } + return result; + } + + protected boolean routerIsUp() { + ExecResult result; + try { + result = executeCommand(healthyCmd); + if (result.getExitCode() != 0 + && result.getStderr().contains("Connection refused") + && result.getStdout().isEmpty()) { + return false; + } else if (result.getExitCode() != 0) { + logger() + .error( + "exit code: {}, stdout: {}, stderr: {}", + result.getExitCode(), + result.getStdout(), + result.getStderr()); + return false; + } else { + return true; + } + } catch (Exception e) { + logger().error(e.getMessage()); + return false; + } + } + + protected boolean isCartridgeHealthy() { + ExecResult result; + try { + result = executeCommand(healthyCmd); + if (result.getExitCode() != 0) { + logger() + .error( + "exitCode: {}, stdout: {}, stderr: {}", + result.getExitCode(), + result.getStdout(), + result.getStderr()); + return false; + } else if (result.getStdout().startsWith("---\n- null\n")) { + return false; + } else if (result.getStdout().contains("true")) { + return true; + } else { + logger() + .warn( + "exitCode: {}, stdout: {}, stderr: {}", + result.getExitCode(), + result.getStdout(), + result.getStderr()); + return false; + } + } catch (Exception e) { + logger().error("Error while waiting for cartridge healthy state: " + e.getMessage()); + return false; + } + } + + @Override + public ExecResult executeScript(String scriptResourcePath) throws Exception { + return TarantoolContainerClientHelper.executeScript(this, scriptResourcePath, this.sslContext); + } + + @Override + public T executeScriptDecoded(String scriptResourcePath) throws Exception { + return TarantoolContainerClientHelper.executeScriptDecoded( + this, scriptResourcePath, this.sslContext); + } + + @Override + public ExecResult executeCommand(String command) throws Exception { + return TarantoolContainerClientHelper.executeCommand(this, command, this.sslContext); + } + + @Override + public T executeCommandDecoded(String command) throws Exception { + return TarantoolContainerClientHelper.executeCommandDecoded(this, command, this.sslContext); + } +} diff --git a/testcontainers/src/main/java/org/testcontainers/containers/TarantoolContainerOperations.java b/testcontainers/src/main/java/org/testcontainers/containers/TarantoolContainerOperations.java new file mode 100644 index 0000000..48c4626 --- /dev/null +++ b/testcontainers/src/main/java/org/testcontainers/containers/TarantoolContainerOperations.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY + * All Rights Reserved. + */ + +package org.testcontainers.containers; + +public interface TarantoolContainerOperations> extends Container { + /** + * Get the Tarantool server exposed port for connecting the client to + * + * @return a port + */ + int getPort(); + + /** + * Get the Tarantool user name for connecting the client with + * + * @return a user name + */ + String getUsername(); + + /** + * Get the Tarantool user password for connecting the client with + * + * @return a user password + */ + String getPassword(); + + /** + * Get the app scripts directory + * + * @return the app directory + */ + String getDirectoryBinding(); + + /** + * Get the app scripts directory in the container + * + * @return the app scripts directory + */ + String getInstanceDir(); + + /** + * Get the Tarantool server internal port for client connections + * + * @return a port + */ + int getInternalPort(); + + /** + * Execute a local script in the Tarantool instance. The path must be classpath-relative. + * `dofile()` function is executed internally, so possible exceptions will be caught as the client + * exceptions. + * + * @param scriptResourcePath the classpath resource path to a script + * @return script execution result + * @throws Exception if failed to connect to the instance or execution fails + */ + Container.ExecResult executeScript(String scriptResourcePath) throws Exception; + + /** + * Execute a local script in the Tarantool instance. The path must be classpath-relative. + * `dofile()` function is executed internally, so possible exceptions will be caught as the client + * exceptions. + * + * @param the result of script + * @param scriptResourcePath the classpath resource path to a script + * @return script execution result in {@link Container.ExecResult} + * @throws Exception if failed to connect to the instance or execution fails + */ + V executeScriptDecoded(String scriptResourcePath) throws Exception; + + /** + * Execute a command in the Tarantool instance. Example of a command: `return 1 + 2, 'foo'` + * + * @param command a valid Lua command or a sequence of Lua commands + * @return command execution result + * @throws Exception if failed to connect to the instance or execution fails + */ + Container.ExecResult executeCommand(String command) throws Exception; + + /** + * Execute a command in the Tarantool instance. Example of a command: `return 1 + 2, 'foo'` + * + * @param the result of script + * @param command a valid Lua command or a sequence of Lua commands + * @return command execution result in {@link Container.ExecResult} + * @throws Exception if failed to connect to the instance or execution fails + */ + V executeCommandDecoded(String command) throws Exception; +} diff --git a/testcontainers/src/main/java/org/testcontainers/containers/VshardClusterContainer.java b/testcontainers/src/main/java/org/testcontainers/containers/VshardClusterContainer.java index a33b459..b8eb8f4 100644 --- a/testcontainers/src/main/java/org/testcontainers/containers/VshardClusterContainer.java +++ b/testcontainers/src/main/java/org/testcontainers/containers/VshardClusterContainer.java @@ -15,10 +15,12 @@ import java.util.concurrent.ExecutionException; import java.util.function.Supplier; -import static org.testcontainers.containers.PathUtils.normalizePath; +import static org.testcontainers.containers.utils.PathUtils.normalizePath; import com.github.dockerjava.api.command.InspectContainerResponse; import lombok.Getter; import org.apache.commons.lang3.ArrayUtils; +import org.testcontainers.containers.utils.SslContext; +import org.testcontainers.containers.utils.TarantoolContainerClientHelper; import org.testcontainers.images.builder.ImageFromDockerfile; /** @@ -49,7 +51,6 @@ public class VshardClusterContainer extends GenericContainer waitFunc) { protected boolean crudIsUp() { ExecResult result; try { - result = executeCommand("return crud._VERSION"); + result = + TarantoolContainerClientHelper.executeCommand( + this, "return crud._VERSION", this.sslContext); if (result.getExitCode() != 0) { logger() .error( @@ -367,7 +369,8 @@ protected boolean crudIsUp() { @Override public ExecResult executeScript(String scriptResourcePath) { try { - return clientHelper.executeScript(scriptResourcePath, this.sslContext); + return TarantoolContainerClientHelper.executeScript( + this, scriptResourcePath, this.sslContext); } catch (IOException | InterruptedException e) { throw new RuntimeException(e); } @@ -376,7 +379,8 @@ public ExecResult executeScript(String scriptResourcePath) { @Override public T executeScriptDecoded(String scriptResourcePath) { try { - return clientHelper.executeScriptDecoded(scriptResourcePath, this.sslContext); + return TarantoolContainerClientHelper.executeScriptDecoded( + this, scriptResourcePath, this.sslContext); } catch (IOException | InterruptedException | ExecutionException e) { throw new RuntimeException(e); } @@ -385,7 +389,7 @@ public T executeScriptDecoded(String scriptResourcePath) { @Override public ExecResult executeCommand(String command) { try { - return clientHelper.executeCommand(command, this.sslContext); + return TarantoolContainerClientHelper.executeCommand(this, command, this.sslContext); } catch (IOException | InterruptedException e) { throw new RuntimeException(e); } @@ -394,7 +398,7 @@ public ExecResult executeCommand(String command) { @Override public T executeCommandDecoded(String command) { try { - return clientHelper.executeCommandDecoded(command, this.sslContext); + return TarantoolContainerClientHelper.executeCommandDecoded(this, command, this.sslContext); } catch (IOException | InterruptedException e) { throw new RuntimeException(e); } @@ -412,7 +416,6 @@ public boolean equals(Object o) { VshardClusterContainer that = (VshardClusterContainer) o; return useFixedPorts == that.useFixedPorts && routerPort == that.routerPort - && Objects.equals(clientHelper, that.clientHelper) && TARANTOOL_RUN_DIR.equals(that.TARANTOOL_RUN_DIR) && Objects.equals(configParser, that.configParser) && routerHost.equals(that.routerHost) @@ -429,7 +432,6 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( super.hashCode(), - clientHelper, TARANTOOL_RUN_DIR, configParser, useFixedPorts, diff --git a/testcontainers/src/main/java/org/testcontainers/containers/tarantool/Tarantool2Container.java b/testcontainers/src/main/java/org/testcontainers/containers/tarantool/Tarantool2Container.java index a3dbb0c..87b49b3 100644 --- a/testcontainers/src/main/java/org/testcontainers/containers/tarantool/Tarantool2Container.java +++ b/testcontainers/src/main/java/org/testcontainers/containers/tarantool/Tarantool2Container.java @@ -129,6 +129,13 @@ public synchronized void stopWithSafeMount() { super.stop(); } + @Override + public TarantoolContainer withFixedExposedPort( + int hostPort, int containerPort) { + this.addFixedExposedPort(hostPort, containerPort); + return this; + } + @Override protected void containerIsStarted(InspectContainerResponse containerInfo) { Utils.bindExposedPorts(this); diff --git a/testcontainers/src/main/java/org/testcontainers/containers/tarantool/Tarantool3Container.java b/testcontainers/src/main/java/org/testcontainers/containers/tarantool/Tarantool3Container.java index b57e0ef..96c8ada 100644 --- a/testcontainers/src/main/java/org/testcontainers/containers/tarantool/Tarantool3Container.java +++ b/testcontainers/src/main/java/org/testcontainers/containers/tarantool/Tarantool3Container.java @@ -140,6 +140,13 @@ public void stopWithSafeMount() { } } + @Override + public TarantoolContainer withFixedExposedPort( + int hostPort, int containerPort) { + this.addFixedExposedPort(hostPort, containerPort); + return this; + } + @Override protected void configure() { try { diff --git a/testcontainers/src/main/java/org/testcontainers/containers/tarantool/TarantoolContainer.java b/testcontainers/src/main/java/org/testcontainers/containers/tarantool/TarantoolContainer.java index 43af6e5..50960d9 100644 --- a/testcontainers/src/main/java/org/testcontainers/containers/tarantool/TarantoolContainer.java +++ b/testcontainers/src/main/java/org/testcontainers/containers/tarantool/TarantoolContainer.java @@ -47,4 +47,6 @@ default void restart(long delay, TimeUnit unit) throws InterruptedException { /** Stop container without deleting data directory */ void stopWithSafeMount(); + + TarantoolContainer withFixedExposedPort(int hostPort, int containerPort); } diff --git a/testcontainers/src/main/java/org/testcontainers/containers/utils/CartridgeConfigParser.java b/testcontainers/src/main/java/org/testcontainers/containers/utils/CartridgeConfigParser.java new file mode 100644 index 0000000..dc301a1 --- /dev/null +++ b/testcontainers/src/main/java/org/testcontainers/containers/utils/CartridgeConfigParser.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY + * All Rights Reserved. + */ + +package org.testcontainers.containers.utils; + +import java.io.InputStream; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import org.yaml.snakeyaml.Yaml; + +public class CartridgeConfigParser { + + private final AtomicReference>> instances = + new AtomicReference<>(); + + public CartridgeConfigParser(String instanceFileName) { + Yaml yaml = new Yaml(); + InputStream inputStream = + this.getClass().getClassLoader().getResourceAsStream(instanceFileName); + instances.set(Collections.unmodifiableMap(yaml.load(inputStream))); + } + + public Integer[] getExposablePorts() { + List ports = + instances.get().values().stream() + .map(Instance::new) + .map(Instance::getBinaryPort) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + ports.addAll( + instances.get().values().stream() + .map(Instance::new) + .map(Instance::getHttpPort) + .filter(Objects::nonNull) + .collect(Collectors.toList())); + return ports.toArray(new Integer[] {}); + } + + static class Instance { + private String workdir; + private String advertiseUri; + private Integer httpPort; + private Integer binaryPort; + + public Instance(Map map) { + this.workdir = (String) map.get("workdir"); + this.httpPort = (Integer) map.get("http_port"); + this.advertiseUri = (String) map.get("advertise_uri"); + this.binaryPort = + this.advertiseUri != null + ? Integer.parseInt(this.advertiseUri.substring(this.advertiseUri.indexOf(':') + 1)) + : null; + } + + public String getWorkdir() { + return workdir; + } + + public void setWorkdir(String workdir) { + this.workdir = workdir; + } + + public String getAdvertiseUri() { + return advertiseUri; + } + + public void setAdvertiseUri(String advertiseUri) { + this.advertiseUri = advertiseUri; + } + + public void setBinaryPort(int binaryPort) { + this.binaryPort = binaryPort; + } + + public Integer getBinaryPort() { + return binaryPort; + } + + public Integer getHttpPort() { + return httpPort; + } + + public void setHttpPort(int httpPort) { + this.httpPort = httpPort; + } + } +} diff --git a/testcontainers/src/main/java/org/testcontainers/containers/utils/PathUtils.java b/testcontainers/src/main/java/org/testcontainers/containers/utils/PathUtils.java new file mode 100644 index 0000000..4b96f71 --- /dev/null +++ b/testcontainers/src/main/java/org/testcontainers/containers/utils/PathUtils.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY + * All Rights Reserved. + */ + +package org.testcontainers.containers.utils; + +import java.nio.file.Path; + +/** + * Utils class for path normalization. + * + * @author Vladimir Rogach + */ +public class PathUtils { + + private PathUtils() {} + + /** + * Removes leading slash under windows from "/C:/work" so the path will match the format expected + * by docker. + * + * @param path to any file + * @return normalized path for docker + */ + public static String normalizePath(String path) { + String result; + if (path.startsWith("/") && path.length() > 3 && path.charAt(2) == ':') { + result = path.substring(1); + } else { + result = path; + } + return result.replace('\\', '/'); + } + + public static String normalizePath(Path path) { + return normalizePath(path.toString()); + } +} diff --git a/testcontainers/src/main/java/org/testcontainers/containers/utils/SslContext.java b/testcontainers/src/main/java/org/testcontainers/containers/utils/SslContext.java new file mode 100644 index 0000000..4199b64 --- /dev/null +++ b/testcontainers/src/main/java/org/testcontainers/containers/utils/SslContext.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY + * All Rights Reserved. + */ + +package org.testcontainers.containers.utils; + +public class SslContext { + private String keyFile; + private String certFile; + + private SslContext() {} + + private SslContext(String keyFile, String certFile) { + this.keyFile = keyFile; + this.certFile = certFile; + } + + public static SslContext getSslContext() { + return new SslContext(); + } + + public static SslContext getSslContext(String keyFile, String certFile) { + return new SslContext(keyFile, certFile); + } + + public String getKeyFile() { + return this.keyFile; + } + + public String getCertFile() { + return this.certFile; + } +} diff --git a/testcontainers/src/main/java/org/testcontainers/containers/utils/TarantoolContainerClientHelper.java b/testcontainers/src/main/java/org/testcontainers/containers/utils/TarantoolContainerClientHelper.java new file mode 100644 index 0000000..c804a37 --- /dev/null +++ b/testcontainers/src/main/java/org/testcontainers/containers/utils/TarantoolContainerClientHelper.java @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY + * All Rights Reserved. + */ + +package org.testcontainers.containers.utils; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import static org.testcontainers.containers.utils.PathUtils.normalizePath; +import lombok.SneakyThrows; +import org.testcontainers.containers.Container; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.TarantoolContainerOperations; +import org.testcontainers.containers.tarantool.Tarantool2Container; +import org.testcontainers.containers.tarantool.Tarantool3Container; +import org.testcontainers.containers.tarantool.Tarantool3WaitStrategy; +import org.testcontainers.containers.tarantool.TarantoolContainer; +import org.testcontainers.containers.tarantool.config.ConfigurationUtils; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; +import org.yaml.snakeyaml.Yaml; + +import io.tarantool.autogen.Tarantool3Configuration; +import io.tarantool.autogen.credentials.Credentials; +import io.tarantool.autogen.credentials.users.Users; +import io.tarantool.autogen.credentials.users.usersProperty.UsersProperty; +import io.tarantool.autogen.groups.Groups; +import io.tarantool.autogen.groups.groupsProperty.GroupsProperty; +import io.tarantool.autogen.groups.groupsProperty.replicasets.Replicasets; +import io.tarantool.autogen.groups.groupsProperty.replicasets.replicasetsProperty.ReplicasetsProperty; +import io.tarantool.autogen.groups.groupsProperty.replicasets.replicasetsProperty.instances.Instances; +import io.tarantool.autogen.groups.groupsProperty.replicasets.replicasetsProperty.instances.instancesProperty.InstancesProperty; +import io.tarantool.autogen.groups.groupsProperty.replicasets.replicasetsProperty.instances.instancesProperty.iproto.Iproto; +import io.tarantool.autogen.groups.groupsProperty.replicasets.replicasetsProperty.instances.instancesProperty.iproto.listen.Listen; + +/** + * Provides a wrapper around a Tarantool client with helper methods + * + * @author Alexey Kuzin + * @author Artyom Dubinin + * @author Ivan Dneprov + */ +public final class TarantoolContainerClientHelper { + + private static final String TMP_DIR = "/tmp"; + private static final Yaml yaml = new Yaml(); + public static final String TARANTOOL_VERSION = System.getenv("TARANTOOL_VERSION"); + public static final String IMAGE_PREFIX = + System.getenv().getOrDefault("TARANTOOL_REGISTRY", "") + "tarantool/tarantool"; + private static final Network NETWORK = Network.newNetwork(); + + private static final String EXECUTE_SCRIPT_ERROR_TEMPLATE = + "Executed script %s with exit code %d, stderr: \"%s\", stdout: \"%s\""; + private static final String EXECUTE_COMMAND_ERROR_TEMPLATE = + "Executed command \"%s\" with exit code %d, stderr: \"%s\", stdout: \"%s\""; + + private static final String ECHO_COMMAND = "echo \"%s\" | tarantoolctl connect %s:%s@%s:%d"; + private static final String MTLS_COMMAND_TEMPLATE = + "echo \" print(require('yaml').encode( {require('net.box').connect( {" + + " uri='%s:%d', params = { transport='ssl', ssl_key_file = '%s', ssl_cert_file = '%s'" + + " }}, { user = '%s', password = '%s' } ):eval('%s')}) " + + " ); os.exit(); \" > container-tmp.lua && tarantool container-tmp.lua"; + private static final String SSL_COMMAND_TEMPLATE = + "echo \" " + + " print(require('yaml').encode( " + + " {require('net.box').connect( " + + " { uri='%s:%d', params = { transport='ssl' }}, " + + " { user = '%s', password = '%s' } " + + " ):eval('%s')}) " + + " ); " + + " os.exit(); " + + "\" > container-tmp.lua &&" + + " tarantool container-tmp.lua"; + private static final String COMMAND_TEMPLATE = + "echo \" " + + " print(require('yaml').encode( " + + " {require('net.box').connect( " + + " '%s:%d', " + + " { user = '%s', password = '%s' } " + + " ):eval('%s')}) " + + " ); " + + " os.exit(); " + + "\" > container-tmp.lua &&" + + " tarantool container-tmp.lua"; + + private static final String API_USER = "api_user"; + + private static final Map CREDS = + new HashMap<>() { + { + put(API_USER, "secret"); + } + }; + + public static Container.ExecResult executeScript( + TarantoolContainer container, String scriptResourcePath, SslContext sslContext) + throws IOException, InterruptedException { + if (!container.isRunning()) { + throw new IllegalStateException("Cannot execute scripts in stopped container"); + } + + String scriptName = Paths.get(scriptResourcePath).getFileName().toString(); + String containerPath = normalizePath(Paths.get(TMP_DIR, scriptName)); + container.copyFileToContainer( + MountableFile.forClasspathResource(scriptResourcePath), containerPath); + return executeCommand(container, String.format("return dofile('%s')", containerPath)); + } + + public static T executeScriptDecoded( + TarantoolContainer container, String scriptResourcePath, SslContext sslContext) + throws IOException, InterruptedException, ExecutionException { + Container.ExecResult result = executeScript(container, scriptResourcePath, sslContext); + + if (result.getExitCode() != 0) { + + String message = + String.format( + EXECUTE_SCRIPT_ERROR_TEMPLATE, + scriptResourcePath, + result.getExitCode(), + result.getStderr(), + result.getStdout()); + + if (result.getExitCode() == 3 || result.getExitCode() == 1) { + throw new ExecutionException(message, new Throwable()); + } + + throw new IllegalStateException(message); + } + + return yaml.load(result.getStdout()); + } + + public static Container.ExecResult executeCommand(TarantoolContainer container, String command) + throws IOException, InterruptedException { + if (!container.isRunning()) { + throw new IllegalStateException("Cannot execute commands in stopped container"); + } + + command = command.replace("\"", "\\\""); + + String bashCommand = + String.format(ECHO_COMMAND, command, API_USER, CREDS.get(API_USER), "localhost", 3301); + + return container.execInContainer("/bin/sh", "-c", bashCommand); + } + + public static Container.ExecResult executeCommand( + TarantoolContainer container, String command, int port) + throws IOException, InterruptedException { + if (!container.isRunning()) { + throw new IllegalStateException("Cannot execute commands in stopped container"); + } + + command = command.replace("\"", "\\\""); + + String bashCommand = + String.format(ECHO_COMMAND, command, API_USER, CREDS.get(API_USER), "localhost", port); + + return container.execInContainer("/bin/sh", "-c", bashCommand); + } + + public static T executeCommandDecoded( + TarantoolContainer container, String command, int port) + throws IOException, InterruptedException { + Container.ExecResult result = executeCommand(container, command, port); + + if (result.getExitCode() != 0) { + throw new RuntimeException(result.getStderr() + "\n" + command); + } + + return yaml.load(result.getStdout()); + } + + public static T executeCommandDecoded(TarantoolContainer container, String command) + throws IOException, InterruptedException { + Container.ExecResult result = executeCommand(container, command); + + if (result.getExitCode() != 0) { + throw new RuntimeException(result.getStderr() + "\n" + command); + } + + return yaml.load(result.getStdout()); + } + + public static TarantoolContainer createTarantoolContainer() { + DockerImageName dockerImage = + DockerImageName.parse(String.format("%s:%s", IMAGE_PREFIX, TARANTOOL_VERSION)); + Path initScriptPath = null; + TarantoolContainer container; + try { + initScriptPath = + Paths.get( + Objects.requireNonNull( + TarantoolContainerClientHelper.class + .getClassLoader() + .getResource("server.lua")) + .toURI()); + } catch (Exception e) { + // ignore + } + + container = + switch (Character.getNumericValue(TARANTOOL_VERSION.charAt(0))) { + case 2 -> Tarantool2Container.builder(dockerImage, initScriptPath).build(); + case 3 -> + new Tarantool3Container(dockerImage, "test-node") + .withConfigPath(createConfig()) + .withNetwork(NETWORK) + .waitingFor( + new Tarantool3WaitStrategy("localhost", API_USER, CREDS.get(API_USER))); + default -> + throw new RuntimeException( + String.format("Unsupported Tarantool version, %s", TARANTOOL_VERSION)); + }; + + return container; + } + + @SneakyThrows + private static Path createConfig() { + final Path pathToConfigFile = + Files.createFile(Paths.get(TMP_DIR).resolve(String.format("%s.yaml", UUID.randomUUID()))); + + final Credentials credentials = + Credentials.builder() + .withUsers( + Users.builder() + .withAdditionalProperty( + API_USER, + UsersProperty.builder() + .withRoles(Collections.singletonList("super")) + .withPassword(CREDS.get(API_USER)) + .build()) + .build()) + .build(); + + final Iproto iproto = + Iproto.builder() + .withListen(Collections.singletonList(Listen.builder().withUri("0.0.0.0:3301").build())) + .build(); + + final InstancesProperty instance = InstancesProperty.builder().withIproto(iproto).build(); + + final ReplicasetsProperty replicaset = + ReplicasetsProperty.builder() + .withInstances( + Instances.builder().withAdditionalProperty("test-node", instance).build()) + .build(); + + final GroupsProperty group = + GroupsProperty.builder() + .withReplicasets( + Replicasets.builder().withAdditionalProperty("test-rs", replicaset).build()) + .build(); + + final Tarantool3Configuration configuration = + Tarantool3Configuration.builder() + .withGroups(Groups.builder().withAdditionalProperty("test-group", group).build()) + .withCredentials(credentials) + .build(); + + ConfigurationUtils.writeToFile(configuration, pathToConfigFile); + return pathToConfigFile; + } + + public static Container.ExecResult executeScript( + TarantoolContainerOperations container, String scriptResourcePath, SslContext sslContext) + throws IOException, InterruptedException { + if (!container.isRunning()) { + throw new IllegalStateException("Cannot execute scripts in stopped container"); + } + + String scriptName = Paths.get(scriptResourcePath).getFileName().toString(); + String containerPath = normalizePath(Paths.get(TMP_DIR, scriptName)); + container.copyFileToContainer( + MountableFile.forClasspathResource(scriptResourcePath), containerPath); + return executeCommand( + container, String.format("return dofile('%s')", containerPath), sslContext); + } + + public static T executeScriptDecoded( + TarantoolContainerOperations container, String scriptResourcePath, SslContext sslContext) + throws IOException, InterruptedException, ExecutionException { + Container.ExecResult result = executeScript(container, scriptResourcePath, sslContext); + + if (result.getExitCode() != 0) { + + if (result.getExitCode() == 3 || result.getExitCode() == 1) { + throw new ExecutionException( + String.format( + EXECUTE_SCRIPT_ERROR_TEMPLATE, + scriptResourcePath, + result.getExitCode(), + result.getStderr(), + result.getStdout()), + new Throwable()); + } + + throw new IllegalStateException( + String.format( + EXECUTE_SCRIPT_ERROR_TEMPLATE, + scriptResourcePath, + result.getExitCode(), + result.getStderr(), + result.getStdout())); + } + + return yaml.load(result.getStdout()); + } + + public static Container.ExecResult executeCommand( + TarantoolContainerOperations container, String command, SslContext sslContext) + throws IOException, InterruptedException { + if (!container.isRunning()) { + throw new IllegalStateException("Cannot execute commands in stopped container"); + } + + command = command.replace("\"", "\\\""); + command = command.replace("\'", "\\\'"); + + String bashCommand; + if (sslContext == null) { // No SSL + bashCommand = + String.format( + COMMAND_TEMPLATE, + container.getHost(), + container.getInternalPort(), + container.getUsername(), + container.getPassword(), + command); + } else if (sslContext.getKeyFile() != null && sslContext.getCertFile() != null) { // mTLS + bashCommand = + String.format( + MTLS_COMMAND_TEMPLATE, + container.getHost(), + container.getInternalPort(), + sslContext.getKeyFile(), + sslContext.getCertFile(), + container.getUsername(), + container.getPassword(), + command); + } else { // SSL + bashCommand = + String.format( + SSL_COMMAND_TEMPLATE, + container.getHost(), + container.getInternalPort(), + container.getUsername(), + container.getPassword(), + command); + } + + return container.execInContainer("sh", "-c", bashCommand); + } + + public static T executeCommandDecoded( + TarantoolContainerOperations container, String command, SslContext sslContext) + throws IOException, InterruptedException { + Container.ExecResult result = executeCommand(container, command, sslContext); + + if (result.getExitCode() != 0) { + throw new IllegalStateException( + String.format( + EXECUTE_COMMAND_ERROR_TEMPLATE, + command, + result.getExitCode(), + result.getStderr(), + result.getStdout())); + } + + return yaml.load(result.getStdout()); + } +} diff --git a/testcontainers/src/test/resources/logback-test.xml b/testcontainers/src/test/resources/logback-test.xml deleted file mode 100644 index 12e2712..0000000 --- a/testcontainers/src/test/resources/logback-test.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - -