diff --git a/documentation/doc-src/java/src/client/TarantoolDBClusterConnectionAbstractExample.java b/documentation/doc-src/java/src/client/TarantoolDBClusterConnectionAbstractExample.java new file mode 100644 index 0000000..505b8fa --- /dev/null +++ b/documentation/doc-src/java/src/client/TarantoolDBClusterConnectionAbstractExample.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY + * All Rights Reserved. + */ + +package client; + +// --8<-- [start:all] + +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.testcontainers.containers.tarantool.TarantoolContainer; +import org.testcontainers.containers.tarantool.config.ConfigurationUtils; +import org.testcontainers.containers.tdb.TDB2ClusterImpl; +import org.testcontainers.containers.tdb.TDBCluster; + +import io.tarantool.autogen.Tarantool3Configuration; +import io.tarantool.autogen.credentials.users.usersProperty.UsersProperty; + +public abstract class TarantoolDBClusterConnectionAbstractExample { + + private static final List ROLES = List.of("super"); + + protected static final String TARANTOOL_DB_IMAGE = + System.getenv().getOrDefault("TARANTOOL_REGISTRY", "") + "tarantooldb:2.2.1"; + + protected static final String SELLER_USER = "seller-user"; + protected static final String SELLER_USER_PWD = "pwd-1"; + + protected static final String USER_1182 = "user-1182"; + protected static final String USER_1182_PWD = "pwd-2"; + + protected static final String FIRST_ROUTER_CONTAINER_NAME = "router-1"; + protected static final String SECOND_ROUTER_CONTAINER_NAME = "router-2"; + + protected static TDBCluster CLUSTER; + + protected abstract void simpleCrudConnection(); + + @BeforeAll + static void beforeAll() { + CLUSTER = createTDBCluster(); + CLUSTER.start(); + } + + @AfterAll + static void afterAll() { + CLUSTER.close(); + } + + @SuppressWarnings("unchecked") + private static TDBCluster createTDBCluster() { + + // Adds custom users to emulate documentation example + Map users = + Map.of( + SELLER_USER, + UsersProperty.builder().withPassword(SELLER_USER_PWD).withRoles(ROLES).build(), + USER_1182, + UsersProperty.builder().withPassword(USER_1182_PWD).withRoles(ROLES).build()); + + final Tarantool3Configuration commonConfigWithoutCustomUsers = + ConfigurationUtils.generateSimpleConfiguration(2, 3, 2); + final Tarantool3Configuration config = + ConfigurationUtils.addUsers(commonConfigWithoutCustomUsers, users); + + return TDB2ClusterImpl.builder(TARANTOOL_DB_IMAGE).withTDB2Configuration(config).build(); + } + + protected static InetSocketAddress getRouterAddress(String routerName) { + final Map> routers = CLUSTER.routers(); + return routers.get(routerName).mappedAddress(); + } +} + +// --8<-- [end:all] diff --git a/documentation/doc-src/java/src/client/TarantoolDBClusterConnectionCartridgeDriverExample.java b/documentation/doc-src/java/src/client/TarantoolDBClusterConnectionCartridgeDriverExample.java new file mode 100644 index 0000000..4991678 --- /dev/null +++ b/documentation/doc-src/java/src/client/TarantoolDBClusterConnectionCartridgeDriverExample.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY + * All Rights Reserved. + */ + +package client; + +// --8<-- [start:all] + +import java.net.InetSocketAddress; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.tarantool.driver.api.TarantoolClient; +import io.tarantool.driver.api.TarantoolClientFactory; +import io.tarantool.driver.api.TarantoolResult; +import io.tarantool.driver.api.TarantoolServerAddress; +import io.tarantool.driver.api.tuple.TarantoolTuple; + +public class TarantoolDBClusterConnectionCartridgeDriverExample + extends TarantoolDBClusterConnectionAbstractExample { + + @Test + @Override + protected void simpleCrudConnection() { + try (TarantoolClient> crudClient = + setupClient()) { + final String helloWorld = "hello world"; + + // Evals return instruction in Tarantool lua + final List helloResponse = + crudClient.eval(String.format("return '%s'", helloWorld)).join(); + Assertions.assertEquals(1, helloResponse.size()); + Assertions.assertEquals(helloWorld, helloResponse.get(0)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static TarantoolClient> setupClient() { + + // Returns routers addresses mapped from docker + final InetSocketAddress firstRouterAddress = getRouterAddress(FIRST_ROUTER_CONTAINER_NAME); + final InetSocketAddress secondRouterAddress = getRouterAddress(SECOND_ROUTER_CONTAINER_NAME); + + // Create crud client instance and connect to routers + return TarantoolClientFactory.createClient() + .withAddresses( + new TarantoolServerAddress( + firstRouterAddress.getHostName(), firstRouterAddress.getPort()), + new TarantoolServerAddress( + secondRouterAddress.getHostName(), secondRouterAddress.getPort())) + + // Two connection groups with different users in one client instance + .withCredentials(USER_1182, USER_1182_PWD) + .withConnections(2) + .withProxyMethodMapping() + .build(); + } +} + +// --8<-- [end:all] diff --git a/documentation/doc-src/java/src/client/TarantoolDBClusterConnectionTJSDKExample.java b/documentation/doc-src/java/src/client/TarantoolDBClusterConnectionTJSDKExample.java new file mode 100644 index 0000000..c9f28b5 --- /dev/null +++ b/documentation/doc-src/java/src/client/TarantoolDBClusterConnectionTJSDKExample.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY + * All Rights Reserved. + */ + +package client; + +// --8<-- [start:all] + +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.tarantool.client.crud.TarantoolCrudClient; +import io.tarantool.client.factory.TarantoolCrudClientBuilder; +import io.tarantool.client.factory.TarantoolFactory; +import io.tarantool.pool.InstanceConnectionGroup; + +public class TarantoolDBClusterConnectionTJSDKExample + extends TarantoolDBClusterConnectionAbstractExample { + + @Test + @Override + protected void simpleCrudConnection() { + + try (final TarantoolCrudClient crudClient = setupClient()) { + final String helloWorld = "hello world"; + + // Evals return instruction in Tarantool lua + final List helloResponse = + crudClient.eval(String.format("return '%s'", helloWorld), String.class).join().get(); + Assertions.assertEquals(1, helloResponse.size()); + Assertions.assertEquals(helloWorld, helloResponse.get(0)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static TarantoolCrudClient setupClient() throws Exception { + + // Returns routers addresses mapped from docker + final InetSocketAddress firstRouterAddress = getRouterAddress(FIRST_ROUTER_CONTAINER_NAME); + final InetSocketAddress secondRouterAddress = getRouterAddress(SECOND_ROUTER_CONTAINER_NAME); + + final TarantoolCrudClientBuilder crudClientBuilder = TarantoolFactory.crud(); + + // Setup first connection group with "seller-user" user and 2 connection to first router + final InstanceConnectionGroup firstRouterConnectionGroup = + InstanceConnectionGroup.builder() + .withHost(firstRouterAddress.getHostName()) + .withPort(firstRouterAddress.getPort()) + .withUser(SELLER_USER) + .withPassword(SELLER_USER_PWD) + .withSize(2) + .withTag(SELLER_USER + "-connection") + .build(); + + // Setup second connection group with "user-1182" user and 3 connection to first router + final InstanceConnectionGroup secondRouterConnectionGroup = + InstanceConnectionGroup.builder() + .withHost(secondRouterAddress.getHostName()) + .withPort(secondRouterAddress.getPort()) + .withUser(USER_1182) + .withPassword(USER_1182_PWD) + .withSize(3) + .withTag(USER_1182 + "-connection") + .build(); + + final List connectionGroupsList = + Arrays.asList(firstRouterConnectionGroup, secondRouterConnectionGroup); + + // Create crud client instance and connect to routers + // Two connection groups with different users in one client instance + return crudClientBuilder.withGroups(connectionGroupsList).build(); + } +} + +// --8<-- [end:all] diff --git a/documentation/doc-src/pages/client/examples/connection/crud-cluster.en.md b/documentation/doc-src/pages/client/examples/connection/crud-cluster.en.md new file mode 100644 index 0000000..52a32a8 --- /dev/null +++ b/documentation/doc-src/pages/client/examples/connection/crud-cluster.en.md @@ -0,0 +1,26 @@ +--- +title: Connecting to Tarantool cluster via crud +--- + +The following example demonstrates connecting to a `Tarantool` cluster via routers with +using the `crud` module: + +=== "tarantool-java-sdk" + + ```title="Подключенние к кластеру при помощи TJSDK" + --8<-- "client/TarantoolDBClusterConnectionTJSDKExample.java:all" + ``` + + ```title="Абстрактный класс для создания кластера в docker" + --8<-- "client/TarantoolDBClusterConnectionAbstractExample.java:all" + ``` + +=== "cartridge-driver" + + ```title="Подключенние к кластеру при помощи Cartridge java" + --8<-- "client/TarantoolDBClusterConnectionCartridgeDriverExample.java:all" + ``` + + ```title="Абстрактный класс для создания кластера в docker" + --8<-- "client/TarantoolDBClusterConnectionAbstractExample.java:all" + ``` diff --git a/documentation/doc-src/pages/client/examples/connection/crud-cluster.md b/documentation/doc-src/pages/client/examples/connection/crud-cluster.md new file mode 100644 index 0000000..c098691 --- /dev/null +++ b/documentation/doc-src/pages/client/examples/connection/crud-cluster.md @@ -0,0 +1,26 @@ +--- +title: Подключение к класетру Tarantool через crud +--- + +Следующий пример демонстрирует подключение к кластеру `Tarantool` через маршрутизаторы с +использованием модуля `crud`: + +=== "tarantool-java-sdk" + + ```title="Подключенние к кластеру при помощи TJSDK" + --8<-- "client/TarantoolDBClusterConnectionTJSDKExample.java:all" + ``` + + ```title="Абстрактный класс для создания кластера в docker" + --8<-- "client/TarantoolDBClusterConnectionAbstractExample.java:all" + ``` + +=== "cartridge-driver" + + ```title="Подключенние к кластеру при помощи Cartridge java" + --8<-- "client/TarantoolDBClusterConnectionCartridgeDriverExample.java:all" + ``` + + ```title="Абстрактный класс для создания кластера в docker" + --8<-- "client/TarantoolDBClusterConnectionAbstractExample.java:all" + ``` diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index 38fdf2f..7516750 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -128,6 +128,7 @@ nav: - Подключение к узлам: - pages/client/examples/connection/index.md - pages/client/examples/connection/single-node.md + - pages/client/examples/connection/crud-cluster.md - Tarantool Testcontainers: - pages/testcontainers/index.md - Одиночный узел: diff --git a/testcontainers/src/main/java/org/testcontainers/containers/tarantool/config/ConfigurationUtils.java b/testcontainers/src/main/java/org/testcontainers/containers/tarantool/config/ConfigurationUtils.java index f3cc8fb..72c0d71 100644 --- a/testcontainers/src/main/java/org/testcontainers/containers/tarantool/config/ConfigurationUtils.java +++ b/testcontainers/src/main/java/org/testcontainers/containers/tarantool/config/ConfigurationUtils.java @@ -392,4 +392,27 @@ public static void bootstrap( throw new ContainerLaunchException("Timed out waiting for cluster to bootstrap", exc); } } + + /** + * Adds additional users into global section of passed configuration. + * + * @return copy instance of passed configuration + */ + public static Tarantool3Configuration addUsers( + Tarantool3Configuration old, Map users) { + if (old == null) { + return null; + } + final Tarantool3Configuration copy = + ConfigurationUtils.create(ConfigurationUtils.writeAsString(old)); + + final Credentials credentials = copy.getCredentials().orElseGet(Credentials::new); + final Users oldUsers = credentials.getUsers().orElseGet(Users::new); + + users.forEach(oldUsers::setAdditionalProperty); + + credentials.setUsers(oldUsers); + copy.setCredentials(credentials); + return copy; + } } diff --git a/testcontainers/src/test/java/org/testcontainers/containers/tarantool/config/ConfigurationUtilsTest.java b/testcontainers/src/test/java/org/testcontainers/containers/tarantool/config/ConfigurationUtilsTest.java new file mode 100644 index 0000000..4396b28 --- /dev/null +++ b/testcontainers/src/test/java/org/testcontainers/containers/tarantool/config/ConfigurationUtilsTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY + * All Rights Reserved. + */ + +package org.testcontainers.containers.tarantool.config; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +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; + +class ConfigurationUtilsTest { + + public static Stream dataForTestAddUsersMethod() { + + /* + /********************************************************** + /* Additional user properties + /********************************************************** + */ + final String userPwd = "user-pwd"; + final String userName = "user-name"; + final UsersProperty usersProperty = UsersProperty.builder().withPassword(userPwd).build(); + Map users = + new HashMap<>() { + { + put(userName, usersProperty); + } + }; + + /* + /********************************************************** + /* Without credentials section + /********************************************************** + */ + final Tarantool3Configuration oldConfigWithoutCredSection = new Tarantool3Configuration(); + final Tarantool3Configuration expectedConfigWithoutCredSection = + Tarantool3Configuration.builder() + .withCredentials( + Credentials.builder() + .withUsers( + Users.builder().withAdditionalProperty(userName, usersProperty).build()) + .build()) + .build(); + + /* + /********************************************************** + /* With credentials section without users + /********************************************************** + */ + final Tarantool3Configuration oldConfigWithCredWithoutUsersSection = + Tarantool3Configuration.builder().withCredentials(new Credentials()).build(); + final Tarantool3Configuration expectedConfigWithCredWithoutUsersSection = + Tarantool3Configuration.builder() + .withCredentials( + Credentials.builder() + .withUsers( + Users.builder().withAdditionalProperty(userName, usersProperty).build()) + .build()) + .build(); + + /* + /********************************************************** + /* With credentials and users sections + /********************************************************** + */ + final Tarantool3Configuration oldConfigWithCredWithUsersSection = + Tarantool3Configuration.builder() + .withCredentials(Credentials.builder().withUsers(new Users()).build()) + .build(); + final Tarantool3Configuration expectedConfigWithCredWithUsersSection = + Tarantool3Configuration.builder() + .withCredentials( + Credentials.builder() + .withUsers( + Users.builder().withAdditionalProperty(userName, usersProperty).build()) + .build()) + .build(); + + /* + /********************************************************** + /* Override users with same name + /********************************************************** + */ + final Tarantool3Configuration oldConfigWithUser = + Tarantool3Configuration.builder() + .withCredentials( + Credentials.builder() + .withUsers( + Users.builder() + .withAdditionalProperty(userName, new UsersProperty()) + .build()) + .build()) + .build(); + final Tarantool3Configuration expectedConfigWithUser = + Tarantool3Configuration.builder() + .withCredentials( + Credentials.builder() + .withUsers( + Users.builder().withAdditionalProperty(userName, usersProperty).build()) + .build()) + .build(); + Assertions.assertNotEquals(oldConfigWithUser, expectedConfigWithUser); + + return Stream.of( + // old config null + Arguments.of(null, Collections.emptyMap(), null), + + // without credentials + Arguments.of(oldConfigWithoutCredSection, users, expectedConfigWithoutCredSection), + + // with credentials and without users + Arguments.of( + oldConfigWithCredWithoutUsersSection, users, expectedConfigWithCredWithoutUsersSection), + + // with credentials and with users + Arguments.of( + oldConfigWithCredWithUsersSection, users, expectedConfigWithCredWithUsersSection), + + // override user with same name + Arguments.of(oldConfigWithUser, users, expectedConfigWithUser)); + } + + @ParameterizedTest + @MethodSource("dataForTestAddUsersMethod") + void testAddUsersMethod( + Tarantool3Configuration oldConfig, + Map additionalUsers, + Tarantool3Configuration expected) { + Assertions.assertEquals(expected, ConfigurationUtils.addUsers(oldConfig, additionalUsers)); + } +}