This is an automated email from the ASF dual-hosted git repository.
tanxinyu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new 38c1d591acf Feat/updated cli (#13194)
38c1d591acf is described below
commit 38c1d591acfabdae4bbf542100090652416d2027
Author: Christofer Dutz <[email protected]>
AuthorDate: Tue Aug 27 05:11:31 2024 +0200
Feat/updated cli (#13194)
---
.idea/icon.png | Bin 0 -> 6736 bytes
.../iotdb/confignode/service/ConfigNode.java | 71 ++++-
.../confignode/service/ConfigNodeCommandLine.java | 128 --------
.../procedure/impl/CreateCQProcedureTest.java | 2 +-
.../assembly/resources/sbin/remove-datanode.bat | 2 -
.../src/assembly/resources/sbin/remove-datanode.sh | 2 -
.../java/org/apache/iotdb/db/service/DataNode.java | 76 ++++-
.../db/service/DataNodeServerCommandLine.java | 224 --------------
.../apache/iotdb/db/service/IoTDBShutdownHook.java | 8 +-
.../org/apache/iotdb/db/service/DaemonTest.java | 8 +-
.../db/service/DataNodeServerCommandLineTest.java | 218 -------------
iotdb-core/node-commons/pom.xml | 4 +
.../apache/iotdb/commons/ServerCommandLine.java | 115 ++++---
.../iotdb/commons/ServerCommandLineTest.java | 337 +++++++++++++++++++++
14 files changed, 568 insertions(+), 627 deletions(-)
diff --git a/.idea/icon.png b/.idea/icon.png
new file mode 100644
index 00000000000..493aca9320e
Binary files /dev/null and b/.idea/icon.png differ
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/ConfigNode.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/ConfigNode.java
index 3d537cdec61..a7f9d3d69a0 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/ConfigNode.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/ConfigNode.java
@@ -22,6 +22,7 @@ package org.apache.iotdb.confignode.service;
import org.apache.iotdb.common.rpc.thrift.TConfigNodeLocation;
import org.apache.iotdb.common.rpc.thrift.TEndPoint;
import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.ServerCommandLine;
import org.apache.iotdb.commons.client.ClientManagerMetrics;
import org.apache.iotdb.commons.concurrent.ThreadModule;
import org.apache.iotdb.commons.concurrent.ThreadName;
@@ -29,7 +30,10 @@ import org.apache.iotdb.commons.concurrent.ThreadPoolMetrics;
import org.apache.iotdb.commons.conf.CommonConfig;
import org.apache.iotdb.commons.conf.CommonDescriptor;
import org.apache.iotdb.commons.conf.IoTDBConstant;
+import org.apache.iotdb.commons.exception.BadNodeUrlException;
+import org.apache.iotdb.commons.exception.ConfigurationException;
import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.commons.exception.IoTDBException;
import org.apache.iotdb.commons.exception.StartupException;
import org.apache.iotdb.commons.service.JMXService;
import org.apache.iotdb.commons.service.RegisterManager;
@@ -43,6 +47,8 @@ import
org.apache.iotdb.confignode.client.sync.SyncConfigNodeClientPool;
import org.apache.iotdb.confignode.conf.ConfigNodeConfig;
import org.apache.iotdb.confignode.conf.ConfigNodeConstant;
import org.apache.iotdb.confignode.conf.ConfigNodeDescriptor;
+import org.apache.iotdb.confignode.conf.ConfigNodeRemoveCheck;
+import org.apache.iotdb.confignode.conf.ConfigNodeStartupCheck;
import org.apache.iotdb.confignode.conf.SystemPropertiesUtils;
import org.apache.iotdb.confignode.manager.ConfigManager;
import org.apache.iotdb.confignode.manager.consensus.ConsensusManager;
@@ -74,7 +80,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
-public class ConfigNode implements ConfigNodeMBean {
+public class ConfigNode extends ServerCommandLine implements ConfigNodeMBean {
private static final Logger LOGGER =
LoggerFactory.getLogger(ConfigNode.class);
@@ -101,11 +107,13 @@ public class ConfigNode implements ConfigNodeMBean {
protected ConfigManager configManager;
- protected ConfigNode() {
+ public ConfigNode() {
+ super("ConfigNode");
// We do not init anything here, so that we can re-initialize the instance
in IT.
+ ConfigNodeHolder.instance = this;
}
- public static void main(String[] args) {
+ public static void main(String[] args) throws Exception {
LOGGER.info(
"{} environment variables: {}",
ConfigNodeConstant.GLOBAL_NAME,
@@ -114,7 +122,60 @@ public class ConfigNode implements ConfigNodeMBean {
"{} default charset is: {}",
ConfigNodeConstant.GLOBAL_NAME,
Charset.defaultCharset().displayName());
- new ConfigNodeCommandLine().doMain(args);
+ ConfigNode configNode = new ConfigNode();
+ int returnCode = configNode.run(args);
+ if (returnCode != 0) {
+ System.exit(returnCode);
+ }
+ }
+
+ @Override
+ protected void start() throws IoTDBException {
+ try {
+ // Do ConfigNode startup checks
+ LOGGER.info("Starting IoTDB {}", IoTDBConstant.VERSION_WITH_BUILD);
+ ConfigNodeStartupCheck checks = new
ConfigNodeStartupCheck(IoTDBConstant.CN_ROLE);
+ checks.startUpCheck();
+ } catch (StartupException | ConfigurationException | IOException e) {
+ LOGGER.error("Meet error when doing start checking", e);
+ throw new IoTDBException("Error starting", -1);
+ }
+ active();
+ }
+
+ @Override
+ protected void remove(Long nodeId) throws IoTDBException {
+ // If the nodeId was null, this is a shorthand for removing the current
dataNode.
+ // In this case we need to find our nodeId.
+ if (nodeId == null) {
+ nodeId = (long) CONF.getConfigNodeId();
+ }
+
+ try {
+ LOGGER.info("Starting to remove ConfigNode with node-id {}", nodeId);
+
+ try {
+ TConfigNodeLocation removeConfigNodeLocation =
+
ConfigNodeRemoveCheck.getInstance().removeCheck(Long.toString(nodeId));
+ if (removeConfigNodeLocation == null) {
+ LOGGER.error(
+ "The ConfigNode to be removed is not in the cluster, or the
input format is incorrect.");
+ return;
+ }
+
+
ConfigNodeRemoveCheck.getInstance().removeConfigNode(removeConfigNodeLocation);
+ } catch (BadNodeUrlException e) {
+ LOGGER.warn("No ConfigNodes need to be removed.", e);
+ return;
+ }
+
+ LOGGER.info(
+ "ConfigNode: {} is removed. If the confignode data directory is no
longer needed, you can delete it manually.",
+ nodeId);
+ } catch (IOException e) {
+ LOGGER.error("Meet error when doing remove ConfigNode", e);
+ throw new IoTDBException("Error removing", -1);
+ }
}
public void active() {
@@ -465,7 +526,7 @@ public class ConfigNode implements ConfigNodeMBean {
private static class ConfigNodeHolder {
- private static ConfigNode instance = new ConfigNode();
+ private static ConfigNode instance;
private ConfigNodeHolder() {
// Empty constructor
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/ConfigNodeCommandLine.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/ConfigNodeCommandLine.java
deleted file mode 100644
index 21ccbab3421..00000000000
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/ConfigNodeCommandLine.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.service;
-
-import org.apache.iotdb.common.rpc.thrift.TConfigNodeLocation;
-import org.apache.iotdb.commons.ServerCommandLine;
-import org.apache.iotdb.commons.conf.IoTDBConstant;
-import org.apache.iotdb.commons.exception.BadNodeUrlException;
-import org.apache.iotdb.commons.exception.ConfigurationException;
-import org.apache.iotdb.commons.exception.StartupException;
-import org.apache.iotdb.confignode.conf.ConfigNodeRemoveCheck;
-import org.apache.iotdb.confignode.conf.ConfigNodeStartupCheck;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-import static
org.apache.iotdb.confignode.conf.ConfigNodeConstant.REMOVE_CONFIGNODE_USAGE;
-
-public class ConfigNodeCommandLine extends ServerCommandLine {
- private static final Logger LOGGER =
LoggerFactory.getLogger(ConfigNodeCommandLine.class);
-
- // Start ConfigNode
- private static final String MODE_START = "-s";
- // Remove ConfigNode
- private static final String MODE_REMOVE = "-r";
-
- private static final String USAGE =
- "Usage: <-s|-r> "
- + "[-D{} <configure folder>] \n"
- + "-s: Start the ConfigNode and join to the cluster\n"
- + "-r: Remove the ConfigNode out of the cluster\n";
-
- @Override
- protected String getUsage() {
- return USAGE;
- }
-
- @Override
- protected int run(String[] args) {
- String mode;
- if (args.length < 1) {
- mode = MODE_START;
- LOGGER.warn(
- "ConfigNode does not specify a startup mode. The default startup
mode {} will be used",
- MODE_START);
- } else {
- mode = args[0];
- }
-
- LOGGER.info("Running mode {}", mode);
- if (MODE_START.equals(mode)) {
- try {
- // Do ConfigNode startup checks
- LOGGER.info("Starting IoTDB {}", IoTDBConstant.VERSION_WITH_BUILD);
- ConfigNodeStartupCheck checks = new
ConfigNodeStartupCheck(IoTDBConstant.CN_ROLE);
- checks.startUpCheck();
- } catch (StartupException | ConfigurationException | IOException e) {
- LOGGER.error("Meet error when doing start checking", e);
- return -1;
- }
- activeConfigNodeInstance();
- } else if (MODE_REMOVE.equals(mode)) {
- // remove ConfigNode
- try {
- doRemoveConfigNode(args);
- } catch (IOException e) {
- LOGGER.error("Meet error when doing remove ConfigNode", e);
- return -1;
- }
- } else {
- LOGGER.error("Unsupported startup mode: {}", mode);
- return -1;
- }
-
- return 0;
- }
-
- protected void activeConfigNodeInstance() {
- ConfigNode.getInstance().active();
- }
-
- protected void doRemoveConfigNode(String[] args) throws IOException {
-
- if (args.length != 2) {
- LOGGER.info(REMOVE_CONFIGNODE_USAGE);
- return;
- }
-
- LOGGER.info("Starting to remove ConfigNode, parameter: {}, {}", args[0],
args[1]);
-
- try {
- TConfigNodeLocation removeConfigNodeLocation =
- ConfigNodeRemoveCheck.getInstance().removeCheck(args[1]);
- if (removeConfigNodeLocation == null) {
- LOGGER.error(
- "The ConfigNode to be removed is not in the cluster, or the input
format is incorrect.");
- return;
- }
-
-
ConfigNodeRemoveCheck.getInstance().removeConfigNode(removeConfigNodeLocation);
- } catch (BadNodeUrlException e) {
- LOGGER.warn("No ConfigNodes need to be removed.", e);
- return;
- }
-
- LOGGER.info(
- "ConfigNode: {} is removed. If the confignode data directory is no
longer needed, you can delete it manually.",
- args[1]);
- }
-}
diff --git
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/CreateCQProcedureTest.java
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/CreateCQProcedureTest.java
index 9915b7217f1..d0e92b32816 100644
---
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/CreateCQProcedureTest.java
+++
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/CreateCQProcedureTest.java
@@ -67,7 +67,7 @@ public class CreateCQProcedureTest {
Mockito.when(cqManager.getExecutor()).thenReturn(executor);
ConfigManager configManager = Mockito.mock(ConfigManager.class);
Mockito.when(configManager.getCQManager()).thenReturn(cqManager);
- ConfigNode configNode = ConfigNode.getInstance();
+ ConfigNode configNode = new ConfigNode();
configNode.setConfigManager(configManager);
try {
diff --git
a/iotdb-core/datanode/src/assembly/resources/sbin/remove-datanode.bat
b/iotdb-core/datanode/src/assembly/resources/sbin/remove-datanode.bat
index 1903e224d8e..f9dc167b956 100644
--- a/iotdb-core/datanode/src/assembly/resources/sbin/remove-datanode.bat
+++ b/iotdb-core/datanode/src/assembly/resources/sbin/remove-datanode.bat
@@ -25,8 +25,6 @@ IF "%~1"=="--help" (
echo Usage:
echo Remove the DataNode with datanode_id
echo ./sbin/remove-datanode.bat [datanode_id]
- echo Remove the DataNode with address:port
- echo ./sbin/remove-datanode.bat [dn_rpc_address:dn_rpc_port]
EXIT /B 0
)
diff --git a/iotdb-core/datanode/src/assembly/resources/sbin/remove-datanode.sh
b/iotdb-core/datanode/src/assembly/resources/sbin/remove-datanode.sh
index ba27784ca3b..8fb2ef5eebf 100755
--- a/iotdb-core/datanode/src/assembly/resources/sbin/remove-datanode.sh
+++ b/iotdb-core/datanode/src/assembly/resources/sbin/remove-datanode.sh
@@ -24,8 +24,6 @@ if [ "$#" -eq 1 ] && [ "$1" == "--help" ]; then
echo "Usage:"
echo "Remove the DataNode with datanode_id"
echo "./sbin/remove-datanode.sh [datanode_id]"
- echo "Remove the DataNode with address:port"
- echo "./sbin/remove-datanode.sh [dn_rpc_address:dn_rpc_port]"
exit 0
fi
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/DataNode.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/DataNode.java
index 5e9be69b6d5..b715a7c6ca7 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/DataNode.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/DataNode.java
@@ -26,12 +26,14 @@ 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.TNodeResource;
+import org.apache.iotdb.commons.ServerCommandLine;
import org.apache.iotdb.commons.client.exception.ClientManagerException;
import org.apache.iotdb.commons.concurrent.IoTDBDefaultThreadExceptionHandler;
import org.apache.iotdb.commons.conf.CommonDescriptor;
import org.apache.iotdb.commons.conf.IoTDBConstant;
import org.apache.iotdb.commons.consensus.ConsensusGroupId;
import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.commons.exception.IoTDBException;
import org.apache.iotdb.commons.exception.StartupException;
import org.apache.iotdb.commons.pipe.config.PipeConfig;
import org.apache.iotdb.commons.pipe.plugin.meta.PipePluginMeta;
@@ -50,6 +52,8 @@ import org.apache.iotdb.commons.utils.FileUtils;
import org.apache.iotdb.commons.utils.PathUtils;
import org.apache.iotdb.confignode.rpc.thrift.TDataNodeRegisterReq;
import org.apache.iotdb.confignode.rpc.thrift.TDataNodeRegisterResp;
+import org.apache.iotdb.confignode.rpc.thrift.TDataNodeRemoveReq;
+import org.apache.iotdb.confignode.rpc.thrift.TDataNodeRemoveResp;
import org.apache.iotdb.confignode.rpc.thrift.TDataNodeRestartReq;
import org.apache.iotdb.confignode.rpc.thrift.TDataNodeRestartResp;
import org.apache.iotdb.confignode.rpc.thrift.TGetJarInListReq;
@@ -120,13 +124,14 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.apache.iotdb.commons.conf.IoTDBConstant.DEFAULT_CLUSTER_NAME;
import static org.apache.iotdb.db.conf.IoTDBStartCheck.PROPERTIES_FILE_NAME;
-public class DataNode implements DataNodeMBean {
+public class DataNode extends ServerCommandLine implements DataNodeMBean {
private static final Logger logger = LoggerFactory.getLogger(DataNode.class);
private static final IoTDBConfig config =
IoTDBDescriptor.getInstance().getConfig();
@@ -162,8 +167,10 @@ public class DataNode implements DataNodeMBean {
private boolean schemaRegionConsensusStarted = false;
private boolean dataRegionConsensusStarted = false;
- private DataNode() {
+ public DataNode() {
+ super("DataNode");
// We do not init anything here, so that we can re-initialize the instance
in IT.
+ DataNodeHolder.INSTANCE = this;
}
// TODO: This needs removal of statics ...
@@ -185,11 +192,16 @@ public class DataNode implements DataNodeMBean {
public static void main(String[] args) {
logger.info("IoTDB-DataNode environment variables: {}",
IoTDBConfig.getEnvironmentVariables());
logger.info("IoTDB-DataNode default charset is: {}",
Charset.defaultCharset().displayName());
- new DataNodeServerCommandLine().doMain(args);
+ DataNode dataNode = new DataNode();
+ int returnCode = dataNode.run(args);
+ if (returnCode != 0) {
+ System.exit(returnCode);
+ }
}
- protected void doAddNode() {
- boolean isFirstStart = false;
+ @Override
+ protected void start() {
+ boolean isFirstStart;
try {
// Check if this DataNode is start for the first time and do other
pre-checks
isFirstStart = prepareDataNode();
@@ -242,6 +254,56 @@ public class DataNode implements DataNodeMBean {
}
}
+ @Override
+ protected void remove(Long nodeId) throws IoTDBException {
+ // If the nodeId was null, this is a shorthand for removing the current
dataNode.
+ // In this case we need to find our nodeId.
+ if (nodeId == null) {
+ nodeId = (long) config.getDataNodeId();
+ }
+
+ logger.info("Starting to remove DataNode with node-id {} from cluster",
nodeId);
+
+ // Load ConfigNodeList from system.properties file
+ ConfigNodeInfo.getInstance().loadConfigNodeList();
+
+ int removeNodeId = nodeId.intValue();
+ try (ConfigNodeClient configNodeClient =
+
ConfigNodeClientManager.getInstance().borrowClient(ConfigNodeInfo.CONFIG_REGION_ID))
{
+ // Find a datanode location with the given node id.
+ Optional<TDataNodeLocation> dataNodeLocationOpt =
+ configNodeClient
+ .getDataNodeConfiguration(-1)
+ .getDataNodeConfigurationMap()
+ .values()
+ .stream()
+ .map(TDataNodeConfiguration::getLocation)
+ .filter(location -> location.getDataNodeId() == removeNodeId)
+ .findFirst();
+ if (!dataNodeLocationOpt.isPresent()) {
+ throw new IoTDBException("Invalid node-id", -1);
+ }
+ TDataNodeLocation dataNodeLocation = dataNodeLocationOpt.get();
+
+ logger.info("Start to remove datanode, removed datanode endpoint: {}",
dataNodeLocation);
+ TDataNodeRemoveReq removeReq =
+ new TDataNodeRemoveReq(Collections.singletonList(dataNodeLocation));
+ TDataNodeRemoveResp removeResp =
configNodeClient.removeDataNode(removeReq);
+ logger.info("Remove result {} ", removeResp);
+ if (removeResp.getStatus().getCode() !=
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ throw new IoTDBException(
+ removeResp.getStatus().toString(),
removeResp.getStatus().getCode());
+ }
+ logger.info(
+ "Submit remove-datanode request successfully, but the process may
fail. "
+ + "more details are shown in the logs of confignode-leader and
removed-datanode, "
+ + "and after the process of removing datanode ends successfully,
"
+ + "you are supposed to delete directory and data of the
removed-datanode manually");
+ } catch (TException | ClientManagerException e) {
+ throw new IoTDBException("Failed removing datanode", e, -1);
+ }
+ }
+
/** Prepare cluster IoTDB-DataNode */
private boolean prepareDataNode() throws StartupException, IOException {
long startTime = System.currentTimeMillis();
@@ -632,7 +694,7 @@ public class DataNode implements DataNodeMBean {
// Get resources for trigger,udf,pipe...
prepareResources();
- Runtime.getRuntime().addShutdownHook(new IoTDBShutdownHook());
+ Runtime.getRuntime().addShutdownHook(new
IoTDBShutdownHook(generateDataNodeLocation()));
setUncaughtExceptionHandler();
logger.info("Recover the schema...");
@@ -1093,7 +1155,7 @@ public class DataNode implements DataNodeMBean {
private static class DataNodeHolder {
- private static final DataNode INSTANCE = new DataNode();
+ private static DataNode INSTANCE;
private DataNodeHolder() {
// Empty constructor
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/DataNodeServerCommandLine.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/DataNodeServerCommandLine.java
deleted file mode 100644
index 92f55888d83..00000000000
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/DataNodeServerCommandLine.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.iotdb.db.service;
-
-import org.apache.iotdb.common.rpc.thrift.TDataNodeConfiguration;
-import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
-import org.apache.iotdb.commons.ServerCommandLine;
-import org.apache.iotdb.commons.client.IClientManager;
-import org.apache.iotdb.commons.client.exception.ClientManagerException;
-import org.apache.iotdb.commons.consensus.ConfigRegionId;
-import org.apache.iotdb.commons.exception.BadNodeUrlException;
-import org.apache.iotdb.commons.exception.IoTDBException;
-import org.apache.iotdb.confignode.rpc.thrift.TDataNodeRemoveReq;
-import org.apache.iotdb.confignode.rpc.thrift.TDataNodeRemoveResp;
-import org.apache.iotdb.db.protocol.client.ConfigNodeClient;
-import org.apache.iotdb.db.protocol.client.ConfigNodeClientManager;
-import org.apache.iotdb.db.protocol.client.ConfigNodeInfo;
-import org.apache.iotdb.rpc.TSStatusCode;
-
-import org.apache.commons.lang3.math.NumberUtils;
-import org.apache.thrift.TException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class DataNodeServerCommandLine extends ServerCommandLine {
-
- private static final Logger LOGGER =
LoggerFactory.getLogger(DataNodeServerCommandLine.class);
-
- // join an established cluster
- public static final String MODE_START = "-s";
- // send a request to remove a node, more arguments: ip-of-removed-node
- // metaport-of-removed-node
- public static final String MODE_REMOVE = "-r";
-
- private final ConfigNodeInfo configNodeInfo;
- private final IClientManager<ConfigRegionId, ConfigNodeClient>
configNodeClientManager;
- private final DataNode dataNode;
-
- private static final String USAGE =
- "Usage: <-s|-r> "
- + "[-D{} <configure folder>] \n"
- + "-s: start the node to the cluster\n"
- + "-r: remove the node out of the cluster\n";
-
- /** Default constructor using the singletons for initializing the
relationship. */
- public DataNodeServerCommandLine() {
- configNodeInfo = ConfigNodeInfo.getInstance();
- configNodeClientManager = ConfigNodeClientManager.getInstance();
- dataNode = DataNode.getInstance();
- }
-
- /**
- * Additional constructor allowing injection of custom instances (mainly for
testing)
- *
- * @param configNodeInfo config node info
- * @param configNodeClientManager config node client manager
- * @param dataNode data node
- */
- public DataNodeServerCommandLine(
- ConfigNodeInfo configNodeInfo,
- IClientManager<ConfigRegionId, ConfigNodeClient> configNodeClientManager,
- DataNode dataNode) {
- this.configNodeInfo = configNodeInfo;
- this.configNodeClientManager = configNodeClientManager;
- this.dataNode = dataNode;
- }
-
- @Override
- protected String getUsage() {
- return USAGE;
- }
-
- @Override
- protected int run(String[] args) throws Exception {
- if (args.length < 1) {
- usage(null);
- return -1;
- }
-
- String mode = args[0];
- LOGGER.info("Running mode {}", mode);
-
- // Start IoTDB kernel first, then start the cluster module
- if (MODE_START.equals(mode)) {
- dataNode.doAddNode();
- } else if (MODE_REMOVE.equals(mode)) {
- return doRemoveDataNode(args);
- } else {
- LOGGER.error("Unrecognized mode {}", mode);
- }
- return 0;
- }
-
- /**
- * remove data-nodes from cluster
- *
- * @param args id or ip:rpc_port for removed datanode
- */
- private int doRemoveDataNode(String[] args)
- throws BadNodeUrlException, TException, IoTDBException,
ClientManagerException {
-
- if (args.length != 2) {
- LOGGER.info("Usage: <node-id>/<ip>:<rpc-port>");
- return -1;
- }
-
- // REMARK: Don't need null or empty-checks for args[0] or args[1], as if
they were
- // empty, the JVM would have not received them.
-
- LOGGER.info("Starting to remove DataNode from cluster, parameter: {}, {}",
args[0], args[1]);
-
- // Load ConfigNodeList from system.properties file
- configNodeInfo.loadConfigNodeList();
-
- List<TDataNodeLocation> dataNodeLocations =
buildDataNodeLocations(args[1]);
- if (dataNodeLocations.isEmpty()) {
- throw new BadNodeUrlException("No DataNode to remove");
- }
- LOGGER.info("Start to remove datanode, removed datanode endpoints: {}",
dataNodeLocations);
- TDataNodeRemoveReq removeReq = new TDataNodeRemoveReq(dataNodeLocations);
- try (ConfigNodeClient configNodeClient =
- configNodeClientManager.borrowClient(ConfigNodeInfo.CONFIG_REGION_ID))
{
- TDataNodeRemoveResp removeResp =
configNodeClient.removeDataNode(removeReq);
- LOGGER.info("Remove result {} ", removeResp);
- if (removeResp.getStatus().getCode() !=
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
- throw new IoTDBException(
- removeResp.getStatus().toString(),
removeResp.getStatus().getCode());
- }
- LOGGER.info(
- "Submit remove-datanode request successfully, but the process may
fail. "
- + "more details are shown in the logs of confignode-leader and
removed-datanode, "
- + "and after the process of removing datanode ends successfully,
"
- + "you are supposed to delete directory and data of the
removed-datanode manually");
- }
- return 0;
- }
-
- /**
- * fetch all datanode info from ConfigNode, then compare with input 'args'
- *
- * @param args datanode id or ip:rpc_port
- * @return TDataNodeLocation list
- */
- private List<TDataNodeLocation> buildDataNodeLocations(String args) {
- List<TDataNodeLocation> dataNodeLocations = new ArrayList<>();
-
- // Now support only single datanode deletion
- if (args.split(",").length > 1) {
- throw new IllegalArgumentException("Currently only removing single nodes
is supported.");
- }
-
- // Below supports multiple datanode deletion, split by ',', and is
reserved for extension
- List<NodeCoordinate> nodeCoordinates = parseCoordinates(args);
- try (ConfigNodeClient client =
- configNodeClientManager.borrowClient(ConfigNodeInfo.CONFIG_REGION_ID))
{
- dataNodeLocations =
-
client.getDataNodeConfiguration(-1).getDataNodeConfigurationMap().values().stream()
- .map(TDataNodeConfiguration::getLocation)
- .filter(
- location ->
- nodeCoordinates.stream()
- .anyMatch(nodeCoordinate ->
nodeCoordinate.matches(location)))
- .collect(Collectors.toList());
- } catch (TException | ClientManagerException e) {
- LOGGER.error("Get data node locations failed", e);
- }
-
- return dataNodeLocations;
- }
-
- protected List<NodeCoordinate> parseCoordinates(String coordinatesString) {
- // Multiple nodeIds are separated by ","
- String[] nodeIdStrings = coordinatesString.split(",");
- List<NodeCoordinate> nodeIdCoordinates = new
ArrayList<>(nodeIdStrings.length);
- for (String nodeId : nodeIdStrings) {
- // In the other case, we expect it to be a numeric value referring to
the node-id
- if (NumberUtils.isCreatable(nodeId)) {
- nodeIdCoordinates.add(new
NodeCoordinateNodeId(Integer.parseInt(nodeId)));
- } else {
- LOGGER.error("Invalid format. Expected a numeric node id, but got:
{}", nodeId);
- }
- }
- return nodeIdCoordinates;
- }
-
- protected interface NodeCoordinate {
- // Returns true if the given location matches this coordinate
- boolean matches(TDataNodeLocation location);
- }
-
- /** Implementation of a NodeCoordinate that uses the node id to match. */
- protected static class NodeCoordinateNodeId implements NodeCoordinate {
- private final int nodeId;
-
- public NodeCoordinateNodeId(int nodeId) {
- this.nodeId = nodeId;
- }
-
- @Override
- public boolean matches(TDataNodeLocation location) {
- return location.isSetDataNodeId() && location.dataNodeId == nodeId;
- }
- }
-}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/IoTDBShutdownHook.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/IoTDBShutdownHook.java
index 05c7b2c5637..8ef6bbdc430 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/IoTDBShutdownHook.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/IoTDBShutdownHook.java
@@ -19,6 +19,7 @@
package org.apache.iotdb.db.service;
+import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
import org.apache.iotdb.commons.client.exception.ClientManagerException;
import org.apache.iotdb.commons.cluster.NodeStatus;
import org.apache.iotdb.commons.concurrent.ThreadName;
@@ -49,8 +50,11 @@ public class IoTDBShutdownHook extends Thread {
private static final Logger logger =
LoggerFactory.getLogger(IoTDBShutdownHook.class);
- public IoTDBShutdownHook() {
+ private final TDataNodeLocation nodeLocation;
+
+ public IoTDBShutdownHook(TDataNodeLocation nodeLocation) {
super(ThreadName.IOTDB_SHUTDOWN_HOOK.getName());
+ this.nodeLocation = nodeLocation;
}
@Override
@@ -120,7 +124,7 @@ public class IoTDBShutdownHook extends Thread {
try (ConfigNodeClient client =
ConfigNodeClientManager.getInstance().borrowClient(ConfigNodeInfo.CONFIG_REGION_ID))
{
isReportSuccess =
-
client.reportDataNodeShutdown(DataNode.generateDataNodeLocation()).getCode()
+ client.reportDataNodeShutdown(nodeLocation).getCode()
== TSStatusCode.SUCCESS_STATUS.getStatusCode();
// Actually stop all services started by the DataNode.
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/DaemonTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/DaemonTest.java
index 8a1ba738a5c..301c801536a 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/DaemonTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/DaemonTest.java
@@ -24,16 +24,16 @@ public class DaemonTest {
@Test
public void testPid() {
- DataNode ioTDB = DataNode.getInstance();
+ DataNode dataNode = new DataNode();
// no pid set, so there is nothing happens
- ioTDB.processPid();
+ dataNode.processPid();
}
@Test
public void testSetPid() {
- DataNode ioTDB = DataNode.getInstance();
+ DataNode dataNode = new DataNode();
System.setProperty("iotdb-pidfile", "./iotdb.pid");
// no pid set, so there is nothing happens
- ioTDB.processPid();
+ dataNode.processPid();
}
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/DataNodeServerCommandLineTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/DataNodeServerCommandLineTest.java
deleted file mode 100644
index ab37e6bc64a..00000000000
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/DataNodeServerCommandLineTest.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.iotdb.db.service;
-
-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.TNodeResource;
-import org.apache.iotdb.common.rpc.thrift.TSStatus;
-import org.apache.iotdb.commons.client.IClientManager;
-import org.apache.iotdb.commons.consensus.ConfigRegionId;
-import org.apache.iotdb.commons.exception.BadNodeUrlException;
-import org.apache.iotdb.confignode.rpc.thrift.TDataNodeConfigurationResp;
-import org.apache.iotdb.confignode.rpc.thrift.TDataNodeRemoveReq;
-import org.apache.iotdb.confignode.rpc.thrift.TDataNodeRemoveResp;
-import org.apache.iotdb.db.protocol.client.ConfigNodeClient;
-import org.apache.iotdb.db.protocol.client.ConfigNodeInfo;
-import org.apache.iotdb.rpc.TSStatusCode;
-
-import junit.framework.TestCase;
-import org.junit.Assert;
-import org.mockito.Mockito;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-public class DataNodeServerCommandLineTest extends TestCase {
-
- // List of well known locations for this test
- protected static final TDataNodeLocation LOCATION_1 =
- new TDataNodeLocation(1, new TEndPoint("1.2.3.4", 6667), null, null,
null, null);
- protected static final TDataNodeLocation LOCATION_2 =
- new TDataNodeLocation(2, new TEndPoint("1.2.3.5", 6667), null, null,
null, null);
- protected static final TDataNodeLocation LOCATION_3 =
- new TDataNodeLocation(3, new TEndPoint("1.2.3.6", 6667), null, null,
null, null);
- // An invalid location
- protected static final TDataNodeLocation INVALID_LOCATION =
- new TDataNodeLocation(23, new TEndPoint("4.3.2.1", 815), null, null,
null, null);
-
- /**
- * In this test we pass an empty args list to the command. This is expected
to fail.
- *
- * @throws Exception nothing should go wrong here.
- */
- public void testNoArgs() throws Exception {
- // No need to initialize these mocks with anything sensible, as they
should never be used.
- ConfigNodeInfo configNodeInfo = null;
- IClientManager<ConfigRegionId, ConfigNodeClient> configNodeClientManager =
null;
- DataNode dataNode = null;
- DataNodeServerCommandLine sut =
- new DataNodeServerCommandLine(configNodeInfo, configNodeClientManager,
dataNode);
-
- int returnCode = sut.run(new String[0]);
-
- // We expect an error code of -1.
- Assert.assertEquals(-1, returnCode);
- }
-
- /**
- * In this test we pass too many arguments to the command. This should also
fail with an error
- * code.
- *
- * @throws Exception nothing should go wrong here.
- */
- public void testTooManyArgs() throws Exception {
- // No need to initialize these mocks with anything sensible, as they
should never be used.
- ConfigNodeInfo configNodeInfo = null;
- IClientManager<ConfigRegionId, ConfigNodeClient> configNodeClientManager =
null;
- DataNode dataNode = null;
- DataNodeServerCommandLine sut =
- new DataNodeServerCommandLine(configNodeInfo, configNodeClientManager,
dataNode);
-
- int returnCode = sut.run(new String[] {"-r", "2", "-s"});
-
- // We expect an error code of -1.
- Assert.assertEquals(-1, returnCode);
- }
-
- /**
- * In this test case we provide the coordinates for the data-node that we
want to delete by
- * providing the node-id of that node.
- *
- * @throws Exception nothing should go wrong here.
- */
- public void testSingleDataNodeRemoveById() throws Exception {
- ConfigNodeInfo configNodeInfo = Mockito.mock(ConfigNodeInfo.class);
- IClientManager<ConfigRegionId, ConfigNodeClient> configNodeClientManager =
- Mockito.mock(IClientManager.class);
- ConfigNodeClient client = Mockito.mock(ConfigNodeClient.class);
-
Mockito.when(configNodeClientManager.borrowClient(Mockito.any(ConfigRegionId.class)))
- .thenReturn(client);
- // This is the result of the getDataNodeConfiguration, which contains the
list of known data
- // nodes.
- TDataNodeConfigurationResp tDataNodeConfigurationResp = new
TDataNodeConfigurationResp();
- tDataNodeConfigurationResp.putToDataNodeConfigurationMap(
- 1, new TDataNodeConfiguration(LOCATION_1, new TNodeResource()));
- tDataNodeConfigurationResp.putToDataNodeConfigurationMap(
- 2, new TDataNodeConfiguration(LOCATION_2, new TNodeResource()));
- tDataNodeConfigurationResp.putToDataNodeConfigurationMap(
- 3, new TDataNodeConfiguration(LOCATION_3, new TNodeResource()));
- Mockito.when(client.getDataNodeConfiguration(Mockito.anyInt()))
- .thenReturn(tDataNodeConfigurationResp);
- // Only return something sensible, if exactly this location is asked to be
deleted.
- Mockito.when(
- client.removeDataNode(new
TDataNodeRemoveReq(Collections.singletonList(LOCATION_2))))
- .thenReturn(
- new TDataNodeRemoveResp(new
TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode())));
- DataNode dataNode = Mockito.mock(DataNode.class);
- DataNodeServerCommandLine sut =
- new DataNodeServerCommandLine(configNodeInfo, configNodeClientManager,
dataNode);
-
- int returnCode = sut.run(new String[] {"-r", "2"});
-
- // Check the overall return code was ok.
- Assert.assertEquals(0, returnCode);
- // Check that the config node client was actually called with a request to
remove the
- // node we want it to remove
- Mockito.verify(client, Mockito.times(1))
- .removeDataNode(new
TDataNodeRemoveReq(Collections.singletonList(LOCATION_2)));
- }
-
- /**
- * In this test case we provide the coordinates for the data-node that we
want to delete by
- * providing the node-id of that node. However, the coordinates are invalid
and therefore the
- * deletion fails with an error.
- *
- * @throws Exception nothing should go wrong here.
- */
- public void testSingleDataNodeRemoveByIdWithInvalidCoordinates() throws
Exception {
- ConfigNodeInfo configNodeInfo = Mockito.mock(ConfigNodeInfo.class);
- IClientManager<ConfigRegionId, ConfigNodeClient> configNodeClientManager =
- Mockito.mock(IClientManager.class);
- ConfigNodeClient client = Mockito.mock(ConfigNodeClient.class);
-
Mockito.when(configNodeClientManager.borrowClient(Mockito.any(ConfigRegionId.class)))
- .thenReturn(client);
- // This is the result of the getDataNodeConfiguration, which contains the
list of known data
- // nodes.
- TDataNodeConfigurationResp tDataNodeConfigurationResp = new
TDataNodeConfigurationResp();
- tDataNodeConfigurationResp.putToDataNodeConfigurationMap(
- 1, new TDataNodeConfiguration(LOCATION_1, new TNodeResource()));
- tDataNodeConfigurationResp.putToDataNodeConfigurationMap(
- 2, new TDataNodeConfiguration(LOCATION_2, new TNodeResource()));
- tDataNodeConfigurationResp.putToDataNodeConfigurationMap(
- 3, new TDataNodeConfiguration(LOCATION_3, new TNodeResource()));
- Mockito.when(client.getDataNodeConfiguration(Mockito.anyInt()))
- .thenReturn(tDataNodeConfigurationResp);
- DataNode dataNode = Mockito.mock(DataNode.class);
- DataNodeServerCommandLine sut =
- new DataNodeServerCommandLine(configNodeInfo, configNodeClientManager,
dataNode);
-
- try {
- sut.run(new String[] {"-r", "23"});
- Assert.fail("This call should have failed");
- } catch (Exception e) {
- // This is actually what we expected
- Assert.assertTrue(e instanceof BadNodeUrlException);
- }
- }
-
- /**
- * In this test case we provide the coordinates for the data-node that we
want to delete by
- * providing the node-id of that node. NOTE: The test was prepared to test
deletion of multiple
- * nodes, however currently we don't support this.
- *
- * @throws Exception nothing should go wrong here.
- */
- public void testMultipleDataNodeRemoveById() throws Exception {
- ConfigNodeInfo configNodeInfo = Mockito.mock(ConfigNodeInfo.class);
- IClientManager<ConfigRegionId, ConfigNodeClient> configNodeClientManager =
- Mockito.mock(IClientManager.class);
- ConfigNodeClient client = Mockito.mock(ConfigNodeClient.class);
-
Mockito.when(configNodeClientManager.borrowClient(Mockito.any(ConfigRegionId.class)))
- .thenReturn(client);
- // This is the result of the getDataNodeConfiguration, which contains the
list of known data
- // nodes.
- TDataNodeConfigurationResp tDataNodeConfigurationResp = new
TDataNodeConfigurationResp();
- tDataNodeConfigurationResp.putToDataNodeConfigurationMap(
- 1, new TDataNodeConfiguration(LOCATION_1, new TNodeResource()));
- tDataNodeConfigurationResp.putToDataNodeConfigurationMap(
- 2, new TDataNodeConfiguration(LOCATION_2, new TNodeResource()));
- tDataNodeConfigurationResp.putToDataNodeConfigurationMap(
- 3, new TDataNodeConfiguration(LOCATION_3, new TNodeResource()));
- Mockito.when(client.getDataNodeConfiguration(Mockito.anyInt()))
- .thenReturn(tDataNodeConfigurationResp);
- // Only return something sensible, if exactly the locations we want are
asked to be deleted.
- Mockito.when(
- client.removeDataNode(new
TDataNodeRemoveReq(Arrays.asList(LOCATION_1, LOCATION_2))))
- .thenReturn(
- new TDataNodeRemoveResp(new
TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode())));
- DataNode dataNode = Mockito.mock(DataNode.class);
- DataNodeServerCommandLine sut =
- new DataNodeServerCommandLine(configNodeInfo, configNodeClientManager,
dataNode);
-
- try {
- sut.run(new String[] {"-r", "1,2"});
- Assert.fail("This call should have failed");
- } catch (Exception e) {
- // This is actually what we expected
- Assert.assertTrue(e instanceof IllegalArgumentException);
- }
- }
-}
diff --git a/iotdb-core/node-commons/pom.xml b/iotdb-core/node-commons/pom.xml
index d34af0347f7..fb513e0e794 100644
--- a/iotdb-core/node-commons/pom.xml
+++ b/iotdb-core/node-commons/pom.xml
@@ -119,6 +119,10 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
+ <dependency>
+ <groupId>commons-cli</groupId>
+ <artifactId>commons-cli</artifactId>
+ </dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/ServerCommandLine.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/ServerCommandLine.java
index 9bd4a4d3097..066e1893865 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/ServerCommandLine.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/ServerCommandLine.java
@@ -18,50 +18,97 @@
*/
package org.apache.iotdb.commons;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.iotdb.commons.exception.IoTDBException;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+import java.io.PrintWriter;
public abstract class ServerCommandLine {
- private static final Logger LOG =
LoggerFactory.getLogger(ServerCommandLine.class);
- /**
- * Implementing subclasses should return a usage string to print out
- *
- * @return usage
- */
- protected abstract String getUsage();
+ private static final Option OPTION_START =
+ Option.builder("s").longOpt("start").desc("start a new node").build();
+ private static final Option OPTION_REMOVE =
+ Option.builder("r")
+ .longOpt("remove")
+ .desc(
+ "remove a node (with the given nodeId or the node started on the
current machine, if omitted)")
+ .hasArg()
+ .type(Number.class)
+ .argName("nodeId")
+ .optionalArg(true)
+ .build();
- /**
- * run command
- *
- * @param args system args
- * @return return 0 if exec success
- */
- protected abstract int run(String[] args) throws Exception;
+ private final String cliName;
+ private final PrintWriter output;
+ private final Options options;
- protected void usage(String message) {
- if (message != null) {
- System.err.println(message);
- System.err.println();
- }
+ public ServerCommandLine(String cliName) {
+ this(cliName, new PrintWriter(System.out));
+ }
- System.err.println(getUsage());
+ public ServerCommandLine(String cliName, PrintWriter output) {
+ this.cliName = cliName;
+ this.output = output;
+ OptionGroup commands = new OptionGroup();
+ commands.addOption(OPTION_START);
+ commands.addOption(OPTION_REMOVE);
+ // Require one option of the group.
+ commands.setRequired(true);
+ options = new Options();
+ options.addOptionGroup(commands);
}
- /**
- * Parse and run the given command line.
- *
- * @param args system args
- */
- public void doMain(String[] args) {
+ public int run(String[] args) {
+ CommandLineParser parser = new DefaultParser();
try {
- int result = run(args);
- if (result != 0) {
- System.exit(result);
+ CommandLine cmd = parser.parse(options, args);
+ // When starting there is no additional argument.
+ if (cmd.hasOption(OPTION_START)) {
+ start();
}
- } catch (Exception e) {
- LOG.error("Failed to execute system command", e);
- System.exit(-1);
+ // As we only support start and remove and one has to be selected,
+ // no need to check if OPTION_REMOVE is set.
+ else {
+ Number nodeId = (Number) cmd.getParsedOptionValue(OPTION_REMOVE);
+ if (nodeId != null) {
+ remove(nodeId.longValue());
+ } else {
+ remove(null);
+ }
+ }
+ // Make sure we exit with the 0 error code
+ return 0;
+ } catch (ParseException e) {
+ output.println(e.getMessage());
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp(
+ output,
+ formatter.getWidth(),
+ cliName,
+ null,
+ options,
+ formatter.getLeftPadding(),
+ formatter.getDescPadding(),
+ null,
+ false);
+ // Forward a generic error code to the calling process
+ return 1;
+ } catch (IoTDBException e) {
+ output.println("An error occurred while running the command: " +
e.getMessage());
+ // Forward the exit code from the exception to the calling process
+ return e.getErrorCode();
}
}
+
+ protected abstract void start() throws IoTDBException;
+
+ protected abstract void remove(Long nodeId) throws IoTDBException;
}
diff --git
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/ServerCommandLineTest.java
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/ServerCommandLineTest.java
new file mode 100644
index 00000000000..a4a42da26e4
--- /dev/null
+++
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/ServerCommandLineTest.java
@@ -0,0 +1,337 @@
+/*
+ * 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.commons;
+
+import org.apache.iotdb.commons.exception.IoTDBException;
+
+import junit.framework.TestCase;
+import org.junit.Assert;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class ServerCommandLineTest extends TestCase {
+
+ /**
+ * In this test, the commandline is called without any args. In this case
the usage should be
+ * output and nothing should be done.
+ */
+ public void testNoArgs() {
+ AtomicBoolean startCalled = new AtomicBoolean(false);
+ AtomicBoolean stopCalled = new AtomicBoolean(false);
+ StringWriter out = new StringWriter();
+ PrintWriter writer = new PrintWriter(out);
+ ServerCommandLine commandLine =
+ new ServerCommandLine("test-cli", writer) {
+ @Override
+ protected void start() {
+ startCalled.set(true);
+ }
+
+ @Override
+ protected void remove(Long nodeId) {
+ stopCalled.set(true);
+ }
+ };
+ int returnCode = commandLine.run(new String[0]);
+
+ Assert.assertEquals(1, returnCode);
+ String consoleOutput = out.toString();
+ Assert.assertTrue(consoleOutput.contains("Missing required option"));
+ // No callbacks should have been called.
+ Assert.assertFalse(startCalled.get());
+ Assert.assertFalse(stopCalled.get());
+ }
+
+ /**
+ * In this test, the commandline is called with an invalid arg. In this case
the usage should be
+ * output and nothing should be done.
+ */
+ public void testInvalidArgs() {
+ AtomicBoolean startCalled = new AtomicBoolean(false);
+ AtomicBoolean stopCalled = new AtomicBoolean(false);
+ StringWriter out = new StringWriter();
+ PrintWriter writer = new PrintWriter(out);
+ ServerCommandLine commandLine =
+ new ServerCommandLine("test-cli", writer) {
+ @Override
+ protected void start() {
+ startCalled.set(true);
+ }
+
+ @Override
+ protected void remove(Long nodeId) {
+ stopCalled.set(true);
+ }
+ };
+ int returnCode = commandLine.run(new String[] {"-z"});
+
+ Assert.assertEquals(1, returnCode);
+ String consoleOutput = out.toString();
+ Assert.assertTrue(consoleOutput.contains("Unrecognized option"));
+ // No callbacks should have been called.
+ Assert.assertFalse(startCalled.get());
+ Assert.assertFalse(stopCalled.get());
+ }
+
+ /**
+ * In this test, the commandline is called with the start option. The start
method should be
+ * called.
+ */
+ public void testStartArg() {
+ AtomicBoolean startCalled = new AtomicBoolean(false);
+ AtomicBoolean stopCalled = new AtomicBoolean(false);
+ StringWriter out = new StringWriter();
+ PrintWriter writer = new PrintWriter(out);
+ ServerCommandLine commandLine =
+ new ServerCommandLine("test-cli", writer) {
+ @Override
+ protected void start() {
+ startCalled.set(true);
+ }
+
+ @Override
+ protected void remove(Long nodeId) {
+ stopCalled.set(true);
+ }
+ };
+ int returnCode = commandLine.run(new String[] {"-s"});
+
+ Assert.assertEquals(0, returnCode);
+ // Nothing should have been output on the console.
+ String consoleOutput = out.toString();
+ Assert.assertTrue(consoleOutput.isEmpty());
+ // Only the start method should have been called.
+ Assert.assertTrue(startCalled.get());
+ Assert.assertFalse(stopCalled.get());
+ }
+
+ /**
+ * In this test, the commandline is called with the remove option, but
without an additional
+ * attribute for providing the node id. The stop method should be called and
"null" should be
+ * provided as node id.
+ */
+ public void testRemoveArg() {
+ AtomicBoolean startCalled = new AtomicBoolean(false);
+ AtomicBoolean stopCalled = new AtomicBoolean(false);
+ AtomicLong stopNodeId = new AtomicLong(-1);
+ StringWriter out = new StringWriter();
+ PrintWriter writer = new PrintWriter(out);
+ ServerCommandLine commandLine =
+ new ServerCommandLine("test-cli", writer) {
+ @Override
+ protected void start() {
+ startCalled.set(true);
+ }
+
+ @Override
+ protected void remove(Long nodeId) {
+ stopCalled.set(true);
+ if (nodeId != null) {
+ stopNodeId.set(nodeId);
+ }
+ }
+ };
+ int returnCode = commandLine.run(new String[] {"-r"});
+
+ Assert.assertEquals(0, returnCode);
+ // Nothing should have been output on the console.
+ String consoleOutput = out.toString();
+ Assert.assertTrue(consoleOutput.isEmpty());
+ // Only the start method should have been called.
+ Assert.assertFalse(startCalled.get());
+ Assert.assertTrue(stopCalled.get());
+ Assert.assertEquals(-1, stopNodeId.get());
+ }
+
+ /**
+ * In this test, the commandline is called with the remove option, with an
additional attribute
+ * for providing the node id. The stop method should be called and the id
should be passed to the
+ * remove callback.
+ */
+ public void testRemoveWithNodeIdArg() {
+ AtomicBoolean startCalled = new AtomicBoolean(false);
+ AtomicBoolean stopCalled = new AtomicBoolean(false);
+ AtomicLong stopNodeId = new AtomicLong(-1);
+ StringWriter out = new StringWriter();
+ PrintWriter writer = new PrintWriter(out);
+ ServerCommandLine commandLine =
+ new ServerCommandLine("test-cli", writer) {
+ @Override
+ protected void start() {
+ startCalled.set(true);
+ }
+
+ @Override
+ protected void remove(Long nodeId) {
+ stopCalled.set(true);
+ if (nodeId != null) {
+ stopNodeId.set(nodeId);
+ }
+ }
+ };
+ int returnCode = commandLine.run(new String[] {"-r", "42"});
+
+ Assert.assertEquals(0, returnCode);
+ // Nothing should have been output on the console.
+ String consoleOutput = out.toString();
+ Assert.assertTrue(consoleOutput.isEmpty());
+ // Only the start method should have been called.
+ Assert.assertFalse(startCalled.get());
+ Assert.assertTrue(stopCalled.get());
+ Assert.assertEquals(42L, stopNodeId.get());
+ }
+
+ /**
+ * In this test, the commandline is called with the remove option, with an
additional attribute
+ * for providing the node id. However, the attribute is not an integer
value, therefore an error
+ * should be thrown.
+ */
+ public void testRemoveWithInvalidNodeIdArg() {
+ AtomicBoolean startCalled = new AtomicBoolean(false);
+ AtomicBoolean stopCalled = new AtomicBoolean(false);
+ AtomicLong stopNodeId = new AtomicLong(-1);
+ StringWriter out = new StringWriter();
+ PrintWriter writer = new PrintWriter(out);
+ ServerCommandLine commandLine =
+ new ServerCommandLine("test-cli", writer) {
+ @Override
+ protected void start() {
+ startCalled.set(true);
+ }
+
+ @Override
+ protected void remove(Long nodeId) {
+ stopCalled.set(true);
+ if (nodeId != null) {
+ stopNodeId.set(nodeId);
+ }
+ }
+ };
+ int returnCode = commandLine.run(new String[] {"-r", "text"});
+
+ Assert.assertEquals(1, returnCode);
+ // Nothing should have been output on the console.
+ String consoleOutput = out.toString();
+ Assert.assertTrue(consoleOutput.contains("For input string"));
+ // No callbacks should have been called.
+ Assert.assertFalse(startCalled.get());
+ Assert.assertFalse(stopCalled.get());
+ }
+
+ /**
+ * In this test, the commandline is called with the both the start and stop
option. This should
+ * result in an error report and no callback should be called.
+ */
+ public void testCallWithMultipleActions() {
+ AtomicBoolean startCalled = new AtomicBoolean(false);
+ AtomicBoolean stopCalled = new AtomicBoolean(false);
+ AtomicLong stopNodeId = new AtomicLong(-1);
+ StringWriter out = new StringWriter();
+ PrintWriter writer = new PrintWriter(out);
+ ServerCommandLine commandLine =
+ new ServerCommandLine("test-cli", writer) {
+ @Override
+ protected void start() {
+ startCalled.set(true);
+ }
+
+ @Override
+ protected void remove(Long nodeId) {
+ stopCalled.set(true);
+ if (nodeId != null) {
+ stopNodeId.set(nodeId);
+ }
+ }
+ };
+ int returnCode = commandLine.run(new String[] {"-r", "42", "-s"});
+
+ Assert.assertEquals(1, returnCode);
+ // Nothing should have been output on the console.
+ String consoleOutput = out.toString();
+ Assert.assertTrue(
+ consoleOutput.contains("but an option from this group has already been
selected"));
+ // No callbacks should have been called.
+ Assert.assertFalse(startCalled.get());
+ Assert.assertFalse(stopCalled.get());
+ }
+
+ /**
+ * In this test, the commandline is called with the start option. The start
method should be
+ * called, however there an exception is thrown, which should be caught.
+ */
+ public void testStartWithErrorArg() {
+ AtomicBoolean stopCalled = new AtomicBoolean(false);
+ StringWriter out = new StringWriter();
+ PrintWriter writer = new PrintWriter(out);
+ ServerCommandLine commandLine =
+ new ServerCommandLine("test-cli", writer) {
+ @Override
+ protected void start() throws IoTDBException {
+ throw new IoTDBException("Error", 23);
+ }
+
+ @Override
+ protected void remove(Long nodeId) {
+ stopCalled.set(true);
+ }
+ };
+ int returnCode = commandLine.run(new String[] {"-s"});
+
+ Assert.assertEquals(23, returnCode);
+ // Nothing should have been output on the console.
+ String consoleOutput = out.toString();
+ Assert.assertTrue(consoleOutput.contains("An error occurred while running
the command"));
+ // Only the start method should have been called.
+ Assert.assertFalse(stopCalled.get());
+ }
+
+ /**
+ * In this test, the commandline is called with the remove option, with an
additional attribute
+ * for providing the node id. The stop method should be called and the id
should be passed to the
+ * remove callback, however there an exception is thrown, which should be
caught.
+ */
+ public void testRemoveWithNodeIdWithErrorArg() {
+ AtomicBoolean startCalled = new AtomicBoolean(false);
+ StringWriter out = new StringWriter();
+ PrintWriter writer = new PrintWriter(out);
+ ServerCommandLine commandLine =
+ new ServerCommandLine("test-cli", writer) {
+ @Override
+ protected void start() {
+ startCalled.set(true);
+ }
+
+ @Override
+ protected void remove(Long nodeId) throws IoTDBException {
+ throw new IoTDBException("Error", 23);
+ }
+ };
+ int returnCode = commandLine.run(new String[] {"-r", "42"});
+
+ Assert.assertEquals(23, returnCode);
+ // Nothing should have been output on the console.
+ String consoleOutput = out.toString();
+ Assert.assertTrue(consoleOutput.contains("An error occurred while running
the command"));
+ // Only the start method should have been called.
+ Assert.assertFalse(startCalled.get());
+ }
+}