diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java index 046ab46ac..0ce4ff898 100644 --- a/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java +++ b/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java @@ -86,6 +86,25 @@ protected static String onCluster() { return ClickHouseServerForTest.isCluster() ? " ON CLUSTER 'test_cluster'" : ""; } + /** + * Waits until each replica of the test cluster returns {@code expectedRows} rows of + * {@code system.} that match {@code condition}, so that the next request sees a + * created or changed user, role, settings profile or grant on any replica. + * Does nothing when the tests do not run on a cluster. + */ + protected static void waitForAllReplicas(String table, String condition, int expectedRows) { + ClickHouseServerForTest.waitForAllReplicas(table, condition, expectedRows); + } + + /** + * Same check as {@link #waitForAllReplicas(String, String, int)}. {@code query} runs the + * replica SQL with the caller's client and returns one matching-row count per replica host. + */ + protected static void waitForAllReplicas(String table, String condition, int expectedRows, + ClickHouseServerForTest.ReplicaQuery query) { + ClickHouseServerForTest.waitForAllReplicas(table, condition, expectedRows, query); + } + protected boolean createDatabase(String dbName) { return ClickHouseServerForTest.runQuery("CREATE DATABASE IF NOT EXISTS `" + dbName + "`" + onCluster()); } diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseServerForTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseServerForTest.java index bd8bd0871..7567c12f2 100644 --- a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseServerForTest.java +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseServerForTest.java @@ -4,19 +4,24 @@ import org.slf4j.LoggerFactory; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; +import org.testng.Assert; import org.testng.annotations.AfterSuite; import org.testng.annotations.BeforeSuite; import com.clickhouse.client.ClickHouseTestEnvironment.ContainerMode; +import com.clickhouse.data.ClickHouseRecord; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.util.Locale; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; import java.util.Collections; +import java.util.List; +import java.util.Locale; import java.util.Map; /** @@ -33,6 +38,8 @@ @SuppressWarnings("squid:S2187") public class ClickHouseServerForTest { private static final Logger LOGGER = LoggerFactory.getLogger(ClickHouseServerForTest.class); + private static final int REPLICA_CHECK_ATTEMPTS = 100; + private static final long REPLICA_CHECK_INTERVAL_MS = 100L; private static final Network network; private static final ClickHouseTestEnvironment environment; @@ -279,6 +286,89 @@ public static boolean runQuery(String sql) { return false; } + /** + * Waits until each replica of the test cluster returns {@code expectedRows} rows of + * {@code system.
} that match {@code condition}. Does nothing outside cluster mode. + * Uses {@link ClickHouseClient}, which resolves to the HTTP client when that module is on the classpath. + */ + public static void waitForAllReplicas(String table, String condition, int expectedRows) { + if (!isCluster()) { + return; + } + ClickHouseNode server = getClickHouseNode(ClickHouseProtocol.HTTP, isCloud(), ClickHouseNode.builder().build()); + try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.HTTP)) { + waitForAllReplicas(table, condition, expectedRows, sql -> matchingRowCounts(client, server, sql)); + } + } + + /** + * Same check as {@link #waitForAllReplicas(String, String, int)}. {@code query} runs the + * replica SQL and returns one matching-row count per replica host. + */ + public static void waitForAllReplicas(String table, String condition, int expectedRows, ReplicaQuery query) { + if (!isCluster()) { + return; + } + String sql = "SELECT hostName() AS host, toInt64(countIf(" + condition + ")) AS matching_rows" + + " FROM clusterAllReplicas('" + ClusterClickHouseContainerStarter.CLUSTER_NAME + "', system." + table + + ") GROUP BY host"; + String state = ""; + for (int attempt = 1; attempt <= REPLICA_CHECK_ATTEMPTS; attempt++) { + try { + long[] counts = query.matchingRowCounts(sql); + state = Arrays.toString(counts); + if (hasRowsOnAllReplicas(counts, expectedRows)) { + return; + } + } catch (Exception e) { + state = e.getMessage(); + } + try { + Thread.sleep(REPLICA_CHECK_INTERVAL_MS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + Assert.fail("Expected " + expectedRows + " rows matching [" + condition + "] in system." + table + " on all " + + ClusterClickHouseContainerStarter.NODE_COUNT + " replicas, but got " + state); + } + + private static long[] matchingRowCounts(ClickHouseClient client, ClickHouseNode server, String sql) + throws ClickHouseException { + try (ClickHouseResponse response = client.read(server).query(sql).executeAndWait()) { + List counts = new ArrayList<>(); + for (ClickHouseRecord record : response.records()) { + counts.add(record.getValue(1).asLong()); + } + long[] result = new long[counts.size()]; + for (int i = 0; i < result.length; i++) { + result[i] = counts.get(i); + } + return result; + } + } + + private static boolean hasRowsOnAllReplicas(long[] counts, int expectedRows) { + if (counts.length != ClusterClickHouseContainerStarter.NODE_COUNT) { + return false; + } + for (long count : counts) { + if (count != expectedRows) { + return false; + } + } + return true; + } + + /** + * Runs the replica-check SQL from {@link #waitForAllReplicas(String, String, int, ReplicaQuery)}. + */ + @FunctionalInterface + public interface ReplicaQuery { + long[] matchingRowCounts(String sql) throws Exception; + } + private static Endpoint resolve(ClickHouseProtocol protocol, int port, boolean applyOverride) { if (starter != null) { int containerPort = (port == ClickHouseProtocol.HTTP.getDefaultPort()) diff --git a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java index 2d8f8ad85..97f3603c6 100644 --- a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java @@ -434,28 +434,46 @@ private static Object[][] sessionRoles() { } @Test(groups = {"integration"}, dataProvider = "sessionRoles") - public void testOperationCustomRoles(String[] roles) throws Exception { + public void testOperationCustomRoles(String[] roleNames) throws Exception { if (isVersionMatch("(,24.3]", newClient().build())) { return; } + String suffix = "_" + RandomStringUtils.insecure().next(8, true, true); + String user = "some_user" + suffix; + String[] roles = Arrays.stream(roleNames).map(role -> role + suffix).toArray(String[]::new); String password = "^1A" + RandomStringUtils.random(12, true, true) + "3b$"; final String rolesList = "\"" + Strings.join("\",\"", roles) + "\""; - try (Client client = newClient().build()) { - client.execute("DROP ROLE IF EXISTS " + rolesList).get().close(); - client.execute("CREATE ROLE " + rolesList).get().close(); - client.execute("DROP USER IF EXISTS some_user").get().close(); - client.execute("CREATE USER some_user IDENTIFIED BY '" + password + "'").get().close(); - client.execute("GRANT " + rolesList + " TO some_user").get().close(); + try (Client client = newClient().build(); + Client userClient = newClient().setUsername(user).setPassword(password).build()) { + try { + createUserWithRoles(client, user, "IDENTIFIED BY '" + password + "'", roles); + QuerySettings settings = new QuerySettings().setDBRoles(Arrays.asList(roles)); + List resp = userClient.queryAll("SELECT currentRoles()", settings); + Set roleSet = new HashSet<>(Arrays.asList(roles)); + Set currentRoles = new HashSet (resp.get(0).getList(1)); + Assert.assertEquals(currentRoles, roleSet, "Roles " + roleSet + " not found in " + currentRoles); + } finally { + client.execute("DROP USER IF EXISTS " + user).get().close(); + client.execute("DROP ROLE IF EXISTS " + rolesList).get().close(); + } } + } - try (Client userClient = newClient().setUsername("some_user").setPassword(password).build()) { - QuerySettings settings = new QuerySettings().setDBRoles(Arrays.asList(roles)); - List resp = userClient.queryAll("SELECT currentRoles()", settings); - Set roleSet = new HashSet<>(Arrays.asList(roles)); - Set currentRoles = new HashSet (resp.get(0).getList(1)); - Assert.assertEquals(currentRoles, roleSet, "Roles " + roleSet + " not found in " + currentRoles); - } + private static void createUserWithRoles(Client client, String user, String identification, String[] roles) + throws Exception { + final String rolesList = "\"" + Strings.join("\",\"", roles) + "\""; + final String roleNames = "'" + Strings.join("','", roles) + "'"; + client.execute("CREATE ROLE " + rolesList).get().close(); + client.execute("CREATE USER " + user + " " + identification).get().close(); + waitForAllReplicas("roles", "name IN (" + roleNames + ")", roles.length, + sql -> client.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray()); + waitForAllReplicas("users", "name = '" + user + "'", 1, + sql -> client.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray()); + client.execute("GRANT " + rolesList + " TO " + user).get().close(); + waitForAllReplicas("role_grants", + "user_name = '" + user + "' AND granted_role_name IN (" + roleNames + ")", roles.length, + sql -> client.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray()); } @DataProvider(name = "clientSessionRoles") @@ -466,27 +484,29 @@ private static Object[][] clientSessionRoles() { }; } @Test(groups = {"integration"}, dataProvider = "clientSessionRoles") - public void testClientCustomRoles(String[] roles) throws Exception { + public void testClientCustomRoles(String[] roleNames) throws Exception { if (isVersionMatch("(,24.3]", newClient().build())) { return; } + String suffix = "_" + RandomStringUtils.insecure().next(8, true, true); + String user = "some_user" + suffix; + String[] roles = Arrays.stream(roleNames).map(role -> role + suffix).toArray(String[]::new); String password = "^1A" + RandomStringUtils.insecure().next(12, true, true) + "3B$"; final String rolesList = "\"" + Strings.join("\",\"", roles) + "\""; - try (Client client = newClient().build()) { - client.execute("DROP ROLE IF EXISTS " + rolesList).get().close(); - client.execute("CREATE ROLE " + rolesList).get().close(); - client.execute("DROP USER IF EXISTS some_user").get().close(); - client.execute("CREATE USER some_user IDENTIFIED WITH sha256_password BY '" + password + "'").get().close(); - client.execute("GRANT " + rolesList + " TO some_user").get().close(); - } - - try (Client userClient = newClient().setUsername("some_user").setPassword(password).build()) { - userClient.setDBRoles(Arrays.asList(roles)); - List resp = userClient.queryAll("SELECT currentRoles()"); - Set roleSet = new HashSet<>(Arrays.asList(roles)); - Set currentRoles = new HashSet (resp.get(0).getList(1)); - Assert.assertEquals(currentRoles, roleSet, "Roles " + roleSet + " not found in " + currentRoles); + try (Client client = newClient().build(); + Client userClient = newClient().setUsername(user).setPassword(password).build()) { + try { + createUserWithRoles(client, user, "IDENTIFIED WITH sha256_password BY '" + password + "'", roles); + userClient.setDBRoles(Arrays.asList(roles)); + List resp = userClient.queryAll("SELECT currentRoles()"); + Set roleSet = new HashSet<>(Arrays.asList(roles)); + Set currentRoles = new HashSet (resp.get(0).getList(1)); + Assert.assertEquals(currentRoles, roleSet, "Roles " + roleSet + " not found in " + currentRoles); + } finally { + client.execute("DROP USER IF EXISTS " + user).get().close(); + client.execute("DROP ROLE IF EXISTS " + rolesList).get().close(); + } } } @@ -507,36 +527,16 @@ public void testRuntimeCredentialChange() throws Exception { adminClient.execute("DROP USER IF EXISTS " + user2).get().close(); adminClient.execute("CREATE USER " + user1 + " IDENTIFIED BY '" + password1 + "'").get().close(); adminClient.execute("CREATE USER " + user2 + " IDENTIFIED BY '" + password2 + "'").get().close(); + waitForAllReplicas("users", "name IN ('" + user1 + "', '" + user2 + "')", 2, + sql -> adminClient.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray()); try (Client userClient = newClient().setUsername(user1).setPassword(password1).build()) { - List firstResponse = null; - for (int i = 0; i < 20; i++) { - try { - firstResponse = userClient.queryAll("SELECT currentUser() AS user"); - break; - } catch (ClientException e) { - if (i == 19) { - throw e; - } - Thread.sleep(250); - } - } + List firstResponse = userClient.queryAll("SELECT currentUser() AS user"); Assert.assertEquals(firstResponse.get(0).getString("user"), user1); userClient.updateUserAndPassword(user2, password2); - List secondResponse = null; - for (int i = 0; i < 20; i++) { - try { - secondResponse = userClient.queryAll("SELECT currentUser() AS user"); - break; - } catch (ClientException e) { - if (i == 19) { - throw e; - } - Thread.sleep(250); - } - } + List secondResponse = userClient.queryAll("SELECT currentUser() AS user"); Assert.assertEquals(secondResponse.get(0).getString("user"), user2); } } finally { diff --git a/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java b/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java index 533a43268..ffb5730fb 100644 --- a/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java @@ -1044,29 +1044,32 @@ public void testSSLAuthentication() throws Exception { return; // Current test is working only with local server because of self-signed certificates. } ClickHouseNode server = getSecureServer(ClickHouseProtocol.HTTP); - try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), true) + String user = "some_user_" + UUID.randomUUID().toString().replace("-", ""); + try (Client adminClient = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), true) .setUsername("dba") .setPassword("dba") .setRootCertificate("containers/clickhouse-server/certs/localhost.crt") .build()) { - try (CommandResponse resp = client.execute("DROP USER IF EXISTS some_user").get()) { - } - try (CommandResponse resp = client.execute("CREATE USER some_user IDENTIFIED WITH ssl_certificate CN 'some_user'").get()) { - } - } + try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), true) + .useSSLAuthentication(true) + .setUsername(user) + .setRootCertificate("containers/clickhouse-server/certs/localhost.crt") + .setClientCertificate("some_user.crt") + .setClientKey("some_user.key") + .compressServerResponse(false) + .build()) { - try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), true) - .useSSLAuthentication(true) - .setUsername("some_user") - .setRootCertificate("containers/clickhouse-server/certs/localhost.crt") - .setClientCertificate("some_user.crt") - .setClientKey("some_user.key") - .compressServerResponse(false) - .build()) { + try (CommandResponse resp = adminClient.execute("CREATE USER " + user + " IDENTIFIED WITH ssl_certificate CN 'some_user'").get()) { + } + waitForAllReplicas("users", "name = '" + user + "'", 1, + sql -> adminClient.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray()); - try (QueryResponse resp = client.query("SELECT 1").get()) { - Assert.assertEquals(resp.getReadRows(), 1); + try (QueryResponse resp = client.query("SELECT 1").get()) { + Assert.assertEquals(resp.getReadRows(), 1); + } + } finally { + adminClient.execute("DROP USER IF EXISTS " + user).get().close(); } } } @@ -1077,43 +1080,47 @@ public void testPasswordAuthentication(String identifyWith, String identifyBy, b return; // Current test is working only with local server because of self-signed certificates. } ClickHouseNode server = getServer(ClickHouseProtocol.HTTP); + String user = "some_user_" + UUID.randomUUID().toString().replace("-", ""); - try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) + try (Client adminClient = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) .setUsername("dba") .setPassword("dba") .build()) { - try (CommandResponse resp = client.execute("DROP USER IF EXISTS some_user").get()) { - } - try (CommandResponse resp = client.execute("CREATE USER some_user IDENTIFIED WITH " + identifyWith + " BY '" + identifyBy + "'").get()) { - } - } catch (Exception e) { - Assert.fail("Failed on setup", e); - } - + try { + try (CommandResponse resp = adminClient.execute("CREATE USER " + user + " IDENTIFIED WITH " + identifyWith + " BY '" + identifyBy + "'").get()) { + } catch (Exception e) { + Assert.fail("Failed on setup", e); + } + waitForAllReplicas("users", "name = '" + user + "'", 1, + sql -> adminClient.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray()); - try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) - .setUsername("some_user") - .setPassword(identifyBy) - .build()) { + try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) + .setUsername(user) + .setPassword(identifyBy) + .build()) { - Assert.assertEquals(client.queryAll("SELECT user()").get(0).getString(1), "some_user"); - } catch (Exception e) { - Assert.fail("Failed to authenticate", e); - } + Assert.assertEquals(client.queryAll("SELECT user()").get(0).getString(1), user); + } catch (Exception e) { + Assert.fail("Failed to authenticate", e); + } - if (failsWithHeaders) { - try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) - .setUsername("some_user") - .setPassword(identifyBy) - .useHTTPBasicAuth(false) - .build()) { + if (failsWithHeaders) { + try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) + .setUsername(user) + .setPassword(identifyBy) + .useHTTPBasicAuth(false) + .build()) { - Assert.expectThrows(ClientException.class, () -> - client.queryAll("SELECT user()").get(0).getString(1)); + Assert.expectThrows(ClientException.class, () -> + client.queryAll("SELECT user()").get(0).getString(1)); - } catch (Exception e) { - Assert.fail("Unexpected exception", e); + } catch (Exception e) { + Assert.fail("Unexpected exception", e); + } + } + } finally { + adminClient.execute("DROP USER IF EXISTS " + user).get().close(); } } } @@ -1141,30 +1148,34 @@ public void testAuthHeaderIsKeptFromUser() throws Exception { String identifyWith = "sha256_password"; String identifyBy = "123ยง"; - try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) + String user = "some_user_" + UUID.randomUUID().toString().replace("-", ""); + try (Client adminClient = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) .setUsername("dba") .setPassword("dba") .build()) { - try (CommandResponse resp = client.execute("DROP USER IF EXISTS some_user").get()) { - } - try (CommandResponse resp = client.execute("CREATE USER some_user IDENTIFIED WITH " + identifyWith + " BY '" + identifyBy + "'").get()) { - } - } catch (Exception e) { - Assert.fail("Failed on setup", e); - } - + try { + try (CommandResponse resp = adminClient.execute("CREATE USER " + user + " IDENTIFIED WITH " + identifyWith + " BY '" + identifyBy + "'").get()) { + } catch (Exception e) { + Assert.fail("Failed on setup", e); + } + waitForAllReplicas("users", "name = '" + user + "'", 1, + sql -> adminClient.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray()); - try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) - .setUsername("some_user") - .setPassword(identifyBy) - .useHTTPBasicAuth(false) // disable basic auth to produce CH headers - .httpHeader(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString(("some_user:" +identifyBy).getBytes())) - .build()) { + try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) + .setUsername(user) + .setPassword(identifyBy) + .useHTTPBasicAuth(false) // disable basic auth to produce CH headers + .httpHeader(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString((user + ":" +identifyBy).getBytes())) + .build()) { - Assert.assertEquals(client.queryAll("SELECT user()").get(0).getString(1), "some_user"); - } catch (Exception e) { - Assert.fail("Failed to authenticate", e); + Assert.assertEquals(client.queryAll("SELECT user()").get(0).getString(1), user); + } catch (Exception e) { + Assert.fail("Failed to authenticate", e); + } + } finally { + adminClient.execute("DROP USER IF EXISTS " + user).get().close(); + } } } diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcIntegrationTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcIntegrationTest.java index 0361f28d2..7b66a95fb 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcIntegrationTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcIntegrationTest.java @@ -1,9 +1,12 @@ package com.clickhouse.jdbc; import com.clickhouse.client.BaseIntegrationTest; +import com.clickhouse.client.ClickHouseNode; import com.clickhouse.client.ClickHouseProtocol; import com.clickhouse.client.ClickHouseServerForTest; +import com.clickhouse.client.api.Client; import com.clickhouse.client.api.ClientConfigProperties; +import com.clickhouse.client.api.enums.Protocol; import com.clickhouse.client.api.internal.ServerSettings; import com.clickhouse.data.ClickHouseVersion; import com.clickhouse.logging.Logger; @@ -109,4 +112,25 @@ protected String getServerVersion() { protected boolean isVersionMatch(String versionExpression) { return ClickHouseVersion.of(getServerVersion()).check(versionExpression); } + + /** + * Uses a client-v2 {@link Client} for the replica check. The v1 {@link com.clickhouse.client.ClickHouseClient} + * implementation is not on this module's classpath. + */ + protected static void waitForAllReplicas(String table, String condition, int expectedRows) { + if (!isCluster()) { + return; + } + ClickHouseNode node = ClickHouseServerForTest.getClickHouseNode(ClickHouseProtocol.HTTP, + ClickHouseServerForTest.isCloud(), ClickHouseNode.builder().build()); + try (Client client = new Client.Builder() + .addEndpoint(Protocol.HTTP, node.getHost(), node.getPort(), ClickHouseServerForTest.isCloud()) + .setUsername(ClickHouseServerForTest.getUsername()) + .setPassword(ClickHouseServerForTest.getPassword()) + .setDefaultDatabase(ClickHouseServerForTest.getDatabase()) + .build()) { + BaseIntegrationTest.waitForAllReplicas(table, condition, expectedRows, + sql -> client.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray()); + } + } } diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ReadonlyProfileTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ReadonlyProfileTest.java index 2c34ed447..0c3322555 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ReadonlyProfileTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ReadonlyProfileTest.java @@ -18,6 +18,10 @@ public class ReadonlyProfileTest extends JdbcIntegrationTest { private String password; + private String user1; + private String user2; + private String profile1; + private String profile2; @BeforeClass(groups = { "integration" }) public void setup() throws SQLException { @@ -27,18 +31,25 @@ public void setup() throws SQLException { // Append fixed character classes so the random password satisfies server // password-complexity policies (uppercase, lowercase, digit, special). password = UUID.randomUUID().toString() + "Aa1!"; + String suffix = "_" + UUID.randomUUID().toString().replace("-", ""); + user1 = "jdbc_test_user_readonly_1" + suffix; + user2 = "jdbc_test_user_readonly_2" + suffix; + profile1 = "jdbc_test_profile_readonly_1" + suffix; + profile2 = "jdbc_test_profile_readonly_2" + suffix; com.clickhouse.client.ClickHouseServerForTest.beforeSuite(); try (Connection conn = getJdbcConnection(); Statement stmt = conn.createStatement()) { - stmt.execute("CREATE SETTINGS PROFILE IF NOT EXISTS jdbc_test_profile_readonly_1 SETTINGS readonly=1"); - stmt.execute("CREATE SETTINGS PROFILE IF NOT EXISTS jdbc_test_profile_readonly_2 SETTINGS readonly=2"); - stmt.execute("DROP USER IF EXISTS jdbc_test_user_readonly_1"); - stmt.execute("DROP USER IF EXISTS jdbc_test_user_readonly_2"); - stmt.execute("CREATE USER jdbc_test_user_readonly_1 IDENTIFIED WITH plaintext_password BY '" + password + "' SETTINGS PROFILE jdbc_test_profile_readonly_1"); - stmt.execute("CREATE USER jdbc_test_user_readonly_2 IDENTIFIED WITH plaintext_password BY '" + password + "' SETTINGS PROFILE jdbc_test_profile_readonly_2"); + stmt.execute("CREATE SETTINGS PROFILE " + profile1 + " SETTINGS readonly=1"); + stmt.execute("CREATE SETTINGS PROFILE " + profile2 + " SETTINGS readonly=2"); + waitForAllReplicas("settings_profiles", "name IN ('" + profile1 + "', '" + profile2 + "')", 2); + stmt.execute("CREATE USER " + user1 + " IDENTIFIED WITH plaintext_password BY '" + password + "' SETTINGS PROFILE " + profile1); + stmt.execute("CREATE USER " + user2 + " IDENTIFIED WITH plaintext_password BY '" + password + "' SETTINGS PROFILE " + profile2); + String users = "'" + user1 + "', '" + user2 + "'"; + waitForAllReplicas("users", "name IN (" + users + ")", 2); String db = getDatabase(); - stmt.execute("GRANT SELECT ON " + db + ".* TO jdbc_test_user_readonly_1"); - stmt.execute("GRANT SELECT ON " + db + ".* TO jdbc_test_user_readonly_2"); + stmt.execute("GRANT SELECT ON " + db + ".* TO " + user1); + stmt.execute("GRANT SELECT ON " + db + ".* TO " + user2); + waitForAllReplicas("grants", "user_name IN (" + users + ")", 2); } } @@ -49,10 +60,10 @@ public void teardown() throws SQLException { } try (Connection conn = getJdbcConnection(); Statement stmt = conn.createStatement()) { - stmt.execute("DROP USER IF EXISTS jdbc_test_user_readonly_1"); - stmt.execute("DROP USER IF EXISTS jdbc_test_user_readonly_2"); - stmt.execute("DROP SETTINGS PROFILE IF EXISTS jdbc_test_profile_readonly_1"); - stmt.execute("DROP SETTINGS PROFILE IF EXISTS jdbc_test_profile_readonly_2"); + stmt.execute("DROP USER IF EXISTS " + user1); + stmt.execute("DROP USER IF EXISTS " + user2); + stmt.execute("DROP SETTINGS PROFILE IF EXISTS " + profile1); + stmt.execute("DROP SETTINGS PROFILE IF EXISTS " + profile2); } } @@ -62,7 +73,7 @@ public void testReadonly1CannotChangeSettings() throws Exception { throw new SkipException("Should not be tested in cloud because creates users and profiles"); } Properties properties = new Properties(); - properties.setProperty("user", "jdbc_test_user_readonly_1"); + properties.setProperty("user", user1); properties.setProperty("password", password); properties.put(ClientConfigProperties.serverSetting(ServerSettings.OUTPUT_FORMAT_BINARY_WRITE_JSON_AS_STRING), "1"); @@ -86,7 +97,7 @@ public void testReadonly2CanChangeSettings() throws Exception { } Properties properties = new Properties(); - properties.setProperty("user", "jdbc_test_user_readonly_2"); + properties.setProperty("user", user2); properties.setProperty("password", password); properties.put(ClientConfigProperties.serverSetting(ServerSettings.OUTPUT_FORMAT_BINARY_WRITE_JSON_AS_STRING), "1"); properties.put(ClientConfigProperties.serverSetting("allow_experimental_json_type"), "1"); diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java index 0657558df..d753f466a 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -556,118 +556,130 @@ public void testSettingRole(SqlParserFacade.SQLParser parser) throws SQLExceptio return; } - List roles = Arrays.asList("role1", "role2", "role3"); + String suffix = "_" + RandomStringUtils.insecure().next(8, true, true); + String user = "some_user" + suffix; + String role1 = "role1" + suffix; + String role2 = "role2" + suffix; + String role3 = "role3" + suffix; + List roles = Arrays.asList(role1, role2, role3); + String roleNames = "'" + String.join("','", roles) + "'"; final String userPass = "^1A" + RandomStringUtils.random(12, true, true) + "3B$"; Properties properties = new Properties(); properties.setProperty(DriverProperties.SQL_PARSER.getKey(), parser.name()); - try (ConnectionImpl conn = (ConnectionImpl) getJdbcConnection(properties)) { - try (Statement stmt = conn.createStatement()) { - stmt.execute("DROP ROLE IF EXISTS " + String.join(", ", roles)); - stmt.execute("DROP USER IF EXISTS some_user"); - stmt.execute("CREATE ROLE " + String.join(", ", roles)); - stmt.execute("CREATE USER some_user IDENTIFIED BY '" + userPass + "'"); - stmt.execute("GRANT " + String.join(", ", roles) + " TO some_user"); - stmt.execute("SET DEFAULT ROLE NONE TO some_user"); - } - } + try (ConnectionImpl adminConn = (ConnectionImpl) getJdbcConnection(properties); + Statement adminStmt = adminConn.createStatement()) { + try { + adminStmt.execute("CREATE ROLE " + String.join(", ", roles)); + adminStmt.execute("CREATE USER " + user + " IDENTIFIED BY '" + userPass + "'"); + waitForAllReplicas("roles", "name IN (" + roleNames + ")", roles.size()); + waitForAllReplicas("users", "name = '" + user + "'", 1); + adminStmt.execute("GRANT " + String.join(", ", roles) + " TO " + user); + adminStmt.execute("SET DEFAULT ROLE NONE TO " + user); + waitForAllReplicas("role_grants", "user_name = '" + user + "' AND granted_role_is_default = 0", + roles.size()); + + Properties info = new Properties(); + info.setProperty("user", user); + info.setProperty("password", userPass); + info.setProperty(DriverProperties.SQL_PARSER.getKey(), parser.name()); + try (ConnectionImpl conn = new ConnectionImpl(getEndpointString(), info)) { + GenericRecord dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); + + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE " + role1); + } - Properties info = new Properties(); - info.setProperty("user", "some_user"); - info.setProperty("password", userPass); - info.setProperty(DriverProperties.SQL_PARSER.getKey(), parser.name()); - try (ConnectionImpl conn = new ConnectionImpl(getEndpointString(), info)) { - GenericRecord dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 1); + assertEquals(dataRecord.getList(1).get(0), role1); - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE role1"); - } - - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 1); - assertEquals(dataRecord.getList(1).get(0), "role1"); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE " + role2); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE role2"); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 1); + assertEquals(dataRecord.getList(1).get(0), role2); - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 1); - assertEquals(dataRecord.getList(1).get(0), "role2"); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE NONE"); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE NONE"); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE \"" + role1 + "\",\"" + role2 + "\""); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE \"role1\",\"role2\""); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 2); + assertEquals(dataRecord.getList(1).get(0), role1); + assertEquals(dataRecord.getList(1).get(1), role2); - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 2); - assertEquals(dataRecord.getList(1).get(0), "role1"); - assertEquals(dataRecord.getList(1).get(1), "role2"); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE \"" + role1 + "\",\"" + role2 + "\",\"" + role3 + "\""); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE \"role1\",\"role2\",\"role3\""); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 3); + assertEquals(dataRecord.getList(1).get(0), role1); + assertEquals(dataRecord.getList(1).get(1), role2); + assertEquals(dataRecord.getList(1).get(2), role3); + } - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 3); - assertEquals(dataRecord.getList(1).get(0), "role1"); - assertEquals(dataRecord.getList(1).get(1), "role2"); - assertEquals(dataRecord.getList(1).get(2), "role3"); - } + Properties disableSavingRoles = new Properties(); + disableSavingRoles.setProperty("user", user); + disableSavingRoles.setProperty("password", userPass); + disableSavingRoles.setProperty(DriverProperties.REMEMBER_LAST_SET_ROLES.getKey(), "false"); + disableSavingRoles.setProperty(DriverProperties.SQL_PARSER.getKey(), parser.name()); + try (ConnectionImpl conn = new ConnectionImpl(getEndpointString(), disableSavingRoles)) { + GenericRecord dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); - Properties disableSavingRoles = new Properties(); - disableSavingRoles.setProperty("user", "some_user"); - disableSavingRoles.setProperty("password", userPass); - disableSavingRoles.setProperty(DriverProperties.REMEMBER_LAST_SET_ROLES.getKey(), "false"); - disableSavingRoles.setProperty(DriverProperties.SQL_PARSER.getKey(), parser.name()); - try (ConnectionImpl conn = new ConnectionImpl(getEndpointString(), disableSavingRoles)) { - GenericRecord dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE " + role1); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE role1"); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE " + role2); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE role2"); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE NONE"); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE NONE"); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE \"" + role1 + "\",\"" + role2 + "\""); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE \"role1\",\"role2\""); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE \"" + role1 + "\",\"" + role2 + "\",\"" + role3 + "\""); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE \"role1\",\"role2\",\"role3\""); + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); + } + } finally { + adminStmt.execute("DROP USER IF EXISTS " + user); + adminStmt.execute("DROP ROLE IF EXISTS " + String.join(", ", roles)); } - - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); } } @@ -1359,18 +1371,24 @@ public void testDDLStatements() throws Exception { if (isCloud()) { return; // skip because we do not want to create extra on cloud instance } + String user = "user011_" + RandomStringUtils.insecure().next(8, true, true); try (Connection conn = getJdbcConnection()) { try (Statement stmt = conn.createStatement()) { - Assert.assertFalse(stmt.execute("CREATE USER IF NOT EXISTS 'user011' IDENTIFIED BY 'password'")); + try { + Assert.assertFalse(stmt.execute("CREATE USER IF NOT EXISTS '" + user + "' IDENTIFIED BY 'password'")); + waitForAllReplicas("users", "name = '" + user + "'", 1); - try (ResultSet rs = stmt.executeQuery("SHOW USERS")) { - boolean found = false; - while (rs.next()) { - if (rs.getString("name").equals("user011")) { - found = true; + try (ResultSet rs = stmt.executeQuery("SHOW USERS")) { + boolean found = false; + while (rs.next()) { + if (rs.getString("name").equals(user)) { + found = true; + } } + Assert.assertTrue(found); } - Assert.assertTrue(found); + } finally { + stmt.execute("DROP USER IF EXISTS '" + user + "'"); } } } diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ShowStatementDatabaseMetaDataTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ShowStatementDatabaseMetaDataTest.java index 73674f3cb..8cc9a660f 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ShowStatementDatabaseMetaDataTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ShowStatementDatabaseMetaDataTest.java @@ -211,22 +211,23 @@ public void testGetColumnsForReadonlyUserWithColumnGrants(String flag, List