diff --git a/iotdb-core/confignode/pom.xml b/iotdb-core/confignode/pom.xml
index a7cd4b6298b5..5d4798f6f2df 100644
--- a/iotdb-core/confignode/pom.xml
+++ b/iotdb-core/confignode/pom.xml
@@ -32,6 +32,7 @@
${confignode.test.skip}
false
${confignode.test.skip}
+ en
@@ -168,6 +169,37 @@
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+
+
+
+ src/main/java/**/*.java
+ src/test/java/**/*.java
+ src/main/i18n/**/*.java
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ add-i18n-source
+ generate-sources
+
+ add-source
+
+
+
+ src/main/i18n/${i18n.locale}
+
+
+
+
+
org.apache.maven.plugins
maven-assembly-plugin
@@ -241,6 +273,12 @@
+
+ with-zh-locale
+
+ zh
+
+
skipConfigNodeTests
diff --git a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
new file mode 100644
index 000000000000..322b57ed59dc
--- /dev/null
+++ b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
@@ -0,0 +1,79 @@
+/*
+ * 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.confignode.i18n;
+
+public final class ConfigNodeMessages {
+
+ public static final String
+ EXCEPTION_CONFIGNODE_SIMPLECONSENSUS_WRITES_ARE_DISABLED_AFTER_A_WAL_CLEANUP_FAILURE_RESTART_IS_REQUIRED_D52F0107 =
+ "ConfigNode SimpleConsensus writes are disabled after a WAL cleanup failure; restart is required.";
+ public static final String
+ EXCEPTION_FAILED_TO_APPLY_CONFIGNODE_ARG_THROUGH_CONSENSUS_LAYER_ARG_B4A985D7 =
+ "Failed to apply ConfigNode %s through consensus layer: %s";
+ public static final String
+ EXCEPTION_FAILED_TO_CLOSE_THE_CONFIGNODE_SIMPLECONSENSUS_LOG_WRITER_BC0F417C =
+ "Failed to close the ConfigNode SimpleConsensus log writer.";
+ public static final String
+ EXCEPTION_FAILED_TO_PERSIST_VERSION_INFO_FOR_CONFIGNODE_ARG_ARG_REGISTRATION_REMAINS_PENDING_AND_MUST_BE_RETRIED_F662C350 =
+ "Failed to persist version info for ConfigNode %d: %s. Registration remains pending and must be retried.";
+ public static final String EXECUTE_NON_QUERY_PLAN_FAILED =
+ "[ConfigRegion Update] Execute non-query plan failed";
+ public static final String FAILED_TO_ROLLBACK_PERSISTED_CONFIGNODE_SIMPLECONSENSUS_LOG =
+ "Failed to rollback persisted ConfigNode SimpleConsensus log. The persisted plan may be replayed after restart. Further SimpleConsensus writes are disabled until restart.";
+ public static final String
+ LOG_CONFIGREGION_UPDATE_FAILED_TO_CLOSE_THE_CONFIGNODE_SIMPLECONSENSUS_LOG_FILEPATH_ARG_RETRY_ARG_188A702E =
+ "[ConfigRegion Update] Failed to close the ConfigNode SimpleConsensus log, filePath: {}, retry: {}.";
+ public static final String
+ LOG_FAILED_TO_ROLL_BACK_AINODE_REGISTRATION_ARG_AFTER_VERSION_INFO_PERSISTENCE_FAILURE_VERSIONUPDATESTATUS_ARG_ROLLBACKSTATUS_ARG_BFF24BD4 =
+ "Failed to roll back AINode registration {} after version info persistence failure. versionUpdateStatus: {}, rollbackStatus: {}";
+ public static final String
+ LOG_FAILED_TO_ROLL_BACK_DATANODE_REGISTRATION_ARG_AFTER_VERSION_INFO_PERSISTENCE_FAILURE_VERSIONUPDATESTATUS_ARG_ROLLBACKSTATUS_ARG_437E6F09 =
+ "Failed to roll back DataNode registration {} after version info persistence failure. versionUpdateStatus: {}, rollbackStatus: {}";
+ public static final String
+ MESSAGE_FAILED_TO_PERSIST_VERSION_INFO_FOR_AINODE_ARG_ARG_THE_REGISTRATION_HAS_BEEN_ROLLED_BACK_PLEASE_RETRY_THE_REGISTRATION_7571C626 =
+ "Failed to persist version info for AINode %d: %s. The registration has been rolled back. Please retry the registration.";
+ public static final String
+ MESSAGE_FAILED_TO_PERSIST_VERSION_INFO_FOR_AINODE_ARG_ARG_THE_REGISTRATION_ROLLBACK_ALSO_FAILED_ARG_MANUAL_CLEANUP_MAY_BE_REQUIRED_BEFORE_RETRYING_THE_REGISTRATION_B9B1DCCD =
+ "Failed to persist version info for AINode %d: %s. The registration rollback also failed: %s. Manual cleanup may be required before retrying the registration.";
+ public static final String
+ MESSAGE_FAILED_TO_PERSIST_VERSION_INFO_FOR_DATANODE_ARG_ARG_THE_REGISTRATION_HAS_BEEN_ROLLED_BACK_PLEASE_RETRY_THE_REGISTRATION_5DDB311F =
+ "Failed to persist version info for DataNode %d: %s. The registration has been rolled back. Please retry the registration.";
+ public static final String
+ MESSAGE_FAILED_TO_PERSIST_VERSION_INFO_FOR_DATANODE_ARG_ARG_THE_REGISTRATION_ROLLBACK_ALSO_FAILED_ARG_MANUAL_CLEANUP_MAY_BE_REQUIRED_BEFORE_RETRYING_THE_REGISTRATION_5463B5FF =
+ "Failed to persist version info for DataNode %d: %s. The registration rollback also failed: %s. Manual cleanup may be required before retrying the registration.";
+ public static final String MESSAGE_STATUS_CODE_ARG_4B3BCD8B = "status code %d";
+ public static final String MESSAGE_UNKNOWN_ERROR_E9EEAFF8 = "unknown error";
+ public static final String PERSIST_CONFIGNODE_SIMPLECONSENSUS_LOG_FAILED =
+ "Persist ConfigNode SimpleConsensus log failed: %s";
+ public static final String
+ PERSIST_CURRENT_CONFIGPHYSICALPLAN_FOR_CONFIGNODE_SIMPLECONSENSUS_MODE_FAILED =
+ "Persist current ConfigPhysicalPlan for ConfigNode SimpleConsensus mode failed";
+ public static final String
+ ROLLBACK_FAILED_CONFIGPHYSICALPLAN_FOR_CONFIGNODE_SIMPLECONSENSUS_MODE_FAILED =
+ "Rollback failed ConfigPhysicalPlan for ConfigNode SimpleConsensus mode failed, planType: {}, logFile: {}, truncateOffset: {}, endIndexBeforeWrite: {}. Further SimpleConsensus writes are disabled until restart.";
+ public static final String SEAL_RECOVERED_CONFIGNODE_SIMPLECONSENSUS_LOG_FAILED =
+ "Seal recovered ConfigNode SimpleConsensus log failed: {}";
+ public static final String SIMPLECONSENSUS_LOG_WRITER_IS_NOT_INITIALIZED =
+ "SimpleConsensus log writer is not initialized.";
+ public static final String UNEXPECTED_INTERRUPTION_DURING_THE_CLOSE_METHOD_OF_LOGWRITER =
+ "[ConfigRegion Update] Unexpected interruption during the close method of logWriter";
+
+ private ConfigNodeMessages() {}
+}
diff --git a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
new file mode 100644
index 000000000000..aaaff13b4785
--- /dev/null
+++ b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
@@ -0,0 +1,78 @@
+/*
+ * 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.confignode.i18n;
+
+public final class ConfigNodeMessages {
+
+ public static final String
+ EXCEPTION_CONFIGNODE_SIMPLECONSENSUS_WRITES_ARE_DISABLED_AFTER_A_WAL_CLEANUP_FAILURE_RESTART_IS_REQUIRED_D52F0107 =
+ "ConfigNode SimpleConsensus 因 WAL 清理失败已禁用写入,需要重启。";
+ public static final String
+ EXCEPTION_FAILED_TO_APPLY_CONFIGNODE_ARG_THROUGH_CONSENSUS_LAYER_ARG_B4A985D7 =
+ "通过共识层应用 ConfigNode %s 失败:%s";
+ public static final String
+ EXCEPTION_FAILED_TO_CLOSE_THE_CONFIGNODE_SIMPLECONSENSUS_LOG_WRITER_BC0F417C =
+ "关闭 ConfigNode SimpleConsensus 日志写入器失败。";
+ public static final String
+ EXCEPTION_FAILED_TO_PERSIST_VERSION_INFO_FOR_CONFIGNODE_ARG_ARG_REGISTRATION_REMAINS_PENDING_AND_MUST_BE_RETRIED_F662C350 =
+ "持久化 ConfigNode %d 的版本信息失败:%s。注册尚未完成,需要重试。";
+ public static final String EXECUTE_NON_QUERY_PLAN_FAILED = "[ConfigRegion Update] 执行非查询计划失败";
+ public static final String FAILED_TO_ROLLBACK_PERSISTED_CONFIGNODE_SIMPLECONSENSUS_LOG =
+ "回滚已持久化的 ConfigNode SimpleConsensus 日志失败。该 plan 可能会在重启后被重放。重启前将禁止后续 SimpleConsensus 写入。";
+ public static final String
+ LOG_CONFIGREGION_UPDATE_FAILED_TO_CLOSE_THE_CONFIGNODE_SIMPLECONSENSUS_LOG_FILEPATH_ARG_RETRY_ARG_188A702E =
+ "[ConfigRegion Update] 关闭 ConfigNode SimpleConsensus 日志失败,filePath: {},重试: {}。";
+ public static final String
+ LOG_FAILED_TO_ROLL_BACK_AINODE_REGISTRATION_ARG_AFTER_VERSION_INFO_PERSISTENCE_FAILURE_VERSIONUPDATESTATUS_ARG_ROLLBACKSTATUS_ARG_BFF24BD4 =
+ "版本信息持久化失败后,回滚 AINode {} 的注册失败。versionUpdateStatus: {},rollbackStatus: {}";
+ public static final String
+ LOG_FAILED_TO_ROLL_BACK_DATANODE_REGISTRATION_ARG_AFTER_VERSION_INFO_PERSISTENCE_FAILURE_VERSIONUPDATESTATUS_ARG_ROLLBACKSTATUS_ARG_437E6F09 =
+ "版本信息持久化失败后,回滚 DataNode {} 的注册失败。versionUpdateStatus: {},rollbackStatus: {}";
+ public static final String
+ MESSAGE_FAILED_TO_PERSIST_VERSION_INFO_FOR_AINODE_ARG_ARG_THE_REGISTRATION_HAS_BEEN_ROLLED_BACK_PLEASE_RETRY_THE_REGISTRATION_7571C626 =
+ "持久化 AINode %d 的版本信息失败:%s。注册已回滚,请重试注册。";
+ public static final String
+ MESSAGE_FAILED_TO_PERSIST_VERSION_INFO_FOR_AINODE_ARG_ARG_THE_REGISTRATION_ROLLBACK_ALSO_FAILED_ARG_MANUAL_CLEANUP_MAY_BE_REQUIRED_BEFORE_RETRYING_THE_REGISTRATION_B9B1DCCD =
+ "持久化 AINode %d 的版本信息失败:%s。注册回滚也失败:%s。重试注册前可能需要手动清理。";
+ public static final String
+ MESSAGE_FAILED_TO_PERSIST_VERSION_INFO_FOR_DATANODE_ARG_ARG_THE_REGISTRATION_HAS_BEEN_ROLLED_BACK_PLEASE_RETRY_THE_REGISTRATION_5DDB311F =
+ "持久化 DataNode %d 的版本信息失败:%s。注册已回滚,请重试注册。";
+ public static final String
+ MESSAGE_FAILED_TO_PERSIST_VERSION_INFO_FOR_DATANODE_ARG_ARG_THE_REGISTRATION_ROLLBACK_ALSO_FAILED_ARG_MANUAL_CLEANUP_MAY_BE_REQUIRED_BEFORE_RETRYING_THE_REGISTRATION_5463B5FF =
+ "持久化 DataNode %d 的版本信息失败:%s。注册回滚也失败:%s。重试注册前可能需要手动清理。";
+ public static final String MESSAGE_STATUS_CODE_ARG_4B3BCD8B = "状态码 %d";
+ public static final String MESSAGE_UNKNOWN_ERROR_E9EEAFF8 = "未知错误";
+ public static final String PERSIST_CONFIGNODE_SIMPLECONSENSUS_LOG_FAILED =
+ "持久化 ConfigNode SimpleConsensus 日志失败:%s";
+ public static final String
+ PERSIST_CURRENT_CONFIGPHYSICALPLAN_FOR_CONFIGNODE_SIMPLECONSENSUS_MODE_FAILED =
+ "为 ConfigNode SimpleConsensus 模式持久化当前 ConfigPhysicalPlan 失败";
+ public static final String
+ ROLLBACK_FAILED_CONFIGPHYSICALPLAN_FOR_CONFIGNODE_SIMPLECONSENSUS_MODE_FAILED =
+ "回滚 ConfigNode SimpleConsensus 模式下失败的 ConfigPhysicalPlan 失败,planType: {}, logFile: {}, truncateOffset: {}, endIndexBeforeWrite: {}。重启前将禁止后续 SimpleConsensus 写入。";
+ public static final String SEAL_RECOVERED_CONFIGNODE_SIMPLECONSENSUS_LOG_FAILED =
+ "封存恢复出的 ConfigNode SimpleConsensus 日志失败:{}";
+ public static final String SIMPLECONSENSUS_LOG_WRITER_IS_NOT_INITIALIZED =
+ "SimpleConsensus log writer 未初始化。";
+ public static final String UNEXPECTED_INTERRUPTION_DURING_THE_CLOSE_METHOD_OF_LOGWRITER =
+ "[ConfigRegion Update] 关闭 logWriter 过程中发生意外中断";
+
+ private ConfigNodeMessages() {}
+}
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/statemachine/ConfigRegionStateMachine.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/statemachine/ConfigRegionStateMachine.java
index bf18b7637186..e96c7e56e684 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/statemachine/ConfigRegionStateMachine.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/statemachine/ConfigRegionStateMachine.java
@@ -24,7 +24,6 @@
import org.apache.iotdb.commons.auth.AuthException;
import org.apache.iotdb.commons.concurrent.IoTDBThreadPoolFactory;
import org.apache.iotdb.commons.concurrent.ThreadName;
-import org.apache.iotdb.commons.concurrent.threadpool.ScheduledExecutorUtil;
import org.apache.iotdb.commons.conf.CommonDescriptor;
import org.apache.iotdb.commons.consensus.ConsensusGroupId;
import org.apache.iotdb.commons.file.SystemFileFactory;
@@ -33,6 +32,7 @@
import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlan;
import org.apache.iotdb.confignode.consensus.request.read.ConfigPhysicalReadPlan;
import org.apache.iotdb.confignode.exception.physical.UnknownPhysicalPlanTypeException;
+import org.apache.iotdb.confignode.i18n.ConfigNodeMessages;
import org.apache.iotdb.confignode.manager.ConfigManager;
import org.apache.iotdb.confignode.manager.consensus.ConsensusManager;
import org.apache.iotdb.confignode.manager.pipe.agent.PipeConfigNodeAgent;
@@ -55,16 +55,15 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/** {@link IStateMachine} for ConfigRegion. */
public class ConfigRegionStateMachine implements IStateMachine, IStateMachine.EventApi {
@@ -83,17 +82,18 @@ public class ConfigRegionStateMachine implements IStateMachine, IStateMachine.Ev
private File simpleLogFile;
private int startIndex;
private int endIndex;
+ private boolean simpleConsensusWritesDisabled;
private static final String CURRENT_FILE_DIR =
ConsensusManager.getConfigRegionDir() + File.separator + "current";
private static final String PROGRESS_FILE_PATH =
CURRENT_FILE_DIR + File.separator + "log_inprogress_";
private static final String FILE_PATH = CURRENT_FILE_DIR + File.separator + "log_";
+ private static final String LOG_INPROGRESS_FILE_PREFIX = "log_inprogress_";
+ private static final String LOG_FILE_PREFIX = "log_";
private static final long LOG_FILE_MAX_SIZE =
CONF.getConfigNodeSimpleConsensusLogSegmentSizeMax();
private final TEndPoint currentNodeTEndPoint;
- private static Pattern LOG_INPROGRESS_PATTERN = Pattern.compile("\\d+");
- private static Pattern LOG_PATTERN = Pattern.compile("(?<=_)(\\d+)$");
public ConfigRegionStateMachine(ConfigManager configManager, ConfigPlanExecutor executor) {
this.executor = executor;
@@ -121,16 +121,32 @@ public TSStatus write(IConsensusRequest request) {
/** Transmit {@link ConfigPhysicalPlan} to {@link ConfigPlanExecutor} */
protected TSStatus write(ConfigPhysicalPlan plan) {
+ SimpleConsensusPersistResult persistResult = null;
+ if (ConsensusFactory.SIMPLE_CONSENSUS.equals(CONF.getConfigNodeConsensusProtocolClass())) {
+ persistResult = persistPlanForSimpleConsensus(plan);
+ final TSStatus persistStatus = persistResult.status;
+ if (persistStatus.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ return persistStatus;
+ }
+ }
+
TSStatus result;
try {
result = executor.executeNonQueryPlan(plan);
- } catch (UnknownPhysicalPlanTypeException e) {
- LOGGER.error("Execute non-query plan failed", e);
+ } catch (UnknownPhysicalPlanTypeException | RuntimeException e) {
+ LOGGER.error(ConfigNodeMessages.EXECUTE_NON_QUERY_PLAN_FAILED, e);
result = new TSStatus(TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode());
}
- if (ConsensusFactory.SIMPLE_CONSENSUS.equals(CONF.getConfigNodeConsensusProtocolClass())) {
- writeLogForSimpleConsensus(plan);
+ if (persistResult != null
+ && result.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()
+ && !rollbackFailedPlanForSimpleConsensus(plan, persistResult)) {
+ final String rollbackFailureMessage =
+ ConfigNodeMessages.FAILED_TO_ROLLBACK_PERSISTED_CONFIGNODE_SIMPLECONSENSUS_LOG;
+ result.setMessage(
+ Optional.ofNullable(result.getMessage())
+ .map(message -> message + " " + rollbackFailureMessage)
+ .orElse(rollbackFailureMessage));
}
if (result.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
@@ -197,7 +213,6 @@ public boolean takeSnapshot(File snapshotDir) {
PipeConfigNodeAgent.runtime()
.listener()
.tryListenToSnapshots(ConfignodeSnapshotParser.getSnapshots());
- return true;
} catch (IOException e) {
if (PipeConfigNodeAgent.runtime().listener().isOpened()) {
LOGGER.warn(
@@ -205,6 +220,7 @@ public boolean takeSnapshot(File snapshotDir) {
e);
}
}
+ return true;
}
return false;
}
@@ -343,6 +359,9 @@ public void start() {
@Override
public void stop() {
+ if (ConsensusFactory.SIMPLE_CONSENSUS.equals(CONF.getConfigNodeConsensusProtocolClass())) {
+ closeSimpleLogWriter();
+ }
// Shutdown leader related service for config pipe
PipeConfigNodeAgent.runtime().notifyLeaderUnavailable();
}
@@ -352,56 +371,126 @@ public boolean isReadOnly() {
return CommonDescriptor.getInstance().getConfig().isReadOnly();
}
- private void writeLogForSimpleConsensus(ConfigPhysicalPlan plan) {
- if (simpleLogFile.length() > LOG_FILE_MAX_SIZE) {
- try {
- simpleLogWriter.force();
- File completedFilePath = new File(FILE_PATH + startIndex + "_" + endIndex);
- Files.move(
- simpleLogFile.toPath(), completedFilePath.toPath(), StandardCopyOption.ATOMIC_MOVE);
- } catch (IOException e) {
- LOGGER.error("Can't force logWriter for ConfigNode SimpleConsensus mode", e);
+ private SimpleConsensusPersistResult persistPlanForSimpleConsensus(ConfigPhysicalPlan plan) {
+ File persistedLogFile = null;
+ long logFileSizeBeforeWrite = 0;
+ int endIndexBeforeWrite = endIndex;
+ try {
+ if (simpleConsensusWritesDisabled) {
+ throw new IOException(
+ ConfigNodeMessages
+ .EXCEPTION_CONFIGNODE_SIMPLECONSENSUS_WRITES_ARE_DISABLED_AFTER_A_WAL_CLEANUP_FAILURE_RESTART_IS_REQUIRED_D52F0107);
}
- for (int retry = 0; retry < 5; retry++) {
- try {
- simpleLogWriter.close();
- } catch (IOException e) {
- LOGGER.warn(
- "Can't close StandAloneLog for ConfigNode SimpleConsensus mode, "
- + "filePath: {}, retry: {}",
- simpleLogFile.getAbsolutePath(),
- retry);
- try {
- // Sleep 1s and retry
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e2) {
- Thread.currentThread().interrupt();
- LOGGER.warn("Unexpected interruption during the close method of logWriter");
- }
- continue;
- }
- break;
+ if (simpleLogWriter == null || simpleLogFile == null) {
+ throw new IOException(ConfigNodeMessages.SIMPLECONSENSUS_LOG_WRITER_IS_NOT_INITIALIZED);
}
- startIndex = endIndex + 1;
- createLogFile(startIndex);
- }
- try {
+ if (simpleLogFile.length() > LOG_FILE_MAX_SIZE) {
+ rollSimpleConsensusLogFile();
+ }
+
+ persistedLogFile = simpleLogFile;
+ logFileSizeBeforeWrite = persistedLogFile.length();
+ endIndexBeforeWrite = endIndex;
+
ByteBuffer buffer = plan.serializeToByteBuffer();
buffer.position(buffer.limit());
simpleLogWriter.write(buffer);
+ simpleLogWriter.force();
endIndex = endIndex + 1;
} catch (Exception e) {
LOGGER.error(
- "Can't serialize current ConfigPhysicalPlan for ConfigNode SimpleConsensus mode", e);
+ ConfigNodeMessages
+ .PERSIST_CURRENT_CONFIGPHYSICALPLAN_FOR_CONFIGNODE_SIMPLECONSENSUS_MODE_FAILED,
+ e);
+ TSStatus status =
+ new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode())
+ .setMessage(
+ String.format(
+ ConfigNodeMessages.PERSIST_CONFIGNODE_SIMPLECONSENSUS_LOG_FAILED,
+ e.getMessage()));
+ // A failed append or force may still leave a complete or partial record in the WAL.
+ if (persistedLogFile != null
+ && !rollbackFailedPlanForSimpleConsensus(
+ plan,
+ SimpleConsensusPersistResult.success(
+ persistedLogFile, logFileSizeBeforeWrite, endIndexBeforeWrite))) {
+ status.setMessage(
+ status.getMessage()
+ + " "
+ + ConfigNodeMessages.FAILED_TO_ROLLBACK_PERSISTED_CONFIGNODE_SIMPLECONSENSUS_LOG);
+ }
+ return SimpleConsensusPersistResult.failure(status);
+ }
+ return SimpleConsensusPersistResult.success(
+ persistedLogFile, logFileSizeBeforeWrite, endIndexBeforeWrite);
+ }
+
+ private boolean rollbackFailedPlanForSimpleConsensus(
+ ConfigPhysicalPlan plan, SimpleConsensusPersistResult persistResult) {
+ try {
+ if (!closeSimpleLogWriter()) {
+ throw new IOException(
+ ConfigNodeMessages
+ .EXCEPTION_FAILED_TO_CLOSE_THE_CONFIGNODE_SIMPLECONSENSUS_LOG_WRITER_BC0F417C);
+ }
+ try (FileChannel channel =
+ FileChannel.open(persistResult.logFile.toPath(), StandardOpenOption.WRITE)) {
+ channel.truncate(persistResult.logFileSizeBeforeWrite);
+ channel.force(true);
+ }
+ simpleLogFile = persistResult.logFile;
+ simpleLogWriter = new LogWriter(simpleLogFile, false);
+ endIndex = persistResult.endIndexBeforeWrite;
+ return true;
+ } catch (IOException e) {
+ // Never append past an entry whose removal could not be made durable.
+ simpleConsensusWritesDisabled = true;
+ LOGGER.error(
+ ConfigNodeMessages
+ .ROLLBACK_FAILED_CONFIGPHYSICALPLAN_FOR_CONFIGNODE_SIMPLECONSENSUS_MODE_FAILED,
+ plan.getType(),
+ persistResult.logFile,
+ persistResult.logFileSizeBeforeWrite,
+ persistResult.endIndexBeforeWrite,
+ e);
+ return false;
}
}
+ private void rollSimpleConsensusLogFile() throws IOException {
+ simpleLogWriter.force();
+ if (!closeSimpleLogWriter()) {
+ simpleConsensusWritesDisabled = true;
+ throw new IOException(
+ ConfigNodeMessages
+ .EXCEPTION_FAILED_TO_CLOSE_THE_CONFIGNODE_SIMPLECONSENSUS_LOG_WRITER_BC0F417C);
+ }
+ try {
+ Files.move(
+ simpleLogFile.toPath(),
+ simpleLogFile.toPath().resolveSibling(LOG_FILE_PREFIX + startIndex + "_" + endIndex),
+ StandardCopyOption.ATOMIC_MOVE);
+ } catch (IOException e) {
+ // The source file and indexes are unchanged when renaming fails. Reopen it for retries.
+ try {
+ simpleLogWriter = new LogWriter(simpleLogFile, false);
+ } catch (IOException reopenException) {
+ simpleConsensusWritesDisabled = true;
+ e.addSuppressed(reopenException);
+ }
+ throw e;
+ }
+ startIndex = endIndex + 1;
+ createLogFile(startIndex);
+ }
+
private void initStandAloneConfigNode() {
File dir = new File(CURRENT_FILE_DIR);
dir.mkdirs();
String[] list = new File(CURRENT_FILE_DIR).list();
+ endIndex = 0;
if (list != null && list.length != 0) {
Arrays.sort(list, new FileComparator());
for (String logFileName : list) {
@@ -418,7 +507,7 @@ private void initStandAloneConfigNode() {
continue;
}
- startIndex = endIndex;
+ final int recoveredStartIndex = parseStartIndex(logFileName);
while (logReader.hasNext()) {
endIndex++;
// Read and re-serialize the PhysicalPlan
@@ -436,33 +525,13 @@ private void initStandAloneConfigNode() {
}
}
logReader.close();
- }
- } else {
- startIndex = 0;
- endIndex = 0;
- }
- startIndex = startIndex + 1;
- createLogFile(endIndex);
-
- ScheduledExecutorService simpleConsensusThread =
- IoTDBThreadPoolFactory.newSingleThreadScheduledExecutor(
- ThreadName.CONFIG_NODE_SIMPLE_CONSENSUS_WAL_FLUSH.getName());
- ScheduledExecutorUtil.safelyScheduleWithFixedDelay(
- simpleConsensusThread,
- this::flushWALForSimpleConsensus,
- 0,
- CONF.getForceWalPeriodForConfigNodeSimpleInMs(),
- TimeUnit.MILLISECONDS);
- }
-
- private void flushWALForSimpleConsensus() {
- if (simpleLogWriter != null) {
- try {
- simpleLogWriter.force();
- } catch (IOException e) {
- LOGGER.error("Can't force logWriter for ConfigNode flushWALForSimpleConsensus", e);
+ if (isInProgressLogFile(logFileName)) {
+ sealRecoveredInProgressLogFile(logFile, recoveredStartIndex, endIndex);
+ }
}
}
+ startIndex = endIndex + 1;
+ createLogFile(startIndex);
}
private void createLogFile(int startIndex) {
@@ -493,18 +562,127 @@ public int compare(String filename1, String filename2) {
}
}
+ private void sealRecoveredInProgressLogFile(
+ File logFile, int recoveredStartIndex, int recoveredEndIndex) {
+ try {
+ if (recoveredStartIndex > recoveredEndIndex) {
+ Files.deleteIfExists(logFile.toPath());
+ return;
+ }
+ Files.move(
+ logFile.toPath(),
+ new File(FILE_PATH + recoveredStartIndex + "_" + recoveredEndIndex).toPath(),
+ StandardCopyOption.ATOMIC_MOVE);
+ } catch (IOException e) {
+ LOGGER.warn(
+ ConfigNodeMessages.SEAL_RECOVERED_CONFIGNODE_SIMPLECONSENSUS_LOG_FAILED, logFile, e);
+ }
+ }
+
+ private boolean isInProgressLogFile(String filename) {
+ return filename.startsWith(LOG_INPROGRESS_FILE_PREFIX);
+ }
+
+ private boolean closeSimpleLogWriter() {
+ if (simpleLogWriter == null) {
+ return true;
+ }
+ for (int retry = 0; retry < 5; retry++) {
+ try {
+ simpleLogWriter.close();
+ simpleLogWriter = null;
+ return true;
+ } catch (IOException e) {
+ LOGGER.warn(
+ ConfigNodeMessages
+ .LOG_CONFIGREGION_UPDATE_FAILED_TO_CLOSE_THE_CONFIGNODE_SIMPLECONSENSUS_LOG_FILEPATH_ARG_RETRY_ARG_188A702E,
+ simpleLogFile == null ? null : simpleLogFile.getAbsolutePath(),
+ retry);
+ try {
+ TimeUnit.SECONDS.sleep(1);
+ } catch (InterruptedException e2) {
+ Thread.currentThread().interrupt();
+ LOGGER.warn(
+ ConfigNodeMessages.UNEXPECTED_INTERRUPTION_DURING_THE_CLOSE_METHOD_OF_LOGWRITER);
+ break;
+ }
+ }
+ }
+ return false;
+ }
+
static long parseEndIndex(String filename) {
- if (filename.startsWith("log_inprogress_")) {
- Matcher matcher = LOG_INPROGRESS_PATTERN.matcher(filename);
- if (matcher.find()) {
- return Long.parseLong(matcher.group());
+ final String endIndexString;
+ if (filename.startsWith(LOG_INPROGRESS_FILE_PREFIX)) {
+ endIndexString = filename.substring(LOG_INPROGRESS_FILE_PREFIX.length());
+ } else if (filename.startsWith(LOG_FILE_PREFIX)) {
+ final int lastSeparatorIndex = filename.lastIndexOf('_');
+ if (lastSeparatorIndex < LOG_FILE_PREFIX.length()) {
+ return 0;
}
+ endIndexString = filename.substring(lastSeparatorIndex + 1);
} else {
- Matcher matcher = LOG_PATTERN.matcher(filename);
- if (matcher.find()) {
- return Long.parseLong(matcher.group());
+ return 0;
+ }
+
+ return isDigits(endIndexString) ? Long.parseLong(endIndexString) : 0;
+ }
+
+ static int parseStartIndex(String filename) {
+ final String startIndexString;
+ if (filename.startsWith(LOG_INPROGRESS_FILE_PREFIX)) {
+ startIndexString = filename.substring(LOG_INPROGRESS_FILE_PREFIX.length());
+ } else if (filename.startsWith(LOG_FILE_PREFIX)) {
+ final int lastSeparatorIndex = filename.lastIndexOf('_');
+ if (lastSeparatorIndex <= LOG_FILE_PREFIX.length()) {
+ return 0;
}
+ startIndexString = filename.substring(LOG_FILE_PREFIX.length(), lastSeparatorIndex);
+ } else {
+ return 0;
+ }
+
+ return isDigits(startIndexString) ? Integer.parseInt(startIndexString) : 0;
+ }
+
+ private static boolean isDigits(String value) {
+ if (value.isEmpty()) {
+ return false;
+ }
+ for (int i = 0; i < value.length(); i++) {
+ if (!Character.isDigit(value.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static class SimpleConsensusPersistResult {
+
+ private final TSStatus status;
+ private final File logFile;
+ private final long logFileSizeBeforeWrite;
+ private final int endIndexBeforeWrite;
+
+ private SimpleConsensusPersistResult(
+ TSStatus status, File logFile, long logFileSizeBeforeWrite, int endIndexBeforeWrite) {
+ this.status = status;
+ this.logFile = logFile;
+ this.logFileSizeBeforeWrite = logFileSizeBeforeWrite;
+ this.endIndexBeforeWrite = endIndexBeforeWrite;
+ }
+
+ private static SimpleConsensusPersistResult success(
+ File logFile, long logFileSizeBeforeWrite, int endIndexBeforeWrite) {
+ return new SimpleConsensusPersistResult(
+ new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode()),
+ logFile,
+ logFileSizeBeforeWrite,
+ endIndexBeforeWrite);
+ }
+
+ private static SimpleConsensusPersistResult failure(TSStatus status) {
+ return new SimpleConsensusPersistResult(status, null, 0, 0);
}
- return 0;
}
}
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/node/NodeManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/node/NodeManager.java
index 81cad2f3befd..d8d6f239a06a 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/node/NodeManager.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/node/NodeManager.java
@@ -20,6 +20,7 @@
package org.apache.iotdb.confignode.manager.node;
import org.apache.iotdb.common.rpc.thrift.TAINodeConfiguration;
+import org.apache.iotdb.common.rpc.thrift.TAINodeLocation;
import org.apache.iotdb.common.rpc.thrift.TConfigNodeLocation;
import org.apache.iotdb.common.rpc.thrift.TDataNodeConfiguration;
import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
@@ -44,6 +45,7 @@
import org.apache.iotdb.confignode.client.sync.SyncDataNodeClientPool;
import org.apache.iotdb.confignode.conf.ConfigNodeConfig;
import org.apache.iotdb.confignode.conf.ConfigNodeDescriptor;
+import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlan;
import org.apache.iotdb.confignode.consensus.request.read.ainode.GetAINodeConfigurationPlan;
import org.apache.iotdb.confignode.consensus.request.read.datanode.GetDataNodeConfigurationPlan;
import org.apache.iotdb.confignode.consensus.request.write.ainode.RegisterAINodePlan;
@@ -61,6 +63,7 @@
import org.apache.iotdb.confignode.consensus.response.datanode.DataNodeConfigurationResp;
import org.apache.iotdb.confignode.consensus.response.datanode.DataNodeRegisterResp;
import org.apache.iotdb.confignode.consensus.response.datanode.DataNodeToStatusResp;
+import org.apache.iotdb.confignode.i18n.ConfigNodeMessages;
import org.apache.iotdb.confignode.manager.ClusterManager;
import org.apache.iotdb.confignode.manager.IManager;
import org.apache.iotdb.confignode.manager.TTLManager;
@@ -281,30 +284,32 @@ public DataSet registerDataNode(TDataNodeRegisterReq req) {
DataNodeRegisterResp resp = new DataNodeRegisterResp();
resp.setConfigNodeList(getRegisteredConfigNodes());
- // Create a new DataNodeHeartbeatCache and force update NodeStatus
int dataNodeId = nodeInfo.generateNextNodeId();
- getLoadManager().getLoadCache().createNodeHeartbeatCache(NodeType.DataNode, dataNodeId);
- // TODO: invoke a force heartbeat to update new DataNode's status immediately
RegisterDataNodePlan registerDataNodePlan =
new RegisterDataNodePlan(req.getDataNodeConfiguration());
// Register new DataNode
registerDataNodePlan.getDataNodeConfiguration().getLocation().setDataNodeId(dataNodeId);
- try {
- getConsensusManager().write(registerDataNodePlan);
- } catch (ConsensusException e) {
- LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ TSStatus registerStatus = writeConfigPhysicalPlan(registerDataNodePlan);
+ if (!isConsensusWriteSuccessful(registerStatus)) {
+ resp.setStatus(registerStatus);
+ return resp;
}
// update datanode's versionInfo
UpdateVersionInfoPlan updateVersionInfoPlan =
new UpdateVersionInfoPlan(req.getVersionInfo(), dataNodeId);
- try {
- getConsensusManager().write(updateVersionInfoPlan);
- } catch (ConsensusException e) {
- LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ TSStatus updateVersionStatus = writeConfigPhysicalPlan(updateVersionInfoPlan);
+ if (!isConsensusWriteSuccessful(updateVersionStatus)) {
+ resp.setStatus(
+ rollbackDataNodeRegistration(
+ registerDataNodePlan.getDataNodeConfiguration().getLocation(), updateVersionStatus));
+ return resp;
}
+ // Create a new DataNodeHeartbeatCache. The heartbeat service will refresh NodeStatus shortly.
+ getLoadManager().getLoadCache().createNodeHeartbeatCache(NodeType.DataNode, dataNodeId);
+
// Bind DataNode metrics
PartitionMetrics.bindDataNodePartitionMetricsWhenUpdate(
MetricService.getInstance(), configManager, dataNodeId);
@@ -312,7 +317,10 @@ public DataSet registerDataNode(TDataNodeRegisterReq req) {
// Adjust the maximum RegionGroup number of each Database
getClusterSchemaManager().adjustMaxRegionGroupNum();
- resp.setStatus(ClusterNodeStartUtils.ACCEPT_NODE_REGISTRATION);
+ resp.setStatus(
+ buildSuccessStatus(
+ ClusterNodeStartUtils.ACCEPT_NODE_REGISTRATION.getMessage(),
+ registerStatus.getMessage()));
resp.setDataNodeId(
registerDataNodePlan.getDataNodeConfiguration().getLocation().getDataNodeId());
resp.setRuntimeConfiguration(getRuntimeConfiguration());
@@ -340,10 +348,10 @@ public TDataNodeRestartResp updateDataNodeIfNecessary(TDataNodeRestartReq req) {
// Update DataNodeConfiguration when modified during restart
UpdateDataNodePlan updateDataNodePlan =
new UpdateDataNodePlan(req.getDataNodeConfiguration());
- try {
- getConsensusManager().write(updateDataNodePlan);
- } catch (ConsensusException e) {
- LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ TSStatus updateStatus = writeConfigPhysicalPlan(updateDataNodePlan);
+ if (!isConsensusWriteSuccessful(updateStatus)) {
+ resp.setStatus(updateStatus);
+ return resp;
}
}
TNodeVersionInfo versionInfo = nodeInfo.getVersionInfo(nodeId);
@@ -351,14 +359,14 @@ public TDataNodeRestartResp updateDataNodeIfNecessary(TDataNodeRestartReq req) {
// Update versionInfo when modified during restart
UpdateVersionInfoPlan updateVersionInfoPlan =
new UpdateVersionInfoPlan(req.getVersionInfo(), nodeId);
- try {
- getConsensusManager().write(updateVersionInfoPlan);
- } catch (ConsensusException e) {
- LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ TSStatus updateStatus = writeConfigPhysicalPlan(updateVersionInfoPlan);
+ if (!isConsensusWriteSuccessful(updateStatus)) {
+ resp.setStatus(updateStatus);
+ return resp;
}
}
- resp.setStatus(ClusterNodeStartUtils.ACCEPT_NODE_RESTART);
+ resp.setStatus(buildSuccessStatus(ClusterNodeStartUtils.ACCEPT_NODE_RESTART.getMessage()));
resp.setRuntimeConfiguration(getRuntimeConfiguration());
resp.setCorrectConsensusGroups(getPartitionManager().getAllReplicaSets(nodeId));
@@ -436,13 +444,12 @@ public TSStatus updateConfigNodeIfNecessary(int configNodeId, TNodeVersionInfo v
// Update versionInfo when modified during restart
UpdateVersionInfoPlan updateConfigNodePlan =
new UpdateVersionInfoPlan(versionInfo, configNodeId);
- try {
- return getConsensusManager().write(updateConfigNodePlan);
- } catch (ConsensusException e) {
- return new TSStatus(TSStatusCode.CONSENSUS_NOT_INITIALIZED.getStatusCode());
+ TSStatus updateStatus = writeConfigPhysicalPlan(updateConfigNodePlan);
+ if (!isConsensusWriteSuccessful(updateStatus)) {
+ return updateStatus;
}
}
- return ClusterNodeStartUtils.ACCEPT_NODE_RESTART;
+ return buildSuccessStatus(ClusterNodeStartUtils.ACCEPT_NODE_RESTART.getMessage());
}
public List getRegisteredAINodeInfoList() {
@@ -488,27 +495,37 @@ public synchronized DataSet registerAINode(TAINodeRegisterReq req) {
}
int aiNodeId = nodeInfo.generateNextNodeId();
- getLoadManager().getLoadCache().createNodeHeartbeatCache(NodeType.AINode, aiNodeId);
RegisterAINodePlan registerAINodePlan = new RegisterAINodePlan(req.getAiNodeConfiguration());
// Register new DataNode
registerAINodePlan.getAINodeConfiguration().getLocation().setAiNodeId(aiNodeId);
- try {
- getConsensusManager().write(registerAINodePlan);
- } catch (ConsensusException e) {
- LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ TSStatus registerStatus = writeConfigPhysicalPlan(registerAINodePlan);
+ if (!isConsensusWriteSuccessful(registerStatus)) {
+ AINodeRegisterResp resp = new AINodeRegisterResp();
+ resp.setConfigNodeList(getRegisteredConfigNodes());
+ resp.setStatus(registerStatus);
+ return resp;
}
// update datanode's versionInfo
UpdateVersionInfoPlan updateVersionInfoPlan =
new UpdateVersionInfoPlan(req.getVersionInfo(), aiNodeId);
- try {
- getConsensusManager().write(updateVersionInfoPlan);
- } catch (ConsensusException e) {
- LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ TSStatus updateVersionStatus = writeConfigPhysicalPlan(updateVersionInfoPlan);
+ if (!isConsensusWriteSuccessful(updateVersionStatus)) {
+ AINodeRegisterResp resp = new AINodeRegisterResp();
+ resp.setConfigNodeList(getRegisteredConfigNodes());
+ resp.setStatus(
+ rollbackAiNodeRegistration(
+ registerAINodePlan.getAINodeConfiguration().getLocation(), updateVersionStatus));
+ return resp;
}
+ getLoadManager().getLoadCache().createNodeHeartbeatCache(NodeType.AINode, aiNodeId);
+
AINodeRegisterResp resp = new AINodeRegisterResp();
- resp.setStatus(ClusterNodeStartUtils.ACCEPT_NODE_REGISTRATION);
+ resp.setStatus(
+ buildSuccessStatus(
+ ClusterNodeStartUtils.ACCEPT_NODE_REGISTRATION.getMessage(),
+ registerStatus.getMessage()));
resp.setConfigNodeList(getRegisteredConfigNodes());
resp.setAINodeId(registerAINodePlan.getAINodeConfiguration().getLocation().getAiNodeId());
return resp;
@@ -550,10 +567,12 @@ public TAINodeRestartResp updateAINodeIfNecessary(TAINodeRestartReq req) {
if (!req.getAiNodeConfiguration().equals(aiNodeConfiguration)) {
// Update AINodeConfiguration when modified during restart
UpdateAINodePlan updateAINodePlan = new UpdateAINodePlan(req.getAiNodeConfiguration());
- try {
- getConsensusManager().write(updateAINodePlan);
- } catch (ConsensusException e) {
- LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ TSStatus updateStatus = writeConfigPhysicalPlan(updateAINodePlan);
+ if (!isConsensusWriteSuccessful(updateStatus)) {
+ TAINodeRestartResp resp = new TAINodeRestartResp();
+ resp.setConfigNodeList(getRegisteredConfigNodes());
+ resp.setStatus(updateStatus);
+ return resp;
}
}
TNodeVersionInfo versionInfo = nodeInfo.getVersionInfo(nodeId);
@@ -561,15 +580,17 @@ public TAINodeRestartResp updateAINodeIfNecessary(TAINodeRestartReq req) {
// Update versionInfo when modified during restart
UpdateVersionInfoPlan updateVersionInfoPlan =
new UpdateVersionInfoPlan(req.getVersionInfo(), nodeId);
- try {
- getConsensusManager().write(updateVersionInfoPlan);
- } catch (ConsensusException e) {
- LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ TSStatus updateStatus = writeConfigPhysicalPlan(updateVersionInfoPlan);
+ if (!isConsensusWriteSuccessful(updateStatus)) {
+ TAINodeRestartResp resp = new TAINodeRestartResp();
+ resp.setConfigNodeList(getRegisteredConfigNodes());
+ resp.setStatus(updateStatus);
+ return resp;
}
}
TAINodeRestartResp resp = new TAINodeRestartResp();
- resp.setStatus(ClusterNodeStartUtils.ACCEPT_NODE_RESTART);
+ resp.setStatus(buildSuccessStatus(ClusterNodeStartUtils.ACCEPT_NODE_RESTART.getMessage()));
resp.setConfigNodeList(getRegisteredConfigNodes());
return resp;
}
@@ -764,21 +785,32 @@ public List getRegisteredConfigNodeInfoList() {
*
* @param configNodeLocation The new ConfigNode.
* @param versionInfo The new ConfigNode's versionInfo.
+ * @throws IllegalStateException if the ConfigNode information or version cannot be persisted
*/
public void applyConfigNode(
TConfigNodeLocation configNodeLocation, TNodeVersionInfo versionInfo) {
ApplyConfigNodePlan applyConfigNodePlan = new ApplyConfigNodePlan(configNodeLocation);
- try {
- getConsensusManager().write(applyConfigNodePlan);
- } catch (ConsensusException e) {
- LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ final TSStatus applyStatus = writeConfigPhysicalPlan(applyConfigNodePlan);
+ if (!isConsensusWriteSuccessful(applyStatus)) {
+ throw new IllegalStateException(
+ String.format(
+ ConfigNodeMessages
+ .EXCEPTION_FAILED_TO_APPLY_CONFIGNODE_ARG_THROUGH_CONSENSUS_LAYER_ARG_B4A985D7,
+ configNodeLocation,
+ describeStatus(applyStatus)));
}
UpdateVersionInfoPlan updateVersionInfoPlan =
new UpdateVersionInfoPlan(versionInfo, configNodeLocation.getConfigNodeId());
- try {
- getConsensusManager().write(updateVersionInfoPlan);
- } catch (ConsensusException e) {
- LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ final TSStatus updateStatus = writeConfigPhysicalPlan(updateVersionInfoPlan);
+ if (!isConsensusWriteSuccessful(updateStatus)) {
+ // ADD_PEER has already joined this node to Raft. Removing it here would also mark the
+ // joining node as Removing. Keep the membership and let REGISTER_SUCCESS retry both plans.
+ throw new IllegalStateException(
+ String.format(
+ ConfigNodeMessages
+ .EXCEPTION_FAILED_TO_PERSIST_VERSION_INFO_FOR_CONFIGNODE_ARG_ARG_REGISTRATION_REMAINS_PENDING_AND_MUST_BE_RETRIED_F662C350,
+ configNodeLocation.getConfigNodeId(),
+ describeStatus(updateStatus)));
}
}
@@ -1131,6 +1163,112 @@ public TDataNodeLocation getLowestLoadDataNode(Set nodes) {
return getRegisteredDataNode(dataNodeId).getLocation();
}
+ private TSStatus writeConfigPhysicalPlan(ConfigPhysicalPlan plan) {
+ try {
+ return getConsensusManager().write(plan);
+ } catch (ConsensusException e) {
+ LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ return new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode())
+ .setMessage(e.getMessage());
+ }
+ }
+
+ private boolean isConsensusWriteSuccessful(TSStatus status) {
+ return status != null && status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode();
+ }
+
+ private TSStatus rollbackDataNodeRegistration(
+ TDataNodeLocation dataNodeLocation, TSStatus versionUpdateStatus) {
+ final TSStatus rollbackStatus =
+ writeConfigPhysicalPlan(
+ new RemoveDataNodePlan(Collections.singletonList(dataNodeLocation)));
+ if (isConsensusWriteSuccessful(rollbackStatus)) {
+ return new TSStatus(versionUpdateStatus.getCode())
+ .setMessage(
+ String.format(
+ ConfigNodeMessages
+ .MESSAGE_FAILED_TO_PERSIST_VERSION_INFO_FOR_DATANODE_ARG_ARG_THE_REGISTRATION_HAS_BEEN_ROLLED_BACK_PLEASE_RETRY_THE_REGISTRATION_5DDB311F,
+ dataNodeLocation.getDataNodeId(),
+ describeStatus(versionUpdateStatus)));
+ }
+
+ LOGGER.error(
+ ConfigNodeMessages
+ .LOG_FAILED_TO_ROLL_BACK_DATANODE_REGISTRATION_ARG_AFTER_VERSION_INFO_PERSISTENCE_FAILURE_VERSIONUPDATESTATUS_ARG_ROLLBACKSTATUS_ARG_437E6F09,
+ dataNodeLocation,
+ versionUpdateStatus,
+ rollbackStatus);
+ return new TSStatus(rollbackStatus.getCode())
+ .setMessage(
+ String.format(
+ ConfigNodeMessages
+ .MESSAGE_FAILED_TO_PERSIST_VERSION_INFO_FOR_DATANODE_ARG_ARG_THE_REGISTRATION_ROLLBACK_ALSO_FAILED_ARG_MANUAL_CLEANUP_MAY_BE_REQUIRED_BEFORE_RETRYING_THE_REGISTRATION_5463B5FF,
+ dataNodeLocation.getDataNodeId(),
+ describeStatus(versionUpdateStatus),
+ describeStatus(rollbackStatus)));
+ }
+
+ private TSStatus rollbackAiNodeRegistration(
+ TAINodeLocation aiNodeLocation, TSStatus versionUpdateStatus) {
+ final TSStatus rollbackStatus = writeConfigPhysicalPlan(new RemoveAINodePlan(aiNodeLocation));
+ if (isConsensusWriteSuccessful(rollbackStatus)) {
+ return new TSStatus(versionUpdateStatus.getCode())
+ .setMessage(
+ String.format(
+ ConfigNodeMessages
+ .MESSAGE_FAILED_TO_PERSIST_VERSION_INFO_FOR_AINODE_ARG_ARG_THE_REGISTRATION_HAS_BEEN_ROLLED_BACK_PLEASE_RETRY_THE_REGISTRATION_7571C626,
+ aiNodeLocation.getAiNodeId(),
+ describeStatus(versionUpdateStatus)));
+ }
+
+ LOGGER.error(
+ ConfigNodeMessages
+ .LOG_FAILED_TO_ROLL_BACK_AINODE_REGISTRATION_ARG_AFTER_VERSION_INFO_PERSISTENCE_FAILURE_VERSIONUPDATESTATUS_ARG_ROLLBACKSTATUS_ARG_BFF24BD4,
+ aiNodeLocation,
+ versionUpdateStatus,
+ rollbackStatus);
+ return new TSStatus(rollbackStatus.getCode())
+ .setMessage(
+ String.format(
+ ConfigNodeMessages
+ .MESSAGE_FAILED_TO_PERSIST_VERSION_INFO_FOR_AINODE_ARG_ARG_THE_REGISTRATION_ROLLBACK_ALSO_FAILED_ARG_MANUAL_CLEANUP_MAY_BE_REQUIRED_BEFORE_RETRYING_THE_REGISTRATION_B9B1DCCD,
+ aiNodeLocation.getAiNodeId(),
+ describeStatus(versionUpdateStatus),
+ describeStatus(rollbackStatus)));
+ }
+
+ private TSStatus buildStatus(int statusCode, String... messages) {
+ final TSStatus status = new TSStatus(statusCode);
+ final StringBuilder builder = new StringBuilder();
+ for (String message : messages) {
+ if (message == null || message.isEmpty()) {
+ continue;
+ }
+ if (!builder.isEmpty()) {
+ builder.append(' ');
+ }
+ builder.append(message);
+ }
+ if (!builder.isEmpty()) {
+ status.setMessage(builder.toString());
+ }
+ return status;
+ }
+
+ private TSStatus buildSuccessStatus(String... messages) {
+ return buildStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode(), messages);
+ }
+
+ private String describeStatus(TSStatus status) {
+ if (status == null) {
+ return ConfigNodeMessages.MESSAGE_UNKNOWN_ERROR_E9EEAFF8;
+ }
+ if (status.getMessage() != null && !status.getMessage().isEmpty()) {
+ return status.getMessage();
+ }
+ return String.format(ConfigNodeMessages.MESSAGE_STATUS_CODE_ARG_4B3BCD8B, status.getCode());
+ }
+
private ConsensusManager getConsensusManager() {
return configManager.getConsensusManager();
}
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/DatabasePartitionTable.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/DatabasePartitionTable.java
index b301b13b56ef..7d8e1ff97fcc 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/DatabasePartitionTable.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/DatabasePartitionTable.java
@@ -243,7 +243,7 @@ public int getRegionGroupCount(TConsensusGroupType type) {
result.getAndIncrement();
}
});
- return result.getAndIncrement();
+ return result.get();
}
/**
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
index 8c91c99859ed..6ea8abdf97ba 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
@@ -231,7 +231,9 @@ public TSStatus offerRegionMaintainTasks(
*/
public TSStatus pollRegionMaintainTask() {
synchronized (regionMaintainTaskList) {
- regionMaintainTaskList.remove(0);
+ if (!regionMaintainTaskList.isEmpty()) {
+ regionMaintainTaskList.remove(0);
+ }
return RpcUtils.SUCCESS_STATUS;
}
}
@@ -986,9 +988,14 @@ public boolean processTakeSnapshot(File snapshotDir) throws TException, IOExcept
databasePartitionTableEntry.getValue().serialize(bufferedOutputStream, protocol);
}
+ final List copiedRegionMaintainTaskList;
+ synchronized (regionMaintainTaskList) {
+ copiedRegionMaintainTaskList = new ArrayList<>(regionMaintainTaskList);
+ }
+
// serialize regionCleanList
- ReadWriteIOUtils.write(regionMaintainTaskList.size(), bufferedOutputStream);
- for (RegionMaintainTask task : regionMaintainTaskList) {
+ ReadWriteIOUtils.write(copiedRegionMaintainTaskList.size(), bufferedOutputStream);
+ for (RegionMaintainTask task : copiedRegionMaintainTaskList) {
task.serialize(bufferedOutputStream, protocol);
}
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/node/AddConfigNodeProcedure.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/node/AddConfigNodeProcedure.java
index b38696a10ed2..c0d45d00d33c 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/node/AddConfigNodeProcedure.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/node/AddConfigNodeProcedure.java
@@ -80,9 +80,9 @@ protected Flow executeFromState(ConfigNodeProcedureEnv env, AddConfigNodeState s
LOG.info("Successfully ADD_PEER {}", tConfigNodeLocation);
break;
case REGISTER_SUCCESS:
- env.notifyRegisterSuccess(tConfigNodeLocation);
- env.createConfigNodeHeartbeatCache(tConfigNodeLocation.getConfigNodeId());
env.applyConfigNode(tConfigNodeLocation, versionInfo);
+ env.createConfigNodeHeartbeatCache(tConfigNodeLocation.getConfigNodeId());
+ env.notifyRegisterSuccess(tConfigNodeLocation);
LOG.info("The ConfigNode: {} is successfully added to the cluster", tConfigNodeLocation);
return Flow.NO_MORE_STATE;
}
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/statemachine/ConfigRegionStateMachineTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/statemachine/ConfigRegionStateMachineTest.java
new file mode 100644
index 000000000000..1df8d6585311
--- /dev/null
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/statemachine/ConfigRegionStateMachineTest.java
@@ -0,0 +1,302 @@
+/*
+ * 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.confignode.consensus.statemachine;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.confignode.conf.ConfigNodeConfig;
+import org.apache.iotdb.confignode.conf.ConfigNodeDescriptor;
+import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlan;
+import org.apache.iotdb.confignode.consensus.request.TestOnlyPlan;
+import org.apache.iotdb.confignode.consensus.request.write.confignode.UpdateVersionInfoPlan;
+import org.apache.iotdb.confignode.i18n.ConfigNodeMessages;
+import org.apache.iotdb.confignode.persistence.executor.ConfigPlanExecutor;
+import org.apache.iotdb.confignode.rpc.thrift.TNodeVersionInfo;
+import org.apache.iotdb.confignode.writelog.io.SingleFileLogReader;
+import org.apache.iotdb.consensus.ConsensusFactory;
+import org.apache.iotdb.db.utils.writelog.LogWriter;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+public class ConfigRegionStateMachineTest {
+
+ @Test
+ public void testParseStartIndex() {
+ Assert.assertEquals(1, ConfigRegionStateMachine.parseStartIndex("log_1_10"));
+ Assert.assertEquals(11, ConfigRegionStateMachine.parseStartIndex("log_11_20"));
+ Assert.assertEquals(21, ConfigRegionStateMachine.parseStartIndex("log_inprogress_21"));
+ Assert.assertEquals(0, ConfigRegionStateMachine.parseStartIndex("invalid"));
+ }
+
+ @Test
+ public void testParseEndIndex() {
+ Assert.assertEquals(10, ConfigRegionStateMachine.parseEndIndex("log_1_10"));
+ Assert.assertEquals(20, ConfigRegionStateMachine.parseEndIndex("log_11_20"));
+ Assert.assertEquals(21, ConfigRegionStateMachine.parseEndIndex("log_inprogress_21"));
+ Assert.assertEquals(0, ConfigRegionStateMachine.parseEndIndex("invalid"));
+ }
+
+ @Test
+ public void testLogFilesSortByEndIndex() {
+ List filenames =
+ new ArrayList<>(Arrays.asList("log_inprogress_21", "log_11_20", "log_1_10"));
+
+ filenames.sort(Comparator.comparingLong(ConfigRegionStateMachine::parseEndIndex));
+
+ Assert.assertEquals(Arrays.asList("log_1_10", "log_11_20", "log_inprogress_21"), filenames);
+ }
+
+ @Test
+ public void testFailedSimpleConsensusWriteRollsBackPersistedPlan() throws Exception {
+ ConfigNodeConfig conf = ConfigNodeDescriptor.getInstance().getConf();
+ String originalConsensusProtocol = conf.getConfigNodeConsensusProtocolClass();
+ Path tempLogFile = Files.createTempFile("confignode-simple-consensus", ".wal");
+ ConfigPlanExecutor executor = Mockito.mock(ConfigPlanExecutor.class);
+ Mockito.when(executor.executeNonQueryPlan(Mockito.any(ConfigPhysicalPlan.class)))
+ .thenReturn(new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode()));
+ ConfigRegionStateMachine stateMachine = new ConfigRegionStateMachine(null, executor);
+
+ try {
+ conf.setConfigNodeConsensusProtocolClass(ConsensusFactory.SIMPLE_CONSENSUS);
+ setField(stateMachine, "simpleLogFile", tempLogFile.toFile());
+ setField(stateMachine, "simpleLogWriter", new LogWriter(tempLogFile.toFile(), false));
+ setField(stateMachine, "endIndex", 0);
+
+ TSStatus status = stateMachine.write(new TestOnlyPlan());
+
+ Assert.assertEquals(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode(), status.getCode());
+ Assert.assertEquals(0, Files.size(tempLogFile));
+ Assert.assertEquals(0, getField(stateMachine, "endIndex"));
+ closeSimpleLogWriter(stateMachine);
+ assertNoReplayablePlans(tempLogFile);
+ } finally {
+ closeSimpleLogWriter(stateMachine);
+ Files.deleteIfExists(tempLogFile);
+ conf.setConfigNodeConsensusProtocolClass(originalConsensusProtocol);
+ }
+ }
+
+ private static void setField(Object target, String fieldName, Object value) throws Exception {
+ Field field = target.getClass().getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(target, value);
+ }
+
+ @Test
+ public void testForceFailureRollsBackAppendedPlan() throws Exception {
+ assertPersistenceFailureRollsBack(true);
+ }
+
+ @Test
+ public void testPartialAppendFailureRollsBackPlan() throws Exception {
+ assertPersistenceFailureRollsBack(false);
+ }
+
+ private void assertPersistenceFailureRollsBack(boolean failOnForce) throws Exception {
+ ConfigNodeConfig conf = ConfigNodeDescriptor.getInstance().getConf();
+ String originalConsensusProtocol = conf.getConfigNodeConsensusProtocolClass();
+ Path logFile = Files.createTempFile("confignode-persist-failure", ".wal");
+ ConfigPlanExecutor executor = Mockito.mock(ConfigPlanExecutor.class);
+ Mockito.when(executor.executeNonQueryPlan(Mockito.any(ConfigPhysicalPlan.class)))
+ .thenReturn(new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode()));
+ ConfigRegionStateMachine stateMachine = new ConfigRegionStateMachine(null, executor);
+ LogWriter writer = Mockito.spy(new LogWriter(logFile.toFile(), false));
+ ConfigPhysicalPlan firstPlan = versionPlan(1);
+ ConfigPhysicalPlan failedPlan = versionPlan(2);
+ ConfigPhysicalPlan retryPlan = versionPlan(3);
+ try {
+ conf.setConfigNodeConsensusProtocolClass(ConsensusFactory.SIMPLE_CONSENSUS);
+ setField(stateMachine, "simpleLogFile", logFile.toFile());
+ setField(stateMachine, "simpleLogWriter", writer);
+ Assert.assertEquals(
+ TSStatusCode.SUCCESS_STATUS.getStatusCode(), stateMachine.write(firstPlan).getCode());
+ long sizeBeforeWrite = Files.size(logFile);
+ if (failOnForce) {
+ Mockito.doThrow(new IOException("injected force failure")).when(writer).force();
+ } else {
+ Mockito.doAnswer(
+ invocation -> {
+ Files.write(logFile, new byte[] {0, 0, 0}, StandardOpenOption.APPEND);
+ throw new IOException("injected partial append failure");
+ })
+ .when(writer)
+ .write(Mockito.any(ByteBuffer.class));
+ }
+
+ TSStatus status = stateMachine.write(failedPlan);
+
+ Assert.assertEquals(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode(), status.getCode());
+ Mockito.verify(executor, Mockito.never()).executeNonQueryPlan(failedPlan);
+ Assert.assertEquals(sizeBeforeWrite, Files.size(logFile));
+ Assert.assertEquals(1, getField(stateMachine, "endIndex"));
+ Assert.assertEquals(
+ TSStatusCode.SUCCESS_STATUS.getStatusCode(), stateMachine.write(retryPlan).getCode());
+ Assert.assertEquals(2, getField(stateMachine, "endIndex"));
+ closeSimpleLogWriter(stateMachine);
+ assertReplayablePlans(logFile, firstPlan, retryPlan);
+ } finally {
+ closeSimpleLogWriter(stateMachine);
+ Files.deleteIfExists(logFile);
+ conf.setConfigNodeConsensusProtocolClass(originalConsensusProtocol);
+ }
+ }
+
+ @Test
+ public void testCleanupFailureDisablesFurtherWrites() throws Exception {
+ ConfigNodeConfig conf = ConfigNodeDescriptor.getInstance().getConf();
+ String originalConsensusProtocol = conf.getConfigNodeConsensusProtocolClass();
+ Path logFile = Files.createTempFile("confignode-cleanup-failure", ".wal");
+ ConfigPlanExecutor executor = Mockito.mock(ConfigPlanExecutor.class);
+ ConfigRegionStateMachine stateMachine = new ConfigRegionStateMachine(null, executor);
+ LogWriter writer = Mockito.spy(new LogWriter(logFile.toFile(), false));
+ try {
+ conf.setConfigNodeConsensusProtocolClass(ConsensusFactory.SIMPLE_CONSENSUS);
+ setField(stateMachine, "simpleLogFile", logFile.toFile());
+ setField(stateMachine, "simpleLogWriter", writer);
+ Mockito.doThrow(new IOException("injected force failure")).when(writer).force();
+ Mockito.doThrow(new IOException("injected close failure")).when(writer).close();
+
+ TSStatus status = stateMachine.write(versionPlan(1));
+
+ Assert.assertEquals(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode(), status.getCode());
+ Assert.assertTrue(
+ status
+ .getMessage()
+ .contains(
+ ConfigNodeMessages.FAILED_TO_ROLLBACK_PERSISTED_CONFIGNODE_SIMPLECONSENSUS_LOG));
+ long failedLogSize = Files.size(logFile);
+ Assert.assertTrue(failedLogSize > 0);
+ Assert.assertEquals(
+ TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode(),
+ stateMachine.write(versionPlan(2)).getCode());
+ Assert.assertEquals(failedLogSize, Files.size(logFile));
+ Mockito.verify(writer, Mockito.times(1)).write(Mockito.any(ByteBuffer.class));
+ Mockito.verify(executor, Mockito.never())
+ .executeNonQueryPlan(Mockito.any(ConfigPhysicalPlan.class));
+ } finally {
+ Mockito.doCallRealMethod().when(writer).close();
+ closeSimpleLogWriter(stateMachine);
+ Files.deleteIfExists(logFile);
+ conf.setConfigNodeConsensusProtocolClass(originalConsensusProtocol);
+ }
+ }
+
+ @Test
+ public void testRotationRenameFailureReopensOriginalLog() throws Exception {
+ ConfigNodeConfig conf = ConfigNodeDescriptor.getInstance().getConf();
+ String originalConsensusProtocol = conf.getConfigNodeConsensusProtocolClass();
+ Path directory = Files.createTempDirectory("confignode-rotation-failure");
+ Path logFile = directory.resolve("log_inprogress_1");
+ Path conflictingTarget = Files.createDirectory(directory.resolve("log_1_1"));
+ Path blocker = Files.createFile(conflictingTarget.resolve("blocker"));
+ ConfigPlanExecutor executor = Mockito.mock(ConfigPlanExecutor.class);
+ Mockito.when(executor.executeNonQueryPlan(Mockito.any(ConfigPhysicalPlan.class)))
+ .thenReturn(new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode()));
+ ConfigRegionStateMachine stateMachine = new ConfigRegionStateMachine(null, executor);
+ ConfigPhysicalPlan firstPlan = versionPlan(1);
+ ConfigPhysicalPlan retryPlan = versionPlan(2);
+ try {
+ conf.setConfigNodeConsensusProtocolClass(ConsensusFactory.SIMPLE_CONSENSUS);
+ setField(stateMachine, "simpleLogFile", logFile.toFile());
+ setField(stateMachine, "simpleLogWriter", new LogWriter(logFile.toFile(), false));
+ setField(stateMachine, "startIndex", 1);
+ stateMachine.write(firstPlan);
+ Method rotate =
+ ConfigRegionStateMachine.class.getDeclaredMethod("rollSimpleConsensusLogFile");
+ rotate.setAccessible(true);
+ try {
+ rotate.invoke(stateMachine);
+ Assert.fail("Expected the conflicting target to prevent renaming");
+ } catch (InvocationTargetException e) {
+ Assert.assertTrue(e.getCause() instanceof IOException);
+ }
+
+ Assert.assertNotNull(getField(stateMachine, "simpleLogWriter"));
+ Assert.assertEquals(1, getField(stateMachine, "startIndex"));
+ Assert.assertEquals(1, getField(stateMachine, "endIndex"));
+ Assert.assertEquals(
+ TSStatusCode.SUCCESS_STATUS.getStatusCode(), stateMachine.write(retryPlan).getCode());
+ closeSimpleLogWriter(stateMachine);
+ assertReplayablePlans(logFile, firstPlan, retryPlan);
+ } finally {
+ closeSimpleLogWriter(stateMachine);
+ Files.deleteIfExists(logFile);
+ Files.deleteIfExists(blocker);
+ Files.deleteIfExists(conflictingTarget);
+ Files.deleteIfExists(directory);
+ conf.setConfigNodeConsensusProtocolClass(originalConsensusProtocol);
+ }
+ }
+
+ private static ConfigPhysicalPlan versionPlan(int nodeId) {
+ return new UpdateVersionInfoPlan(new TNodeVersionInfo("version", "build"), nodeId);
+ }
+
+ private static void assertReplayablePlans(Path logFile, ConfigPhysicalPlan... plans)
+ throws Exception {
+ SingleFileLogReader reader = new SingleFileLogReader(logFile.toFile());
+ try {
+ for (ConfigPhysicalPlan plan : plans) {
+ Assert.assertTrue(reader.hasNext());
+ Assert.assertEquals(plan, reader.next());
+ }
+ Assert.assertFalse(reader.hasNext());
+ } finally {
+ reader.close();
+ }
+ }
+
+ private static Object getField(Object target, String fieldName) throws Exception {
+ Field field = target.getClass().getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return field.get(target);
+ }
+
+ private static void assertNoReplayablePlans(Path tempLogFile) throws Exception {
+ SingleFileLogReader logReader = new SingleFileLogReader(tempLogFile.toFile());
+ try {
+ Assert.assertFalse(logReader.hasNext());
+ } finally {
+ logReader.close();
+ }
+ }
+
+ private static void closeSimpleLogWriter(ConfigRegionStateMachine stateMachine) throws Exception {
+ LogWriter logWriter = (LogWriter) getField(stateMachine, "simpleLogWriter");
+ if (logWriter != null) {
+ logWriter.close();
+ }
+ }
+}
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/node/NodeManagerRegistrationTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/node/NodeManagerRegistrationTest.java
new file mode 100644
index 000000000000..518de349b970
--- /dev/null
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/node/NodeManagerRegistrationTest.java
@@ -0,0 +1,259 @@
+/*
+ * 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.confignode.manager.node;
+
+import org.apache.iotdb.common.rpc.thrift.TConfigNodeLocation;
+import org.apache.iotdb.common.rpc.thrift.TDataNodeConfiguration;
+import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
+import org.apache.iotdb.common.rpc.thrift.TEndPoint;
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.cluster.NodeStatus;
+import org.apache.iotdb.commons.cluster.NodeType;
+import org.apache.iotdb.commons.conf.CommonDescriptor;
+import org.apache.iotdb.confignode.conf.ConfigNodeConfig;
+import org.apache.iotdb.confignode.conf.ConfigNodeDescriptor;
+import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlan;
+import org.apache.iotdb.confignode.consensus.request.write.confignode.ApplyConfigNodePlan;
+import org.apache.iotdb.confignode.consensus.request.write.confignode.RemoveConfigNodePlan;
+import org.apache.iotdb.confignode.consensus.request.write.confignode.UpdateVersionInfoPlan;
+import org.apache.iotdb.confignode.consensus.request.write.datanode.RegisterDataNodePlan;
+import org.apache.iotdb.confignode.consensus.request.write.datanode.RemoveDataNodePlan;
+import org.apache.iotdb.confignode.consensus.response.datanode.DataNodeRegisterResp;
+import org.apache.iotdb.confignode.i18n.ConfigNodeMessages;
+import org.apache.iotdb.confignode.manager.ClusterManager;
+import org.apache.iotdb.confignode.manager.IManager;
+import org.apache.iotdb.confignode.manager.consensus.ConsensusManager;
+import org.apache.iotdb.confignode.manager.load.LoadManager;
+import org.apache.iotdb.confignode.manager.load.cache.LoadCache;
+import org.apache.iotdb.confignode.manager.schema.ClusterSchemaManager;
+import org.apache.iotdb.confignode.persistence.node.NodeInfo;
+import org.apache.iotdb.confignode.rpc.thrift.TDataNodeRegisterReq;
+import org.apache.iotdb.confignode.rpc.thrift.TDataNodeRestartReq;
+import org.apache.iotdb.confignode.rpc.thrift.TDataNodeRestartResp;
+import org.apache.iotdb.confignode.rpc.thrift.TNodeVersionInfo;
+import org.apache.iotdb.consensus.exception.ConsensusException;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class NodeManagerRegistrationTest {
+
+ private IManager configManager;
+ private ConsensusManager consensusManager;
+ private LoadManager loadManager;
+ private LoadCache loadCache;
+ private ClusterSchemaManager clusterSchemaManager;
+ private ClusterManager clusterManager;
+ private NodeInfo nodeInfo;
+ private NodeManager nodeManager;
+
+ @Before
+ public void setUp() {
+ configManager = Mockito.mock(IManager.class);
+ consensusManager = Mockito.mock(ConsensusManager.class);
+ loadManager = Mockito.mock(LoadManager.class);
+ loadCache = Mockito.mock(LoadCache.class);
+ clusterSchemaManager = Mockito.mock(ClusterSchemaManager.class);
+ clusterManager = Mockito.mock(ClusterManager.class);
+ nodeInfo = new NodeInfo();
+ nodeManager = new NodeManager(configManager, nodeInfo);
+
+ when(configManager.getConsensusManager()).thenReturn(consensusManager);
+ when(configManager.getLoadManager()).thenReturn(loadManager);
+ when(loadManager.getLoadCache()).thenReturn(loadCache);
+ when(configManager.getClusterSchemaManager()).thenReturn(clusterSchemaManager);
+ when(configManager.getClusterManager()).thenReturn(clusterManager);
+ }
+
+ @Test
+ public void testRegisterDataNodeStopsWhenRegisterWriteFails() throws ConsensusException {
+ TSStatus failureStatus =
+ new TSStatus(TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode()).setMessage("redirect");
+ when(consensusManager.write(any())).thenReturn(failureStatus);
+
+ DataNodeRegisterResp resp =
+ (DataNodeRegisterResp) nodeManager.registerDataNode(generateDataNodeRegisterReq(1));
+
+ Assert.assertEquals(failureStatus, resp.getStatus());
+ verify(loadCache, never()).createNodeHeartbeatCache(eq(NodeType.DataNode), anyInt());
+ verify(clusterSchemaManager, never()).adjustMaxRegionGroupNum();
+ }
+
+ @Test
+ public void testRegisterDataNodeRollsBackWhenVersionWriteFails() throws ConsensusException {
+ TSStatus successStatus = new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode());
+ TSStatus failureStatus =
+ new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode())
+ .setMessage("update failed");
+ when(consensusManager.write(any())).thenReturn(successStatus, failureStatus, successStatus);
+
+ DataNodeRegisterResp resp =
+ (DataNodeRegisterResp) nodeManager.registerDataNode(generateDataNodeRegisterReq(1));
+
+ Assert.assertEquals(
+ TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode(), resp.getStatus().getCode());
+ Assert.assertEquals(
+ String.format(
+ ConfigNodeMessages
+ .MESSAGE_FAILED_TO_PERSIST_VERSION_INFO_FOR_DATANODE_ARG_ARG_THE_REGISTRATION_HAS_BEEN_ROLLED_BACK_PLEASE_RETRY_THE_REGISTRATION_5DDB311F,
+ 0,
+ failureStatus.getMessage()),
+ resp.getStatus().getMessage());
+ verify(loadCache, never()).createNodeHeartbeatCache(eq(NodeType.DataNode), anyInt());
+ verify(clusterSchemaManager, never()).adjustMaxRegionGroupNum();
+
+ ArgumentCaptor planCaptor =
+ ArgumentCaptor.forClass(ConfigPhysicalPlan.class);
+ verify(consensusManager, Mockito.times(3)).write(planCaptor.capture());
+ Assert.assertTrue(planCaptor.getAllValues().get(0) instanceof RegisterDataNodePlan);
+ Assert.assertTrue(planCaptor.getAllValues().get(1) instanceof UpdateVersionInfoPlan);
+ Assert.assertTrue(planCaptor.getAllValues().get(2) instanceof RemoveDataNodePlan);
+ }
+
+ @Test
+ public void testRestartDataNodeReturnsFailureWhenUpdateWriteFails() throws ConsensusException {
+ final TDataNodeConfiguration registeredConfig = generateDataNodeConfiguration(1, "127.0.0.1");
+ nodeInfo.registerDataNode(new RegisterDataNodePlan(registeredConfig));
+ when(clusterManager.getClusterIdWithRetry(anyLong())).thenReturn("cluster");
+
+ TSStatus failureStatus =
+ new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode())
+ .setMessage("update failed");
+ when(consensusManager.write(any())).thenReturn(failureStatus);
+
+ final TDataNodeRestartReq req = new TDataNodeRestartReq();
+ req.setDataNodeConfiguration(generateDataNodeConfiguration(1, "127.0.0.2"));
+ req.setVersionInfo(new TNodeVersionInfo("version", "build"));
+
+ final TDataNodeRestartResp resp = nodeManager.updateDataNodeIfNecessary(req);
+
+ Assert.assertEquals(failureStatus, resp.getStatus());
+ }
+
+ @Test
+ public void testApplyConfigNodeRetainsMembershipAndRetriesVersionWrite()
+ throws ConsensusException {
+ TSStatus failureStatus =
+ new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode())
+ .setMessage("version update failed");
+ AtomicBoolean failVersionUpdate = new AtomicBoolean(true);
+ when(consensusManager.write(any()))
+ .thenAnswer(
+ invocation -> {
+ ConfigPhysicalPlan plan = invocation.getArgument(0);
+ if (plan instanceof ApplyConfigNodePlan) {
+ return nodeInfo.applyConfigNode((ApplyConfigNodePlan) plan);
+ }
+ if (plan instanceof RemoveConfigNodePlan) {
+ return nodeInfo.removeConfigNode((RemoveConfigNodePlan) plan);
+ }
+ if (failVersionUpdate.getAndSet(false)) {
+ return failureStatus;
+ }
+ return nodeInfo.updateVersionInfo((UpdateVersionInfoPlan) plan);
+ });
+ TConfigNodeLocation location =
+ new TConfigNodeLocation(
+ 1, new TEndPoint("127.0.0.1", 10710), new TEndPoint("127.0.0.1", 10720));
+ TNodeVersionInfo version = new TNodeVersionInfo("version", "build");
+ TNodeVersionInfo previousVersion = nodeInfo.getVersionInfo(location.getConfigNodeId());
+ ConfigNodeConfig conf = ConfigNodeDescriptor.getInstance().getConf();
+ int originalNodeId = conf.getConfigNodeId();
+ NodeStatus originalNodeStatus = CommonDescriptor.getInstance().getConfig().getNodeStatus();
+ String originalStatusReason = CommonDescriptor.getInstance().getConfig().getStatusReason();
+ try {
+ // Model the joining Raft member applying its own registration and rollback plans.
+ conf.setConfigNodeId(location.getConfigNodeId());
+ CommonDescriptor.getInstance().getConfig().setNodeStatus(NodeStatus.Running);
+ try {
+ nodeManager.applyConfigNode(location, version);
+ Assert.fail("Expected the failed version write to keep registration pending");
+ } catch (IllegalStateException e) {
+ Assert.assertEquals(
+ String.format(
+ ConfigNodeMessages
+ .EXCEPTION_FAILED_TO_PERSIST_VERSION_INFO_FOR_CONFIGNODE_ARG_ARG_REGISTRATION_REMAINS_PENDING_AND_MUST_BE_RETRIED_F662C350,
+ location.getConfigNodeId(),
+ failureStatus.getMessage()),
+ e.getMessage());
+ }
+ Assert.assertEquals(
+ NodeStatus.Running, CommonDescriptor.getInstance().getConfig().getNodeStatus());
+ Assert.assertEquals(Collections.singletonList(location), nodeInfo.getRegisteredConfigNodes());
+ Assert.assertEquals(previousVersion, nodeInfo.getVersionInfo(location.getConfigNodeId()));
+
+ nodeManager.applyConfigNode(location, version);
+
+ Assert.assertEquals(
+ NodeStatus.Running, CommonDescriptor.getInstance().getConfig().getNodeStatus());
+ Assert.assertEquals(Collections.singletonList(location), nodeInfo.getRegisteredConfigNodes());
+ Assert.assertEquals(version, nodeInfo.getVersionInfo(location.getConfigNodeId()));
+ } finally {
+ conf.setConfigNodeId(originalNodeId);
+ CommonDescriptor.getInstance().getConfig().setNodeStatus(originalNodeStatus);
+ CommonDescriptor.getInstance().getConfig().setStatusReason(originalStatusReason);
+ }
+
+ ArgumentCaptor planCaptor =
+ ArgumentCaptor.forClass(ConfigPhysicalPlan.class);
+ verify(consensusManager, Mockito.times(4)).write(planCaptor.capture());
+ Assert.assertTrue(planCaptor.getAllValues().get(0) instanceof ApplyConfigNodePlan);
+ Assert.assertTrue(planCaptor.getAllValues().get(1) instanceof UpdateVersionInfoPlan);
+ Assert.assertTrue(planCaptor.getAllValues().get(2) instanceof ApplyConfigNodePlan);
+ Assert.assertTrue(planCaptor.getAllValues().get(3) instanceof UpdateVersionInfoPlan);
+ verify(consensusManager, never()).write(any(RemoveConfigNodePlan.class));
+ }
+
+ private TDataNodeRegisterReq generateDataNodeRegisterReq(int dataNodeId) {
+ final TDataNodeRegisterReq req = new TDataNodeRegisterReq();
+ req.setDataNodeConfiguration(generateDataNodeConfiguration(dataNodeId, "127.0.0.1"));
+ req.setVersionInfo(new TNodeVersionInfo("version", "build"));
+ return req;
+ }
+
+ private TDataNodeConfiguration generateDataNodeConfiguration(int dataNodeId, String ip) {
+ final TDataNodeLocation dataNodeLocation = new TDataNodeLocation();
+ dataNodeLocation.setDataNodeId(dataNodeId);
+ dataNodeLocation.setClientRpcEndPoint(new TEndPoint(ip, 6667 + dataNodeId));
+ dataNodeLocation.setInternalEndPoint(new TEndPoint(ip, 10730 + dataNodeId));
+ dataNodeLocation.setMPPDataExchangeEndPoint(new TEndPoint(ip, 10740 + dataNodeId));
+ dataNodeLocation.setDataRegionConsensusEndPoint(new TEndPoint(ip, 10760 + dataNodeId));
+ dataNodeLocation.setSchemaRegionConsensusEndPoint(new TEndPoint(ip, 10750 + dataNodeId));
+
+ final TDataNodeConfiguration dataNodeConfiguration = new TDataNodeConfiguration();
+ dataNodeConfiguration.setLocation(dataNodeLocation);
+ return dataNodeConfiguration;
+ }
+}
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/NodeInfoTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/NodeInfoTest.java
index 5ce94932036d..1774ec1812ca 100644
--- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/NodeInfoTest.java
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/NodeInfoTest.java
@@ -26,8 +26,11 @@
import org.apache.iotdb.common.rpc.thrift.TNodeResource;
import org.apache.iotdb.commons.exception.StartupException;
import org.apache.iotdb.confignode.consensus.request.write.confignode.ApplyConfigNodePlan;
+import org.apache.iotdb.confignode.consensus.request.write.confignode.UpdateVersionInfoPlan;
import org.apache.iotdb.confignode.consensus.request.write.datanode.RegisterDataNodePlan;
import org.apache.iotdb.confignode.persistence.node.NodeInfo;
+import org.apache.iotdb.confignode.rpc.thrift.TNodeVersionInfo;
+import org.apache.iotdb.rpc.TSStatusCode;
import org.apache.commons.io.FileUtils;
import org.apache.thrift.TException;
@@ -73,6 +76,49 @@ public void testSnapshot() throws TException, IOException {
Assert.assertEquals(nodeInfo, nodeInfo1);
}
+ @Test
+ public void testRegistrationPlansAreIdempotentForWalReplay() {
+ NodeInfo replayNodeInfo = new NodeInfo();
+
+ TDataNodeConfiguration dataNodeConfiguration = generateTDataNodeConfiguration(100);
+ RegisterDataNodePlan registerDataNodePlan = new RegisterDataNodePlan(dataNodeConfiguration);
+ Assert.assertEquals(
+ TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+ replayNodeInfo.registerDataNode(registerDataNodePlan).getCode());
+ Assert.assertEquals(
+ TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+ replayNodeInfo.registerDataNode(registerDataNodePlan).getCode());
+ Assert.assertEquals(1, replayNodeInfo.getRegisteredDataNodeCount());
+ Assert.assertEquals(
+ dataNodeConfiguration,
+ replayNodeInfo.getRegisteredDataNode(dataNodeConfiguration.getLocation().getDataNodeId()));
+
+ TConfigNodeLocation configNodeLocation =
+ new TConfigNodeLocation(
+ 20000, new TEndPoint("127.0.0.1", 22200), new TEndPoint("127.0.0.1", 22300));
+ ApplyConfigNodePlan applyConfigNodePlan = new ApplyConfigNodePlan(configNodeLocation);
+ Assert.assertEquals(
+ TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+ replayNodeInfo.applyConfigNode(applyConfigNodePlan).getCode());
+ Assert.assertEquals(
+ TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+ replayNodeInfo.applyConfigNode(applyConfigNodePlan).getCode());
+ Assert.assertEquals(1, replayNodeInfo.getRegisteredConfigNodes().size());
+ Assert.assertEquals(configNodeLocation, replayNodeInfo.getRegisteredConfigNodes().get(0));
+
+ TNodeVersionInfo versionInfo = new TNodeVersionInfo("version", "build");
+ UpdateVersionInfoPlan updateVersionInfoPlan =
+ new UpdateVersionInfoPlan(versionInfo, configNodeLocation.getConfigNodeId());
+ Assert.assertEquals(
+ TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+ replayNodeInfo.updateVersionInfo(updateVersionInfoPlan).getCode());
+ Assert.assertEquals(
+ TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+ replayNodeInfo.updateVersionInfo(updateVersionInfoPlan).getCode());
+ Assert.assertEquals(
+ versionInfo, replayNodeInfo.getVersionInfo(configNodeLocation.getConfigNodeId()));
+ }
+
private void registerConfigNodes() {
for (int i = 0; i < 3; i++) {
ApplyConfigNodePlan applyConfigNodePlan =
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/PartitionInfoTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/PartitionInfoTest.java
index f6f209c93564..9d8d4648b8f0 100644
--- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/PartitionInfoTest.java
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/PartitionInfoTest.java
@@ -38,6 +38,7 @@
import org.apache.iotdb.confignode.consensus.request.write.region.OfferRegionMaintainTasksPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.PollSpecificRegionMaintainTaskPlan;
import org.apache.iotdb.confignode.consensus.response.partition.RegionInfoListResp;
+import org.apache.iotdb.confignode.exception.DatabaseNotExistsException;
import org.apache.iotdb.confignode.persistence.partition.PartitionInfo;
import org.apache.iotdb.confignode.persistence.partition.maintainer.RegionCreateTask;
import org.apache.iotdb.confignode.persistence.partition.maintainer.RegionDeleteTask;
@@ -286,6 +287,34 @@ public void testShowRegion() {
});
}
+ @Test
+ public void testRegionGroupCount() throws DatabaseNotExistsException {
+ partitionInfo.createDatabase(
+ new DatabaseSchemaPlan(
+ ConfigPhysicalPlanType.CreateDatabase, new TDatabaseSchema("root.region_count")));
+
+ CreateRegionGroupsPlan createRegionGroupsPlan = new CreateRegionGroupsPlan();
+ createRegionGroupsPlan.addRegionGroup(
+ "root.region_count",
+ generateTRegionReplicaSet(
+ testFlag.SchemaPartition.getFlag(),
+ generateTConsensusGroupId(
+ testFlag.SchemaPartition.getFlag(), TConsensusGroupType.SchemaRegion)));
+ createRegionGroupsPlan.addRegionGroup(
+ "root.region_count",
+ generateTRegionReplicaSet(
+ testFlag.DataPartition.getFlag(),
+ generateTConsensusGroupId(
+ testFlag.DataPartition.getFlag(), TConsensusGroupType.DataRegion)));
+ partitionInfo.createRegionGroups(createRegionGroupsPlan);
+
+ Assert.assertEquals(
+ 1,
+ partitionInfo.getRegionGroupCount("root.region_count", TConsensusGroupType.SchemaRegion));
+ Assert.assertEquals(
+ 1, partitionInfo.getRegionGroupCount("root.region_count", TConsensusGroupType.DataRegion));
+ }
+
private TRegionReplicaSet generateTRegionReplicaSet(
int startFlag, TConsensusGroupId tConsensusGroupId) {
TRegionReplicaSet tRegionReplicaSet = new TRegionReplicaSet();