diff --git a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/jdbc_test/JdbcThinLobTestApplication.java b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/jdbc_test/JdbcThinLobTestApplication.java new file mode 100644 index 0000000000000..a64c96d5ce4ef --- /dev/null +++ b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/jdbc_test/JdbcThinLobTestApplication.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.ducktest.tests.jdbc_test; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryUsage; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import com.fasterxml.jackson.databind.JsonNode; +import org.apache.ignite.internal.IgniteInterruptedCheckedException; +import org.apache.ignite.internal.ducktest.utils.IgniteAwareApplication; +import org.apache.ignite.internal.util.typedef.internal.U; + +/** + * Simple application that used in smoke tests + */ +public class JdbcThinLobTestApplication extends IgniteAwareApplication { + /** {@inheritDoc} */ + @Override public void run(JsonNode jsonNode) throws ClassNotFoundException, SQLException { + int blobSize = Optional.ofNullable(jsonNode.get("blob_size")).map(JsonNode::asInt).orElse(1); + int clobSize = Optional.ofNullable(jsonNode.get("clob_size")).map(JsonNode::asInt).orElse(1); + String action = Optional.ofNullable(jsonNode.get("action")).map(JsonNode::asText).orElse("insert"); + + int id = 1; + + try (Connection conn = thinJdbcDataSource.getConnection()) { + conn.createStatement().execute("CREATE TABLE IF NOT EXISTS query(id INT, clob VARCHAR, blob BINARY, PRIMARY KEY(id)) " + + "WITH \"cache_name=query,template=WITH_STATISTICS_ENABLED\""); + + if ("insert".equals(action)) { + PreparedStatement insertStatement = conn.prepareStatement("INSERT INTO query(id, clob, blob) VALUES(?, ?, ?)"); + + insertStatement.setInt(1, id); + + Clob clob = getClob(conn, clobSize); + insertStatement.setClob(2, clob); + + Blob blob = getBlob(conn, blobSize); + insertStatement.setBlob(3, blob); + + insertStatement.execute(); + } + else if ("select".equals(action)) { + PreparedStatement selectStatement = conn.prepareStatement("SELECT * FROM query WHERE id = ?"); + + selectStatement.setInt(1, id); + + ResultSet resultSet = selectStatement.executeQuery(); + + while (resultSet.next()) { + Clob clob = resultSet.getClob("clob"); + + recordResult("CLOB_SIZE", clob.length()); + recordResult("CLOB", clob.getSubString(1, Math.min((int)clob.length(), 64))); + + Blob blob = resultSet.getBlob("blob"); + byte[] bytes = blob.getBytes(1, Math.min((int)blob.length(), 64)); + + recordResult("BLOB_SIZE", blob.length()); + recordResult("BLOB", U.byteArray2String(bytes, "0x%02X", ",0x%02X")); + } + } + + markInitialized(); + + while (!terminated()) { + try { + U.sleep(100); // Keeping node/txs alive. + } + catch (IgniteInterruptedCheckedException ignored) { + log.info("Waiting interrupted."); + } + } + + recordMemoryPeakUsage(); + + markFinished(); + } + } + + /** + * @param conn Connection. + * @param size CLOB size. + * @return CLOB of specified size. + */ + private Clob getClob(Connection conn, int size) throws SQLException { + Clob clob = conn.createClob(); + + if (size > 0) { + StringBuilder sb = new StringBuilder(size); + + for (int i = 0; i < size; i++) { + sb.append("Ж"); + } + + clob.setString(1, sb.toString()); + } + return clob; + } + + /** + * @param conn Connection. + * @param size BLOB size. + * @return BLOB with random data. + */ + private Blob getBlob(Connection conn, int size) throws SQLException { + Blob blob = conn.createBlob(); + + if (size > 0) { + byte[] bytes = new byte[size]; + + new Random().nextBytes(bytes); + + blob.setBytes(1, bytes); + } + + return blob; + } + + /** + * @return Memory usage. + */ + private MemoryUsage getPeakMemoryUsage() { + List pools = ManagementFactory.getMemoryPoolMXBeans(); + + return pools.stream().filter(pool -> pool.getName().contains("G1")) + .map(MemoryPoolMXBean::getPeakUsage) + .reduce((MemoryUsage a, MemoryUsage b) -> + new MemoryUsage(a.getInit() + b.getInit(), a.getUsed() + b.getUsed(), + a.getCommitted() + b.getCommitted(), + Math.max(a.getMax() + b.getMax(), a.getCommitted() + b.getCommitted()))) + .orElse(null); + } + + /** + */ + private void recordMemoryPeakUsage() { + MemoryUsage peakMemoryUsage = getPeakMemoryUsage(); + + recordResult("PEAK_MEMORY_USED", peakMemoryUsage.getUsed()); + recordResult("PEAK_MEMORY_COMMITTED", peakMemoryUsage.getCommitted()); + recordResult("PEAK_MEMORY_MAX", peakMemoryUsage.getMax()); + recordResult("PEAK_MEMORY_INIT", peakMemoryUsage.getInit()); + } +} diff --git a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/utils/IgniteAwareApplication.java b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/utils/IgniteAwareApplication.java index 4d6c0ef086bb0..6157c94b0ace9 100644 --- a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/utils/IgniteAwareApplication.java +++ b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/utils/IgniteAwareApplication.java @@ -21,6 +21,7 @@ import java.lang.management.ThreadInfo; import com.fasterxml.jackson.databind.JsonNode; import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteJdbcThinDataSource; import org.apache.ignite.client.IgniteClient; import org.apache.ignite.cluster.ClusterState; import org.apache.ignite.internal.IgniteEx; @@ -71,6 +72,9 @@ public abstract class IgniteAwareApplication { /** Client. */ protected IgniteClient client; + /** Thin JDBC DataSource. */ + protected IgniteJdbcThinDataSource thinJdbcDataSource; + /** Cfg path. */ protected String cfgPath; diff --git a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/utils/IgniteAwareApplicationService.java b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/utils/IgniteAwareApplicationService.java index 19b13a151b2a3..ddb0ef191c586 100644 --- a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/utils/IgniteAwareApplicationService.java +++ b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/utils/IgniteAwareApplicationService.java @@ -47,7 +47,10 @@ private enum IgniteServiceType { THIN_CLIENT, /** Run application without precreated connections. */ - NONE + NONE, + + /** Run application with the precreated thin JDBC data source. */ + THIN_JDBC } /** @@ -106,6 +109,13 @@ else if (svcType == IgniteServiceType.THIN_CLIENT) { } else if (svcType == IgniteServiceType.NONE) app.start(jsonNode); + else if (svcType == IgniteServiceType.THIN_JDBC) { + log.info("Create thin jdbc ignite data source..."); + + app.thinJdbcDataSource = IgnitionEx.loadSpringBean(cfgPath, "thin.jdbc.cfg"); + + app.start(jsonNode); + } else throw new IllegalArgumentException("Unknown service type " + svcType); } diff --git a/modules/ducktests/tests/ignitetest/services/utils/__init__.py b/modules/ducktests/tests/ignitetest/services/utils/__init__.py index c2830bce18a71..efe71e8ad117a 100644 --- a/modules/ducktests/tests/ignitetest/services/utils/__init__.py +++ b/modules/ducktests/tests/ignitetest/services/utils/__init__.py @@ -29,3 +29,4 @@ class IgniteServiceType(IntEnum): NODE = 0 THIN_CLIENT = 1 NONE = 2 + THIN_JDBC = 3 diff --git a/modules/ducktests/tests/ignitetest/services/utils/config_template.py b/modules/ducktests/tests/ignitetest/services/utils/config_template.py index 0af5f7cd77b5b..16a6a9a353367 100644 --- a/modules/ducktests/tests/ignitetest/services/utils/config_template.py +++ b/modules/ducktests/tests/ignitetest/services/utils/config_template.py @@ -24,6 +24,7 @@ ZK_TEMPLATE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "zk", "templates") DEFAULT_IGNITE_CONF = "ignite.xml.j2" DEFAULT_THIN_CLIENT_CONF = "thin_client_config.xml.j2" +DEFAULT_THIN_JDBC_CONF = "thin_jdbc_config.xml.j2" DEFAULT_LOG4J2_CONF = "log4j2.xml.j2" TEMPLATE_PATHES = [IGNITE_TEMPLATE_PATH, ZK_TEMPLATE_PATH] @@ -85,6 +86,14 @@ def __init__(self, path=DEFAULT_THIN_CLIENT_CONF): super().__init__(path) +class IgniteThinJdbcConfigTemplate(ConfigTemplate): + """ + Ignite client node configuration. + """ + def __init__(self, path=DEFAULT_THIN_JDBC_CONF): + super().__init__(path) + + class IgniteLoggerConfigTemplate(ConfigTemplate): """ Ignite logger configuration. diff --git a/modules/ducktests/tests/ignitetest/services/utils/ignite_aware.py b/modules/ducktests/tests/ignitetest/services/utils/ignite_aware.py index 0a0b581f30139..b6e2fe05e1420 100644 --- a/modules/ducktests/tests/ignitetest/services/utils/ignite_aware.py +++ b/modules/ducktests/tests/ignitetest/services/utils/ignite_aware.py @@ -101,7 +101,8 @@ def await_started(self, nodes=None): """ Awaits start finished. """ - if self.config.service_type in (IgniteServiceType.NONE, IgniteServiceType.THIN_CLIENT): + if self.config.service_type in (IgniteServiceType.NONE, IgniteServiceType.THIN_CLIENT, + IgniteServiceType.THIN_JDBC): return self.logger.info("Waiting for IgniteAware(s) to start ...") diff --git a/modules/ducktests/tests/ignitetest/services/utils/ignite_configuration/__init__.py b/modules/ducktests/tests/ignitetest/services/utils/ignite_configuration/__init__.py index 6f83f603e0416..1fdec71e3b4cb 100644 --- a/modules/ducktests/tests/ignitetest/services/utils/ignite_configuration/__init__.py +++ b/modules/ducktests/tests/ignitetest/services/utils/ignite_configuration/__init__.py @@ -166,3 +166,35 @@ def service_type(self): Application mode. """ return IgniteServiceType.THIN_CLIENT + + +class IgniteThinJdbcConfiguration(NamedTuple): + addresses: list = [] + version: IgniteVersion = DEV_BRANCH + ssl_params: SslParams = None + username: str = None + password: str = None + + def prepare_ssl(self, test_globals, shared_root): + """ + Updates ssl configuration from globals. + """ + ssl_params = None + if self.ssl_params is None and is_ssl_enabled(test_globals): + ssl_params = get_ssl_params(test_globals, shared_root, IGNITE_CLIENT_ALIAS) + if ssl_params: + return self._replace(ssl_params=ssl_params) + return self + + def prepare_for_env(self, cluster, node): + """ + Updates configuration based on current environment. + """ + return self + + @property + def service_type(self): + """ + Application mode. + """ + return IgniteServiceType.THIN_JDBC diff --git a/modules/ducktests/tests/ignitetest/services/utils/ignite_spec.py b/modules/ducktests/tests/ignitetest/services/utils/ignite_spec.py index e8c2c036b8b36..66b1b95cb5ea6 100644 --- a/modules/ducktests/tests/ignitetest/services/utils/ignite_spec.py +++ b/modules/ducktests/tests/ignitetest/services/utils/ignite_spec.py @@ -29,7 +29,7 @@ from ignitetest.services.utils import IgniteServiceType from ignitetest.services.utils.config_template import IgniteClientConfigTemplate, IgniteServerConfigTemplate, \ - IgniteLoggerConfigTemplate, IgniteThinClientConfigTemplate + IgniteLoggerConfigTemplate, IgniteThinClientConfigTemplate, IgniteThinJdbcConfigTemplate from ignitetest.services.utils.jvm_utils import create_jvm_settings, merge_jvm_settings from ignitetest.services.utils.path import get_home_dir, IgnitePathAware from ignitetest.services.utils.ssl.ssl_params import is_ssl_enabled @@ -142,6 +142,9 @@ def config_templates(self): if self.service.config.service_type == IgniteServiceType.THIN_CLIENT: config_templates.append((IgnitePathAware.IGNITE_THIN_CLIENT_CONFIG_NAME, IgniteThinClientConfigTemplate())) + if self.service.config.service_type == IgniteServiceType.THIN_JDBC: + config_templates.append((IgnitePathAware.IGNITE_THIN_CLIENT_CONFIG_NAME, IgniteThinJdbcConfigTemplate())) + return config_templates def extend_config(self, config): diff --git a/modules/ducktests/tests/ignitetest/services/utils/jmx_utils.py b/modules/ducktests/tests/ignitetest/services/utils/jmx_utils.py index e01bb4c7d6efc..462e7864e1629 100644 --- a/modules/ducktests/tests/ignitetest/services/utils/jmx_utils.py +++ b/modules/ducktests/tests/ignitetest/services/utils/jmx_utils.py @@ -99,7 +99,7 @@ def mbean_attribute(self, mbean, attr): :return: Attribute value """ cmd = "echo $'open %s\\n get -b %s %s \\n close' | %s | sed 's/%s = \\(.*\\);/\\1/'" \ - % (self.pid, mbean, attr, self.jmx_util_cmd, attr) + % (self.pid, mbean.replace(' ', '\\ '), attr, self.jmx_util_cmd, attr) return iter(s.strip() for s in self.__run_cmd(cmd)) diff --git a/modules/ducktests/tests/ignitetest/services/utils/templates/thin_jdbc_config.xml.j2 b/modules/ducktests/tests/ignitetest/services/utils/templates/thin_jdbc_config.xml.j2 new file mode 100644 index 0000000000000..0c72cddc362f8 --- /dev/null +++ b/modules/ducktests/tests/ignitetest/services/utils/templates/thin_jdbc_config.xml.j2 @@ -0,0 +1,46 @@ +{# + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +#} + + + + + + + {% for address in config.addresses %} + {{ address }} + {% endfor %} + + + + {% if config.username %} + + + {% endif %} + + {% if config.ssl_params %} + + + + + + {% endif %} + + diff --git a/modules/ducktests/tests/ignitetest/tests/jdbc_thin_blob_test.py b/modules/ducktests/tests/ignitetest/tests/jdbc_thin_blob_test.py new file mode 100644 index 0000000000000..29c6c7b572de1 --- /dev/null +++ b/modules/ducktests/tests/ignitetest/tests/jdbc_thin_blob_test.py @@ -0,0 +1,173 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This module contains client queries tests. +""" +from ducktape.mark import parametrize + +from ignitetest.services.ignite import IgniteService +from ignitetest.services.ignite_app import IgniteApplicationService +from ignitetest.services.utils.control_utility import ControlUtility +from ignitetest.services.utils.ignite_configuration import IgniteConfiguration, IgniteThinJdbcConfiguration +from ignitetest.services.utils.ignite_configuration.cache import CacheConfiguration +from ignitetest.services.utils.ignite_configuration.data_storage import DataRegionConfiguration, \ + DataStorageConfiguration +from ignitetest.services.utils.jmx_utils import JmxClient +from ignitetest.services.utils.ssl.client_connector_configuration import ClientConnectorConfiguration +from ignitetest.utils import cluster, ignite_versions +from ignitetest.utils.ignite_test import IgniteTest +from ignitetest.utils.version import DEV_BRANCH, IgniteVersion + + +class JdbcThinLobTest(IgniteTest): + """ + cluster - cluster size. + JAVA_CLIENT_CLASS_NAME - running classname. + to use with ssl enabled: + export GLOBALS='{"ssl":{"enabled":true}}' . + """ + @cluster(num_nodes=4) + @ignite_versions(str(DEV_BRANCH)) + # @parametrize(blob_size=1*1024*1024*1024, clob_size=1*1024*1024*1024, server_heap=2, client_heap=1) + # @parametrize(clob_size=512*1024*1024, blob_size=1, server_heap=8, client_heap=8) + @parametrize(clob_size=0, blob_size=1*1024*1024*1024, server_heap=12, client_heap=12) + # @parametrize(clob_size=256*1024*1024, blob_size=0, server_heap=12, client_heap=12) + def test_jdbc_thin_lob(self, ignite_version, blob_size, clob_size, server_heap, client_heap): + """ + Thin client IndexQuery test. + :param ignite_version Ignite node version. + """ + + server_config = IgniteConfiguration(version=IgniteVersion(ignite_version), + client_connector_configuration=ClientConnectorConfiguration(), + data_storage=DataStorageConfiguration( + checkpoint_frequency=10000, + metrics_enabled=True, + wal_segment_size=2 * 1024 * 1024 * 1024 - 1, + max_wal_archive_size=20 * 1024 * 1024 * 1024, + default=DataRegionConfiguration( + persistence_enabled=True, + metrics_enabled=True, + initial_size=4 * 1024 * 1024 * 1024, + max_size=4 * 1024 * 1024 * 1024 + ) + ), + + caches=[ + CacheConfiguration(name="WITH_STATISTICS_ENABLED*", + statistics_enabled=True, + backups=1) + ]) + + ignite = IgniteService(self.test_context, server_config, 2, + merge_with_default=False, + jvm_opts=["-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", + f"-Xmx{server_heap}g", f"-Xms{server_heap}g", + "-Xlog:safepoint*=debug:file=/mnt/service/logs/safepoint.log" + ":time,uptime,level,tags", + "-Xlog:gc*=debug,gc+stats*=debug,gc+ergo*=debug" + ":/mnt/service/logs/gc.log:uptime,time,level,tags"]) + + ignite.start() + + ControlUtility(ignite).activate() + + address = ignite.nodes[0].account.hostname + ":" + str(server_config.client_connector_configuration.port) + + cls = "org.apache.ignite.internal.ducktest.tests.jdbc_test.JdbcThinLobTestApplication" + + client_insert = IgniteApplicationService( + self.test_context, + IgniteThinJdbcConfiguration( + version=IgniteVersion(ignite_version), + addresses=[address] + ), + java_class_name=cls, + num_nodes=1, + merge_with_default=False, + jvm_opts=["-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", + f"-Xmx{client_heap}g", f"-Xms{client_heap}g", + "-Xlog:safepoint*=debug:file=/mnt/service/logs/safepoint.log" + ":time,uptime,level,tags", + "-Xlog:gc*=debug,gc+stats*=debug,gc+ergo*=debug" + ":/mnt/service/logs/gc.log:uptime,time,level,tags", + "-DappId=ignite", + "-Dlog4j.configDebug=true", + "-Dlog4j.configurationFile=file:/mnt/service/config/ignite-ducktape-log4j2.xml"], + params={ + "blob_size": blob_size, + "clob_size": clob_size, + "action": "insert" + }) + + client_insert.start() + + client_select = IgniteApplicationService( + self.test_context, + IgniteThinJdbcConfiguration( + version=IgniteVersion(ignite_version), + addresses=[address] + ), + java_class_name=cls, + num_nodes=1, + merge_with_default=False, + jvm_opts=["-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", + f"-Xmx{client_heap}g", f"-Xms{client_heap}g", + "-Xlog:safepoint*=debug:file=/mnt/service/logs/safepoint.log" + ":time,uptime,level,tags", + "-Xlog:gc*=debug,gc+stats*=debug,gc+ergo*=debug" + ":/mnt/service/logs/gc.log:uptime,time,level,tags", + "-DappId=ignite", + "-Dlog4j.configDebug=true", + "-Dlog4j.configurationFile=file:/mnt/service/config/ignite-ducktape-log4j2.xml"], + params={ + "action": "select" + }) + + client_select.start() + + data = { + # "CLOB": clients.extract_result("CLOB"), + "clob_size_gb": float("{:.2f}".format(int(client_select.extract_result("CLOB_SIZE")) / 1024 / 1024 / 1024)), + # "BLOB": clients.extract_result("BLOB"), + "blob_size_gb": float("{:.2f}".format(int(client_select.extract_result("BLOB_SIZE")) / 1024 / 1024 / 1024)), + "server_peak_heap_usage_gb": get_peak_memory_usage(ignite.nodes), + "client_insert_peak_heap_usage_gb": get_peak_memory_usage(client_insert.nodes), + "client_select_peak_heap_usage_gb": get_peak_memory_usage(client_select.nodes) + } + + client_insert.stop() + client_select.stop() + + ignite.stop() + + return data + + +def get_peak_memory_usage(nodes): + def node_peak_memory_usage(node): + client = JmxClient(node) + + eden_mbean = client.find_mbean('.*G1 Eden Space,type=MemoryPool', domain="java.lang") + old_mbean = client.find_mbean('.*G1 Old Gen,type=MemoryPool', domain="java.lang") + survivor_mbean = client.find_mbean('.*G1 Survivor Space,type=MemoryPool', domain="java.lang") + + return float("{:.2f}".format((int(next(client.mbean_attribute(eden_mbean.name, 'PeakUsage.used'))) + + int(next(client.mbean_attribute(old_mbean.name, 'PeakUsage.used'))) + + int(next(client.mbean_attribute(survivor_mbean.name, 'PeakUsage.used')))) / + 1024 / 1024 / 1024)) + + return {node.name: node_peak_memory_usage(node) for node in nodes}