| /* |
| * 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.logging.internal.executors.LoggingExecutors; |
| import org.apache.geode.logging.internal.log4j.api.LogService; |
| 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); |
| } |
| } |
| } |
| } |