This is an automated email from the ASF dual-hosted git repository. HTHou pushed a commit to branch codex/ipv6-endpoint-support in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit 736c55af84010e10fac8cdcfb8aa1f6baf7ca296 Author: HTHou <[email protected]> AuthorDate: Thu Jul 9 11:41:03 2026 +0800 Support IPv6 endpoint URLs --- .../iotdb/it/env/cluster/env/AbstractEnv.java | 29 ++++---- .../it/env/cluster/node/AbstractNodeWrapper.java | 3 +- .../iotdb/it/env/remote/env/RemoteServerEnv.java | 23 ++++--- .../IoTDBIoTConsensusV23C3DBasicITBase.java | 19 ++++-- .../manual/enhanced/IoTDBPipeClusterIT.java | 2 +- .../auto/enhanced/IoTDBPipeClusterIT.java | 2 +- iotdb-client/client-py/iotdb/Session.py | 66 +++++++++++++------ .../client-py/tests/unit/test_session_node_url.py | 54 +++++++++++++++ .../src/main/java/org/apache/iotdb/jdbc/Utils.java | 77 +++++++++++++--------- .../test/java/org/apache/iotdb/jdbc/UtilsTest.java | 42 ++++++++++++ .../main/java/org/apache/iotdb/rpc/UrlUtils.java | 48 ++++++++++++-- .../java/org/apache/iotdb/rpc/UrlUtilsTest.java | 48 ++++++++++++++ .../apache/iotdb/session/SessionConnection.java | 11 ++-- .../org/apache/iotdb/session/pool/SessionPool.java | 7 +- iotdb-core/ainode/iotdb/ainode/core/config.py | 13 +++- .../ainode/iotdb/ainode/core/ingress/iotdb.py | 11 +++- .../ainode/resources/conf/iotdb-ainode.properties | 3 +- .../apache/iotdb/consensus/ratis/utils/Utils.java | 9 ++- .../iotdb/consensus/ratis/utils/UtilsTest.java | 54 +++++++++++++++ .../client/IoTDBDataNodeAsyncClientManager.java | 3 +- .../thrift/async/IoTDBDataRegionAsyncSink.java | 3 +- .../execution/config/sys/TestConnectionTask.java | 3 +- .../conf/iotdb-system.properties.template | 10 +-- .../apache/iotdb/commons/utils/NodeUrlUtils.java | 9 +-- .../iotdb/commons/utils/NodeUrlUtilsTest.java | 7 ++ 25 files changed, 433 insertions(+), 123 deletions(-) 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 e5d4e19834d..c4534fb5a7e 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; @@ -150,9 +151,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); @@ -162,9 +162,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); @@ -1038,7 +1037,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 @@ -1618,18 +1618,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); @@ -1652,10 +1648,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..409ec744641 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; @@ -606,7 +607,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/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/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/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/iotdb-client/client-py/iotdb/Session.py b/iotdb-client/client-py/iotdb/Session.py index 0a5cb43fd54..a65d40ab2ad 100644 --- a/iotdb-client/client-py/iotdb/Session.py +++ b/iotdb-client/client-py/iotdb/Session.py @@ -18,46 +18,47 @@ 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 +68,32 @@ 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 + + class Session(object): DEFAULT_FETCH_SIZE = 5000 DEFAULT_USER = "root" @@ -158,9 +185,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 +286,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): 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..fbfde69122f --- /dev/null +++ b/iotdb-client/client-py/tests/unit/test_session_node_url.py @@ -0,0 +1,54 @@ +# 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 + + +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]) 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..fa394f91c69 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 @@ -90,6 +90,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 +230,14 @@ 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?use_ssl=true", new Properties()); + + assertEquals("::1", params.getHost()); + assertEquals(6667, params.getPort()); + 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..f0c08e164ce 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 @@ -24,7 +24,8 @@ import org.apache.iotdb.common.rpc.thrift.TEndPoint; /** 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 +33,57 @@ public class UrlUtils { * Parse TEndPoint from a given TEndPointUrl * example:[D80:0000:0000:0000:ABAA:0000:00C2:0002]:22227 * - * @param endPointUrl ip:port + * @param endPointUrl host:port or [ipv6]:port * @return TEndPoint null if parse error */ public static TEndPoint parseTEndPointIpv4AndIpv6Url(String endPointUrl) { TEndPoint endPoint = new TEndPoint(); + 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(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(); + } 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); - } endPoint.setIp(ip); endPoint.setPort(Integer.parseInt(port)); } 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; + } + + 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..9c7061321a8 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 @@ -51,6 +51,21 @@ 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 public void testParseIPV4URL() { String hostAndPoint = "192.0.0.1:22227"; @@ -58,4 +73,37 @@ 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)); + } } 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-core/ainode/iotdb/ainode/core/config.py b/iotdb-core/ainode/iotdb/ainode/core/config.py index 4995dda7bf3..fdc56d774cd 100644 --- a/iotdb-core/ainode/iotdb/ainode/core/config.py +++ b/iotdb-core/ainode/iotdb/ainode/core/config.py @@ -466,13 +466,22 @@ 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 """ - 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: + 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/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 381206258f1..247e45d27ec 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; @@ -562,7 +563,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/protocol/thrift/async/IoTDBDataRegionAsyncSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java index 250ebeabb39..685620a60b7 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; @@ -876,7 +877,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/node-commons/src/assembly/resources/conf/iotdb-system.properties.template b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template index 05ff507226d..e04e53f5d6d 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)); } }
