| /* |
| * 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.remote; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.stream.Collectors; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.logging.log4j.Logger; |
| import org.springframework.util.ReflectionUtils; |
| |
| import org.apache.geode.SystemFailure; |
| import org.apache.geode.annotations.VisibleForTesting; |
| import org.apache.geode.distributed.internal.InternalConfigurationPersistenceService; |
| import org.apache.geode.internal.util.ArgumentRedactor; |
| import org.apache.geode.logging.internal.log4j.api.LogService; |
| import org.apache.geode.management.cli.Result; |
| import org.apache.geode.management.cli.SingleGfshCommand; |
| import org.apache.geode.management.cli.UpdateAllConfigurationGroupsMarker; |
| import org.apache.geode.management.internal.cli.GfshParseResult; |
| import org.apache.geode.management.internal.cli.exceptions.UserErrorException; |
| import org.apache.geode.management.internal.cli.result.CommandResult; |
| import org.apache.geode.management.internal.cli.result.model.InfoResultModel; |
| import org.apache.geode.management.internal.cli.result.model.ResultModel; |
| import org.apache.geode.management.internal.exceptions.EntityNotFoundException; |
| import org.apache.geode.security.NotAuthorizedException; |
| |
| /** |
| * this executes the command using method reflection. It logs all possible exceptions, and generates |
| * GemfireErrorResult based on the exceptions. |
| * |
| * For AuthorizationExceptions, it logs it and then rethrow it. |
| */ |
| public class CommandExecutor { |
| public static final String RUN_ON_MEMBER_CHANGE_NOT_PERSISTED = |
| "Configuration change is not persisted because the command is executed on specific member."; |
| public static final String SERVICE_NOT_RUNNING_CHANGE_NOT_PERSISTED = |
| "Cluster configuration service is not running. Configuration change is not persisted."; |
| |
| private Logger logger = LogService.getLogger(); |
| |
| /** |
| * |
| * @return always return ResultModel for online command, for offline command, return either |
| * ResultModel or ExitShellRequest |
| */ |
| public Object execute(GfshParseResult parseResult) { |
| return execute(null, parseResult); |
| } |
| |
| /** |
| * @return always return ResultModel for online command, for offline command, return either |
| * ResultModel or ExitShellRequest |
| */ |
| @VisibleForTesting |
| public Object execute(Object command, GfshParseResult parseResult) { |
| String userInput = parseResult.getUserInput(); |
| if (userInput != null) { |
| logger.info("Executing command: " + ArgumentRedactor.redact(userInput)); |
| } |
| |
| try { |
| Object result = invokeCommand(command, parseResult); |
| |
| // if some custom command returns Result instead of ResultModel, we need to turn that |
| // into ResultModel in order to be processed later on. |
| if (result instanceof CommandResult) { |
| result = ((CommandResult) result).getResultData(); |
| } else if (result instanceof Result) { |
| Result customResult = (Result) result; |
| result = new ResultModel(); |
| InfoResultModel info = ((ResultModel) result).addInfo(); |
| while (customResult.hasNextLine()) { |
| info.addLine(customResult.nextLine()); |
| } |
| customResult.resetToFirstLine(); |
| } |
| |
| if (result == null) { |
| return ResultModel.createError("Command returned null: " + parseResult); |
| } |
| return result; |
| } |
| |
| // for Authorization Exception, we need to throw them for higher level code to catch |
| catch (NotAuthorizedException e) { |
| logger.error("Not authorized to execute \"" + parseResult + "\".", e); |
| throw e; |
| } |
| |
| // for these exceptions, needs to create a UserErrorResult (still reported as error by gfsh) |
| // no need to log since this is a user error |
| catch (UserErrorException | IllegalStateException | IllegalArgumentException e) { |
| return ResultModel.createError(e.getMessage()); |
| } |
| |
| // if entity not found, depending on the thrower's intention, report either as success or error |
| // no need to log since this is a user error |
| catch (EntityNotFoundException e) { |
| if (e.isStatusOK()) { |
| return ResultModel.createInfo("Skipping: " + e.getMessage()); |
| } else { |
| return ResultModel.createError(e.getMessage()); |
| } |
| } |
| |
| // all other exceptions, log it and build an error result. |
| catch (Exception e) { |
| logger.error("Could not execute \"" + parseResult + "\".", e); |
| return ResultModel.createError( |
| "Error while processing command <" + parseResult + "> Reason : " + e.getMessage()); |
| } |
| |
| // for errors more lower-level than Exception, just throw them. |
| catch (VirtualMachineError e) { |
| SystemFailure.initiateFailure(e); |
| throw e; |
| } catch (Throwable t) { |
| SystemFailure.checkFailure(); |
| throw t; |
| } |
| } |
| |
| protected Object callInvokeMethod(Object command, GfshParseResult parseResult) { |
| return ReflectionUtils.invokeMethod(parseResult.getMethod(), command, |
| parseResult.getArguments()); |
| } |
| |
| protected Object invokeCommand(Object command, GfshParseResult parseResult) { |
| // if no command instance is passed in, use the one in the parseResult |
| if (command == null) { |
| command = parseResult.getInstance(); |
| } |
| |
| Object result = callInvokeMethod(command, parseResult); |
| |
| if (!(command instanceof SingleGfshCommand)) { |
| return result; |
| } |
| |
| SingleGfshCommand gfshCommand = (SingleGfshCommand) command; |
| ResultModel resultModel = (ResultModel) result; |
| if (resultModel.getStatus() == Result.Status.ERROR) { |
| return result; |
| } |
| |
| // if command result is ok, we will need to see if we need to update cluster configuration |
| InfoResultModel infoResultModel = resultModel.addInfo(ResultModel.INFO_SECTION); |
| InternalConfigurationPersistenceService ccService = |
| gfshCommand.getConfigurationPersistenceService(); |
| if (ccService == null) { |
| infoResultModel.addLine(SERVICE_NOT_RUNNING_CHANGE_NOT_PERSISTED); |
| return resultModel; |
| } |
| |
| if (parseResult.getParamValue("member") != null) { |
| infoResultModel.addLine(RUN_ON_MEMBER_CHANGE_NOT_PERSISTED); |
| return resultModel; |
| } |
| |
| List<String> groupsToUpdate; |
| String groupInput = parseResult.getParamValueAsString("group"); |
| |
| if (!StringUtils.isBlank(groupInput)) { |
| groupsToUpdate = Arrays.asList(groupInput.split(",")); |
| } else if (gfshCommand instanceof UpdateAllConfigurationGroupsMarker) { |
| groupsToUpdate = ccService.getGroups().stream().collect(Collectors.toList()); |
| } else { |
| groupsToUpdate = Arrays.asList("cluster"); |
| } |
| |
| for (String group : groupsToUpdate) { |
| ccService.updateCacheConfig(group, cacheConfig -> { |
| try { |
| if (gfshCommand.updateConfigForGroup(group, cacheConfig, resultModel.getConfigObject())) { |
| infoResultModel |
| .addLine("Cluster configuration for group '" + group + "' is updated."); |
| } else { |
| infoResultModel |
| .addLine("Cluster configuration for group '" + group + "' is not updated."); |
| } |
| } catch (Exception e) { |
| String message = "Failed to update cluster configuration for " + group + "."; |
| logger.error(message, e); |
| // for now, if one cc update failed, we will set this flag. Will change this when we can |
| // add lines to the result returned by the command |
| infoResultModel.addLine(message + ". Reason: " + e.getMessage()); |
| return null; |
| } |
| return cacheConfig; |
| }); |
| } |
| |
| return resultModel; |
| } |
| } |