Feat/updated cli (#13194)

This commit is contained in:
Christofer Dutz 2024-08-27 05:11:31 +02:00 committed by GitHub
parent 8f54b80812
commit 38c1d591ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 569 additions and 628 deletions

BIN
.idea/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -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

View File

@ -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]);
}
}

View File

@ -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 {

View File

@ -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
)

View File

@ -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

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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.

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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;
}

View File

@ -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());
}
}