Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
/*
* 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.relational.it.schema;

import org.apache.iotdb.commons.cluster.NodeStatus;
import org.apache.iotdb.consensus.ConsensusFactory;
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.it.framework.IoTDBTestRunner;
import org.apache.iotdb.itbase.category.ClusterIT;
import org.apache.iotdb.itbase.env.BaseEnv;

import org.junit.Assert;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Collections;
import java.util.concurrent.Callable;

import static org.junit.Assert.assertTrue;

@RunWith(IoTDBTestRunner.class)
@Category({ClusterIT.class})
public class IoTDBDeviceTemplateIT {

private static final Logger LOGGER = LoggerFactory.getLogger(IoTDBDeviceTemplateIT.class);

private static final String DATABASE = "root.device_template_ha";
private static final String TEMPLATE1 = "temp1";
private static final String TEMPLATE2 = "temp2";
private static final String DEVICE1 = DATABASE + ".dev01";
private static final String DEVICE2 = DATABASE + ".dev02";

private static void initCluster() {
EnvFactory.getEnv()
.getConfig()
.getCommonConfig()
.setConfigNodeConsensusProtocolClass(ConsensusFactory.RATIS_CONSENSUS)
.setSchemaRegionConsensusProtocolClass(ConsensusFactory.RATIS_CONSENSUS)
.setDataRegionConsensusProtocolClass(ConsensusFactory.IOT_CONSENSUS)
.setSchemaReplicationFactor(3)
.setDataReplicationFactor(2);
EnvFactory.getEnv().getConfig().getConfigNodeConfig().setMetadataLeaseFenceMs(20000);
EnvFactory.getEnv().initClusterEnvironment(1, 3);
}

private static void cleanCluster() {
EnvFactory.getEnv().cleanClusterEnvironment();
}

private void preSetup(final Statement statement) throws Exception {
statement.execute("CREATE DATABASE " + DATABASE);
statement.execute("CREATE DEVICE TEMPLATE " + TEMPLATE1 + " (s1 INT32, s2 INT64)");
statement.execute("SET DEVICE TEMPLATE " + TEMPLATE1 + " TO " + DATABASE);
statement.execute("CREATE TIMESERIES OF DEVICE TEMPLATE ON " + DEVICE1);
statement.execute("INSERT INTO " + DEVICE1 + "(time, s1, s2) VALUES(1, 1, 1)");
}

@Test
public void testHAWithOneDataNodeIsDown() throws Exception {
initCluster();
try {
final DataNodeWrapper liveDataNode = EnvFactory.getEnv().getDataNodeWrapper(0);
final DataNodeWrapper victimDataNode = EnvFactory.getEnv().getDataNodeWrapper(2);
try (final Connection connection =
EnvFactory.getEnv()
.getConnection(
liveDataNode,
SessionConfig.DEFAULT_USER,
SessionConfig.DEFAULT_PASSWORD,
BaseEnv.TREE_SQL_DIALECT);
final Statement statement = connection.createStatement()) {

preSetup(statement);

victimDataNode.stop();
Assert.assertFalse("victim DataNode should be stopped", victimDataNode.isAlive());

executeDeviceTemplateHATests(statement);
}
} finally {
cleanCluster();
}
}

@Test
public void testHAWithOneDataNodeIsReadOnly() throws Exception {
initCluster();
try {
final DataNodeWrapper liveDataNode = EnvFactory.getEnv().getDataNodeWrapper(0);
final DataNodeWrapper victimDataNode = EnvFactory.getEnv().getDataNodeWrapper(2);

try (final Connection connection =
EnvFactory.getEnv()
.getConnection(
liveDataNode,
SessionConfig.DEFAULT_USER,
SessionConfig.DEFAULT_PASSWORD,
BaseEnv.TREE_SQL_DIALECT);
final Statement statement = connection.createStatement()) {

preSetup(statement);

try (final Connection victimConn =
EnvFactory.getEnv()
.getConnection(
victimDataNode,
SessionConfig.DEFAULT_USER,
SessionConfig.DEFAULT_PASSWORD,
BaseEnv.TABLE_SQL_DIALECT);
final Statement victimStmt = victimConn.createStatement()) {

victimStmt.execute("SET SYSTEM TO READONLY ON LOCAL");

EnvFactory.getEnv()
.ensureNodeStatus(
Collections.singletonList(victimDataNode),
Collections.singletonList(NodeStatus.ReadOnly));
}

executeDeviceTemplateHATests(statement);
}
} finally {
cleanCluster();
}
}

private void executeDeviceTemplateHATests(final Statement statement) throws Exception {
// 1. SHOW — verify all SHOW operations work
LOGGER.info("1. start to test high availability of show device template operations");
assertTrue(
"SHOW DEVICE TEMPLATES must include " + TEMPLATE1, templateExists(statement, TEMPLATE1));
assertTrue(
"SHOW NODES IN DEVICE TEMPLATE must include s1",
templateHasMeasurement(statement, TEMPLATE1, "s1"));
assertTrue(
"SHOW PATHS SET DEVICE TEMPLATE must include " + DATABASE,
pathSetTemplate(statement, TEMPLATE1, DATABASE));
assertTrue(
"SHOW PATHS USING DEVICE TEMPLATE must include " + DEVICE1,
pathUsingTemplate(statement, TEMPLATE1, DEVICE1));

// 2. ALTER — add a measurement to t1
LOGGER.info("2. start to test high availability of alter device template procedure");
assertStatementEffect(
statement,
"ALTER DEVICE TEMPLATE " + TEMPLATE1 + " ADD (s3 FLOAT)",
() -> templateHasMeasurement(statement, TEMPLATE1, "s3"),
"ALTER DEVICE TEMPLATE must succeed with one DataNode down");

// 3. DEACTIVATE — deactivate t1 from dev01
LOGGER.info("3. start to test high availability of deactivate device template procedure");
assertStatementEffect(
statement,
"DEACTIVATE DEVICE TEMPLATE " + TEMPLATE1 + " FROM " + DEVICE1,
() -> !pathUsingTemplate(statement, TEMPLATE1, DEVICE1),
"DEACTIVATE DEVICE TEMPLATE must succeed with one DataNode down");

// 4. UNSET — unset t1 from database
LOGGER.info("4. start to test high availability of unset device template procedure");
assertStatementEffect(
statement,
"UNSET DEVICE TEMPLATE " + TEMPLATE1 + " FROM " + DATABASE,
() -> !pathSetTemplate(statement, TEMPLATE1, DATABASE),
"UNSET DEVICE TEMPLATE must succeed with one DataNode down");

// 5. DROP — drop t1
LOGGER.info("5. start to test high availability of drop device template procedure");
assertStatementEffect(
statement,
"DROP DEVICE TEMPLATE " + TEMPLATE1,
() -> !templateExists(statement, TEMPLATE1),
"DROP DEVICE TEMPLATE must succeed with one DataNode down");

// 6. CREATE — create a new aligned template t2
LOGGER.info("6. start to test high availability of create device template procedure");
assertStatementEffect(
statement,
"CREATE DEVICE TEMPLATE " + TEMPLATE2 + " ALIGNED (lat FLOAT, lon FLOAT)",
() -> templateExists(statement, TEMPLATE2),
"CREATE DEVICE TEMPLATE must succeed with one DataNode down");
assertTrue(
"SHOW NODES IN DEVICE TEMPLATE must include lat",
templateHasMeasurement(statement, TEMPLATE2, "lat"));
assertTrue(
"SHOW NODES IN DEVICE TEMPLATE must include lon",
templateHasMeasurement(statement, TEMPLATE2, "lon"));

// 7. SET — set t2 to database
LOGGER.info("7. start to test high availability of set device template procedure");
assertStatementEffect(
statement,
"SET DEVICE TEMPLATE " + TEMPLATE2 + " TO " + DATABASE,
() -> pathSetTemplate(statement, TEMPLATE2, DATABASE),
"SET DEVICE TEMPLATE must succeed with one DataNode down");

// 8. ACTIVATE — activate t2 on dev02
LOGGER.info("8. start to test high availability of activate device template procedure");
assertStatementEffect(
statement,
"CREATE TIMESERIES OF DEVICE TEMPLATE ON " + DEVICE2,
() -> pathUsingTemplate(statement, TEMPLATE2, DEVICE2),
"CREATE TIMESERIES USING DEVICE TEMPLATE must succeed with one DataNode down");
}

private void assertStatementEffect(
final Statement statement,
final String sql,
final Callable<Boolean> effect,
final String message)
throws Exception {
statement.execute(sql);
assertTrue(message, effect.call());
}

private boolean templateExists(final Statement statement, final String templateName)
throws Exception {
try (final ResultSet resultSet = statement.executeQuery("SHOW DEVICE TEMPLATES")) {
while (resultSet.next()) {
if (templateName.equalsIgnoreCase(resultSet.getString(1))) {
return true;
}
}
}
return false;
}

private boolean templateHasMeasurement(
final Statement statement, final String templateName, final String measurement)
throws Exception {
try (final ResultSet resultSet =
statement.executeQuery("SHOW NODES IN DEVICE TEMPLATE " + templateName)) {
while (resultSet.next()) {
if (measurement.equalsIgnoreCase(resultSet.getString(1))) {
return true;
}
}
}
return false;
}

private boolean pathSetTemplate(
final Statement statement, final String templateName, final String path) throws Exception {
try (final ResultSet resultSet =
statement.executeQuery("SHOW PATHS SET DEVICE TEMPLATE " + templateName)) {
while (resultSet.next()) {
if (path.equalsIgnoreCase(resultSet.getString(1))) {
return true;
}
}
}
return false;
}

private boolean pathUsingTemplate(
final Statement statement, final String templateName, final String path) throws Exception {
try (final ResultSet resultSet =
statement.executeQuery("SHOW PATHS USING DEVICE TEMPLATE " + templateName)) {
while (resultSet.next()) {
if (path.equalsIgnoreCase(resultSet.getString(1))) {
return true;
}
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@ private ConfigNodeMessages() {}
public static final String LOG_REDIRECTION_RECOMMENDED_REMOVECONFIGNODE_BUT_NO_LEADER_ENDPOINT_PROVIDED_ABORT_RETRY_520A4C64 = "Redirection recommended for removeConfigNode but no leader endpoint provided, abort retry.";
public static final String LOG_FAILED_WRITE_AUDIT_LOG_DATANODE_ARG_RESPONSE_ARG_691ABC90 = "Failed to write audit log to DataNode {}, response: {}";
public static final String LOG_FAILED_WRITE_AUDIT_LOG_DATANODE_ARG_90F15E13 = "Failed to write audit log to DataNode {}";
public static final String LOG_DATANODE_FAILED_TO_EXECUTE_METADATA_CACHE_PROPAGATION = "DataNode {} failed to execute metadata cache propagation, status: {}";
public static final String EXCEPTION_UNKNOWN_PHYSICALPLANTYPE_ARG_7F21B699 = "Unknown PhysicalPlanType: %d";
public static final String LOG_CANNOT_READ_MORE_PHYSICALPLANS_ARG_SUCCESSFULLY_READ_INDEX_ARG_REASON_2EC90E78 = "Cannot read more PhysicalPlans from {}, successfully read index is {}. The reason is";
public static final String EXCEPTION_FILE_11296840 = "file: ";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ public final class ManagerMessages {
"Failed to sync consumer group meta. Result status: {}.";
public static final String FAILED_TO_SYNC_PIPE_META_RESULT_STATUS =
"Failed to sync pipe meta. Result status: {}.";
public static final String FAILED_TO_SYNC_TEMPLATE_EXTENSION_INFO_TO_DATANODE =
"Failed to sync template {} extension info to DataNode {}";
public static final String EXIST_DATANODE_FAILED_TO_EXECUTE_AND_COULD_NOT_BE_FENCED =
"Exist DataNode failed to execute template %s extension and could not be fenced; template state of DataNodes may be inconsistent";
public static final String FAILED_TO_SYNC_TOPIC_META_RESULT_STATUS =
"Failed to sync topic meta. Result status: {}.";
public static final String FAILED_TO_UNBIND_FROM_PIPE_CONFIG_REGION_CONNECTOR_METRICS_CONNECTOR =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ public final class ProcedureMessages {
"Failed to rollback pre-release {} for table {}.{} info to DataNode, failure results: {}";
public static final String FAILED_TO_PROVE_AN_UNREACHABLE_DN_IS_FENCED = "Failed to prove an unreachable DataNode is fenced";
public static final String FAILED_TO_ROLLBACK_PRE_RELEASE_TEMPLATE_INFO_OF_TEMPLATE_SET =
"Failed to rollback pre release template info of template {} set on path {} on DataNode {}";
"Failed to rollback pre release template info of template {} set on path {} because {}";
public static final String FAILED_TO_ROLLBACK_PRE_SET_TEMPLATE_ON_PATH_DUE_TO =
"Failed to rollback pre set template {} on path {} due to {}";
public static final String FAILED_TO_ROLLBACK_PRE_UNSET_TEMPLATE_OPERATION_OF_TEMPLATE_SET =
Expand All @@ -510,7 +510,7 @@ public final class ProcedureMessages {
"Failed to serialize finalDataPartitionTables";
public static final String FAILED_TO_SERIALIZE_SKIPDATANODE = "Failed to serialize skipDataNode";
public static final String FAILED_TO_SET_SCHEMAENGINE_TEMPLATE_ON_PATH_BECAUSE_THERE_S =
"Failed to set schemaengine template %s on path %s because there's failure on DataNode %s";
"Failed to set schemaengine template %s on path %s because %s";
public static final String FAILED_TO_START_PIPE_DETAILS_METADATA_WILL_BE_SYNCHRONIZED_LATER =
"Failed to start pipe {}, details: {}, metadata will be synchronized later.";
public static final String FAILED_TO_STOP_AINODE_BECAUSE_BUT_THE_REMOVE_PROCESS_WILL =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ private ConfigNodeMessages() {}
"向 DataNode {} 写入审计日志失败,响应:{}";
public static final String LOG_FAILED_WRITE_AUDIT_LOG_DATANODE_ARG_90F15E13 =
"向 DataNode {} 写入审计日志失败";
public static final String LOG_DATANODE_FAILED_TO_EXECUTE_METADATA_CACHE_PROPAGATION = "DataNode {} 执行元数据缓存传播失败,状态:{}";
public static final String EXCEPTION_UNKNOWN_PHYSICALPLANTYPE_ARG_7F21B699 =
"未知的 PhysicalPlanType: %d";
public static final String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ public final class ManagerMessages {
"同步 consumer group 元数据失败。结果状态:{}。";
public static final String FAILED_TO_SYNC_PIPE_META_RESULT_STATUS =
"同步 pipe 元数据失败。结果状态:{}。";
public static final String FAILED_TO_SYNC_TEMPLATE_EXTENSION_INFO_TO_DATANODE =
"将模板 {} 的扩展信息同步到 DataNode {} 失败";
public static final String EXIST_DATANODE_FAILED_TO_EXECUTE_AND_COULD_NOT_BE_FENCED =
"存在 DataNode 执行模板 %s 扩展失败且未能被隔离,DataNode之间模板状态可能不一致";
public static final String FAILED_TO_SYNC_TOPIC_META_RESULT_STATUS =
"同步 topic 元数据失败。结果状态:{}。";
public static final String FAILED_TO_UNBIND_FROM_PIPE_CONFIG_REGION_CONNECTOR_METRICS_CONNECTOR =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ public final class ProcedureMessages {
public static final String FAILED_TO_PROVE_AN_UNREACHABLE_DN_IS_FENCED =
"不能认定一个不可达的datanode处于隔离状态";
public static final String FAILED_TO_ROLLBACK_PRE_RELEASE_TEMPLATE_INFO_OF_TEMPLATE_SET =
"回滚 DataNode {} 上路径 {} 处模板 {} 的预释放模板信息失败";
"回滚模板 {} 在路径 {} 处的预释放模板信息失败,原因:{}";
public static final String FAILED_TO_ROLLBACK_PRE_SET_TEMPLATE_ON_PATH_DUE_TO =
"回滚在路径 {} 上预设置的模板 {} 失败,原因:{}";
public static final String FAILED_TO_ROLLBACK_PRE_UNSET_TEMPLATE_OPERATION_OF_TEMPLATE_SET =
Expand All @@ -494,7 +494,7 @@ public final class ProcedureMessages {
"序列化 finalDataPartitionTables 失败";
public static final String FAILED_TO_SERIALIZE_SKIPDATANODE = "序列化 skipDataNode 失败";
public static final String FAILED_TO_SET_SCHEMAENGINE_TEMPLATE_ON_PATH_BECAUSE_THERE_S =
"在路径 %s 上设置 schemaengine 模板 %s 失败,原因:DataNode %s 上存在失败";
"设置 schemaengine 模板 %s 到路径 %s 失败,原因:%s";
public static final String FAILED_TO_START_PIPE_DETAILS_METADATA_WILL_BE_SYNCHRONIZED_LATER =
"启动 pipe {} 失败,详情:{},元数据将稍后同步。";
public static final String FAILED_TO_STOP_AINODE_BECAUSE_BUT_THE_REMOVE_PROCESS_WILL =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3308,7 +3308,8 @@ public TDataNodeLeaseRecoveryResp reloadCacheAfterLeaseRecovery() {
}
return new TDataNodeLeaseRecoveryResp()
.setStatus(RpcUtils.SUCCESS_STATUS)
.setTableInfo(clusterSchemaManager.getAllTableInfoForDataNodeActivation());
.setTableInfo(clusterSchemaManager.getAllTableInfoForDataNodeActivation())
.setTemplateInfo(clusterSchemaManager.getAllTemplateSetInfo());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reloadCacheAfterLeaseRecovery() should not return SUCCESS when the template snapshot cannot be read.

ClusterSchemaManager#getAllTemplateSetInfo() catches ConsensusException and returns new byte[0]. The recovery RPC then returns SUCCESS with that empty value. On the DataNode side, the field is considered set, so the template cache is cleared and the metadata lease returns to NORMAL.

The added regression tests reproduce both steps: an injected ConfigRegion read failure produces a zero-length template snapshot, and that snapshot still produces a SUCCESS (code 200) lease-recovery response.

This can unfence a DataNode with all template metadata missing. Please propagate the snapshot read failure through the RPC status so the DataNode remains fenced.

}

@Override
Expand Down
Loading