This is an automated email from the ASF dual-hosted git repository.
HTHou pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new 0ef17ba7677 Support IPv6 endpoint URLs (#18162)
0ef17ba7677 is described below
commit 0ef17ba76771bf34d9c3fe6f6041a068d7dea7a4
Author: Haonan <[email protected]>
AuthorDate: Wed Jul 15 19:04:59 2026 +0800
Support IPv6 endpoint URLs (#18162)
Co-authored-by: Caideyipi <[email protected]>
---
.../iotdb/it/env/cluster/ClusterConstant.java | 1 +
.../iotdb/it/env/cluster/env/AbstractEnv.java | 29 ++-
.../it/env/cluster/node/AbstractNodeWrapper.java | 6 +-
.../iotdb/it/env/remote/env/RemoteServerEnv.java | 23 ++-
.../org/apache/iotdb/db/it/IoTDBIPv6ClusterIT.java | 124 ++++++++++++
.../iotdb/db/it/IoTDBIPv6ExternalServiceIT.java | 220 +++++++++++++++++++++
.../IoTDBIoTConsensusV23C3DBasicITBase.java | 19 +-
.../org/apache/iotdb/it/utils/IPv6TestUtils.java | 60 ++++++
.../manual/enhanced/IoTDBPipeClusterIT.java | 2 +-
.../dual/treemodel/auto/basic/IoTDBPipeIPv6IT.java | 166 ++++++++++++++++
.../auto/enhanced/IoTDBPipeClusterIT.java | 2 +-
.../apache/iotdb/session/it/IoTDBIPv6ClientIT.java | 142 +++++++++++++
iotdb-client/client-cpp/src/rpc/NodesSupplier.cpp | 5 +-
iotdb-client/client-cpp/src/rpc/RpcCommon.cpp | 23 +++
iotdb-client/client-cpp/src/rpc/RpcCommon.h | 2 +
iotdb-client/client-cpp/src/session/Session.cpp | 4 +-
iotdb-client/client-cpp/test/cpp/sessionIT.cpp | 15 ++
iotdb-client/client-cpp/test/main.cpp | 5 +-
iotdb-client/client-py/iotdb/Session.py | 85 ++++++--
.../client-py/tests/unit/test_session_node_url.py | 85 ++++++++
.../src/main/java/org/apache/iotdb/jdbc/Utils.java | 77 +++++---
.../test/java/org/apache/iotdb/jdbc/UtilsTest.java | 46 +++++
.../main/java/org/apache/iotdb/rpc/UrlUtils.java | 85 ++++++--
.../java/org/apache/iotdb/rpc/UrlUtilsTest.java | 80 ++++++++
.../apache/iotdb/session/i18n/SessionMessages.java | 2 +
.../apache/iotdb/session/i18n/SessionMessages.java | 2 +
.../org/apache/iotdb/session/NodesSupplier.java | 5 +-
.../java/org/apache/iotdb/session/Session.java | 5 +-
.../apache/iotdb/session/SessionConnection.java | 11 +-
.../org/apache/iotdb/session/pool/SessionPool.java | 7 +-
.../apache/iotdb/session/util/SessionUtils.java | 8 +-
iotdb-core/ainode/iotdb/ainode/core/config.py | 17 +-
.../ainode/iotdb/ainode/core/ingress/iotdb.py | 11 +-
.../ainode/resources/conf/iotdb-ainode.properties | 3 +-
iotdb-core/ainode/tests/test_config.py | 71 +++++++
.../apache/iotdb/consensus/ratis/utils/Utils.java | 9 +-
.../iotdb/consensus/ratis/utils/UtilsTest.java | 54 +++++
.../client/IoTDBDataNodeAsyncClientManager.java | 8 +-
.../client/IoTDBDataNodeSyncClientManager.java | 6 +-
.../thrift/async/IoTDBDataRegionAsyncSink.java | 3 +-
.../execution/config/sys/TestConnectionTask.java | 3 +-
.../sink/IoTDBDataNodeAsyncClientManagerTest.java | 120 +++++++++++
.../conf/iotdb-system.properties.template | 10 +-
.../apache/iotdb/commons/utils/NodeUrlUtils.java | 9 +-
.../iotdb/commons/utils/NodeUrlUtilsTest.java | 7 +
45 files changed, 1532 insertions(+), 145 deletions(-)
diff --git
a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/ClusterConstant.java
b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/ClusterConstant.java
index 691a13509d9..9118f51f436 100644
---
a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/ClusterConstant.java
+++
b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/ClusterConstant.java
@@ -44,6 +44,7 @@ public class ClusterConstant {
public static final String DEFAULT_DATA_NODE_COMMON_PROPERTIES =
"DefaultDataNodeCommonProperties";
public static final String DATA_REGION_PER_DATANODE =
"integrationTest.dataRegionPerDataNode";
+ public static final String TEST_NODE_ADDRESS = "IoTDBTestNodeAddress";
// Cluster Configurations
public static final String CLUSTER_CONFIGURATIONS = "ClusterConfigurations";
diff --git
a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/env/AbstractEnv.java
b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/env/AbstractEnv.java
index ae724c7081d..3819bd32a8d 100644
---
a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/env/AbstractEnv.java
+++
b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/env/AbstractEnv.java
@@ -64,6 +64,7 @@ import org.apache.iotdb.jdbc.Constant;
import org.apache.iotdb.jdbc.IoTDBConnection;
import org.apache.iotdb.rpc.IoTDBConnectionException;
import org.apache.iotdb.rpc.TSStatusCode;
+import org.apache.iotdb.rpc.UrlUtils;
import org.apache.iotdb.session.Session;
import org.apache.iotdb.session.TableSessionBuilder;
import org.apache.iotdb.session.pool.SessionPool;
@@ -151,9 +152,8 @@ public abstract class AbstractEnv implements BaseEnv {
final String configNodeMetricContent =
getUrlContent(
Config.IOTDB_HTTP_URL_PREFIX
- + configNode.getIp()
- + ":"
- + configNode.getMetricPort()
+ + UrlUtils.formatTEndPointIpv4AndIpv6Url(
+ configNode.getIp(), configNode.getMetricPort())
+ "/metrics",
authHeader);
result.add(configNodeMetricContent);
@@ -163,9 +163,8 @@ public abstract class AbstractEnv implements BaseEnv {
final String dataNodeMetricContent =
getUrlContent(
Config.IOTDB_HTTP_URL_PREFIX
- + dataNode.getIp()
- + ":"
- + dataNode.getMetricPort()
+ + UrlUtils.formatTEndPointIpv4AndIpv6Url(
+ dataNode.getIp(), dataNode.getMetricPort())
+ "/metrics",
authHeader);
result.add(dataNodeMetricContent);
@@ -1042,7 +1041,8 @@ public abstract class AbstractEnv implements BaseEnv {
final String password,
final String sqlDialect)
throws SQLException {
- final String endpoint = dataNode.getIp() + ":" + dataNode.getPort();
+ final String endpoint =
+ UrlUtils.formatTEndPointIpv4AndIpv6Url(dataNode.getIp(),
dataNode.getPort());
final Connection writeConnection =
DriverManager.getConnection(
Config.IOTDB_URL_PREFIX
@@ -1633,18 +1633,14 @@ public abstract class AbstractEnv implements BaseEnv {
.forEach(
node ->
nodeIds.put(
- node.getInternalEndPoint().getIp()
- + ":"
- + node.getInternalEndPoint().getPort(),
+
UrlUtils.convertTEndPointIpv4AndIpv6Url(node.getInternalEndPoint()),
node.getConfigNodeId()));
showClusterResp
.getDataNodeList()
.forEach(
node ->
nodeIds.put(
- node.getClientRpcEndPoint().getIp()
- + ":"
- + node.getClientRpcEndPoint().getPort(),
+
UrlUtils.convertTEndPointIpv4AndIpv6Url(node.getClientRpcEndPoint()),
node.getDataNodeId()));
for (int j = 0; j < nodes.size(); j++) {
BaseNodeWrapper nodeWrapper = nodes.get(j);
@@ -1667,10 +1663,9 @@ public abstract class AbstractEnv implements BaseEnv {
continue;
}
if (nodeWrapper instanceof DataNodeWrapper &&
targetStatus.equals(NodeStatus.Running)) {
- final String[] ipPort =
nodeWrapper.getIpAndPortString().split(":");
- final String ip = ipPort[0];
- final int port = Integer.parseInt(ipPort[1]);
- try (TSocket socket = new TSocket(new TConfiguration(), ip, port,
1000)) {
+ try (TSocket socket =
+ new TSocket(
+ new TConfiguration(), nodeWrapper.getIp(),
nodeWrapper.getPort(), 1000)) {
socket.open();
} catch (final TTransportException e) {
errorMessages.add(
diff --git
a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/node/AbstractNodeWrapper.java
b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/node/AbstractNodeWrapper.java
index 5a271c1ead2..8d3f5f2cc9d 100644
---
a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/node/AbstractNodeWrapper.java
+++
b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/node/AbstractNodeWrapper.java
@@ -27,6 +27,7 @@ import org.apache.iotdb.it.env.cluster.config.MppCommonConfig;
import org.apache.iotdb.it.env.cluster.config.MppJVMConfig;
import org.apache.iotdb.it.framework.IoTDBTestLogger;
import org.apache.iotdb.itbase.env.BaseNodeWrapper;
+import org.apache.iotdb.rpc.UrlUtils;
import org.apache.tsfile.external.commons.io.FileUtils;
import org.apache.tsfile.external.commons.io.file.PathUtils;
@@ -123,6 +124,7 @@ import static
org.apache.iotdb.it.env.cluster.ClusterConstant.TAB;
import static org.apache.iotdb.it.env.cluster.ClusterConstant.TARGET;
import static
org.apache.iotdb.it.env.cluster.ClusterConstant.TEMPLATE_NODE_LIB_PATH;
import static
org.apache.iotdb.it.env.cluster.ClusterConstant.TEMPLATE_NODE_PATH;
+import static
org.apache.iotdb.it.env.cluster.ClusterConstant.TEST_NODE_ADDRESS;
import static org.apache.iotdb.it.env.cluster.ClusterConstant.TRIGGER_LIB_DIR;
import static org.apache.iotdb.it.env.cluster.ClusterConstant.UDF_LIB_DIR;
import static org.apache.iotdb.it.env.cluster.ClusterConstant.USER_DIR;
@@ -181,7 +183,7 @@ public abstract class AbstractNodeWrapper implements
BaseNodeWrapper {
this.testClassName = testClassName;
this.testMethodName = testMethodName;
this.portList = portList;
- this.nodeAddress = "127.0.0.1";
+ this.nodeAddress = System.getProperty(TEST_NODE_ADDRESS, "127.0.0.1");
this.nodePort = portList[0];
this.metricPort = portList[portList.length - 2];
jmxPort = this.portList[portList.length - 1];
@@ -606,7 +608,7 @@ public abstract class AbstractNodeWrapper implements
BaseNodeWrapper {
@Override
public final String getIpAndPortString() {
- return this.getIp() + ":" + this.getPort();
+ return UrlUtils.formatTEndPointIpv4AndIpv6Url(this.getIp(),
this.getPort());
}
protected String workDirFilePath(String dirName, String fileName) {
diff --git
a/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java
b/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java
index 586eff60494..3790c37dcea 100644
---
a/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java
+++
b/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java
@@ -42,6 +42,7 @@ import org.apache.iotdb.itbase.env.ClusterConfig;
import org.apache.iotdb.jdbc.Config;
import org.apache.iotdb.jdbc.Constant;
import org.apache.iotdb.rpc.IoTDBConnectionException;
+import org.apache.iotdb.rpc.UrlUtils;
import org.apache.iotdb.session.Session;
import org.apache.iotdb.session.TableSessionBuilder;
import org.apache.iotdb.session.pool.SessionPool;
@@ -72,6 +73,10 @@ public class RemoteServerEnv implements BaseEnv {
private IClientManager<TEndPoint, SyncConfigNodeIServiceClient>
clientManager;
private RemoteClusterConfig clusterConfig = new RemoteClusterConfig();
+ private String remoteEndpointUrl(String port) {
+ return UrlUtils.formatTEndPointIpv4AndIpv6Url(ip_addr,
Integer.parseInt(port));
+ }
+
@Override
public void initClusterEnvironment() {
try (Connection connection = EnvFactory.getEnv().getConnection();
@@ -116,11 +121,11 @@ public class RemoteServerEnv implements BaseEnv {
List<String> result = new ArrayList<>();
result.add(
getUrlContent(
- Config.IOTDB_HTTP_URL_PREFIX + ip_addr + ":" +
configNodeMetricPort + "/metrics",
+ Config.IOTDB_HTTP_URL_PREFIX +
remoteEndpointUrl(configNodeMetricPort) + "/metrics",
authHeader));
result.add(
getUrlContent(
- Config.IOTDB_HTTP_URL_PREFIX + ip_addr + ":" + dataNodeMetricPort
+ "/metrics",
+ Config.IOTDB_HTTP_URL_PREFIX +
remoteEndpointUrl(dataNodeMetricPort) + "/metrics",
authHeader));
return result;
}
@@ -133,7 +138,7 @@ public class RemoteServerEnv implements BaseEnv {
Class.forName(Config.JDBC_DRIVER_NAME);
connection =
DriverManager.getConnection(
- Config.IOTDB_URL_PREFIX + ip_addr + ":" + port,
+ Config.IOTDB_URL_PREFIX + remoteEndpointUrl(port),
BaseEnv.constructProperties(username, password, sqlDialect));
} catch (ClassNotFoundException e) {
e.printStackTrace();
@@ -170,9 +175,7 @@ public class RemoteServerEnv implements BaseEnv {
connection =
DriverManager.getConnection(
Config.IOTDB_URL_PREFIX
- + ip_addr
- + ":"
- + port
+ + remoteEndpointUrl(port)
+ "?"
+ VERSION
+ "="
@@ -300,7 +303,7 @@ public class RemoteServerEnv implements BaseEnv {
@Override
public ITableSession getTableSessionConnection() throws
IoTDBConnectionException {
return new TableSessionBuilder()
- .nodeUrls(Collections.singletonList(ip_addr + ":" + port))
+ .nodeUrls(Collections.singletonList(remoteEndpointUrl(port)))
.build();
}
@@ -308,7 +311,7 @@ public class RemoteServerEnv implements BaseEnv {
public ITableSession getTableSessionConnectionWithDB(String database)
throws IoTDBConnectionException {
return new TableSessionBuilder()
- .nodeUrls(Collections.singletonList(ip_addr + ":" + port))
+ .nodeUrls(Collections.singletonList(remoteEndpointUrl(port)))
.database(database)
.build();
}
@@ -332,7 +335,7 @@ public class RemoteServerEnv implements BaseEnv {
public ITableSession getTableSessionConnection(String userName, String
password)
throws IoTDBConnectionException {
return new TableSessionBuilder()
- .nodeUrls(Collections.singletonList(ip_addr + ":" + port))
+ .nodeUrls(Collections.singletonList(remoteEndpointUrl(port)))
.username(userName)
.password(password)
.build();
@@ -356,7 +359,7 @@ public class RemoteServerEnv implements BaseEnv {
public ISession getSessionConnection(List<String> nodeUrls) throws
IoTDBConnectionException {
Session session =
new Session.Builder()
- .nodeUrls(Collections.singletonList(ip_addr + ":" + port))
+ .nodeUrls(Collections.singletonList(remoteEndpointUrl(port)))
.username(SessionConfig.DEFAULT_USER)
.password(SessionConfig.DEFAULT_PASSWORD)
.fetchSize(SessionConfig.DEFAULT_FETCH_SIZE)
diff --git
a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBIPv6ClusterIT.java
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBIPv6ClusterIT.java
new file mode 100644
index 00000000000..c01e89c3b98
--- /dev/null
+++
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBIPv6ClusterIT.java
@@ -0,0 +1,124 @@
+/*
+ * 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.iotdb.db.it;
+
+import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.client.sync.SyncConfigNodeIServiceClient;
+import org.apache.iotdb.confignode.rpc.thrift.TShowClusterResp;
+import org.apache.iotdb.consensus.ConsensusFactory;
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.env.cluster.node.ConfigNodeWrapper;
+import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.it.utils.IPv6TestUtils;
+import org.apache.iotdb.itbase.category.ClusterIT;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+import static org.apache.iotdb.it.utils.IPv6TestUtils.IPV6_LOOPBACK_ADDRESS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({ClusterIT.class})
+public class IoTDBIPv6ClusterIT {
+
+ private static String previousTestNodeAddress;
+
+ @BeforeClass
+ public static void setUp() {
+ IPv6TestUtils.assumeIPv6LoopbackAvailable();
+ previousTestNodeAddress = IPv6TestUtils.setTestNodeAddressToIPv6Loopback();
+ EnvFactory.getEnv()
+ .getConfig()
+ .getCommonConfig()
+
.setSchemaRegionConsensusProtocolClass(ConsensusFactory.RATIS_CONSENSUS)
+ .setDataRegionConsensusProtocolClass(ConsensusFactory.IOT_CONSENSUS)
+ .setSchemaReplicationFactor(3)
+ .setDataReplicationFactor(3);
+ EnvFactory.getEnv().initClusterEnvironment(1, 3);
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ EnvFactory.getEnv().cleanClusterEnvironment();
+ IPv6TestUtils.restoreTestNodeAddress(previousTestNodeAddress);
+ }
+
+ @Test
+ public void clusterCanCommunicateThroughIPv6Loopback() throws Exception {
+ for (ConfigNodeWrapper configNode :
EnvFactory.getEnv().getConfigNodeWrapperList()) {
+ assertEquals(IPV6_LOOPBACK_ADDRESS, configNode.getIp());
+ assertTrue(configNode.getIpAndPortString().startsWith("[::1]:"));
+ }
+ for (DataNodeWrapper dataNode :
EnvFactory.getEnv().getDataNodeWrapperList()) {
+ assertEquals(IPV6_LOOPBACK_ADDRESS, dataNode.getIp());
+ assertTrue(dataNode.getIpAndPortString().startsWith("[::1]:"));
+ }
+
+ try (SyncConfigNodeIServiceClient client =
+ (SyncConfigNodeIServiceClient)
EnvFactory.getEnv().getLeaderConfigNodeConnection()) {
+ final TShowClusterResp showClusterResp = client.showCluster();
+ final TSStatus status = showClusterResp.getStatus();
+ assertEquals(TSStatusCode.SUCCESS_STATUS.getStatusCode(),
status.getCode());
+ assertEquals(1, showClusterResp.getConfigNodeListSize());
+ assertEquals(3, showClusterResp.getDataNodeListSize());
+ showClusterResp
+ .getConfigNodeList()
+ .forEach(
+ configNode ->
+ assertEquals(IPV6_LOOPBACK_ADDRESS,
configNode.getInternalEndPoint().getIp()));
+
showClusterResp.getDataNodeList().forEach(this::assertDataNodeLocationUsesIPv6);
+ }
+
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ statement.execute("CREATE DATABASE root.ipv6_cluster");
+ statement.execute("CREATE TIMESERIES root.ipv6_cluster.d1.s1 INT64");
+ statement.execute("INSERT INTO root.ipv6_cluster.d1(time, s1) VALUES (1,
300)");
+ try (ResultSet resultSet = statement.executeQuery("SELECT s1 FROM
root.ipv6_cluster.d1")) {
+ assertTrue(resultSet.next());
+ assertEquals(1L, resultSet.getLong(1));
+ assertEquals(300L, resultSet.getLong(2));
+ assertFalse(resultSet.next());
+ }
+ }
+ }
+
+ private void assertDataNodeLocationUsesIPv6(final TDataNodeLocation
dataNodeLocation) {
+ assertEquals(IPV6_LOOPBACK_ADDRESS,
dataNodeLocation.getClientRpcEndPoint().getIp());
+ assertEquals(IPV6_LOOPBACK_ADDRESS,
dataNodeLocation.getInternalEndPoint().getIp());
+ assertEquals(IPV6_LOOPBACK_ADDRESS,
dataNodeLocation.getMPPDataExchangeEndPoint().getIp());
+ assertEquals(
+ IPV6_LOOPBACK_ADDRESS,
dataNodeLocation.getSchemaRegionConsensusEndPoint().getIp());
+ assertEquals(IPV6_LOOPBACK_ADDRESS,
dataNodeLocation.getDataRegionConsensusEndPoint().getIp());
+ }
+}
diff --git
a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBIPv6ExternalServiceIT.java
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBIPv6ExternalServiceIT.java
new file mode 100644
index 00000000000..e63d428992c
--- /dev/null
+++
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBIPv6ExternalServiceIT.java
@@ -0,0 +1,220 @@
+/*
+ * 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.iotdb.db.it;
+
+import org.apache.iotdb.isession.ISession;
+import org.apache.iotdb.isession.SessionDataSet;
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.it.utils.IPv6TestUtils;
+import org.apache.iotdb.itbase.category.ClusterIT;
+import org.apache.iotdb.itbase.category.LocalStandaloneIT;
+import org.apache.iotdb.rpc.StatementExecutionException;
+import org.apache.iotdb.rpc.UrlUtils;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+import org.apache.tsfile.read.common.Field;
+import org.apache.tsfile.read.common.RowRecord;
+import org.awaitility.Awaitility;
+import org.fusesource.mqtt.client.BlockingConnection;
+import org.fusesource.mqtt.client.MQTT;
+import org.fusesource.mqtt.client.QoS;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.iotdb.it.utils.IPv6TestUtils.IPV6_LOOPBACK_ADDRESS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({LocalStandaloneIT.class, ClusterIT.class})
+public class IoTDBIPv6ExternalServiceIT {
+
+ private static final String USER = "root";
+ private static final String PASSWORD = "root";
+
+ private static String previousTestNodeAddress;
+
+ @BeforeClass
+ public static void setUp() {
+ IPv6TestUtils.assumeIPv6LoopbackAvailable();
+ previousTestNodeAddress = IPv6TestUtils.setTestNodeAddressToIPv6Loopback();
+
EnvFactory.getEnv().getConfig().getDataNodeConfig().setEnableRestService(true);
+ EnvFactory.getEnv()
+ .getConfig()
+ .getDataNodeConfig()
+ .setEnableMQTTService(true)
+ .setMqttPayloadFormatter("json");
+ EnvFactory.getEnv().initClusterEnvironment(1, 1);
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ try {
+ EnvFactory.getEnv().cleanClusterEnvironment();
+ } finally {
+ IPv6TestUtils.restoreTestNodeAddress(previousTestNodeAddress);
+ }
+ }
+
+ @Test
+ public void restServiceCanCommunicateThroughIPv6Loopback() throws Exception {
+ final DataNodeWrapper dataNode = EnvFactory.getEnv().getDataNodeWrapper(0);
+ assertEquals(IPV6_LOOPBACK_ADDRESS, dataNode.getIp());
+ final String restAddress =
+ UrlUtils.formatTEndPointIpv4AndIpv6Url(dataNode.getIp(),
dataNode.getRestServicePort());
+
+ try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
+ Awaitility.await()
+ .atMost(30, TimeUnit.SECONDS)
+ .pollInterval(1, TimeUnit.SECONDS)
+ .ignoreExceptions()
+ .untilAsserted(() -> assertRestPingSucceeds(httpClient,
restAddress));
+
+ executeRestNonQuery(
+ httpClient, restAddress, "CREATE TIMESERIES root.ipv6_rest.d1.s1
WITH DATATYPE=INT64");
+ executeRestNonQuery(
+ httpClient, restAddress, "INSERT INTO root.ipv6_rest.d1(time, s1)
VALUES (1, 100)");
+ }
+
+ try (ISession session = EnvFactory.getEnv().getSessionConnection();
+ SessionDataSet dataSet =
+ session.executeQueryStatement("SELECT s1 FROM root.ipv6_rest.d1"))
{
+ assertTrue(dataSet.hasNext());
+ final RowRecord row = dataSet.next();
+ assertEquals(1L, row.getTimestamp());
+ assertEquals(100L, row.getFields().get(0).getLongV());
+ assertFalse(dataSet.hasNext());
+ }
+ }
+
+ @Test
+ public void mqttServiceCanCommunicateThroughIPv6Loopback() throws Exception {
+ final DataNodeWrapper dataNode = EnvFactory.getEnv().getDataNodeWrapper(0);
+ assertEquals(IPV6_LOOPBACK_ADDRESS, dataNode.getIp());
+ final String mqttAddress =
+ UrlUtils.formatTEndPointIpv4AndIpv6Url(dataNode.getIp(),
dataNode.getMqttPort());
+
+ final MQTT mqtt = new MQTT();
+ mqtt.setHost("tcp://" + mqttAddress);
+ mqtt.setUserName(USER);
+ mqtt.setPassword(PASSWORD);
+ mqtt.setConnectAttemptsMax(3);
+ mqtt.setReconnectDelay(1000);
+ mqtt.setClientId("ipv6Client");
+
+ final byte[] payload =
+ ("{"
+ + "\"device\":\"root.ipv6_mqtt.d1\","
+ + "\"timestamp\":1,"
+ + "\"measurements\":[\"s1\"],"
+ + "\"values\":[200.0]"
+ + "}")
+ .getBytes(StandardCharsets.UTF_8);
+ final BlockingConnection connection = mqtt.blockingConnection();
+ try {
+ connection.connect();
+ try (ISession session = EnvFactory.getEnv().getSessionConnection()) {
+ Awaitility.await()
+ .atMost(30, TimeUnit.SECONDS)
+ .pollInterval(1, TimeUnit.SECONDS)
+ .until(
+ () -> {
+ connection.publish("root.ipv6_mqtt.d1", payload,
QoS.AT_LEAST_ONCE, false);
+ return mqttValueIsVisible(session);
+ });
+ }
+ } finally {
+ if (connection.isConnected()) {
+ connection.disconnect();
+ }
+ }
+ }
+
+ private static void assertRestPingSucceeds(
+ final CloseableHttpClient httpClient, final String restAddress) throws
IOException {
+ try (CloseableHttpResponse response =
+ httpClient.execute(new HttpGet("http://" + restAddress + "/ping"))) {
+ assertEquals(200, response.getStatusLine().getStatusCode());
+ final JsonObject result =
+ JsonParser.parseString(EntityUtils.toString(response.getEntity(),
StandardCharsets.UTF_8))
+ .getAsJsonObject();
+ assertEquals(200, result.get("code").getAsInt());
+ }
+ }
+
+ private static void executeRestNonQuery(
+ final CloseableHttpClient httpClient, final String restAddress, final
String sql)
+ throws IOException {
+ final HttpPost httpPost = new HttpPost("http://" + restAddress +
"/rest/v2/nonQuery");
+ httpPost.addHeader("Content-type", "application/json; charset=utf-8");
+ httpPost.setHeader("Accept", "application/json");
+ httpPost.setHeader(
+ "Authorization",
+ Base64.getEncoder()
+ .encodeToString((USER + ":" +
PASSWORD).getBytes(StandardCharsets.UTF_8)));
+ final JsonObject request = new JsonObject();
+ request.addProperty("sql", sql);
+ httpPost.setEntity(new StringEntity(request.toString(),
StandardCharsets.UTF_8));
+
+ try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+ assertEquals(200, response.getStatusLine().getStatusCode());
+ final JsonObject result =
+ JsonParser.parseString(EntityUtils.toString(response.getEntity(),
StandardCharsets.UTF_8))
+ .getAsJsonObject();
+ assertEquals(200, result.get("code").getAsInt());
+ }
+ }
+
+ private static boolean mqttValueIsVisible(final ISession session) throws
Exception {
+ try (SessionDataSet dataSet =
+ session.executeQueryStatement("SELECT s1 FROM root.ipv6_mqtt.d1 WHERE
time = 1")) {
+ if (!dataSet.hasNext()) {
+ return false;
+ }
+ final List<Field> fields = dataSet.next().getFields();
+ return fields.size() == 1 && Math.abs(fields.get(0).getDoubleV() -
200.0) < 0.001;
+ } catch (StatementExecutionException e) {
+ if (e.getMessage() != null && e.getMessage().contains("does not exist"))
{
+ return false;
+ }
+ throw e;
+ }
+ }
+}
diff --git
a/integration-test/src/test/java/org/apache/iotdb/db/it/iotconsensusv2/IoTDBIoTConsensusV23C3DBasicITBase.java
b/integration-test/src/test/java/org/apache/iotdb/db/it/iotconsensusv2/IoTDBIoTConsensusV23C3DBasicITBase.java
index e07e6e4ebc1..094b5aebb94 100644
---
a/integration-test/src/test/java/org/apache/iotdb/db/it/iotconsensusv2/IoTDBIoTConsensusV23C3DBasicITBase.java
+++
b/integration-test/src/test/java/org/apache/iotdb/db/it/iotconsensusv2/IoTDBIoTConsensusV23C3DBasicITBase.java
@@ -25,6 +25,7 @@ import org.apache.iotdb.isession.SessionConfig;
import org.apache.iotdb.it.env.EnvFactory;
import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper;
import org.apache.iotdb.itbase.env.BaseEnv;
+import org.apache.iotdb.rpc.UrlUtils;
import org.apache.tsfile.utils.Pair;
import org.awaitility.Awaitility;
@@ -283,7 +284,9 @@ public abstract class IoTDBIoTConsensusV23C3DBasicITBase
LOGGER.info("Step 6: Verifying schema consistency on each DataNode
independently...");
List<DataNodeWrapper> dataNodeWrappers =
EnvFactory.getEnv().getDataNodeWrapperList();
for (DataNodeWrapper wrapper : dataNodeWrappers) {
- String nodeDescription = "DataNode " + wrapper.getIp() + ":" +
wrapper.getPort();
+ String nodeDescription =
+ "DataNode "
+ + UrlUtils.formatTEndPointIpv4AndIpv6Url(wrapper.getIp(),
wrapper.getPort());
LOGGER.info("Verifying schema on {}", nodeDescription);
Awaitility.await()
.atMost(60, TimeUnit.SECONDS)
@@ -307,7 +310,10 @@ public abstract class IoTDBIoTConsensusV23C3DBasicITBase
LOGGER.info(
"Step 7: Stopping each DataNode in turn and verifying remaining
nodes show consistent schema...");
for (DataNodeWrapper stoppedNode : dataNodeWrappers) {
- String stoppedDesc = "DataNode " + stoppedNode.getIp() + ":" +
stoppedNode.getPort();
+ String stoppedDesc =
+ "DataNode "
+ + UrlUtils.formatTEndPointIpv4AndIpv6Url(
+ stoppedNode.getIp(), stoppedNode.getPort());
LOGGER.info("Stopping {}", stoppedDesc);
stoppedNode.stopForcibly();
Assert.assertFalse(stoppedDesc + " should be stopped",
stoppedNode.isAlive());
@@ -318,7 +324,10 @@ public abstract class IoTDBIoTConsensusV23C3DBasicITBase
if (aliveNode == stoppedNode) {
continue;
}
- String aliveDesc = "DataNode " + aliveNode.getIp() + ":" +
aliveNode.getPort();
+ String aliveDesc =
+ "DataNode "
+ + UrlUtils.formatTEndPointIpv4AndIpv6Url(
+ aliveNode.getIp(), aliveNode.getPort());
Awaitility.await()
.pollDelay(1, TimeUnit.SECONDS)
.atMost(90, TimeUnit.SECONDS)
@@ -446,7 +455,9 @@ public abstract class IoTDBIoTConsensusV23C3DBasicITBase
protected void waitForReplicationComplete(DataNodeWrapper leaderNode) {
final long timeoutSeconds = 120;
final String metricsUrl =
- "http://" + leaderNode.getIp() + ":" + leaderNode.getMetricPort() +
"/metrics";
+ "http://"
+ + UrlUtils.formatTEndPointIpv4AndIpv6Url(leaderNode.getIp(),
leaderNode.getMetricPort())
+ + "/metrics";
LOGGER.info(
"Waiting for consensus pipe syncLag to reach 0 on leader DataNode
(url: {}, timeout: {}s)...",
metricsUrl,
diff --git
a/integration-test/src/test/java/org/apache/iotdb/it/utils/IPv6TestUtils.java
b/integration-test/src/test/java/org/apache/iotdb/it/utils/IPv6TestUtils.java
new file mode 100644
index 00000000000..aed309c1cce
--- /dev/null
+++
b/integration-test/src/test/java/org/apache/iotdb/it/utils/IPv6TestUtils.java
@@ -0,0 +1,60 @@
+/*
+ * 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.iotdb.it.utils;
+
+import org.junit.Assume;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+
+import static
org.apache.iotdb.it.env.cluster.ClusterConstant.TEST_NODE_ADDRESS;
+
+public final class IPv6TestUtils {
+
+ public static final String IPV6_LOOPBACK_ADDRESS = "::1";
+
+ private IPv6TestUtils() {
+ // Utility class
+ }
+
+ public static void assumeIPv6LoopbackAvailable() {
+ try (ServerSocket socket = new ServerSocket()) {
+ socket.bind(new
InetSocketAddress(InetAddress.getByName(IPV6_LOOPBACK_ADDRESS), 0));
+ } catch (IOException e) {
+ Assume.assumeNoException("IPv6 loopback is not available", e);
+ }
+ }
+
+ public static String setTestNodeAddressToIPv6Loopback() {
+ final String previousAddress = System.getProperty(TEST_NODE_ADDRESS);
+ System.setProperty(TEST_NODE_ADDRESS, IPV6_LOOPBACK_ADDRESS);
+ return previousAddress;
+ }
+
+ public static void restoreTestNodeAddress(final String previousAddress) {
+ if (previousAddress == null) {
+ System.clearProperty(TEST_NODE_ADDRESS);
+ } else {
+ System.setProperty(TEST_NODE_ADDRESS, previousAddress);
+ }
+ }
+}
diff --git
a/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/tablemodel/manual/enhanced/IoTDBPipeClusterIT.java
b/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/tablemodel/manual/enhanced/IoTDBPipeClusterIT.java
index c99eb5d941a..a5e505f4fd7 100644
---
a/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/tablemodel/manual/enhanced/IoTDBPipeClusterIT.java
+++
b/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/tablemodel/manual/enhanced/IoTDBPipeClusterIT.java
@@ -130,7 +130,7 @@ public class IoTDBPipeClusterIT extends
AbstractPipeTableModelDualManualIT {
private void testMachineDowntime(String sink) {
StringBuilder a = new StringBuilder();
for (DataNodeWrapper nodeWrapper : receiverEnv.getDataNodeWrapperList()) {
- a.append(nodeWrapper.getIp()).append(":").append(nodeWrapper.getPort());
+ a.append(nodeWrapper.getIpAndPortString());
a.append(",");
}
a.deleteCharAt(a.length() - 1);
diff --git
a/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/basic/IoTDBPipeIPv6IT.java
b/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/basic/IoTDBPipeIPv6IT.java
new file mode 100644
index 00000000000..716d188c2b1
--- /dev/null
+++
b/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/basic/IoTDBPipeIPv6IT.java
@@ -0,0 +1,166 @@
+/*
+ * 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.iotdb.pipe.it.dual.treemodel.auto.basic;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.client.sync.SyncConfigNodeIServiceClient;
+import org.apache.iotdb.commons.pipe.agent.plugin.builtin.BuiltinPipePlugin;
+import org.apache.iotdb.confignode.rpc.thrift.TCreatePipeReq;
+import org.apache.iotdb.db.it.utils.TestUtils;
+import org.apache.iotdb.it.env.MultiEnvFactory;
+import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.it.utils.IPv6TestUtils;
+import org.apache.iotdb.itbase.category.MultiClusterIT2DualTreeAutoBasic;
+import
org.apache.iotdb.pipe.it.dual.treemodel.auto.AbstractPipeDualTreeModelAutoIT;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.iotdb.it.utils.IPv6TestUtils.IPV6_LOOPBACK_ADDRESS;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({MultiClusterIT2DualTreeAutoBasic.class})
+public class IoTDBPipeIPv6IT extends AbstractPipeDualTreeModelAutoIT {
+
+ private static String previousTestNodeAddress;
+
+ @BeforeClass
+ public static void setUpIPv6() {
+ IPv6TestUtils.assumeIPv6LoopbackAvailable();
+ previousTestNodeAddress = IPv6TestUtils.setTestNodeAddressToIPv6Loopback();
+ }
+
+ @AfterClass
+ public static void tearDownIPv6() {
+ IPv6TestUtils.restoreTestNodeAddress(previousTestNodeAddress);
+ }
+
+ @Override
+ @Before
+ public void setUp() {
+ MultiEnvFactory.createEnv(2);
+ senderEnv = MultiEnvFactory.getEnv(0);
+ receiverEnv = MultiEnvFactory.getEnv(1);
+ setupConfig();
+ senderEnv.initClusterEnvironment(1, 1);
+ receiverEnv.initClusterEnvironment(1, 1);
+ }
+
+ @Override
+ protected void setupConfig() {
+ super.setupConfig();
+ senderEnv
+ .getConfig()
+ .getCommonConfig()
+ .setDataReplicationFactor(1)
+ .setSchemaReplicationFactor(1);
+ receiverEnv
+ .getConfig()
+ .getCommonConfig()
+ .setDataReplicationFactor(1)
+ .setSchemaReplicationFactor(1);
+ }
+
+ @Test
+ public void testSyncAndAsyncSinksThroughIPv6Loopback() throws Exception {
+ assertDataNodesUseIPv6(senderEnv.getDataNodeWrapperList());
+ assertDataNodesUseIPv6(receiverEnv.getDataNodeWrapperList());
+
+ final String receiverNodeUrl =
receiverEnv.getDataNodeWrapper(0).getIpAndPortString();
+ Assert.assertTrue(receiverNodeUrl.startsWith("[::1]:"));
+
+ try (final SyncConfigNodeIServiceClient client =
+ (SyncConfigNodeIServiceClient)
senderEnv.getLeaderConfigNodeConnection()) {
+ createAndStartPipe(
+ client,
+ "ipv6_async_pipe",
+ "root.ipv6_async.**",
+ BuiltinPipePlugin.IOTDB_THRIFT_ASYNC_CONNECTOR.getPipePluginName(),
+ receiverNodeUrl);
+ createAndStartPipe(
+ client,
+ "ipv6_sync_pipe",
+ "root.ipv6_sync.**",
+ BuiltinPipePlugin.IOTDB_THRIFT_SYNC_CONNECTOR.getPipePluginName(),
+ receiverNodeUrl);
+ }
+
+ TestUtils.executeNonQueries(
+ senderEnv,
+ Arrays.asList(
+ "insert into root.ipv6_async.d1(time, s1) values (1, 1)",
+ "insert into root.ipv6_sync.d1(time, s1) values (1, 1)",
+ "flush"),
+ null);
+
+ TestUtils.assertDataEventuallyOnEnv(
+ receiverEnv,
+ "select count(s1) from root.ipv6_async.d1",
+ "count(root.ipv6_async.d1.s1),",
+ Collections.singleton("1,"));
+ TestUtils.assertDataEventuallyOnEnv(
+ receiverEnv,
+ "select count(s1) from root.ipv6_sync.d1",
+ "count(root.ipv6_sync.d1.s1),",
+ Collections.singleton("1,"));
+ }
+
+ private static void createAndStartPipe(
+ final SyncConfigNodeIServiceClient client,
+ final String pipeName,
+ final String sourcePath,
+ final String sinkName,
+ final String receiverNodeUrl)
+ throws Exception {
+ final Map<String, String> sourceAttributes = new HashMap<>();
+ sourceAttributes.put("source.path", sourcePath);
+
+ final Map<String, String> sinkAttributes = new HashMap<>();
+ sinkAttributes.put("sink", sinkName);
+ sinkAttributes.put("sink.batch.enable", "false");
+ sinkAttributes.put("sink.node-urls", receiverNodeUrl);
+
+ final TSStatus status =
+ client.createPipe(
+ new TCreatePipeReq(pipeName,
sinkAttributes).setExtractorAttributes(sourceAttributes));
+ Assert.assertEquals(TSStatusCode.SUCCESS_STATUS.getStatusCode(),
status.getCode());
+ Assert.assertEquals(
+ TSStatusCode.SUCCESS_STATUS.getStatusCode(),
client.startPipe(pipeName).getCode());
+ }
+
+ private static void assertDataNodesUseIPv6(final Iterable<DataNodeWrapper>
dataNodes) {
+ for (final DataNodeWrapper dataNode : dataNodes) {
+ Assert.assertEquals(IPV6_LOOPBACK_ADDRESS, dataNode.getIp());
+ Assert.assertTrue(dataNode.getIpAndPortString().startsWith("[::1]:"));
+ }
+ }
+}
diff --git
a/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/enhanced/IoTDBPipeClusterIT.java
b/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/enhanced/IoTDBPipeClusterIT.java
index 15ae9fca4c5..529147a77b0 100644
---
a/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/enhanced/IoTDBPipeClusterIT.java
+++
b/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/enhanced/IoTDBPipeClusterIT.java
@@ -134,7 +134,7 @@ public class IoTDBPipeClusterIT extends
AbstractPipeDualTreeModelAutoIT {
private void testMachineDowntime(String sink) {
StringBuilder a = new StringBuilder();
for (DataNodeWrapper nodeWrapper : receiverEnv.getDataNodeWrapperList()) {
- a.append(nodeWrapper.getIp()).append(":").append(nodeWrapper.getPort());
+ a.append(nodeWrapper.getIpAndPortString());
a.append(",");
}
a.deleteCharAt(a.length() - 1);
diff --git
a/integration-test/src/test/java/org/apache/iotdb/session/it/IoTDBIPv6ClientIT.java
b/integration-test/src/test/java/org/apache/iotdb/session/it/IoTDBIPv6ClientIT.java
new file mode 100644
index 00000000000..32d0d059ac7
--- /dev/null
+++
b/integration-test/src/test/java/org/apache/iotdb/session/it/IoTDBIPv6ClientIT.java
@@ -0,0 +1,142 @@
+/*
+ * 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.iotdb.session.it;
+
+import org.apache.iotdb.isession.ISession;
+import org.apache.iotdb.isession.ITableSession;
+import org.apache.iotdb.isession.SessionDataSet;
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.it.utils.IPv6TestUtils;
+import org.apache.iotdb.itbase.category.ClusterIT;
+import org.apache.iotdb.itbase.category.LocalStandaloneIT;
+import org.apache.iotdb.jdbc.Config;
+import org.apache.iotdb.rpc.IoTDBConnectionException;
+import org.apache.iotdb.rpc.StatementExecutionException;
+
+import org.apache.tsfile.read.common.RowRecord;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.Collections;
+
+import static org.apache.iotdb.it.utils.IPv6TestUtils.IPV6_LOOPBACK_ADDRESS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({LocalStandaloneIT.class, ClusterIT.class})
+public class IoTDBIPv6ClientIT {
+
+ private static String previousTestNodeAddress;
+
+ @BeforeClass
+ public static void setUp() {
+ IPv6TestUtils.assumeIPv6LoopbackAvailable();
+ previousTestNodeAddress = IPv6TestUtils.setTestNodeAddressToIPv6Loopback();
+ EnvFactory.getEnv().initClusterEnvironment(1, 1);
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ EnvFactory.getEnv().cleanClusterEnvironment();
+ IPv6TestUtils.restoreTestNodeAddress(previousTestNodeAddress);
+ }
+
+ @Test
+ public void clientsCanConnectThroughIPv6Loopback() throws Exception {
+ final DataNodeWrapper dataNode = EnvFactory.getEnv().getDataNodeWrapper(0);
+ assertEquals(IPV6_LOOPBACK_ADDRESS, dataNode.getIp());
+ assertTrue(dataNode.getIpAndPortString().startsWith("[::1]:"));
+
+ try (Connection connection =
+ DriverManager.getConnection(
+ Config.IOTDB_URL_PREFIX + dataNode.getIpAndPortString(),
+ System.getProperty("User", "root"),
+ System.getProperty("Password", "root"));
+ Statement statement = connection.createStatement()) {
+ statement.execute("CREATE DATABASE root.ipv6_client");
+ statement.execute("CREATE TIMESERIES root.ipv6_client.d1.s1 INT64");
+ statement.execute("INSERT INTO root.ipv6_client.d1(time, s1) VALUES (1,
100)");
+ try (ResultSet resultSet = statement.executeQuery("SELECT s1 FROM
root.ipv6_client.d1")) {
+ assertTrue(resultSet.next());
+ assertEquals(1L, resultSet.getLong(1));
+ assertEquals(100L, resultSet.getLong(2));
+ assertFalse(resultSet.next());
+ }
+ }
+
+ try (ISession session =
+ EnvFactory.getEnv()
+
.getSessionConnection(Collections.singletonList(dataNode.getIpAndPortString())))
{
+ try (SessionDataSet dataSet =
+ session.executeQueryStatement("SELECT s1 FROM root.ipv6_client.d1"))
{
+ Assert.assertTrue(dataSet.hasNext());
+ final RowRecord record = dataSet.next();
+ assertEquals(1L, record.getTimestamp());
+ assertEquals(100L, record.getFields().get(0).getLongV());
+ Assert.assertFalse(dataSet.hasNext());
+ }
+ }
+
+ try (ISession session = EnvFactory.getEnv().getSessionConnection()) {
+ try (SessionDataSet dataSet =
+ session.executeQueryStatement("SELECT s1 FROM root.ipv6_client.d1"))
{
+ Assert.assertTrue(dataSet.hasNext());
+ final RowRecord record = dataSet.next();
+ assertEquals(1L, record.getTimestamp());
+ assertEquals(100L, record.getFields().get(0).getLongV());
+ Assert.assertFalse(dataSet.hasNext());
+ }
+ }
+
+ verifyTableSession(dataNode);
+ }
+
+ private static void verifyTableSession(final DataNodeWrapper dataNode)
+ throws IoTDBConnectionException, StatementExecutionException {
+ try (ITableSession session =
+ EnvFactory.getEnv()
+
.getTableSessionConnection(Collections.singletonList(dataNode.getIpAndPortString())))
{
+ session.executeNonQueryStatement("CREATE DATABASE IF NOT EXISTS
ipv6_table");
+ session.executeNonQueryStatement("USE ipv6_table");
+ session.executeNonQueryStatement("CREATE TABLE table1(tag1 STRING TAG,
s1 INT64 FIELD)");
+ session.executeNonQueryStatement("INSERT INTO table1(time, tag1, s1)
VALUES (1, 'd1', 200)");
+ try (SessionDataSet dataSet =
+ session.executeQueryStatement("SELECT time, s1 FROM table1 WHERE
tag1 = 'd1'")) {
+ Assert.assertTrue(dataSet.hasNext());
+ final RowRecord record = dataSet.next();
+ assertEquals(1L, record.getFields().get(0).getLongV());
+ assertEquals(200L, record.getFields().get(1).getLongV());
+ Assert.assertFalse(dataSet.hasNext());
+ }
+ }
+ }
+}
diff --git a/iotdb-client/client-cpp/src/rpc/NodesSupplier.cpp
b/iotdb-client/client-cpp/src/rpc/NodesSupplier.cpp
index 604099a82d1..55fd13a08f4 100644
--- a/iotdb-client/client-cpp/src/rpc/NodesSupplier.cpp
+++ b/iotdb-client/client-cpp/src/rpc/NodesSupplier.cpp
@@ -17,6 +17,7 @@
* under the License.
*/
#include "NodesSupplier.h"
+#include "RpcCommon.h"
#include "Session.h"
#include "SessionDataSet.h"
#include <algorithm>
@@ -188,7 +189,7 @@ std::vector<TEndPoint>
NodesSupplier::fetchLatestEndpoints() {
port = record->fields.at(columnPortIdx).intV.value();
}
- if (ip == "0.0.0.0") {
+ if (UrlUtils::isWildcardAddress(ip)) {
log_warn("Skipping invalid node: " + ip + ":" +
std::to_string(port));
continue;
}
@@ -230,4 +231,4 @@ void NodesSupplier::stopBackgroundRefresh() noexcept {
refreshThread_.join();
}
}
-}
\ No newline at end of file
+}
diff --git a/iotdb-client/client-cpp/src/rpc/RpcCommon.cpp
b/iotdb-client/client-cpp/src/rpc/RpcCommon.cpp
index c83b39a5fb5..18c97b4420c 100644
--- a/iotdb-client/client-cpp/src/rpc/RpcCommon.cpp
+++ b/iotdb-client/client-cpp/src/rpc/RpcCommon.cpp
@@ -212,3 +212,26 @@ TEndPoint UrlUtils::parseTEndPointIpv4AndIpv6Url(const
string& endPointUrl) {
return endPoint;
}
+
+bool UrlUtils::isWildcardAddress(const string& host) {
+ if (host.empty()) {
+ return false;
+ }
+
+ const bool bracketed = host.size() >= 2 && host.front() == '[' &&
host.back() == ']';
+ const size_t begin = bracketed ? 1 : 0;
+ const size_t end = bracketed ? host.size() - 1 : host.size();
+ if (end - begin == 7 && host.compare(begin, end - begin, "0.0.0.0") == 0) {
+ return true;
+ }
+
+ bool hasColon = false;
+ for (size_t index = begin; index < end; ++index) {
+ if (host[index] == ':') {
+ hasColon = true;
+ } else if (host[index] != '0') {
+ return false;
+ }
+ }
+ return hasColon;
+}
diff --git a/iotdb-client/client-cpp/src/rpc/RpcCommon.h
b/iotdb-client/client-cpp/src/rpc/RpcCommon.h
index 721150bd6ca..48bff46ffe6 100644
--- a/iotdb-client/client-cpp/src/rpc/RpcCommon.h
+++ b/iotdb-client/client-cpp/src/rpc/RpcCommon.h
@@ -76,6 +76,8 @@ private:
public:
static TEndPoint parseTEndPointIpv4AndIpv6Url(const std::string&
endPointUrl);
+
+ static bool isWildcardAddress(const std::string& host);
};
#endif
diff --git a/iotdb-client/client-cpp/src/session/Session.cpp
b/iotdb-client/client-cpp/src/session/Session.cpp
index 7dd0a7813ed..cf429af0c4d 100644
--- a/iotdb-client/client-cpp/src/session/Session.cpp
+++ b/iotdb-client/client-cpp/src/session/Session.cpp
@@ -2096,7 +2096,7 @@ void Session::Impl::handleQueryRedirection(TEndPoint
endPoint) {
void Session::Impl::handleRedirection(const std::string& deviceId, TEndPoint
endPoint) {
if (!enableRedirection_)
return;
- if (endPoint.ip == "0.0.0.0")
+ if (UrlUtils::isWildcardAddress(endPoint.ip))
return;
getDefaultSessionConnection();
deviceIdToEndpoint[deviceId] = endPoint;
@@ -2122,7 +2122,7 @@ void Session::Impl::handleRedirection(const
std::shared_ptr<storage::IDeviceID>&
TEndPoint endPoint) {
if (!enableRedirection_)
return;
- if (endPoint.ip == "0.0.0.0")
+ if (UrlUtils::isWildcardAddress(endPoint.ip))
return;
getDefaultSessionConnection();
tableModelDeviceIdToEndpoint[deviceId] = endPoint;
diff --git a/iotdb-client/client-cpp/test/cpp/sessionIT.cpp
b/iotdb-client/client-cpp/test/cpp/sessionIT.cpp
index 97c078d9d59..45624f0300d 100644
--- a/iotdb-client/client-cpp/test/cpp/sessionIT.cpp
+++ b/iotdb-client/client-cpp/test/cpp/sessionIT.cpp
@@ -907,6 +907,21 @@ TEST_CASE("UrlUtils - parseTEndPointIpv4AndIpv6Url",
"[UrlUtils]") {
}
}
+TEST_CASE("UrlUtils - isWildcardAddress", "[UrlUtils]") {
+ REQUIRE(UrlUtils::isWildcardAddress("0.0.0.0"));
+ REQUIRE(UrlUtils::isWildcardAddress("[0.0.0.0]"));
+ REQUIRE(UrlUtils::isWildcardAddress("::"));
+ REQUIRE(UrlUtils::isWildcardAddress("[::]"));
+ REQUIRE(UrlUtils::isWildcardAddress("0:0:0:0:0:0:0:0"));
+ REQUIRE(UrlUtils::isWildcardAddress("0::0"));
+
+ REQUIRE_FALSE(UrlUtils::isWildcardAddress(""));
+ REQUIRE_FALSE(UrlUtils::isWildcardAddress("127.0.0.1"));
+ REQUIRE_FALSE(UrlUtils::isWildcardAddress("localhost"));
+ REQUIRE_FALSE(UrlUtils::isWildcardAddress("::1"));
+ REQUIRE_FALSE(UrlUtils::isWildcardAddress("[::1]"));
+}
+
TEST_CASE("TsBlock deserialize rejects truncated malicious payload",
"[TsBlockDeserialize]") {
std::string data(18, '\0');
data[3] = '\x10';
diff --git a/iotdb-client/client-cpp/test/main.cpp
b/iotdb-client/client-cpp/test/main.cpp
index 1bc3425882f..474cb552abb 100644
--- a/iotdb-client/client-cpp/test/main.cpp
+++ b/iotdb-client/client-cpp/test/main.cpp
@@ -30,6 +30,9 @@ struct SessionListener : Catch::TestEventListenerBase {
using TestEventListenerBase::TestEventListenerBase;
void testCaseStarting(Catch::TestCaseInfo const& testInfo) override {
+ if (testInfo.tagsAsString().find("[UrlUtils]") != std::string::npos) {
+ return;
+ }
if (!session) {
SessionBuilder builder;
session = builder.host("127.0.0.1")
@@ -55,4 +58,4 @@ struct SessionListener : Catch::TestEventListenerBase {
}
};
-CATCH_REGISTER_LISTENER(SessionListener)
\ No newline at end of file
+CATCH_REGISTER_LISTENER(SessionListener)
diff --git a/iotdb-client/client-py/iotdb/Session.py
b/iotdb-client/client-py/iotdb/Session.py
index 0a5cb43fd54..e4ed38ab228 100644
--- a/iotdb-client/client-py/iotdb/Session.py
+++ b/iotdb-client/client-py/iotdb/Session.py
@@ -16,48 +16,50 @@
# under the License.
#
+import ipaddress
import logging
import random
-import sys
import struct
+import sys
import warnings
+
+from iotdb.utils.SessionDataSet import SessionDataSet
from thrift.protocol import TBinaryProtocol, TCompactProtocol
from thrift.transport import TSocket, TTransport
from tzlocal import get_localzone_name
-from iotdb.utils.SessionDataSet import SessionDataSet
from .template.Template import Template
from .template.TemplateQueryType import TemplateQueryType
from .thrift.common.ttypes import TEndPoint
from .thrift.rpc.IClientRPCService import (
Client,
- TSCreateTimeseriesReq,
+ TSAppendSchemaTemplateReq,
+ TSCloseSessionReq,
TSCreateAlignedTimeseriesReq,
+ TSCreateMultiTimeseriesReq,
+ TSCreateSchemaTemplateReq,
+ TSCreateTimeseriesReq,
+ TSDropSchemaTemplateReq,
+ TSExecuteStatementReq,
TSInsertRecordReq,
+ TSInsertRecordsOfOneDeviceReq,
+ TSInsertRecordsReq,
TSInsertStringRecordReq,
TSInsertTabletReq,
- TSExecuteStatementReq,
- TSOpenSessionReq,
- TSCreateMultiTimeseriesReq,
- TSCloseSessionReq,
TSInsertTabletsReq,
- TSInsertRecordsReq,
- TSInsertRecordsOfOneDeviceReq,
- TSCreateSchemaTemplateReq,
- TSDropSchemaTemplateReq,
- TSAppendSchemaTemplateReq,
+ TSOpenSessionReq,
TSPruneSchemaTemplateReq,
+ TSQueryTemplateReq,
TSSetSchemaTemplateReq,
TSUnsetSchemaTemplateReq,
- TSQueryTemplateReq,
)
from .thrift.rpc.ttypes import (
TSDeleteDataReq,
+ TSInsertStringRecordsOfOneDeviceReq,
+ TSLastDataQueryReq,
TSProtocolVersion,
- TSSetTimeZoneReq,
TSRawDataQueryReq,
- TSLastDataQueryReq,
- TSInsertStringRecordsOfOneDeviceReq,
+ TSSetTimeZoneReq,
)
from .tsfile.utils.date_utils import parse_date_to_int
from .utils import rpc_utils
@@ -67,6 +69,48 @@ logger = logging.getLogger("IoTDB")
warnings.simplefilter("always", DeprecationWarning)
+def _parse_endpoint_url(endpoint_url):
+ if endpoint_url.startswith("["):
+ end_index = endpoint_url.find("]")
+ if end_index <= 1 or end_index + 1 >= len(endpoint_url):
+ raise RuntimeError("invalid node url: {}".format(endpoint_url))
+ if endpoint_url[end_index + 1] != ":":
+ raise RuntimeError("invalid node url: {}".format(endpoint_url))
+ return endpoint_url[1:end_index], _parse_endpoint_port(
+ endpoint_url, endpoint_url[end_index + 2 :]
+ )
+
+ if "[" in endpoint_url or "]" in endpoint_url:
+ raise RuntimeError("invalid node url: {}".format(endpoint_url))
+ split = endpoint_url.rsplit(":", 1)
+ if len(split) != 2:
+ raise RuntimeError("invalid node url: {}".format(endpoint_url))
+ return split[0], _parse_endpoint_port(endpoint_url, split[1])
+
+
+def _parse_endpoint_port(endpoint_url, port):
+ try:
+ return int(port)
+ except ValueError as e:
+ raise RuntimeError("invalid node url: {}".format(endpoint_url)) from e
+
+
+def _is_wildcard_address(host):
+ if host is None:
+ return False
+ normalized_host = host
+ if normalized_host.startswith("[") and normalized_host.endswith("]"):
+ normalized_host = normalized_host[1:-1]
+ if normalized_host == "0.0.0.0":
+ return True
+ if ":" not in normalized_host:
+ return False
+ try:
+ return ipaddress.ip_address(normalized_host).is_unspecified
+ except ValueError:
+ return False
+
+
class Session(object):
DEFAULT_FETCH_SIZE = 5000
DEFAULT_USER = "root"
@@ -158,9 +202,9 @@ class Session(object):
session.__hosts = []
session.__ports = []
for node_url in node_urls:
- split = node_url.split(":")
- session.__hosts.append(split[0])
- session.__ports.append(int(split[1]))
+ host, port = _parse_endpoint_url(node_url)
+ session.__hosts.append(host)
+ session.__ports.append(port)
session.__host = session.__hosts[0]
session.__port = session.__ports[0]
session.__default_endpoint = TEndPoint(session.__host, session.__port)
@@ -259,6 +303,7 @@ class Session(object):
def __get_transport(self, endpoint):
if self.__use_ssl:
import ssl
+
from thrift.transport import TSSLSocket
if sys.version_info >= (3, 10):
@@ -1986,7 +2031,7 @@ class Session(object):
def handle_redirection(self, device_id, endpoint: TEndPoint):
if self.__enable_redirection:
- if endpoint.ip == "0.0.0.0":
+ if _is_wildcard_address(endpoint.ip):
return 0
if (
device_id not in self.__device_id_to_endpoint
diff --git a/iotdb-client/client-py/tests/unit/test_session_node_url.py
b/iotdb-client/client-py/tests/unit/test_session_node_url.py
new file mode 100644
index 00000000000..d7a59cb4275
--- /dev/null
+++ b/iotdb-client/client-py/tests/unit/test_session_node_url.py
@@ -0,0 +1,85 @@
+# 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.
+#
+
+import pytest
+from iotdb.Session import Session, _is_wildcard_address
+
+
+def test_init_from_node_urls_with_bracketed_ipv6():
+ session = Session.init_from_node_urls(["[::1]:6667"])
+
+ assert session._Session__hosts == ["::1"]
+ assert session._Session__ports == [6667]
+
+
+def test_init_from_node_urls_with_legacy_ipv6():
+ session = Session.init_from_node_urls(["::1:6667"])
+
+ assert session._Session__hosts == ["::1"]
+ assert session._Session__ports == [6667]
+
+
+def test_init_from_node_urls_with_hostname():
+ session = Session.init_from_node_urls(["localhost:6667"])
+
+ assert session._Session__hosts == ["localhost"]
+ assert session._Session__ports == [6667]
+
+
[email protected](
+ "node_url",
+ [
+ "[::1]6667",
+ "[::1]:",
+ "[::1:6667",
+ ],
+)
+def test_init_from_node_urls_rejects_malformed_bracketed_ipv6(node_url):
+ with pytest.raises(RuntimeError):
+ Session.init_from_node_urls([node_url])
+
+
[email protected](
+ "host",
+ [
+ "0.0.0.0",
+ "[0.0.0.0]",
+ "::",
+ "[::]",
+ "0:0:0:0:0:0:0:0",
+ "0::0",
+ ],
+)
+def test_wildcard_address(host):
+ assert _is_wildcard_address(host)
+
+
[email protected](
+ "host",
+ [
+ None,
+ "127.0.0.1",
+ "192.0.2.1",
+ "localhost",
+ "example.com",
+ "::1",
+ "[::1]",
+ ],
+)
+def test_not_wildcard_address(host):
+ assert not _is_wildcard_address(host)
diff --git a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/Utils.java
b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/Utils.java
index 412093f7386..49aa0860a39 100644
--- a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/Utils.java
+++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/Utils.java
@@ -60,41 +60,58 @@ public class Utils {
String suffixURL = null;
if (url.startsWith(Config.IOTDB_URL_PREFIX)) {
String subURL = url.substring(Config.IOTDB_URL_PREFIX.length());
- int i = subURL.lastIndexOf(COLON);
- host = subURL.substring(0, i);
- params.setHost(host);
- i++;
- // parse port
- int port = 0;
- for (; i < subURL.length() && Character.isDigit(subURL.charAt(i)); i++) {
- port = port * 10 + (subURL.charAt(i) - '0');
+ int i = -1;
+ if (subURL.startsWith("[")) {
+ int endIndex = subURL.indexOf("]");
+ if (endIndex > 1
+ && endIndex + 1 < subURL.length()
+ && COLON.equals(subURL.substring(endIndex + 1, endIndex + 2))) {
+ host = subURL.substring(1, endIndex);
+ i = endIndex + 2;
+ }
+ } else if (!subURL.contains("[") && !subURL.contains("]")) {
+ i = subURL.lastIndexOf(COLON);
+ if (i > 0) {
+ host = subURL.substring(0, i);
+ i++;
+ }
}
- suffixURL = i < subURL.length() ? subURL.substring(i) : "";
- // legal port
- if (port >= 1 && port <= 65535) {
- params.setPort(port);
-
- // parse database
- if (i < subURL.length() && subURL.charAt(i) == SLASH) {
- int endIndex = subURL.indexOf(PARAMETER_SEPARATOR, i + 1);
- String database;
- if (endIndex <= i + 1) {
- if (i + 1 == subURL.length()) {
- database = null;
+
+ if (i > 0) {
+ params.setHost(host);
+ // parse port
+ int port = 0;
+ int portStartIndex = i;
+ for (; i < subURL.length() && Character.isDigit(subURL.charAt(i));
i++) {
+ port = port * 10 + (subURL.charAt(i) - '0');
+ }
+ suffixURL = i < subURL.length() ? subURL.substring(i) : "";
+ // legal port
+ if (i > portStartIndex && port >= 1 && port <= 65535) {
+ params.setPort(port);
+
+ // parse database
+ if (i < subURL.length() && subURL.charAt(i) == SLASH) {
+ int endIndex = subURL.indexOf(PARAMETER_SEPARATOR, i + 1);
+ String database;
+ if (endIndex <= i + 1) {
+ if (i + 1 == subURL.length()) {
+ database = null;
+ } else {
+ database = subURL.substring(i + 1);
+ }
+ suffixURL = "";
} else {
- database = subURL.substring(i + 1);
+ database = subURL.substring(i + 1, endIndex);
+ suffixURL = subURL.substring(endIndex);
}
- suffixURL = "";
- } else {
- database = subURL.substring(i + 1, endIndex);
- suffixURL = subURL.substring(endIndex);
+ params.setDb(database);
}
- params.setDb(database);
- }
- matcher = SUFFIX_URL_PATTERN.matcher(suffixURL);
- if (matcher.matches() && parseUrlParam(subURL, info)) {
- isUrlLegal = true;
+ matcher = SUFFIX_URL_PATTERN.matcher(suffixURL);
+ if (matcher.matches() && parseUrlParam(subURL, info)) {
+ isUrlLegal = true;
+ }
}
}
}
diff --git
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/UtilsTest.java
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/UtilsTest.java
index a84010ad4ec..cd350a539ce 100644
--- a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/UtilsTest.java
+++ b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/UtilsTest.java
@@ -27,6 +27,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import java.util.Optional;
import java.util.Properties;
import static org.junit.Assert.assertEquals;
@@ -90,6 +91,38 @@ public class UtilsTest {
assertEquals(params.getPassword(), userPwd);
}
+ @Test
+ public void testParseBracketedIPV6URL() throws IoTDBURLException {
+ String host = "AD80:E32B:CA25:B3AE:DA4A:DAAF:EEAE:BBBE";
+ int port = 6667;
+ Properties properties = new Properties();
+
+ IoTDBConnectionParams params =
+ Utils.parseUrl(String.format(Config.IOTDB_URL_PREFIX + "[%s]:%s/",
host, port), properties);
+ assertEquals(host, params.getHost());
+ assertEquals(port, params.getPort());
+
+ params =
+ Utils.parseUrl(String.format(Config.IOTDB_URL_PREFIX + "[%s]:%s",
host, port), properties);
+ assertEquals(host, params.getHost());
+ assertEquals(port, params.getPort());
+ }
+
+ @Test(expected = IoTDBURLException.class)
+ public void testParseBracketedIPV6URLWithoutPortSeparator() throws
IoTDBURLException {
+ Utils.parseUrl(Config.IOTDB_URL_PREFIX + "[::1]6667", new Properties());
+ }
+
+ @Test(expected = IoTDBURLException.class)
+ public void testParseBracketedIPV6URLWithoutPort() throws IoTDBURLException {
+ Utils.parseUrl(Config.IOTDB_URL_PREFIX + "[::1]:", new Properties());
+ }
+
+ @Test(expected = IoTDBURLException.class)
+ public void testParseBracketedIPV6URLWithoutEndMark() throws
IoTDBURLException {
+ Utils.parseUrl(Config.IOTDB_URL_PREFIX + "[::1:6667", new Properties());
+ }
+
@Test(expected = IoTDBURLException.class)
public void testParseWrongUrl1() throws IoTDBURLException {
Properties properties = new Properties();
@@ -198,4 +231,17 @@ public class UtilsTest {
assertEquals("/tmp/url-keystore.p12", params.getKeyStore());
assertEquals("url_key_pass", params.getKeyStorePwd());
}
+
+ @Test
+ public void testParseSslConfigFromBracketedIpv6Url() throws
IoTDBURLException {
+ IoTDBConnectionParams params =
+ Utils.parseUrl(
+ "jdbc:iotdb://[::1]:6667/ipv6_db?use_ssl=true&sql_dialect=table",
new Properties());
+
+ assertEquals("::1", params.getHost());
+ assertEquals(6667, params.getPort());
+ assertEquals(Optional.of("ipv6_db"), params.getDb());
+ assertEquals(Constant.TABLE_DIALECT, params.getSqlDialect());
+ assertTrue(params.isUseSSL());
+ }
}
diff --git
a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/UrlUtils.java
b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/UrlUtils.java
index 39b2390a736..2a02a365d2a 100644
--- a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/UrlUtils.java
+++ b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/UrlUtils.java
@@ -21,10 +21,14 @@ package org.apache.iotdb.rpc;
import org.apache.iotdb.common.rpc.thrift.TEndPoint;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
/** The UrlUtils */
public class UrlUtils {
private static final String PORT_SEPARATOR = ":";
- private static final String ABB_COLON = "[";
+ private static final String IPV6_BEGIN_MARK = "[";
+ private static final String IPV6_END_MARK = "]";
private UrlUtils() {}
@@ -32,22 +36,79 @@ public class UrlUtils {
* Parse TEndPoint from a given TEndPointUrl
* example:[D80:0000:0000:0000:ABAA:0000:00C2:0002]:22227
*
- * @param endPointUrl ip:port
- * @return TEndPoint null if parse error
+ * @param endPointUrl host:port or [ipv6]:port
+ * @return parsed TEndPoint
+ * @throws NumberFormatException if the bracketed endpoint format is invalid
or the port is not a
+ * number
*/
public static TEndPoint parseTEndPointIpv4AndIpv6Url(String endPointUrl) {
TEndPoint endPoint = new TEndPoint();
- if (endPointUrl.contains(PORT_SEPARATOR)) {
- int point_position = endPointUrl.lastIndexOf(PORT_SEPARATOR);
- String port =
endPointUrl.substring(endPointUrl.lastIndexOf(PORT_SEPARATOR) + 1);
- String ip = endPointUrl.substring(0, point_position);
- // If the ip/host part is provided as IPv6 address, cut off the
surrounding square brackets.
- if (ip.contains(ABB_COLON)) {
- ip = ip.substring(1, ip.length() - 1);
+ if (endPointUrl.startsWith(IPV6_BEGIN_MARK)) {
+ int endIndex = endPointUrl.indexOf(IPV6_END_MARK);
+ if (endIndex <= 1
+ || endIndex + 1 >= endPointUrl.length()
+ || !PORT_SEPARATOR.equals(endPointUrl.substring(endIndex + 1,
endIndex + 2))) {
+ throw new NumberFormatException();
}
- endPoint.setIp(ip);
- endPoint.setPort(Integer.parseInt(port));
+ endPoint.setIp(endPointUrl.substring(1, endIndex));
+ endPoint.setPort(Integer.parseInt(endPointUrl.substring(endIndex + 2)));
+ return endPoint;
+ }
+ if (endPointUrl.contains(IPV6_BEGIN_MARK) ||
endPointUrl.contains(IPV6_END_MARK)) {
+ throw new NumberFormatException();
+ }
+ int separatorIndex = endPointUrl.lastIndexOf(PORT_SEPARATOR);
+ if (separatorIndex <= 0 || separatorIndex == endPointUrl.length() - 1) {
+ throw new NumberFormatException();
}
+ endPoint.setIp(endPointUrl.substring(0, separatorIndex));
+ endPoint.setPort(Integer.parseInt(endPointUrl.substring(separatorIndex +
1)));
return endPoint;
}
+
+ /**
+ * Convert TEndPoint to a URL string. IPv6 literals are surrounded by square
brackets so the last
+ * colon remains an unambiguous port separator.
+ */
+ public static String convertTEndPointIpv4AndIpv6Url(TEndPoint endPoint) {
+ return formatTEndPointIpv4AndIpv6Url(endPoint.getIp(), endPoint.getPort());
+ }
+
+ /** Format host and port as host:port or [ipv6]:port. This method expects
host without a port. */
+ public static String formatTEndPointIpv4AndIpv6Url(String host, int port) {
+ String formattedHost = host;
+ if (isIpv6Literal(host) && !isBracketedIpv6Literal(host)) {
+ formattedHost = IPV6_BEGIN_MARK + host + IPV6_END_MARK;
+ }
+ return formattedHost + PORT_SEPARATOR + port;
+ }
+
+ public static boolean isWildcardAddress(String host) {
+ if (host == null) {
+ return false;
+ }
+ String normalizedHost = host;
+ if (isBracketedIpv6Literal(host)) {
+ normalizedHost = host.substring(1, host.length() - 1);
+ }
+ if ("0.0.0.0".equals(normalizedHost)) {
+ return true;
+ }
+ if (!isIpv6Literal(normalizedHost)) {
+ return false;
+ }
+ try {
+ return InetAddress.getByName(normalizedHost).isAnyLocalAddress();
+ } catch (UnknownHostException e) {
+ return false;
+ }
+ }
+
+ private static boolean isIpv6Literal(String host) {
+ return host.contains(PORT_SEPARATOR);
+ }
+
+ private static boolean isBracketedIpv6Literal(String host) {
+ return host.startsWith(IPV6_BEGIN_MARK) && host.endsWith(IPV6_END_MARK);
+ }
}
diff --git
a/iotdb-client/service-rpc/src/test/java/org/apache/iotdb/rpc/UrlUtilsTest.java
b/iotdb-client/service-rpc/src/test/java/org/apache/iotdb/rpc/UrlUtilsTest.java
index 1d206973d13..fb1b5b1359b 100644
---
a/iotdb-client/service-rpc/src/test/java/org/apache/iotdb/rpc/UrlUtilsTest.java
+++
b/iotdb-client/service-rpc/src/test/java/org/apache/iotdb/rpc/UrlUtilsTest.java
@@ -24,6 +24,8 @@ import org.apache.iotdb.common.rpc.thrift.TEndPoint;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
public class UrlUtilsTest {
@@ -51,6 +53,36 @@ public class UrlUtilsTest {
assertEquals(endPoint.getPort(), 22227);
}
+ @Test(expected = NumberFormatException.class)
+ public void testParseBracketedIPV6URLWithoutPortSeparator() {
+ UrlUtils.parseTEndPointIpv4AndIpv6Url("[D80::ABAA:0]22227");
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testParseBracketedIPV6URLWithoutPort() {
+ UrlUtils.parseTEndPointIpv4AndIpv6Url("[D80::ABAA:0]:");
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testParseBracketedIPV6URLWithoutEndMark() {
+ UrlUtils.parseTEndPointIpv4AndIpv6Url("[D80::ABAA:0:22227");
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testParseURLWithoutPortSeparator() {
+ UrlUtils.parseTEndPointIpv4AndIpv6Url("localhost");
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testParseURLWithoutHost() {
+ UrlUtils.parseTEndPointIpv4AndIpv6Url(":22227");
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testParseURLWithoutPort() {
+ UrlUtils.parseTEndPointIpv4AndIpv6Url("localhost:");
+ }
+
@Test
public void testParseIPV4URL() {
String hostAndPoint = "192.0.0.1:22227";
@@ -58,4 +90,52 @@ public class UrlUtilsTest {
assertEquals(endPoint.getIp(), "192.0.0.1");
assertEquals(endPoint.getPort(), 22227);
}
+
+ @Test
+ public void testConvertIPV4URL() {
+ assertEquals(
+ "192.0.0.1:22227",
+ UrlUtils.convertTEndPointIpv4AndIpv6Url(new TEndPoint("192.0.0.1",
22227)));
+ }
+
+ @Test
+ public void testConvertHostNameURL() {
+ assertEquals("localhost:22227",
UrlUtils.formatTEndPointIpv4AndIpv6Url("localhost", 22227));
+ }
+
+ @Test
+ public void testConvertIPV6URL() {
+ assertEquals(
+ "[D80:0000:0000:0000:ABAA:00BB:EEAA:BBDD]:22227",
+ UrlUtils.convertTEndPointIpv4AndIpv6Url(
+ new TEndPoint("D80:0000:0000:0000:ABAA:00BB:EEAA:BBDD", 22227)));
+ }
+
+ @Test
+ public void testConvertIPV6AbbURL() {
+ assertEquals(
+ "[D80::ABAA:0]:22227",
+ UrlUtils.convertTEndPointIpv4AndIpv6Url(new TEndPoint("D80::ABAA:0",
22227)));
+ }
+
+ @Test
+ public void testConvertBracketedIPV6URL() {
+ assertEquals(
+ "[D80::ABAA:0]:22227",
UrlUtils.formatTEndPointIpv4AndIpv6Url("[D80::ABAA:0]", 22227));
+ }
+
+ @Test
+ public void testWildcardAddress() {
+ assertTrue(UrlUtils.isWildcardAddress("0.0.0.0"));
+ assertTrue(UrlUtils.isWildcardAddress("::"));
+ assertTrue(UrlUtils.isWildcardAddress("[::]"));
+ assertTrue(UrlUtils.isWildcardAddress("0:0:0:0:0:0:0:0"));
+ assertTrue(UrlUtils.isWildcardAddress("0::0"));
+
+ assertFalse(UrlUtils.isWildcardAddress(null));
+ assertFalse(UrlUtils.isWildcardAddress("127.0.0.1"));
+ assertFalse(UrlUtils.isWildcardAddress("localhost"));
+ assertFalse(UrlUtils.isWildcardAddress("::1"));
+ assertFalse(UrlUtils.isWildcardAddress("[::1]"));
+ }
}
diff --git
a/iotdb-client/session/src/main/i18n/en/org/apache/iotdb/session/i18n/SessionMessages.java
b/iotdb-client/session/src/main/i18n/en/org/apache/iotdb/session/i18n/SessionMessages.java
index 2ade601350a..69b87b2db16 100644
---
a/iotdb-client/session/src/main/i18n/en/org/apache/iotdb/session/i18n/SessionMessages.java
+++
b/iotdb-client/session/src/main/i18n/en/org/apache/iotdb/session/i18n/SessionMessages.java
@@ -67,6 +67,8 @@ public final class SessionMessages {
// SessionUtils
public static final String NODE_URLS_IS_NULL = "nodeUrls is null";
+ public static final String EXCEPTION_NODEURL_INCORRECT_FORMAT_C1463B2C =
+ "NodeUrl Incorrect format";
// InternalNode (template)
public static final String DUPLICATED_CHILD_IN_TEMPLATE =
diff --git
a/iotdb-client/session/src/main/i18n/zh/org/apache/iotdb/session/i18n/SessionMessages.java
b/iotdb-client/session/src/main/i18n/zh/org/apache/iotdb/session/i18n/SessionMessages.java
index 3d297e3e7d9..2f28590ac88 100644
---
a/iotdb-client/session/src/main/i18n/zh/org/apache/iotdb/session/i18n/SessionMessages.java
+++
b/iotdb-client/session/src/main/i18n/zh/org/apache/iotdb/session/i18n/SessionMessages.java
@@ -66,6 +66,8 @@ public final class SessionMessages {
// SessionUtils
public static final String NODE_URLS_IS_NULL = "nodeUrls 为 null";
+ public static final String EXCEPTION_NODEURL_INCORRECT_FORMAT_C1463B2C =
+ "NodeUrl 格式不正确";
// InternalNode (template)
public static final String DUPLICATED_CHILD_IN_TEMPLATE =
diff --git
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/NodesSupplier.java
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/NodesSupplier.java
index c1f43fa30d4..81d83450f72 100644
---
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/NodesSupplier.java
+++
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/NodesSupplier.java
@@ -22,6 +22,7 @@ package org.apache.iotdb.session;
import org.apache.iotdb.common.rpc.thrift.TEndPoint;
import org.apache.iotdb.isession.INodeSupplier;
import org.apache.iotdb.isession.SessionDataSet;
+import org.apache.iotdb.rpc.UrlUtils;
import org.apache.iotdb.session.i18n.SessionMessages;
import org.slf4j.Logger;
@@ -257,8 +258,8 @@ public class NodesSupplier implements INodeSupplier,
Runnable {
List<TEndPoint> res = new ArrayList<>();
while (iterator.next()) {
String ip = iterator.getString(IP_COLUMN_NAME);
- // ignore 0.0.0.0
- if (!"0.0.0.0".equals(ip)) {
+ // ignore wildcard addresses
+ if (!UrlUtils.isWildcardAddress(ip)) {
String port = iterator.getString(PORT_COLUMN_NAME);
if (ip != null && port != null) {
res.add(new TEndPoint(ip, Integer.parseInt(port)));
diff --git
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java
index 6819325a9b2..36581b9690c 100644
--- a/iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java
+++ b/iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java
@@ -32,6 +32,7 @@ import org.apache.iotdb.rpc.IoTDBConnectionException;
import org.apache.iotdb.rpc.NoValidValueException;
import org.apache.iotdb.rpc.RedirectException;
import org.apache.iotdb.rpc.StatementExecutionException;
+import org.apache.iotdb.rpc.UrlUtils;
import
org.apache.iotdb.service.rpc.thrift.TCreateTimeseriesUsingSchemaTemplateReq;
import org.apache.iotdb.service.rpc.thrift.TSAppendSchemaTemplateReq;
import org.apache.iotdb.service.rpc.thrift.TSBackupConfigurationResp;
@@ -1414,7 +1415,7 @@ public class Session implements ISession {
private void handleRedirection(String deviceId, TEndPoint endpoint) {
if (enableRedirection) {
// no need to redirection
- if (endpoint.ip.equals("0.0.0.0")) {
+ if (UrlUtils.isWildcardAddress(endpoint.ip)) {
return;
}
if (!deviceIdToEndpoint.containsKey(deviceId)
@@ -1440,7 +1441,7 @@ public class Session implements ISession {
private void handleRedirection(IDeviceID deviceId, TEndPoint endpoint) {
if (enableRedirection) {
// no need to redirection
- if (endpoint.ip.equals("0.0.0.0")) {
+ if (UrlUtils.isWildcardAddress(endpoint.ip)) {
return;
}
if (!tableModelDeviceIdToEndpoint.containsKey(deviceId)
diff --git
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/SessionConnection.java
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/SessionConnection.java
index ad6699aaf87..b498784ad2c 100644
---
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/SessionConnection.java
+++
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/SessionConnection.java
@@ -30,6 +30,7 @@ import org.apache.iotdb.rpc.RedirectException;
import org.apache.iotdb.rpc.RpcUtils;
import org.apache.iotdb.rpc.StatementExecutionException;
import org.apache.iotdb.rpc.TSStatusCode;
+import org.apache.iotdb.rpc.UrlUtils;
import org.apache.iotdb.service.rpc.thrift.IClientRPCService;
import
org.apache.iotdb.service.rpc.thrift.TCreateTimeseriesUsingSchemaTemplateReq;
import org.apache.iotdb.service.rpc.thrift.TSAggregationQueryReq;
@@ -83,7 +84,6 @@ import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@@ -1314,14 +1314,11 @@ public class SessionConnection {
if (endPointList == null) {
return MSG_RECONNECTION_FAIL;
}
- StringJoiner urls = new StringJoiner(",");
+ List<String> urls = new ArrayList<>();
for (TEndPoint end : endPointList) {
- StringJoiner url = new StringJoiner(":");
- url.add(end.getIp());
- url.add(String.valueOf(end.getPort()));
- urls.add(url.toString());
+ urls.add(UrlUtils.convertTEndPointIpv4AndIpv6Url(end));
}
- return MSG_RECONNECTION_FAIL.concat(urls.toString());
+ return MSG_RECONNECTION_FAIL.concat(String.join(",", urls));
}
@Override
diff --git
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/pool/SessionPool.java
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/pool/SessionPool.java
index 07c0bc4d7e6..b3de497df39 100644
---
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/pool/SessionPool.java
+++
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/pool/SessionPool.java
@@ -32,6 +32,7 @@ import org.apache.iotdb.isession.template.Template;
import org.apache.iotdb.isession.util.Version;
import org.apache.iotdb.rpc.IoTDBConnectionException;
import org.apache.iotdb.rpc.StatementExecutionException;
+import org.apache.iotdb.rpc.UrlUtils;
import org.apache.iotdb.service.rpc.thrift.TSBackupConfigurationResp;
import org.apache.iotdb.service.rpc.thrift.TSConnectionInfoResp;
import org.apache.iotdb.session.DummyNodesSupplier;
@@ -426,7 +427,7 @@ public class SessionPool implements ISessionPool {
this.version = version;
this.thriftDefaultBufferSize = thriftDefaultBufferSize;
this.thriftMaxFrameSize = thriftMaxFrameSize;
- this.formattedNodeUrls = String.format("%s:%s", host, port);
+ this.formattedNodeUrls = UrlUtils.formatTEndPointIpv4AndIpv6Url(host,
port);
initThreadPool();
initAvailableNodes(Collections.singletonList(new TEndPoint(host, port)));
}
@@ -469,7 +470,7 @@ public class SessionPool implements ISessionPool {
this.version = version;
this.thriftDefaultBufferSize = thriftDefaultBufferSize;
this.thriftMaxFrameSize = thriftMaxFrameSize;
- this.formattedNodeUrls = String.format("%s:%s", host, port);
+ this.formattedNodeUrls = UrlUtils.formatTEndPointIpv4AndIpv6Url(host,
port);
this.useSSL = useSSL;
this.trustStore = trustStore;
this.trustStorePwd = trustStorePwd;
@@ -574,7 +575,7 @@ public class SessionPool implements ISessionPool {
this.host = builder.host;
this.port = builder.rpcPort;
this.nodeUrls = null;
- this.formattedNodeUrls = String.format("%s:%s", host, port);
+ this.formattedNodeUrls = UrlUtils.formatTEndPointIpv4AndIpv6Url(host,
port);
if (enableAutoFetch) {
initAvailableNodes(Collections.singletonList(new TEndPoint(host,
port)));
} else {
diff --git
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/util/SessionUtils.java
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/util/SessionUtils.java
index 3fbb0de0f73..26a2e46e59d 100644
---
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/util/SessionUtils.java
+++
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/util/SessionUtils.java
@@ -487,8 +487,12 @@ public class SessionUtils {
}
List<TEndPoint> endPointsList = new ArrayList<>();
for (String nodeUrl : nodeUrls) {
- TEndPoint endPoint = UrlUtils.parseTEndPointIpv4AndIpv6Url(nodeUrl);
- endPointsList.add(endPoint);
+ try {
+ endPointsList.add(UrlUtils.parseTEndPointIpv4AndIpv6Url(nodeUrl));
+ } catch (NumberFormatException e) {
+ throw new NumberFormatException(
+ SessionMessages.EXCEPTION_NODEURL_INCORRECT_FORMAT_C1463B2C);
+ }
}
return endPointsList;
}
diff --git a/iotdb-core/ainode/iotdb/ainode/core/config.py
b/iotdb-core/ainode/iotdb/ainode/core/config.py
index 4995dda7bf3..53217f6c27b 100644
--- a/iotdb-core/ainode/iotdb/ainode/core/config.py
+++ b/iotdb-core/ainode/iotdb/ainode/core/config.py
@@ -466,13 +466,24 @@ def load_properties(filepath, sep="=", comment_char="#"):
def parse_endpoint_url(endpoint_url: str) -> TEndPoint:
"""Parse TEndPoint from a given endpoint url.
Args:
- endpoint_url: an endpoint url, format: ip:port
+ endpoint_url: an endpoint url, format: host:port or [ipv6]:port
Returns:
TEndPoint
Raises:
- BadNodeUrlError
+ BadNodeUrlException
"""
- split = endpoint_url.split(":")
+ if endpoint_url.startswith("["):
+ end_index = endpoint_url.find("]")
+ if end_index <= 1 or end_index + 1 >= len(endpoint_url):
+ raise BadNodeUrlException(endpoint_url)
+ if endpoint_url[end_index + 1] != ":":
+ raise BadNodeUrlException(endpoint_url)
+ split = [endpoint_url[1:end_index], endpoint_url[end_index + 2 :]]
+ else:
+ if "[" in endpoint_url or "]" in endpoint_url:
+ raise BadNodeUrlException(endpoint_url)
+ split = endpoint_url.rsplit(":", 1)
+
if len(split) != 2:
raise BadNodeUrlException(endpoint_url)
diff --git a/iotdb-core/ainode/iotdb/ainode/core/ingress/iotdb.py
b/iotdb-core/ainode/iotdb/ainode/core/ingress/iotdb.py
index be1dd9bf1c2..bfe4f56d518 100644
--- a/iotdb-core/ainode/iotdb/ainode/core/ingress/iotdb.py
+++ b/iotdb-core/ainode/iotdb/ainode/core/ingress/iotdb.py
@@ -50,6 +50,13 @@ def _cache_enable() -> bool:
return AINodeDescriptor().get_config().get_ain_data_storage_cache_size() > 0
+def _format_endpoint_url(ip: str, port: int) -> str:
+ formatted_ip = ip
+ if ":" in ip and not (ip.startswith("[") and ip.endswith("]")):
+ formatted_ip = f"[{ip}]"
+ return f"{formatted_ip}:{port}"
+
+
class IoTDBTreeModelDataset(BasicDatabaseForecastDataset):
cache = MemoryLRUCache()
@@ -84,7 +91,7 @@ class IoTDBTreeModelDataset(BasicDatabaseForecastDataset):
self.TIME_CONDITION = " where time>=%s and time<%s"
self.session = Session.init_from_node_urls(
- node_urls=[f"{ip}:{port}"],
+ node_urls=[_format_endpoint_url(ip, port)],
user=username,
password=password,
use_ssl=AINodeDescriptor()
@@ -262,7 +269,7 @@ class IoTDBTableModelDataset(BasicDatabaseForecastDataset):
)
table_session_config = TableSessionConfig(
- node_urls=[f"{ip}:{port}"],
+ node_urls=[_format_endpoint_url(ip, port)],
username=username,
password=password,
use_ssl=AINodeDescriptor()
diff --git a/iotdb-core/ainode/resources/conf/iotdb-ainode.properties
b/iotdb-core/ainode/resources/conf/iotdb-ainode.properties
index 5b653d678a6..6a8f02a62f7 100644
--- a/iotdb-core/ainode/resources/conf/iotdb-ainode.properties
+++ b/iotdb-core/ainode/resources/conf/iotdb-ainode.properties
@@ -23,11 +23,12 @@ cluster_name=defaultCluster
# ConfigNode address registered at AINode startup.
# Allow modifications only before starting the service for the first time.
+# Format: address:port or [ipv6]:port, e.g. 127.0.0.1:10710 or [::1]:10710
# Datatype: String
ain_seed_config_node=127.0.0.1:10710
# Used for connection of DataNode/ConfigNode clients
-# Could set 127.0.0.1(for local test) or ipv4 address
+# Could set 127.0.0.1(for local test), ipv4/ipv6 address, or hostname.
# Datatype: String
ain_rpc_address=127.0.0.1
diff --git a/iotdb-core/ainode/tests/test_config.py
b/iotdb-core/ainode/tests/test_config.py
new file mode 100644
index 00000000000..52955c51137
--- /dev/null
+++ b/iotdb-core/ainode/tests/test_config.py
@@ -0,0 +1,71 @@
+# 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.
+#
+import sys
+import types
+import unittest
+from importlib import import_module
+
+
+class TEndPoint:
+ def __init__(self, ip=None, port=None):
+ self.ip = ip
+ self.port = port
+
+
+thrift_module = types.ModuleType("iotdb.thrift")
+common_module = types.ModuleType("iotdb.thrift.common")
+ttypes_module = types.ModuleType("iotdb.thrift.common.ttypes")
+ttypes_module.TEndPoint = TEndPoint
+sys.modules.setdefault("iotdb.thrift", thrift_module)
+sys.modules.setdefault("iotdb.thrift.common", common_module)
+sys.modules.setdefault("iotdb.thrift.common.ttypes", ttypes_module)
+
+config_module = import_module("iotdb.ainode.core.config")
+exception_module = import_module("iotdb.ainode.core.exception")
+parse_endpoint_url = config_module.parse_endpoint_url
+BadNodeUrlException = exception_module.BadNodeUrlException
+
+
+class ParseEndpointUrlTest(unittest.TestCase):
+ def test_parse_bracketed_ipv6_endpoint(self):
+ endpoint = parse_endpoint_url("[::1]:6667")
+
+ self.assertEqual("::1", endpoint.ip)
+ self.assertEqual(6667, endpoint.port)
+
+ def test_parse_legacy_ipv6_endpoint(self):
+ endpoint = parse_endpoint_url("::1:6667")
+
+ self.assertEqual("::1", endpoint.ip)
+ self.assertEqual(6667, endpoint.port)
+
+ def test_reject_malformed_endpoint(self):
+ malformed_urls = [
+ "[]:123",
+ "foo[::1]:6667",
+ "foo]bar:6667",
+ ]
+
+ for endpoint_url in malformed_urls:
+ with self.subTest(endpoint_url=endpoint_url):
+ with self.assertRaises(BadNodeUrlException):
+ parse_endpoint_url(endpoint_url)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git
a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/utils/Utils.java
b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/utils/Utils.java
index cb71ed3fa4d..7b052cebf97 100644
---
a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/utils/Utils.java
+++
b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/utils/Utils.java
@@ -30,6 +30,7 @@ import org.apache.iotdb.consensus.config.RatisConfig;
import org.apache.iotdb.consensus.i18n.ConsensusMessages;
import org.apache.iotdb.rpc.AutoScalingBufferWriteTransport;
import org.apache.iotdb.rpc.RpcSslUtils;
+import org.apache.iotdb.rpc.UrlUtils;
import org.apache.ratis.client.RaftClientConfigKeys;
import org.apache.ratis.conf.Parameters;
@@ -82,7 +83,7 @@ public class Utils {
private Utils() {}
public static String hostAddress(TEndPoint endpoint) {
- return String.format("%s:%d", endpoint.getIp(), endpoint.getPort());
+ return UrlUtils.convertTEndPointIpv4AndIpv6Url(endpoint);
}
public static String fromTEndPointToString(TEndPoint endpoint) {
@@ -103,8 +104,7 @@ public class Utils {
}
public static TEndPoint fromRaftPeerAddressToTEndPoint(String address) {
- String[] items = address.split(":");
- return new TEndPoint(items[0], Integer.parseInt(items[1]));
+ return UrlUtils.parseTEndPointIpv4AndIpv6Url(address);
}
public static int fromRaftPeerIdToNodeId(RaftPeerId id) {
@@ -112,8 +112,7 @@ public class Utils {
}
public static TEndPoint fromRaftPeerProtoToTEndPoint(RaftPeerProto proto) {
- String[] items = proto.getAddress().split(":");
- return new TEndPoint(items[0], Integer.parseInt(items[1]));
+ return fromRaftPeerAddressToTEndPoint(proto.getAddress());
}
// priority is used as ordinal of leader election
diff --git
a/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/ratis/utils/UtilsTest.java
b/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/ratis/utils/UtilsTest.java
index b1ba2195006..5633459d387 100644
---
a/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/ratis/utils/UtilsTest.java
+++
b/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/ratis/utils/UtilsTest.java
@@ -19,11 +19,13 @@
package org.apache.iotdb.consensus.ratis.utils;
+import org.apache.iotdb.common.rpc.thrift.TEndPoint;
import org.apache.iotdb.commons.consensus.ConfigRegionId;
import org.apache.iotdb.commons.consensus.ConsensusGroupId;
import org.apache.iotdb.consensus.config.RatisConfig;
import org.apache.ratis.protocol.RaftGroupId;
+import org.apache.ratis.protocol.RaftPeer;
import org.apache.ratis.util.TimeDuration;
import org.junit.Assert;
import org.junit.Test;
@@ -64,4 +66,56 @@ public class UtilsTest {
TimeDuration.valueOf(52700, TimeUnit.MILLISECONDS),
Utils.getMaxRetrySleepTime(clientConfig));
}
+
+ @Test
+ public void testRaftPeerAddressRoundTripWithIpv4() {
+ TEndPoint endPoint = new TEndPoint("192.0.0.1", 10720);
+
+ Assert.assertEquals("192.0.0.1:10720", Utils.hostAddress(endPoint));
+ Assert.assertEquals(
+ endPoint,
Utils.fromRaftPeerAddressToTEndPoint(Utils.hostAddress(endPoint)));
+ }
+
+ @Test
+ public void testRaftPeerAddressRoundTripWithHostName() {
+ TEndPoint endPoint = new TEndPoint("localhost", 10720);
+
+ Assert.assertEquals("localhost:10720", Utils.hostAddress(endPoint));
+ Assert.assertEquals(
+ endPoint,
Utils.fromRaftPeerAddressToTEndPoint(Utils.hostAddress(endPoint)));
+ }
+
+ @Test
+ public void testRaftPeerAddressRoundTripWithIpv6() {
+ TEndPoint endPoint = new TEndPoint("::1", 10720);
+
+ Assert.assertEquals("[::1]:10720", Utils.hostAddress(endPoint));
+ Assert.assertEquals(
+ endPoint,
Utils.fromRaftPeerAddressToTEndPoint(Utils.hostAddress(endPoint)));
+ Assert.assertEquals(endPoint,
Utils.fromRaftPeerAddressToTEndPoint("::1:10720"));
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testRaftPeerAddressRejectsBracketedIpv6WithoutPortSeparator() {
+ Utils.fromRaftPeerAddressToTEndPoint("[::1]10720");
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testRaftPeerAddressRejectsBracketedIpv6WithoutPort() {
+ Utils.fromRaftPeerAddressToTEndPoint("[::1]:");
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testRaftPeerAddressRejectsBracketedIpv6WithoutEndMark() {
+ Utils.fromRaftPeerAddressToTEndPoint("[::1:10720");
+ }
+
+ @Test
+ public void testRaftPeerProtoRoundTripWithIpv6() {
+ TEndPoint endPoint = new TEndPoint("0:0:0:0:0:0:0:1", 10720);
+ RaftPeer raftPeer = Utils.fromNodeInfoAndPriorityToRaftPeer(1, endPoint,
0);
+
+ Assert.assertEquals("[0:0:0:0:0:0:0:1]:10720", raftPeer.getAddress());
+ Assert.assertEquals(endPoint,
Utils.fromRaftPeerProtoToTEndPoint(raftPeer.getRaftPeerProto()));
+ }
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/client/IoTDBDataNodeAsyncClientManager.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/client/IoTDBDataNodeAsyncClientManager.java
index 460c3c5f846..230bc80c98f 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/client/IoTDBDataNodeAsyncClientManager.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/client/IoTDBDataNodeAsyncClientManager.java
@@ -39,6 +39,7 @@ import
org.apache.iotdb.db.pipe.sink.payload.evolvable.request.PipeTransferDataN
import org.apache.iotdb.pipe.api.exception.PipeConnectionException;
import org.apache.iotdb.pipe.api.exception.PipeException;
import org.apache.iotdb.rpc.TSStatusCode;
+import org.apache.iotdb.rpc.UrlUtils;
import org.apache.iotdb.service.rpc.thrift.TPipeTransferResp;
import org.apache.thrift.TException;
@@ -404,7 +405,10 @@ public class IoTDBDataNodeAsyncClientManager extends
IoTDBClientManager
}
public void updateLeaderCache(final String deviceId, final TEndPoint
endPoint) {
- if (!useLeaderCache || deviceId == null || endPoint == null) {
+ if (!useLeaderCache
+ || deviceId == null
+ || endPoint == null
+ || UrlUtils.isWildcardAddress(endPoint.getIp())) {
return;
}
@@ -561,7 +565,7 @@ public class IoTDBDataNodeAsyncClientManager extends
IoTDBClientManager
"%s-%s",
receiverAttributes,
endPoints.stream()
- .map(endPoint -> String.format("%s:%s", endPoint.getIp(),
endPoint.getPort()))
+ .map(UrlUtils::convertTEndPointIpv4AndIpv6Url)
.distinct()
.sorted()
.collect(Collectors.joining(",", "[", "]")));
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/client/IoTDBDataNodeSyncClientManager.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/client/IoTDBDataNodeSyncClientManager.java
index df966c78acc..e8f081cff00 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/client/IoTDBDataNodeSyncClientManager.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/client/IoTDBDataNodeSyncClientManager.java
@@ -29,6 +29,7 @@ import org.apache.iotdb.db.conf.IoTDBDescriptor;
import org.apache.iotdb.db.i18n.DataNodePipeMessages;
import
org.apache.iotdb.db.pipe.sink.payload.evolvable.request.PipeTransferDataNodeHandshakeV1Req;
import
org.apache.iotdb.db.pipe.sink.payload.evolvable.request.PipeTransferDataNodeHandshakeV2Req;
+import org.apache.iotdb.rpc.UrlUtils;
import org.apache.tsfile.utils.Pair;
import org.slf4j.Logger;
@@ -119,7 +120,10 @@ public class IoTDBDataNodeSyncClientManager extends
IoTDBSyncClientManager
}
public void updateLeaderCache(final String deviceId, final TEndPoint
endPoint) {
- if (!useLeaderCache || deviceId == null || endPoint == null) {
+ if (!useLeaderCache
+ || deviceId == null
+ || endPoint == null
+ || UrlUtils.isWildcardAddress(endPoint.getIp())) {
return;
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
index a97f4ed3000..9466543d164 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
@@ -66,6 +66,7 @@ import
org.apache.iotdb.pipe.api.event.dml.insertion.TsFileInsertionEvent;
import org.apache.iotdb.pipe.api.exception.PipeConnectionException;
import org.apache.iotdb.pipe.api.exception.PipeException;
import org.apache.iotdb.rpc.TSStatusCode;
+import org.apache.iotdb.rpc.UrlUtils;
import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq;
import com.google.common.collect.ImmutableSet;
@@ -912,7 +913,7 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink
implements PipeSinkWithS
}
private static String format(final TEndPoint endPoint) {
- return Objects.isNull(endPoint) ? null : endPoint.getIp() + ":" +
endPoint.getPort();
+ return Objects.isNull(endPoint) ? null :
UrlUtils.convertTEndPointIpv4AndIpv6Url(endPoint);
}
//////////////////////////// Operations for close
////////////////////////////
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/TestConnectionTask.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/TestConnectionTask.java
index 483bb3ae1ff..e71af67cafd 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/TestConnectionTask.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/TestConnectionTask.java
@@ -33,6 +33,7 @@ import
org.apache.iotdb.db.queryengine.plan.execution.config.ConfigTaskResult;
import org.apache.iotdb.db.queryengine.plan.execution.config.IConfigTask;
import
org.apache.iotdb.db.queryengine.plan.execution.config.executor.IConfigTaskExecutor;
import org.apache.iotdb.rpc.TSStatusCode;
+import org.apache.iotdb.rpc.UrlUtils;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
@@ -173,7 +174,7 @@ public class TestConnectionTask implements IConfigTask {
}
private static String endPointToString(TEndPoint endPoint) {
- return endPoint.getIp() + ":" + endPoint.getPort();
+ return UrlUtils.convertTEndPointIpv4AndIpv6Url(endPoint);
}
private static void sortTestConnectionResp(TTestConnectionResp origin) {
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/IoTDBDataNodeAsyncClientManagerTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/IoTDBDataNodeAsyncClientManagerTest.java
index f564dbbd70d..c26f35ddc77 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/IoTDBDataNodeAsyncClientManagerTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/IoTDBDataNodeAsyncClientManagerTest.java
@@ -22,16 +22,24 @@ package org.apache.iotdb.db.pipe.sink;
import org.apache.iotdb.common.rpc.thrift.TEndPoint;
import org.apache.iotdb.commons.audit.UserEntity;
import org.apache.iotdb.db.pipe.sink.client.IoTDBDataNodeAsyncClientManager;
+import
org.apache.iotdb.db.pipe.sink.client.IoTDBDataNodeCacheLeaderClientManager;
+import org.apache.iotdb.db.pipe.sink.client.IoTDBDataNodeSyncClientManager;
import org.junit.Assert;
import org.junit.Test;
import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.ExecutorService;
public class IoTDBDataNodeAsyncClientManagerTest {
+ private static final List<String> WILDCARD_ADDRESSES =
+ Arrays.asList("0.0.0.0", "::", "[::]", "0:0:0:0:0:0:0:0", "0::0");
+
@Test
public void testReceiverAttributesShouldDifferentiateSkipIfNoPrivileges()
throws Exception {
final IoTDBDataNodeAsyncClientManager managerWithSkipIf =
@@ -114,6 +122,85 @@ public class IoTDBDataNodeAsyncClientManagerTest {
}
}
+ @Test
+ public void
testAsyncManagerShouldIgnoreWildcardAndAcceptIPv6LeaderEndPoints() {
+ final TEndPoint originalEndPoint = new TEndPoint("127.0.0.1", 6667);
+ final List<TEndPoint> endPoints = new
ArrayList<>(Collections.singletonList(originalEndPoint));
+ final IoTDBDataNodeAsyncClientManager manager =
createAsyncManager(endPoints);
+
+ try {
+ for (int i = 0; i < WILDCARD_ADDRESSES.size(); i++) {
+ final String deviceId = "async-wildcard-device-" + i;
+ manager.updateLeaderCache(deviceId, new
TEndPoint(WILDCARD_ADDRESSES.get(i), 6667 + i));
+
+ Assert.assertEquals(Collections.singletonList(originalEndPoint),
endPoints);
+ Assert.assertNull(
+
IoTDBDataNodeCacheLeaderClientManager.LEADER_CACHE_MANAGER.getLeaderEndPoint(deviceId));
+ }
+
+ final String ipv6DeviceId = "async-ipv6-device";
+ final TEndPoint ipv6EndPoint = new TEndPoint("::1", 6677);
+ manager.updateLeaderCache(ipv6DeviceId, ipv6EndPoint);
+
+ Assert.assertEquals(Arrays.asList(originalEndPoint, ipv6EndPoint),
endPoints);
+ Assert.assertEquals(
+ ipv6EndPoint,
+
IoTDBDataNodeCacheLeaderClientManager.LEADER_CACHE_MANAGER.getLeaderEndPoint(
+ ipv6DeviceId));
+ } finally {
+ manager.close();
+ }
+ }
+
+ @Test
+ public void
testSyncManagerShouldIgnoreWildcardAndAcceptIPv6LeaderEndPoints() {
+ final TEndPoint originalEndPoint = new TEndPoint("127.0.0.1", 6667);
+ final List<TEndPoint> endPoints = new
ArrayList<>(Collections.singletonList(originalEndPoint));
+ final TestIoTDBDataNodeSyncClientManager manager =
+ new TestIoTDBDataNodeSyncClientManager(endPoints);
+
+ try {
+ for (int i = 0; i < WILDCARD_ADDRESSES.size(); i++) {
+ final String deviceId = "sync-wildcard-device-" + i;
+ manager.updateLeaderCache(deviceId, new
TEndPoint(WILDCARD_ADDRESSES.get(i), 6667 + i));
+
+ Assert.assertEquals(Collections.singletonList(originalEndPoint),
endPoints);
+ Assert.assertEquals(0, manager.getReconstructionCount());
+ Assert.assertNull(
+
IoTDBDataNodeCacheLeaderClientManager.LEADER_CACHE_MANAGER.getLeaderEndPoint(deviceId));
+ }
+
+ final String ipv6DeviceId = "sync-ipv6-device";
+ final TEndPoint ipv6EndPoint = new TEndPoint("::1", 6677);
+ manager.updateLeaderCache(ipv6DeviceId, ipv6EndPoint);
+
+ Assert.assertEquals(Arrays.asList(originalEndPoint, ipv6EndPoint),
endPoints);
+ Assert.assertEquals(1, manager.getReconstructionCount());
+ Assert.assertEquals(
+ ipv6EndPoint,
+
IoTDBDataNodeCacheLeaderClientManager.LEADER_CACHE_MANAGER.getLeaderEndPoint(
+ ipv6DeviceId));
+ } finally {
+ manager.close();
+ }
+ }
+
+ private static IoTDBDataNodeAsyncClientManager createAsyncManager(
+ final List<TEndPoint> endPoints) {
+ return new IoTDBDataNodeAsyncClientManager(
+ endPoints,
+ true,
+ "round-robin",
+ new UserEntity(1L, "user", "cli-host"),
+ "password",
+ true,
+ "sync",
+ true,
+ true,
+ false,
+ true);
+ }
+
private static String getReceiverAttributes(final
IoTDBDataNodeAsyncClientManager manager)
throws Exception {
final Field field =
@@ -142,4 +229,37 @@ public class IoTDBDataNodeAsyncClientManagerTest {
field.setAccessible(true);
return (ExecutorService) field.get(manager);
}
+
+ private static class TestIoTDBDataNodeSyncClientManager extends
IoTDBDataNodeSyncClientManager {
+
+ private int reconstructionCount;
+
+ private TestIoTDBDataNodeSyncClientManager(final List<TEndPoint>
endPoints) {
+ super(
+ endPoints,
+ false,
+ null,
+ null,
+ null,
+ null,
+ true,
+ "round-robin",
+ new UserEntity(1L, "user", "cli-host"),
+ "password",
+ true,
+ "sync",
+ true,
+ true,
+ true);
+ }
+
+ @Override
+ protected void reconstructClient(final TEndPoint endPoint) {
+ reconstructionCount++;
+ }
+
+ private int getReconstructionCount() {
+ return reconstructionCount;
+ }
+ }
}
diff --git
a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
index c54878bfe1b..bfb98ebadc1 100644
---
a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
+++
b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
@@ -36,7 +36,7 @@ cluster_name=defaultCluster
# For other ConfigNodes that to join the cluster, cn_seed_config_node points
to any running ConfigNode's cn_internal_address:cn_internal_port.
# Note: After this ConfigNode successfully joins the cluster for the first
time, this parameter is no longer used.
# Each node automatically maintains the list of ConfigNodes and traverses
connections when restarting.
-# Format: address:port e.g. 127.0.0.1:10710
+# Format: address:port or [ipv6]:port e.g. 127.0.0.1:10710 or [::1]:10710
# effectiveMode: first_start
# Datatype: String
cn_seed_config_node=127.0.0.1:10710
@@ -44,7 +44,7 @@ cn_seed_config_node=127.0.0.1:10710
# dn_seed_config_node points to any running ConfigNode's
cn_internal_address:cn_internal_port.
# Note: After this DataNode successfully joins the cluster for the first time,
this parameter is no longer used.
# Each node automatically maintains the list of ConfigNodes and traverses
connections when restarting.
-# Format: address:port e.g. 127.0.0.1:10710
+# Format: address:port or [ipv6]:port e.g. 127.0.0.1:10710 or [::1]:10710
# effectiveMode: first_start
# Datatype: String
dn_seed_config_node=127.0.0.1:10710
@@ -54,7 +54,7 @@ dn_seed_config_node=127.0.0.1:10710
####################
# Used for RPC communication inside cluster.
-# Could set 127.0.0.1(for local test) or ipv4 address.
+# Could set 127.0.0.1(for local test), ipv4/ipv6 address, or hostname.
# effectiveMode: first_start
# Datatype: String
cn_internal_address=127.0.0.1
@@ -70,7 +70,7 @@ cn_internal_port=10710
cn_consensus_port=10720
# Used for connection of IoTDB native clients(Session)
-# Could set 127.0.0.1(for local test) or ipv4 address
+# Could set 127.0.0.1(for local test), ipv4/ipv6 address, or hostname.
# effectiveMode: restart
# Datatype: String
dn_rpc_address=127.0.0.1
@@ -82,7 +82,7 @@ dn_rpc_address=127.0.0.1
dn_rpc_port=6667
# Used for communication inside cluster.
-# could set 127.0.0.1(for local test) or ipv4 address.
+# Could set 127.0.0.1(for local test), ipv4/ipv6 address, or hostname.
# effectiveMode: first_start
# Datatype: String
dn_internal_address=127.0.0.1
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/NodeUrlUtils.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/NodeUrlUtils.java
index 0c6aab02566..87d50fc75c7 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/NodeUrlUtils.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/NodeUrlUtils.java
@@ -49,13 +49,10 @@ public class NodeUrlUtils {
* Convert TEndPoint to TEndPointUrl
*
* @param endPoint TEndPoint
- * @return TEndPointUrl with format ip:port
+ * @return TEndPointUrl with format host:port or [ipv6]:port
*/
public static String convertTEndPointUrl(TEndPoint endPoint) {
- StringJoiner url = new StringJoiner(":");
- url.add(endPoint.getIp());
- url.add(String.valueOf(endPoint.getPort()));
- return url.toString();
+ return UrlUtils.convertTEndPointIpv4AndIpv6Url(endPoint);
}
/**
@@ -75,7 +72,7 @@ public class NodeUrlUtils {
/**
* Parse TEndPoint from a given TEndPointUrl
*
- * @param endPointUrl ip:port
+ * @param endPointUrl host:port or [ipv6]:port
* @return TEndPoint
* @throws BadNodeUrlException Throw when unable to parse
*/
diff --git
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/utils/NodeUrlUtilsTest.java
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/utils/NodeUrlUtilsTest.java
index 6f6e86ac8de..df1c6a216d4 100644
---
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/utils/NodeUrlUtilsTest.java
+++
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/utils/NodeUrlUtilsTest.java
@@ -69,6 +69,9 @@ public class NodeUrlUtilsTest {
new TEndPoint("::13.1.68.3", 6669));
final String endPointUrls =
"AD80:E32B:CA25:B3AE:DC4C:DAAF:CCDE:2345:6667,[0:0:0:0:0:FFFF:129.144.52.38]:6668,[::13.1.68.3]:6669";
+ final String convertedEndPointUrls =
+
"[AD80:E32B:CA25:B3AE:DC4C:DAAF:CCDE:2345]:6667,[0:0:0:0:0:FFFF:129.144.52.38]:6668,[::13.1.68.3]:6669";
+ Assert.assertEquals(convertedEndPointUrls,
NodeUrlUtils.convertTEndPointUrls(endPoints));
Assert.assertEquals(endPoints,
NodeUrlUtils.parseTEndPointUrls(endPointUrls));
}
@@ -90,6 +93,10 @@ public class NodeUrlUtilsTest {
new TEndPoint("AD80:E32B:CA25:B3AE:DC4C:DAAF:CDDE:ABFD",
22282)));
final String configNodeUrls =
"0,AD80:E32B:CA25:B3AE:DC4C:DAAF:CDDE:ABFD:22277,[AD80:E32B:CA25:B3AE:DC4C:DAAF:CDDE:ABFD]:22278;1,AD80:E32B:CA25:B3AE:DC4C:DAAF:CDDE:ABFD:22279,AD80:E32B:CA25:B3AE:DC4C:DAAF:CDDE:ABFD:22280;2,AD80:E32B:CA25:B3AE:DC4C:DAAF:CDDE:ABFD:22281,AD80:E32B:CA25:B3AE:DC4C:DAAF:CDDE:ABFD:22282";
+ final String convertedConfigNodeUrls =
+
"0,[AD80:E32B:CA25:B3AE:DC4C:DAAF:CDDE:ABFD]:22277,[AD80:E32B:CA25:B3AE:DC4C:DAAF:CDDE:ABFD]:22278;1,[AD80:E32B:CA25:B3AE:DC4C:DAAF:CDDE:ABFD]:22279,[AD80:E32B:CA25:B3AE:DC4C:DAAF:CDDE:ABFD]:22280;2,[AD80:E32B:CA25:B3AE:DC4C:DAAF:CDDE:ABFD]:22281,[AD80:E32B:CA25:B3AE:DC4C:DAAF:CDDE:ABFD]:22282";
+ Assert.assertEquals(
+ convertedConfigNodeUrls,
NodeUrlUtils.convertTConfigNodeUrls(configNodeLocations));
Assert.assertEquals(configNodeLocations,
NodeUrlUtils.parseTConfigNodeUrls(configNodeUrls));
}
}