/* * 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 org.apache.geode.SystemFailure; import org.apache.geode.cache.execute.ResultCollector; import org.apache.geode.distributed.DistributedMember; import org.apache.geode.management.cli.CliMetaData; import org.apache.geode.management.cli.ConverterHint; import org.apache.geode.management.cli.Result; import org.apache.geode.management.cli.Result.Status; import org.apache.geode.management.internal.cli.AbstractCliAroundInterceptor; import org.apache.geode.management.internal.cli.CliUtil; import org.apache.geode.management.internal.cli.GfshParseResult; import org.apache.geode.management.internal.cli.functions.CliFunctionResult; import org.apache.geode.management.internal.cli.functions.DeployFunction; import org.apache.geode.management.internal.cli.functions.ListDeployedFunction; import org.apache.geode.management.internal.cli.functions.UndeployFunction; import org.apache.geode.management.internal.cli.i18n.CliStrings; import org.apache.geode.management.internal.cli.remote.CommandExecutionContext; import org.apache.geode.management.internal.cli.result.CommandResultException; import org.apache.geode.management.internal.cli.result.FileResult; import org.apache.geode.management.internal.cli.result.ResultBuilder; import org.apache.geode.management.internal.cli.result.TabularResultData; import org.apache.geode.management.internal.security.ResourceOperation; import org.apache.geode.security.NotAuthorizedException; import org.apache.geode.security.ResourcePermission.Operation; import org.apache.geode.security.ResourcePermission.Resource; import org.springframework.shell.core.annotation.CliAvailabilityIndicator; import org.springframework.shell.core.annotation.CliCommand; import org.springframework.shell.core.annotation.CliOption; import java.io.FileNotFoundException; import java.io.IOException; import java.text.DecimalFormat; import java.util.List; import java.util.Map; import java.util.Set; /** * Commands for deploying, un-deploying and listing files deployed using the command line shell. * <p/> * * @see org.apache.geode.management.internal.cli.commands.AbstractCommandsSupport * @since GemFire 7.0 */ public final class DeployCommands extends AbstractCommandsSupport { private final DeployFunction deployFunction = new DeployFunction(); private final UndeployFunction undeployFunction = new UndeployFunction(); private final ListDeployedFunction listDeployedFunction = new ListDeployedFunction(); /** * Deploy one or more JAR files to members of a group or all members. * * @param groups Group(s) to deploy the JAR to or null for all members * @param jar JAR file to deploy * @param dir Directory of JAR files to deploy * @return The result of the attempt to deploy */ @CliCommand(value = {CliStrings.DEPLOY}, help = CliStrings.DEPLOY__HELP) @CliMetaData( interceptor = "org.apache.geode.management.internal.cli.commands.DeployCommands$Interceptor", relatedTopic = {CliStrings.TOPIC_GEODE_CONFIG}) public final Result deploy( @CliOption(key = {CliStrings.DEPLOY__GROUP}, help = CliStrings.DEPLOY__GROUP__HELP, optionContext = ConverterHint.MEMBERGROUP) @CliMetaData( valueSeparator = ",") String[] groups, @CliOption(key = {CliStrings.DEPLOY__JAR}, help = CliStrings.DEPLOY__JAR__HELP) String jar, @CliOption(key = {CliStrings.DEPLOY__DIR}, help = CliStrings.DEPLOY__DIR__HELP) String dir) { try { // since deploy function can potentially do a lot of damage to security, this action should // require these following privileges securityService.authorizeClusterManage(); securityService.authorizeClusterWrite(); securityService.authorizeDataManage(); securityService.authorizeDataWrite(); TabularResultData tabularData = ResultBuilder.createTabularResultData(); byte[][] shellBytesData = CommandExecutionContext.getBytesFromShell(); String[] jarNames = CliUtil.bytesToNames(shellBytesData); byte[][] jarBytes = CliUtil.bytesToData(shellBytesData); Set<DistributedMember> targetMembers; targetMembers = CliUtil.findMembers(groups, null); if (targetMembers.size() > 0) { // this deploys the jars to all the matching servers ResultCollector<?, ?> resultCollector = CliUtil.executeFunction(this.deployFunction, new Object[] {jarNames, jarBytes}, targetMembers); List<CliFunctionResult> results = CliFunctionResult.cleanResults((List<?>) resultCollector.getResult()); for (CliFunctionResult result : results) { if (result.getThrowable() != null) { tabularData.accumulate("Member", result.getMemberIdOrName()); tabularData.accumulate("Deployed JAR", ""); tabularData.accumulate("Deployed JAR Location", "ERROR: " + result.getThrowable().getClass().getName() + ": " + result.getThrowable().getMessage()); tabularData.setStatus(Status.ERROR); } else { String[] strings = (String[]) result.getSerializables(); for (int i = 0; i < strings.length; i += 2) { tabularData.accumulate("Member", result.getMemberIdOrName()); tabularData.accumulate("Deployed JAR", strings[i]); tabularData.accumulate("Deployed JAR Location", strings[i + 1]); } } } } Result result = ResultBuilder.buildResult(tabularData); persistClusterConfiguration(result, () -> getSharedConfiguration().addJarsToThisLocator(jarNames, jarBytes, groups)); return result; } catch (NotAuthorizedException e) { // for NotAuthorizedException, will catch this later in the code throw e; } catch (VirtualMachineError e) { SystemFailure.initiateFailure(e); throw e; } catch (Throwable t) { SystemFailure.checkFailure(); return ResultBuilder.createGemFireErrorResult(String .format("Exception while attempting to deploy: (%1$s)", toString(t, isDebugging()))); } } /** * Undeploy one or more JAR files from members of a group or all members. * * @param groups Group(s) to undeploy the JAR from or null for all members * @param jars JAR(s) to undeploy (separated by comma) * @return The result of the attempt to undeploy */ @CliCommand(value = {CliStrings.UNDEPLOY}, help = CliStrings.UNDEPLOY__HELP) @CliMetaData(relatedTopic = {CliStrings.TOPIC_GEODE_CONFIG}) @ResourceOperation(resource = Resource.DATA, operation = Operation.MANAGE) public final Result undeploy( @CliOption(key = {CliStrings.UNDEPLOY__GROUP}, help = CliStrings.UNDEPLOY__GROUP__HELP, optionContext = ConverterHint.MEMBERGROUP) @CliMetaData( valueSeparator = ",") String[] groups, @CliOption(key = {CliStrings.UNDEPLOY__JAR}, help = CliStrings.UNDEPLOY__JAR__HELP, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE) @CliMetaData( valueSeparator = ",") String jars) { try { TabularResultData tabularData = ResultBuilder.createTabularResultData(); boolean accumulatedData = false; Set<DistributedMember> targetMembers; try { targetMembers = CliUtil.findMembersOrThrow(groups, null); } catch (CommandResultException crex) { return crex.getResult(); } ResultCollector<?, ?> rc = CliUtil.executeFunction(this.undeployFunction, new Object[] {jars}, targetMembers); List<CliFunctionResult> results = CliFunctionResult.cleanResults((List<?>) rc.getResult()); for (CliFunctionResult result : results) { if (result.getThrowable() != null) { tabularData.accumulate("Member", result.getMemberIdOrName()); tabularData.accumulate("Un-Deployed JAR", ""); tabularData.accumulate("Un-Deployed JAR Location", "ERROR: " + result.getThrowable().getClass().getName() + ": " + result.getThrowable().getMessage()); accumulatedData = true; tabularData.setStatus(Status.ERROR); } else { String[] strings = (String[]) result.getSerializables(); for (int i = 0; i < strings.length; i += 2) { tabularData.accumulate("Member", result.getMemberIdOrName()); tabularData.accumulate("Un-Deployed JAR", strings[i]); tabularData.accumulate("Un-Deployed From JAR Location", strings[i + 1]); accumulatedData = true; } } } if (!accumulatedData) { return ResultBuilder.createInfoResult(CliStrings.UNDEPLOY__NO_JARS_FOUND_MESSAGE); } Result result = ResultBuilder.buildResult(tabularData); if (tabularData.getStatus().equals(Status.OK)) { persistClusterConfiguration(result, () -> getSharedConfiguration() .removeJars(jars == null ? null : jars.split(","), groups)); } return result; } catch (VirtualMachineError e) { SystemFailure.initiateFailure(e); throw e; } catch (Throwable th) { SystemFailure.checkFailure(); return ResultBuilder.createGemFireErrorResult("Exception while attempting to un-deploy: " + th.getClass().getName() + ": " + th.getMessage()); } } /** * List all currently deployed JARs for members of a group or for all members. * * @param group Group for which to list JARs or null for all members * @return List of deployed JAR files */ @CliCommand(value = {CliStrings.LIST_DEPLOYED}, help = CliStrings.LIST_DEPLOYED__HELP) @CliMetaData(relatedTopic = {CliStrings.TOPIC_GEODE_CONFIG}) @ResourceOperation(resource = Resource.CLUSTER, operation = Operation.READ) public final Result listDeployed(@CliOption(key = {CliStrings.LIST_DEPLOYED__GROUP}, help = CliStrings.LIST_DEPLOYED__GROUP__HELP) @CliMetaData( valueSeparator = ",") String group) { try { TabularResultData tabularData = ResultBuilder.createTabularResultData(); boolean accumulatedData = false; Set<DistributedMember> targetMembers; try { targetMembers = CliUtil.findMembersOrThrow(group, null); } catch (CommandResultException crex) { return crex.getResult(); } ResultCollector<?, ?> rc = CliUtil.executeFunction(this.listDeployedFunction, null, targetMembers); List<CliFunctionResult> results = CliFunctionResult.cleanResults((List<?>) rc.getResult()); for (CliFunctionResult result : results) { if (result.getThrowable() != null) { tabularData.accumulate("Member", result.getMemberIdOrName()); tabularData.accumulate("JAR", ""); tabularData.accumulate("JAR Location", "ERROR: " + result.getThrowable().getClass().getName() + ": " + result.getThrowable().getMessage()); accumulatedData = true; tabularData.setStatus(Status.ERROR); } else { String[] strings = (String[]) result.getSerializables(); for (int i = 0; i < strings.length; i += 2) { tabularData.accumulate("Member", result.getMemberIdOrName()); tabularData.accumulate("JAR", strings[i]); tabularData.accumulate("JAR Location", strings[i + 1]); accumulatedData = true; } } } if (!accumulatedData) { return ResultBuilder.createInfoResult(CliStrings.LIST_DEPLOYED__NO_JARS_FOUND_MESSAGE); } return ResultBuilder.buildResult(tabularData); } catch (VirtualMachineError e) { SystemFailure.initiateFailure(e); throw e; } catch (Throwable th) { SystemFailure.checkFailure(); return ResultBuilder.createGemFireErrorResult("Exception while attempting to list deployed: " + th.getClass().getName() + ": " + th.getMessage()); } } @CliAvailabilityIndicator({CliStrings.DEPLOY, CliStrings.UNDEPLOY, CliStrings.LIST_DEPLOYED}) public final boolean isConnected() { if (!CliUtil.isGfshVM()) { return true; } return (getGfsh() != null && getGfsh().isConnectedAndReady()); } /** * Interceptor used by gfsh to intercept execution of deploy command at "shell". */ public static class Interceptor extends AbstractCliAroundInterceptor { private final DecimalFormat numFormatter = new DecimalFormat("###,##0.00"); @Override public Result preExecution(GfshParseResult parseResult) { Map<String, String> paramValueMap = parseResult.getParamValueStrings(); String jar = paramValueMap.get("jar"); jar = (jar == null) ? null : jar.trim(); String dir = paramValueMap.get("dir"); dir = (dir == null) ? null : dir.trim(); String group = paramValueMap.get("group"); group = (group == null) ? null : group.trim(); String jarOrDir = (jar != null ? jar : dir); if (jar == null && dir == null) { return ResultBuilder.createUserErrorResult( "Parameter \"jar\" or \"dir\" is required. Use \"help <command name>\" for assistance."); } FileResult fileResult; try { fileResult = new FileResult(new String[] {jar != null ? jar : dir}); } catch (FileNotFoundException fnfex) { return ResultBuilder.createGemFireErrorResult("'" + jarOrDir + "' not found."); } catch (IOException ioex) { return ResultBuilder.createGemFireErrorResult("I/O error when reading jar/dir: " + ioex.getClass().getName() + ": " + ioex.getMessage()); } // Only do this additional check if a dir was provided if (dir != null) { String message = "\nDeploying files: " + fileResult.getFormattedFileList() + "\nTotal file size is: " + this.numFormatter.format(((double) fileResult.computeFileSizeTotal() / 1048576)) + "MB\n\nContinue? "; if (readYesNo(message, Response.YES) == Response.NO) { return ResultBuilder .createShellClientAbortOperationResult("Aborted deploy of " + jarOrDir + "."); } } return fileResult; } @Override public Result postExecution(GfshParseResult parseResult, Result commandResult) { return null; } } }