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{} ] \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 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 configNodeClientManager; - private final DataNode dataNode; - - private static final String USAGE = - "Usage: <-s|-r> " - + "[-D{} ] \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 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: /:"); - 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 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 buildDataNodeLocations(String args) { - List 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 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 parseCoordinates(String coordinatesString) { - // Multiple nodeIds are separated by "," - String[] nodeIdStrings = coordinatesString.split(","); - List 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 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 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 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 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 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 @@ org.apache.commons commons-lang3 + + commons-cli + commons-cli + com.google.guava guava 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(); - } - - System.err.println(getUsage()); + public ServerCommandLine(String cliName) { + this(cliName, new PrintWriter(System.out)); } - /** - * Parse and run the given command line. - * - * @param args system args - */ - public void doMain(String[] args) { + 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); + } + + 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()); + } +}