This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch dev/1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/dev/1.3 by this push:
new 2806466afac [To dev/1.3] Improve schema metadata error messages and
recovery logs (#18937) (#18975)
2806466afac is described below
commit 2806466afacbbd1d280af228dd541c136d14743f
Author: Caideyipi <[email protected]>
AuthorDate: Thu Oct 22 11:42:43 2026 +0800
[To dev/1.3] Improve schema metadata error messages and recovery logs
(#18937) (#18975)
---
.../org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java | 2 +-
.../iotdb/db/i18n/DataNodeSchemaMessages.java | 26 ++++
.../iotdb/db/i18n/DataNodeSchemaMessages.java | 19 +++
.../apache/iotdb/db/schemaengine/SchemaEngine.java | 9 +-
.../schemaregion/impl/SchemaRegionMemoryImpl.java | 12 +-
.../schemaregion/utils/MetaFormatUtils.java | 41 +++++--
.../template/ClusterTemplateManager.java | 32 +++--
.../iotdb/db/metadata/MetaFormatUtilsTest.java | 110 +++++++++++++++++
.../template/ClusterTemplateManagerTest.java | 131 +++++++++++++++++++++
9 files changed, 349 insertions(+), 33 deletions(-)
diff --git
a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java
index ddb298da4f5..d69c62532ef 100644
---
a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java
+++
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java
@@ -99,7 +99,7 @@ public class IoTDBSimpleQueryIT {
} catch (Exception e) {
assertEquals(
TSStatusCode.ILLEGAL_PARAMETER.getStatusCode()
- + ": SDT compression deviation cannot be negative. Failed to
create timeseries for path root.sg1.d0.s1",
+ + ": SDT property compdev must be non-negative, but was [-2].
Failed to create timeseries for path root.sg1.d0.s1",
e.getMessage());
}
diff --git
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
index df31f7e084d..4b027a8a644 100644
---
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
+++
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
@@ -26,4 +26,30 @@ public final class DataNodeSchemaMessages {
public static final String
EXCEPTION_CANNOT_RECOVER_DUPLICATED_SCHEMAREGION_ARG_FOUND_IN_DATABASES_ARG_AND_ARG_D9F60E05
=
"Cannot recover duplicated SchemaRegion [%s] found in databases [%s]
and [%s].";
+
+ public static final String FAILED_TO_RECOVER_TAG_INDEX =
+ "Failed to recover tag index for measurement [{}] in database [{}],
SchemaRegion [{}], tag offset [{}].";
+ public static final String SCHEMA_REGION_FAILED_TO_RECOVER =
+ "SchemaRegion [%d] in StorageGroup [%s] failed to recover.";
+ public static final String SDT_COMPRESSION_DEVIATION_REQUIRED =
+ "SDT compression requires the compdev property";
+ public static final String SDT_COMPRESSION_DEVIATION_NEGATIVE =
+ "SDT property compdev must be non-negative, but was [%s]";
+ public static final String SDT_COMPRESSION_DEVIATION_FORMAT_ERROR =
+ "SDT property compdev must be a number, but was [%s]";
+ public static final String SDT_COMPRESSION_MAX_GREATER_THAN_MIN =
+ "SDT property compmaxtime [%s] must be greater than compmintime [%s]";
+ public static final String SDT_COMPRESSION_TIME_NEGATIVE =
+ "SDT property %s must be non-negative, but was [%s]";
+ public static final String SDT_COMPRESSION_TIME_FORMAT_ERROR =
+ "SDT property %s must be a 64-bit integer, but was [%s]";
+ public static final String SDT_ENABLED_NO_COMPRESSION_TIME =
+ "Timeseries [{}] uses SDT without property [{}]; using default [{}].";
+ public static final String CREATE_TEMPLATE_ERROR =
+ "Failed to create schema template [%s] on ConfigNode: %s";
+ public static final String GET_ALL_TEMPLATE_ERROR =
+ "Failed to get schema templates from ConfigNode: %s";
+ public static final String GET_TEMPLATE_INFO_ERROR =
+ "Failed to get schema template [%s] from ConfigNode: %s";
+ public static final String ILLEGAL_PATH_LOG = "illegal path {}";
}
diff --git
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
index 5fd43c669fc..7c94694932e 100644
---
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
+++
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
@@ -26,4 +26,23 @@ public final class DataNodeSchemaMessages {
public static final String
EXCEPTION_CANNOT_RECOVER_DUPLICATED_SCHEMAREGION_ARG_FOUND_IN_DATABASES_ARG_AND_ARG_D9F60E05
=
"无法恢复重复的 SchemaRegion [%s],其同时存在于数据库 [%s] 和 [%s] 中。";
+
+ public static final String FAILED_TO_RECOVER_TAG_INDEX =
+ "恢复测点 [{}] 的 tag 索引失败,数据库为 [{}],SchemaRegion 为 [{}],tag 偏移量为 [{}]。";
+ public static final String SCHEMA_REGION_FAILED_TO_RECOVER =
+ "SchemaRegion [%d] 在 StorageGroup [%s] 中恢复失败。";
+ public static final String SDT_COMPRESSION_DEVIATION_REQUIRED = "SDT 压缩必须设置
compdev 属性";
+ public static final String SDT_COMPRESSION_DEVIATION_NEGATIVE = "SDT 属性
compdev 必须为非负数,实际值为 [%s]";
+ public static final String SDT_COMPRESSION_DEVIATION_FORMAT_ERROR =
+ "SDT 属性 compdev 必须为数字,实际值为 [%s]";
+ public static final String SDT_COMPRESSION_MAX_GREATER_THAN_MIN =
+ "SDT 属性 compmaxtime [%s] 必须大于 compmintime [%s]";
+ public static final String SDT_COMPRESSION_TIME_NEGATIVE = "SDT 属性 %s
必须为非负数,实际值为 [%s]";
+ public static final String SDT_COMPRESSION_TIME_FORMAT_ERROR = "SDT 属性 %s
必须为 64 位整数,实际值为 [%s]";
+ public static final String SDT_ENABLED_NO_COMPRESSION_TIME =
+ "时间序列 [{}] 已启用 SDT 但未设置属性 [{}],使用默认值 [{}]。";
+ public static final String CREATE_TEMPLATE_ERROR = "在 ConfigNode 上创建元数据模板
[%s] 失败:%s";
+ public static final String GET_ALL_TEMPLATE_ERROR = "从 ConfigNode
获取元数据模板列表失败:%s";
+ public static final String GET_TEMPLATE_INFO_ERROR = "从 ConfigNode 获取元数据模板
[%s] 失败:%s";
+ public static final String ILLEGAL_PATH_LOG = "非法路径 {}";
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/SchemaEngine.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/SchemaEngine.java
index 504eed7b904..abfc243ad91 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/SchemaEngine.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/SchemaEngine.java
@@ -342,11 +342,12 @@ public class SchemaEngine {
timeRecord);
return schemaRegion;
} catch (MetadataException e) {
- logger.error(
+ throw new RuntimeException(
String.format(
- "SchemaRegion [%d] in StorageGroup [%s] failed to recover.",
- schemaRegionId.getId(), storageGroup.getFullPath()));
- throw new RuntimeException(e);
+ DataNodeSchemaMessages.SCHEMA_REGION_FAILED_TO_RECOVER,
+ schemaRegionId.getId(),
+ storageGroup.getFullPath()),
+ e);
}
};
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionMemoryImpl.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionMemoryImpl.java
index 1c89a9b613b..65e8e2f95ee 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionMemoryImpl.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionMemoryImpl.java
@@ -37,6 +37,7 @@ import org.apache.iotdb.db.conf.IoTDBDescriptor;
import
org.apache.iotdb.db.exception.metadata.SchemaDirCreationFailureException;
import org.apache.iotdb.db.exception.metadata.SchemaQuotaExceededException;
import org.apache.iotdb.db.exception.metadata.SeriesOverflowException;
+import org.apache.iotdb.db.i18n.DataNodeSchemaMessages;
import org.apache.iotdb.db.queryengine.common.schematree.ClusterSchemaTree;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.metedata.write.CreateTimeSeriesNode;
import org.apache.iotdb.db.schemaengine.metric.ISchemaRegionMetric;
@@ -109,8 +110,6 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
-import static org.apache.tsfile.common.constant.TsFileConstant.PATH_SEPARATOR;
-
/**
* This class takes the responsibility of serialization of all the metadata
info of one certain
* schema region and persistent it into files. This class contains the
interfaces to modify the
@@ -508,9 +507,12 @@ public class SchemaRegionMemoryImpl implements
ISchemaRegion {
tagManager.recoverIndex(measurementMNode.getOffset(),
measurementMNode);
} catch (IOException e) {
logger.error(
- "Failed to recover tagIndex for {} in schemaRegion {}.",
- storageGroupFullPath + PATH_SEPARATOR +
measurementMNode.getFullPath(),
- schemaRegionId);
+ DataNodeSchemaMessages.FAILED_TO_RECOVER_TAG_INDEX,
+ measurementMNode.getName(),
+ storageGroupFullPath,
+ schemaRegionId,
+ measurementMNode.getOffset(),
+ e);
}
},
deviceMNode -> {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/MetaFormatUtils.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/MetaFormatUtils.java
index 239a2e0bd4f..b7f5c3d629e 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/MetaFormatUtils.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/MetaFormatUtils.java
@@ -24,6 +24,7 @@ import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.db.conf.IoTDBConfig;
import org.apache.iotdb.db.exception.metadata.AliasAlreadyExistException;
import org.apache.iotdb.db.exception.metadata.IllegalParameterOfPathException;
+import org.apache.iotdb.db.i18n.DataNodeSchemaMessages;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -145,17 +146,24 @@ public class MetaFormatUtils {
private static void checkSDTFormat(String path, Map<String, String> props)
throws IllegalParameterOfPathException {
if (!props.containsKey(SDT_COMP_DEV)) {
- throw new IllegalParameterOfPathException("SDT compression deviation is
required", path);
+ throw new IllegalParameterOfPathException(
+ DataNodeSchemaMessages.SDT_COMPRESSION_DEVIATION_REQUIRED, path);
}
try {
double d = Double.parseDouble(props.get(SDT_COMP_DEV));
if (d < 0) {
throw new IllegalParameterOfPathException(
- "SDT compression deviation cannot be negative", path);
+ String.format(
+ DataNodeSchemaMessages.SDT_COMPRESSION_DEVIATION_NEGATIVE,
props.get(SDT_COMP_DEV)),
+ path);
}
} catch (NumberFormatException e) {
- throw new IllegalParameterOfPathException("SDT compression deviation
formatting error", path);
+ throw new IllegalParameterOfPathException(
+ String.format(
+ DataNodeSchemaMessages.SDT_COMPRESSION_DEVIATION_FORMAT_ERROR,
+ props.get(SDT_COMP_DEV)),
+ path);
}
long compMinTime = sdtCompressionTimeFormat(SDT_COMP_MIN_TIME, props,
path);
@@ -163,7 +171,11 @@ public class MetaFormatUtils {
if (compMaxTime <= compMinTime) {
throw new IllegalParameterOfPathException(
- "SDT compression maximum time needs to be greater than compression
minimum time", path);
+ String.format(
+ DataNodeSchemaMessages.SDT_COMPRESSION_MAX_GREATER_THAN_MIN,
+ compMaxTime,
+ compMinTime),
+ path);
}
}
@@ -172,20 +184,23 @@ public class MetaFormatUtils {
throws IllegalParameterOfPathException {
boolean isCompMaxTime = compTime.equals(SDT_COMP_MAX_TIME);
long time = isCompMaxTime ? Long.MAX_VALUE : 0;
- String s = isCompMaxTime ? "maximum" : "minimum";
if (props.containsKey(compTime)) {
+ String value = props.get(compTime);
try {
- time = Long.parseLong(props.get(compTime));
- if (time < 0) {
- throw new IllegalParameterOfPathException(
- String.format("SDT compression %s time cannot be negative", s),
path);
- }
- } catch (IllegalParameterOfPathException e) {
+ time = Long.parseLong(value);
+ } catch (NumberFormatException e) {
+ throw new IllegalParameterOfPathException(
+ String.format(
+ DataNodeSchemaMessages.SDT_COMPRESSION_TIME_FORMAT_ERROR,
compTime, value),
+ path);
+ }
+ if (time < 0) {
throw new IllegalParameterOfPathException(
- String.format("SDT compression %s time formatting error", s),
path);
+
String.format(DataNodeSchemaMessages.SDT_COMPRESSION_TIME_NEGATIVE, compTime,
value),
+ path);
}
} else {
- logger.info("{} enabled SDT but did not set compression {} time", path,
s);
+ logger.debug(DataNodeSchemaMessages.SDT_ENABLED_NO_COMPRESSION_TIME,
path, compTime, time);
}
return time;
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManager.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManager.java
index 0350b7883e0..8e19e191da5 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManager.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManager.java
@@ -38,6 +38,7 @@ import
org.apache.iotdb.confignode.rpc.thrift.TGetPathsSetTemplatesReq;
import org.apache.iotdb.confignode.rpc.thrift.TGetPathsSetTemplatesResp;
import org.apache.iotdb.confignode.rpc.thrift.TGetTemplateResp;
import org.apache.iotdb.confignode.rpc.thrift.TSetSchemaTemplateReq;
+import org.apache.iotdb.db.i18n.DataNodeSchemaMessages;
import org.apache.iotdb.db.protocol.client.ConfigNodeClient;
import org.apache.iotdb.db.protocol.client.ConfigNodeClientManager;
import org.apache.iotdb.db.protocol.client.ConfigNodeInfo;
@@ -130,9 +131,11 @@ public class ClusterTemplateManager implements
ITemplateManager {
e,
TSStatusCode.CREATE_TEMPLATE_ERROR.getStatusCode()));
} catch (ClientManagerException | TException e) {
- throw new RuntimeException(
- new IoTDBException(
- "create template error.", e,
TSStatusCode.CREATE_TEMPLATE_ERROR.getStatusCode()));
+ throw wrapTemplateRpcException(
+ String.format(
+ DataNodeSchemaMessages.CREATE_TEMPLATE_ERROR,
statement.getName(), e.getMessage()),
+ e,
+ TSStatusCode.CREATE_TEMPLATE_ERROR);
}
}
@@ -175,9 +178,10 @@ public class ClusterTemplateManager implements
ITemplateManager {
throw new IoTDBRuntimeException(resp.getStatus());
}
} catch (ClientManagerException | TException e) {
- throw new RuntimeException(
- new IoTDBException(
- "get all template error.",
TSStatusCode.UNDEFINED_TEMPLATE.getStatusCode()));
+ throw wrapTemplateRpcException(
+ String.format(DataNodeSchemaMessages.GET_ALL_TEMPLATE_ERROR,
e.getMessage()),
+ e,
+ TSStatusCode.UNDEFINED_TEMPLATE);
}
return templatesList;
}
@@ -196,12 +200,20 @@ public class ClusterTemplateManager implements
ITemplateManager {
throw new IoTDBException(resp.status);
}
} catch (ClientManagerException | TException e) {
- throw new RuntimeException(
- new IoTDBException(
- "get template info error.",
TSStatusCode.UNDEFINED_TEMPLATE.getStatusCode()));
+ throw wrapTemplateRpcException(
+ String.format(DataNodeSchemaMessages.GET_TEMPLATE_INFO_ERROR, name,
e.getMessage()),
+ e,
+ TSStatusCode.UNDEFINED_TEMPLATE);
}
}
+ private static RuntimeException wrapTemplateRpcException(
+ String message, Exception cause, TSStatusCode statusCode) {
+ IoTDBException exception = new IoTDBException(message,
statusCode.getStatusCode());
+ exception.addSuppressed(cause);
+ return new RuntimeException(exception);
+ }
+
@Override
public void setSchemaTemplate(String queryId, String name, PartialPath path)
{
try (ConfigNodeClient configNodeClient =
@@ -256,7 +268,7 @@ public class ClusterTemplateManager implements
ITemplateManager {
try {
listPath.add(new PartialPath(item));
} catch (IllegalPathException e) {
- e.printStackTrace();
+ LOGGER.error(DataNodeSchemaMessages.ILLEGAL_PATH_LOG,
item, e);
}
});
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/MetaFormatUtilsTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/MetaFormatUtilsTest.java
new file mode 100644
index 00000000000..7fc550a7fa3
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/MetaFormatUtilsTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.metadata;
+
+import org.apache.iotdb.db.exception.metadata.IllegalParameterOfPathException;
+import org.apache.iotdb.db.schemaengine.schemaregion.utils.MetaFormatUtils;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class MetaFormatUtilsTest {
+
+ private static final String PATH = "root.sg.d.s";
+
+ @Test
+ public void testNonNumericSdtProperties() {
+ for (String property : new String[] {"compdev", "compmintime",
"compmaxtime"}) {
+ assertInvalidProperty(property, "invalid");
+ }
+ }
+
+ @Test
+ public void testNegativeSdtProperties() {
+ for (String property : new String[] {"compdev", "compmintime",
"compmaxtime"}) {
+ assertInvalidProperty(property, "-1");
+ }
+ }
+
+ @Test
+ public void testSdtTimeOverflow() {
+ for (String property : new String[] {"compmintime", "compmaxtime"}) {
+ assertInvalidProperty(property, "9223372036854775808");
+ assertInvalidProperty(property, "-9223372036854775809");
+ }
+ }
+
+ @Test
+ public void testInvalidSdtTimeRange() {
+ Map<String, String> props = sdtProps();
+ props.put("compmintime", "20");
+ props.put("compmaxtime", "10");
+ IllegalParameterOfPathException exception =
+ assertThrows(
+ IllegalParameterOfPathException.class,
+ () -> MetaFormatUtils.checkTimeseriesProps(PATH, props));
+
+ assertUserParameterError(exception);
+ assertTrue(exception.getMessage().contains("compmintime [20]"));
+ assertTrue(exception.getMessage().contains("compmaxtime [10]"));
+ }
+
+ @Test
+ public void testValidSdtTimeBoundsAndDefaults() throws
IllegalParameterOfPathException {
+ Map<String, String> props = sdtProps();
+ MetaFormatUtils.checkTimeseriesProps(PATH, props);
+ props.put("compmintime", "0");
+ MetaFormatUtils.checkTimeseriesProps(PATH, props);
+ props.put("compmaxtime", Long.toString(Long.MAX_VALUE));
+ MetaFormatUtils.checkTimeseriesProps(PATH, props);
+ }
+
+ private static void assertInvalidProperty(String property, String value) {
+ Map<String, String> props = sdtProps();
+ props.put(property, value);
+ IllegalParameterOfPathException exception =
+ assertThrows(
+ IllegalParameterOfPathException.class,
+ () -> MetaFormatUtils.checkTimeseriesProps(PATH, props));
+
+ assertUserParameterError(exception);
+ assertTrue(exception.getMessage().contains(property));
+ assertTrue(exception.getMessage().contains("[" + value + "]"));
+ }
+
+ private static void assertUserParameterError(IllegalParameterOfPathException
exception) {
+ assertEquals(TSStatusCode.ILLEGAL_PARAMETER.getStatusCode(),
exception.getErrorCode());
+ assertTrue(exception.isUserException());
+ assertTrue(exception.getMessage().contains(PATH));
+ }
+
+ private static Map<String, String> sdtProps() {
+ Map<String, String> props = new HashMap<>();
+ props.put("loss", "sdt");
+ props.put("compdev", "0.01");
+ return props;
+ }
+}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManagerTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManagerTest.java
new file mode 100644
index 00000000000..c029542daf3
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManagerTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.schemaengine.template;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.client.IClientManager;
+import org.apache.iotdb.commons.consensus.ConfigRegionId;
+import org.apache.iotdb.db.protocol.client.ConfigNodeClient;
+import org.apache.iotdb.db.protocol.client.ConfigNodeClientManager;
+import org.apache.iotdb.db.protocol.client.ConfigNodeInfo;
+import org.apache.iotdb.db.protocol.thrift.OperationType;
+import
org.apache.iotdb.db.queryengine.plan.statement.metadata.template.CreateSchemaTemplateStatement;
+import org.apache.iotdb.db.utils.ErrorHandlingUtils;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.apache.thrift.TException;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.file.metadata.enums.CompressionType;
+import org.apache.tsfile.file.metadata.enums.TSEncoding;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.Collections;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(ConfigNodeClientManager.class)
+@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*",
"javax.management.*"})
+public class ClusterTemplateManagerTest {
+
+ @SuppressWarnings("unchecked")
+ private static final IClientManager<ConfigRegionId, ConfigNodeClient>
CLIENT_MANAGER =
+ Mockito.mock(IClientManager.class);
+
+ private ConfigNodeClient client;
+
+ @Before
+ public void setUp() throws Exception {
+ Mockito.reset(CLIENT_MANAGER);
+ client = Mockito.mock(ConfigNodeClient.class);
+ PowerMockito.mockStatic(ConfigNodeClientManager.class);
+
PowerMockito.when(ConfigNodeClientManager.getInstance()).thenReturn(CLIENT_MANAGER);
+
Mockito.when(CLIENT_MANAGER.borrowClient(ConfigNodeInfo.CONFIG_REGION_ID)).thenReturn(client);
+ }
+
+ @Test
+ public void testGetTemplateRpcFailure() throws Exception {
+ TException rpcFailure = new TException("connection refused");
+ Mockito.when(client.getTemplate("test_template")).thenThrow(rpcFailure);
+
+ RuntimeException exception =
+ assertThrows(
+ RuntimeException.class,
+ () ->
ClusterTemplateManager.getInstance().getTemplate("test_template"));
+
+ TSStatus status = assertRpcFailure(exception, rpcFailure,
TSStatusCode.UNDEFINED_TEMPLATE);
+ assertTrue(status.getMessage().contains("test_template"));
+ }
+
+ @Test
+ public void testGetAllTemplatesRpcFailure() throws Exception {
+ TException rpcFailure = new TException("connection refused");
+ Mockito.when(client.getAllTemplates()).thenThrow(rpcFailure);
+
+ RuntimeException exception =
+ assertThrows(
+ RuntimeException.class, () ->
ClusterTemplateManager.getInstance().getAllTemplates());
+
+ assertRpcFailure(exception, rpcFailure, TSStatusCode.UNDEFINED_TEMPLATE);
+ }
+
+ @Test
+ public void testCreateTemplateRpcFailure() throws Exception {
+ TException rpcFailure = new TException("connection refused");
+
Mockito.when(client.createSchemaTemplate(Mockito.any())).thenThrow(rpcFailure);
+ CreateSchemaTemplateStatement statement =
+ new CreateSchemaTemplateStatement(
+ "test_template",
+ Collections.singletonList(Collections.singletonList("s")),
+
Collections.singletonList(Collections.singletonList(TSDataType.INT32)),
+
Collections.singletonList(Collections.singletonList(TSEncoding.PLAIN)),
+
Collections.singletonList(Collections.singletonList(CompressionType.UNCOMPRESSED)),
+ false);
+
+ RuntimeException exception =
+ assertThrows(
+ RuntimeException.class,
+ () ->
ClusterTemplateManager.getInstance().createSchemaTemplate(statement));
+
+ TSStatus status = assertRpcFailure(exception, rpcFailure,
TSStatusCode.CREATE_TEMPLATE_ERROR);
+ assertTrue(status.getMessage().contains("test_template"));
+ }
+
+ private static TSStatus assertRpcFailure(
+ RuntimeException exception, TException rpcFailure, TSStatusCode
statusCode) {
+ TSStatus status =
+ ErrorHandlingUtils.onQueryException(exception,
OperationType.EXECUTE_STATEMENT);
+ assertEquals(statusCode.getStatusCode(), status.getCode());
+ assertTrue(status.getMessage().contains("ConfigNode"));
+ assertTrue(status.getMessage().contains("connection refused"));
+ assertEquals(1, exception.getCause().getSuppressed().length);
+ assertSame(rpcFailure, exception.getCause().getSuppressed()[0]);
+ return status;
+ }
+}