blob: f574371242b8b74a696531bf8d1a80bcdfe28f69 [file] [log] [blame]
/*
* 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.geode.management.internal.cli.commands;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.logging.log4j.Logger;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.apache.geode.cache.execute.Execution;
import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.FunctionException;
import org.apache.geode.cache.execute.FunctionService;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.internal.logging.LoggingExecutors;
import org.apache.geode.management.cli.CliMetaData;
import org.apache.geode.management.cli.GfshCommand;
import org.apache.geode.management.internal.cli.AbstractCliAroundInterceptor;
import org.apache.geode.management.internal.cli.GfshParseResult;
import org.apache.geode.management.internal.cli.functions.ShutDownFunction;
import org.apache.geode.management.internal.cli.i18n.CliStrings;
import org.apache.geode.management.internal.cli.result.model.ResultModel;
import org.apache.geode.management.internal.security.ResourceOperation;
import org.apache.geode.security.ResourcePermission;
public class ShutdownCommand extends GfshCommand {
private static final String DEFAULT_TIME_OUT = "10";
private static final Logger logger = LogService.getLogger();
@CliCommand(value = CliStrings.SHUTDOWN, help = CliStrings.SHUTDOWN__HELP)
@CliMetaData(relatedTopic = {CliStrings.TOPIC_GEODE_LIFECYCLE},
interceptor = "org.apache.geode.management.internal.cli.commands.ShutdownCommand$ShutdownCommandInterceptor")
@ResourceOperation(resource = ResourcePermission.Resource.CLUSTER,
operation = ResourcePermission.Operation.MANAGE)
public ResultModel shutdown(
@CliOption(key = CliStrings.SHUTDOWN__TIMEOUT, unspecifiedDefaultValue = DEFAULT_TIME_OUT,
help = CliStrings.SHUTDOWN__TIMEOUT__HELP) int userSpecifiedTimeout,
@CliOption(key = CliStrings.INCLUDE_LOCATORS, unspecifiedDefaultValue = "false",
specifiedDefaultValue = "true",
help = CliStrings.INCLUDE_LOCATORS_HELP) boolean shutdownLocators)
throws InterruptedException, ExecutionException, TimeoutException {
if (userSpecifiedTimeout < Integer.parseInt(DEFAULT_TIME_OUT)) {
return ResultModel.createInfo(CliStrings.SHUTDOWN__MSG__IMPROPER_TIMEOUT);
}
// convert to milliseconds
long timeout = userSpecifiedTimeout * 1000L;
InternalCache cache = (InternalCache) getCache();
int numDataNodes = getAllNormalMembers().size();
Set<DistributedMember> locators = getAllMembers();
Set<DistributedMember> dataNodes = getAllNormalMembers();
locators.removeAll(dataNodes);
if (!shutdownLocators && numDataNodes == 0) {
return ResultModel.createInfo(CliStrings.SHUTDOWN__MSG__NO_DATA_NODE_FOUND);
}
String managerName = cache.getJmxManagerAdvisor().getDistributionManager().getId().getId();
final DistributedMember manager = getMember(managerName);
dataNodes.remove(manager);
// shut down all data members excluding this manager if manager is a data node
long timeElapsed = shutDownNodeWithTimeOut(timeout, dataNodes);
timeout = timeout - timeElapsed;
// shut down locators one by one
if (shutdownLocators) {
if (manager == null) {
return ResultModel.createError(CliStrings.SHUTDOWN__MSG__MANAGER_NOT_FOUND);
}
// remove current locator as that would get shutdown last
if (locators.contains(manager)) {
locators.remove(manager);
}
for (DistributedMember locator : locators) {
Set<DistributedMember> lsSet = new HashSet<>();
lsSet.add(locator);
long elapsedTime = shutDownNodeWithTimeOut(timeout, lsSet);
timeout = timeout - elapsedTime;
}
}
if (locators.contains(manager) && !shutdownLocators) { // This means manager is a locator and
// shutdownLocators is false. Hence we
// should not stop the manager
return ResultModel.createInfo("Shutdown is triggered");
}
// now shut down this manager
Set<DistributedMember> mgrSet = new HashSet<>();
mgrSet.add(manager);
// No need to check further timeout as this is the last node we will be
// shutting down
shutDownNodeWithTimeOut(timeout, mgrSet);
return ResultModel.createInfo("Shutdown is triggered");
}
/**
* @param timeout user specified timeout
* @param nodesToBeStopped list of nodes to be stopped
* @return Elapsed time to shutdown the given nodes;
*/
private long shutDownNodeWithTimeOut(long timeout, Set<DistributedMember> nodesToBeStopped)
throws TimeoutException, InterruptedException, ExecutionException {
long shutDownTimeStart = System.currentTimeMillis();
shutdownNode(timeout, nodesToBeStopped);
long shutDownTimeEnd = System.currentTimeMillis();
long timeElapsed = shutDownTimeEnd - shutDownTimeStart;
if (timeElapsed > timeout || Boolean.getBoolean("ThrowTimeoutException")) {
// The second check for ThrowTimeoutException is a test hook
throw new TimeoutException();
}
return timeElapsed;
}
private void shutdownNode(final long timeout, final Set<DistributedMember> includeMembers)
throws TimeoutException, InterruptedException, ExecutionException {
ExecutorService exec = LoggingExecutors.newSingleThreadExecutor("ShutdownCommand", false);
try {
final Function shutDownFunction = new ShutDownFunction();
logger.info("Gfsh executing shutdown on members " + includeMembers);
Callable<String> shutdownNodes = () -> {
try {
Execution execution = FunctionService.onMembers(includeMembers);
execution.execute(shutDownFunction);
} catch (FunctionException functionEx) {
// Expected Exception as the function is shutting down the target members and the result
// collector will get member departed exception
}
return "SUCCESS";
};
Future<String> result = exec.submit(shutdownNodes);
result.get(timeout, TimeUnit.MILLISECONDS);
} catch (TimeoutException te) {
logger.error("TimeoutException in shutting down members." + includeMembers);
throw te;
} catch (InterruptedException e) {
logger.error("InterruptedException in shutting down members." + includeMembers);
throw e;
} catch (ExecutionException e) {
logger.error("ExecutionException in shutting down members." + includeMembers);
throw e;
} finally {
exec.shutdownNow();
}
}
public static class ShutdownCommandInterceptor extends AbstractCliAroundInterceptor {
@Override
public ResultModel preExecution(GfshParseResult parseResult) {
// This hook is for testing purpose only.
if (Boolean.getBoolean(CliStrings.IGNORE_INTERCEPTORS)) {
return ResultModel.createInfo(CliStrings.SHUTDOWN__MSG__SHUTDOWN_ENTIRE_DS);
}
Response response = readYesNo(CliStrings.SHUTDOWN__MSG__WARN_USER, Response.YES);
if (response == Response.NO) {
return ResultModel.createInfo(CliStrings.SHUTDOWN__MSG__ABORTING_SHUTDOWN);
} else {
return ResultModel.createInfo(CliStrings.SHUTDOWN__MSG__SHUTDOWN_ENTIRE_DS);
}
}
}
}