/*
* RHQ Management Platform
* Copyright (C) 2014 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.modules.plugins.wildfly10.patching;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.bundle.BundleResourceDeployment;
import org.rhq.core.domain.bundle.BundleResourceDeploymentHistory;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.pluginapi.bundle.BundleDeployRequest;
import org.rhq.core.pluginapi.bundle.BundleDeployResult;
import org.rhq.core.pluginapi.bundle.BundleFacet;
import org.rhq.core.pluginapi.bundle.BundleManagerProvider;
import org.rhq.core.pluginapi.bundle.BundlePurgeRequest;
import org.rhq.core.pluginapi.bundle.BundlePurgeResult;
import org.rhq.core.pluginapi.inventory.ResourceComponent;
import org.rhq.core.pluginapi.inventory.ResourceContext;
import org.rhq.core.system.ProcessExecutionResults;
import org.rhq.core.util.StringUtil;
import org.rhq.modules.plugins.wildfly10.AS7Mode;
import org.rhq.modules.plugins.wildfly10.ASConnection;
import org.rhq.modules.plugins.wildfly10.ASConnectionParams;
import org.rhq.modules.plugins.wildfly10.ServerControl;
import org.rhq.modules.plugins.wildfly10.helper.ServerPluginConfiguration;
import org.rhq.modules.plugins.wildfly10.json.Address;
import org.rhq.modules.plugins.wildfly10.json.Operation;
import org.rhq.modules.plugins.wildfly10.json.ReadAttribute;
import org.rhq.modules.plugins.wildfly10.util.PatchDetails;
/**
* @author Lukas Krejci
* @since 4.13
*/
public final class PatchHandlerComponent implements ResourceComponent<ResourceComponent<?>>, BundleFacet {
private static final Log LOG = LogFactory.getLog(PatchHandlerComponent.class);
private static final String PATCH_ROLLBACK_COMMAND = "patch rollback --reset-configuration=false --patch-id=";
private ResourceContext<ResourceComponent<?>> context;
@Override
public AvailabilityType getAvailability() {
return AvailabilityType.UP;
}
@Override
public BundleDeployResult deployBundle(BundleDeployRequest request) {
ServerControl control = onServer(request);
Result<Void> check = sanityCheck(control, request.getReferencedConfiguration(),
request.getBundleManagerProvider(), request.getResourceDeployment(),
!isTakeOver(request.getResourceDeployment().getBundleDeployment().getConfiguration()));
if (check.failed()) {
BundleDeployResult result = new BundleDeployResult();
result.setErrorMessage(check.errorMessage);
return result;
}
if (request.isRevert()) {
return handleRevert(request);
}
BundleDeployResult result = new BundleDeployResult();
ASConnection connection = new ASConnection(
ASConnectionParams.createFrom(new ServerPluginConfiguration(request.getReferencedConfiguration())));
ProcessExecutionResults results;
BundleManagerProvider bmp = request.getBundleManagerProvider();
BundleResourceDeployment rd = request.getResourceDeployment();
Result<Boolean> stop = stopIfNeeded(connection, control,
request.getResourceDeployment().getBundleDeployment().getConfiguration(), bmp, rd);
if (stop.failed()) {
result.setErrorMessage(stop.errorMessage);
return result;
}
boolean startUp = stop.result;
try {
StringBuilder command = new StringBuilder("patch apply --path=").append(
request.getPackageVersionFiles().values().iterator().next().getAbsolutePath());
Configuration bundleConfig = request.getResourceDeployment().getBundleDeployment().getConfiguration();
String override = bundleConfig.getSimpleValue("override");
String overrideAll = bundleConfig.getSimpleValue("override-all");
String preserve = bundleConfig.getSimpleValue("preserve");
String overrideModules = bundleConfig.getSimpleValue("override-modules");
if (override != null) {
command.append(" --override=").append(override);
}
if (overrideAll != null) {
command.append(" --override-all=").append(Boolean.valueOf(overrideAll));
}
if (preserve != null) {
command.append(" --preserve=").append(preserve);
}
if (overrideModules != null) {
command.append(" --override-modules=").append(overrideModules);
}
// as a last thing before the deployment, check the patch history
Result<List<PatchDetails>> historyBeforeDeployment = getPatchHistory(control, "deploy");
if (historyBeforeDeployment.failed()) {
result.setErrorMessage(historyBeforeDeployment.errorMessage);
return result;
}
results = control.cli().disconnected(true).executeCliCommand(command.toString());
switch (handleExecutionResults(results, bmp, rd, true)) {
case EXECUTION_ERROR:
result
.setErrorMessage("Error while trying to execute patch command: " + results.getError().getMessage());
return result;
case TIMEOUT:
result.setErrorMessage("Patch application timed out. Output was: " + results.getCapturedOutput());
return result;
case ERROR:
result.setErrorMessage("Patch application failed with error code " + results.getExitCode() + ".");
return result;
}
String errorMessage = storeState(control, request.getResourceDeployment(),
request.getReferencedConfiguration(), historyBeforeDeployment.result);
if (errorMessage != null) {
result.setErrorMessage(errorMessage);
return result;
}
} finally {
if (startUp) {
String errorMessage = startServer(connection, control, bmp, rd);
if (errorMessage != null) {
result.setErrorMessage(errorMessage);
}
}
}
return result;
}
@Override
public BundlePurgeResult purgeBundle(BundlePurgeRequest request) {
BundlePurgeResult result = new BundlePurgeResult();
ServerControl control = ServerControl.onServer(request.getReferencedConfiguration(),
AS7Mode.valueOf(request.getDestinationTarget().getPath()), context.getSystemInformation());
Result<Void> check = sanityCheck(control, request.getReferencedConfiguration(),
request.getBundleManagerProvider(), request.getLiveResourceDeployment(), false);
if (check.failed()) {
result.setErrorMessage(check.errorMessage);
return result;
}
Result<BundleMetadata> metadata = BundleMetadata
.forDeployment(request.getLiveResourceDeployment(), request.getReferencedConfiguration());
if (metadata.failed()) {
result.setErrorMessage(metadata.errorMessage);
return result;
}
LinkedHashSet<String> pidsToRollback = new LinkedHashSet<String>();
for (BundleMetadata.DeploymentMetadata dm : metadata.result.deployments) {
for (PatchDetails pd : dm.applied) {
pidsToRollback.add(pd.getId());
}
}
ASConnection connection = new ASConnection(
ASConnectionParams.createFrom(new ServerPluginConfiguration(request.getReferencedConfiguration())));
String errorMessage = rollbackPatches(control, request.getBundleManagerProvider(),
request.getLiveResourceDeployment(), connection, "purge", new ArrayList<String>(pidsToRollback));
if (errorMessage != null) {
result.setErrorMessage(errorMessage);
return result;
}
forgetState(request.getLiveResourceDeployment(), request.getReferencedConfiguration());
return result;
}
private BundleDeployResult handleRevert(BundleDeployRequest request) {
BundleDeployResult result = new BundleDeployResult();
//this is what was recorded during prior deployments
Result<BundleMetadata> latestDeploymentMetadata = BundleMetadata
.forDeployment(request.getResourceDeployment(), request.getReferencedConfiguration());
if (latestDeploymentMetadata.failed()) {
result.setErrorMessage(latestDeploymentMetadata.errorMessage);
return result;
}
//this is what we're reverting to.
Result<String[]> pids = getPids(request.getResourceDeployment(), "revert");
if (pids.failed()) {
result.setErrorMessage(pids.errorMessage);
return result;
}
// determine the pids that have been deployed on top of the bundle being reverted to.
// this is a linked set because it can happen that a single patch gets applied multiple times
// through but we obviously want to only roll it back once, at the position of the latest application.
// The circumstance of patch being applied multiple times can happen when:
// 1) patch is applied through destination A
// 2) patch revert fails due to an outside change
// 3) manual intervention in CLI recovers to the original state
// 4) patch is applied again
Set<String> pidsToRollback = new LinkedHashSet<String>();
List<BundleMetadata.DeploymentMetadata> deploymentsToForget = new ArrayList<BundleMetadata.DeploymentMetadata>();
String stopPid = pids.result[0];
boolean stopPidFound = false;
outer: for (BundleMetadata.DeploymentMetadata dm : latestDeploymentMetadata.result.deployments) {
for (PatchDetails pd : dm.applied) {
String pid = pd.getId();
if (pid.equals(stopPid)) {
stopPidFound = true;
break outer;
}
pidsToRollback.add(pid);
}
deploymentsToForget.add(dm);
}
if (!stopPidFound) {
result.setErrorMessage("The patch to revert to (" + stopPid +
") was not previously deployed by RHQ. This means that this server joined the resource group '" +
request.getResourceDeployment().getBundleDeployment().getDestination().getGroup().getName() +
"' after this patch was already deployed. To prevent accidental damage no changes will be made to the server.");
return result;
}
ASConnection connection = new ASConnection(
ASConnectionParams.createFrom(new ServerPluginConfiguration(request.getReferencedConfiguration())));
ServerControl control = onServer(request);
String errorMessage = rollbackPatches(control, request.getBundleManagerProvider(),
request.getResourceDeployment(), connection, "revert", new ArrayList<String>(pidsToRollback));
if (errorMessage != null) {
result.setErrorMessage(errorMessage);
return result;
}
for (BundleMetadata.DeploymentMetadata dm : deploymentsToForget) {
dm.forget(request.getResourceDeployment(), request.getReferencedConfiguration());
}
return result;
}
@Override
public void start(ResourceContext<ResourceComponent<?>> context) {
this.context = context;
}
@Override
public void stop() {
}
private boolean waitForServerToStart(ASConnection connection) throws InterruptedException {
boolean up = false;
while (!up) {
up = isServerUp(connection);
if (!up) {
if (context.getComponentInvocationContext().isInterrupted()) {
// Operation canceled or timed out
throw new InterruptedException();
}
Thread.sleep(SECONDS.toMillis(1));
}
}
return true;
}
private ExecutionResult handleExecutionResults(ProcessExecutionResults results, BundleManagerProvider bmp,
BundleResourceDeployment resourceDeployment, boolean doAudit) {
ExecutionResult ret = ExecutionResult.OK;
if (results.getError() != null) {
ret = ExecutionResult.EXECUTION_ERROR;
} else if (results.getExitCode() == null) {
ret = ExecutionResult.TIMEOUT;
} else if (results.getExitCode() != 0) {
ret = ExecutionResult.ERROR;
}
if (doAudit) {
audit(bmp, resourceDeployment, "Output", ret == ExecutionResult.OK ? "Standard" : "Error", ret.status(),
results.getCapturedOutput());
}
return ret;
}
private void audit(BundleManagerProvider bmp, BundleResourceDeployment resourceDeployment, String action,
String info, BundleResourceDeploymentHistory.Status status, String message) {
try {
bmp.auditDeployment(resourceDeployment, action, info,
BundleResourceDeploymentHistory.Category.AUDIT_MESSAGE, status, message, null);
} catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Failed to report audit deployment.", e);
}
}
}
private enum ExecutionResult {
EXECUTION_ERROR, TIMEOUT, ERROR, OK;
public BundleResourceDeploymentHistory.Status status() {
return this == OK ? BundleResourceDeploymentHistory.Status.SUCCESS :
BundleResourceDeploymentHistory.Status.FAILURE;
}
}
private String fullErrorMessage(String basicMessage, List<String> patchIds, int attemptedPatchIdIndex, String action) {
String message = basicMessage;
if (attemptedPatchIdIndex > 0) {
message += " The following patches were successfully " + action + ": " + StringUtil.collectionToString(
patchIds.subList(0, attemptedPatchIdIndex)) + ".";
}
if (attemptedPatchIdIndex < patchIds.size() - 1) {
message += " The following patches were NOT " + action + ": " +
StringUtil
.collectionToString(patchIds.subList(attemptedPatchIdIndex + 1, patchIds.size()))
+ ".";
}
return message;
}
private ServerControl onServer(BundleDeployRequest request) {
return ServerControl.onServer(request.getReferencedConfiguration(),
AS7Mode.valueOf(request.getDestinationTarget().getPath()), context.getSystemInformation());
}
private String rollbackPatches(ServerControl control, BundleManagerProvider bmp, BundleResourceDeployment rd,
ASConnection connection, String operation, List<String> pids) {
if (pids.isEmpty()) {
return null;
}
ProcessExecutionResults results;
ServerControl.Cli cli = control.cli().disconnected(true);
Result<List<PatchDetails>> history = getPatchHistory(control, operation);
if (history.failed()) {
return history.errorMessage;
}
List<PatchDetails> installedPatches = history.result;
List<String> pidsToRollback = new ArrayList<String>(pids);
List<String> noLongerRemovablePids = new ArrayList<String>();
Set<String> installedPids = new HashSet<String>();
int lastPidToRollback = 0;
int installedPidIdx = 0;
for (; installedPidIdx < installedPatches.size() &&
lastPidToRollback < pidsToRollback.size(); ++installedPidIdx) {
PatchDetails installed = installedPatches.get(installedPidIdx);
String pidToRollback = pidsToRollback.get(lastPidToRollback);
String installedId = installed.getId();
if (installedId.equals(pidToRollback)) {
lastPidToRollback++;
} else {
while (!installedId.equals(pidToRollback) && lastPidToRollback < pidsToRollback.size()) {
pidToRollback = pidsToRollback.get(lastPidToRollback);
noLongerRemovablePids.add(pidsToRollback.remove(lastPidToRollback));
}
if (installedId.equals(pidToRollback)) {
lastPidToRollback++;
}
}
installedPids.add(installedId);
}
for (; installedPidIdx < installedPatches.size(); ++installedPidIdx) {
installedPids.add(installedPatches.get(installedPidIdx).getId());
}
// remove pids that we have not seen installed
if (lastPidToRollback < pidsToRollback.size()) {
List<String> uninstalledPids = pidsToRollback.subList(lastPidToRollback, pidsToRollback.size());
noLongerRemovablePids.addAll(uninstalledPids);
uninstalledPids.clear();
}
boolean inconsistent = false;
for (String pid : noLongerRemovablePids) {
if (installedPids.contains(pid)) {
// the current patch state is inconsistent with what we're expecting, because
// we see pids that are still installed but are no longer on rollback-able positions
// even though they should.
// If they're no longer installed then that's actually OK, we'd be rolling them back anyway.
inconsistent = true;
break;
}
}
if (pidsToRollback.isEmpty()) {
if (noLongerRemovablePids.isEmpty()) {
audit(bmp, rd, "Rollback", "Nothing To Do", BundleResourceDeploymentHistory.Status.WARN,
"None of the patches " + pids + " is installed anymore.");
return null;
} else {
String message = "The following patches were not rolled back due to other patches having been applied in the meantime: " +
noLongerRemovablePids + ". No other patches can be rolled back.";
if (inconsistent) {
return message;
} else {
audit(bmp, rd, "Rollback", "Missing patches", BundleResourceDeploymentHistory.Status.WARN,
"The following patches were to be rolled back but they aren't installed anymore: " +
noLongerRemovablePids + ".");
}
}
}
Configuration deploymentConfiguration = rd.getBundleDeployment().getConfiguration();
//if the server is online, let's bring it down for the duration of the rollback.
Result<Boolean> stop = stopIfNeeded(connection, control, deploymentConfiguration, bmp, rd);
if (stop.failed()) {
return stop.errorMessage;
}
boolean serverWasUp = stop.result;
List<String> patchCommands = new ArrayList<String>();
for (String pid : pidsToRollback) {
patchCommands.add(PATCH_ROLLBACK_COMMAND + pid);
}
String errorMessage = null;
try {
int i = 0;
for (String command : patchCommands) {
results = cli.executeCliCommand(command);
switch (handleExecutionResults(results, bmp, rd, true)) {
case EXECUTION_ERROR:
return fullErrorMessage("Error trying to run patch rollback: " +
results.getError().getMessage(), pids, i - 1, "rolled back");
case TIMEOUT:
return fullErrorMessage("Patch rollback timed out. Captured output: " +
results.getCapturedOutput(), pids, i - 1, "rolled back");
case ERROR:
return fullErrorMessage("Patch rollback failed with error code " + results.getExitCode()
+ ".", pids, i - 1, "rolled back");
}
++i;
}
} finally {
if (!noLongerRemovablePids.isEmpty()) {
if (inconsistent) {
errorMessage = "The following patches were not rolled back due to other patches having been applied in the meantime: " +
noLongerRemovablePids;
} else {
audit(bmp, rd, "Rollback", "Missing patches", BundleResourceDeploymentHistory.Status.WARN,
"The following patches were to be rolled back but they aren't installed anymore: " +
noLongerRemovablePids + ".");
}
}
if (serverWasUp) {
String startError = startServer(connection, control, bmp, rd);
if (startError != null) {
if (errorMessage != null) {
audit(bmp, rd, "Restart", "Failure", BundleResourceDeploymentHistory.Status.FAILURE,
startError);
} else {
errorMessage = startError;
}
}
}
}
return errorMessage;
}
private boolean isServerUp(ASConnection connection) {
Operation op = new ReadAttribute(new Address(), "release-version");
try {
org.rhq.modules.plugins.wildfly10.json.Result res = connection.execute(op);
if (res.isSuccess()) { // If op succeeds, server is not down
return true;
}
} catch (Exception e) {
//do absolutely nothing
//if an exception is thrown that means the server is still down, so consider this
//a single failed attempt, equivalent to res.isSuccess == false
}
return false;
}
private Result<Boolean> stopIfNeeded(ASConnection connection, ServerControl control,
Configuration bundleDeploymentConfiguration, BundleManagerProvider bmp, BundleResourceDeployment resourceDeployment) {
boolean doRestart = Boolean.valueOf(bundleDeploymentConfiguration.getSimpleValue("restart", "true"));
if (doRestart && isServerUp(connection)) {
audit(bmp, resourceDeployment, "Stop", "Stop", null,
"The server is running. Stopping it before any operation on patches.");
ProcessExecutionResults results = control.lifecycle().shutdownServer();
switch (handleExecutionResults(results, bmp, resourceDeployment, true)) {
case EXECUTION_ERROR:
return new Result<Boolean>(false, "Error trying to shutdown the server: " + results.getError().getMessage());
case TIMEOUT:
return new Result<Boolean>(false, "Stopping the server timed out. Captured output: " +
results.getCapturedOutput());
case ERROR:
return new Result<Boolean>(false, "Stopping the server failed with error code " + results.getExitCode() +
" and output: " + results.getCapturedOutput());
}
return new Result<Boolean>(true, null);
}
return new Result<Boolean>(false, null);
}
private String startServer(ASConnection connection, ServerControl control, BundleManagerProvider bmp, BundleResourceDeployment resourceDeployment) {
audit(bmp, resourceDeployment, "Start", "Start", null, "Starting the server back up.");
ProcessExecutionResults results = control.lifecycle().startServer();
switch (handleExecutionResults(results, bmp, resourceDeployment, false)) {
case EXECUTION_ERROR:
return "Error trying to start the server. " + results.getError().getMessage();
case ERROR:
return "Starting the server failed with error code " + results.getExitCode() + " and output: " +
results.getCapturedOutput();
// ignore timeout, because starting the server actually would always be detected as doing it, because the start
// script never stops...
}
try {
waitForServerToStart(connection);
} catch (InterruptedException e) {
String message = "Interrupted while waiting for the server to start up after applying the patch";
Thread.currentThread().interrupt();
return message;
}
return null;
}
private Result<Void> sanityCheck(ServerControl serverControl, Configuration referencedConfiguration,
BundleManagerProvider bmp, BundleResourceDeployment resourceDeployment, boolean uniqueDeploymentRequired) {
ProcessExecutionResults results = serverControl.cli().disconnected(true).executeCliCommand("help --commands");
switch (handleExecutionResults(results, bmp, resourceDeployment, false)) {
case EXECUTION_ERROR:
return
Result.error("Failed to check availability of patch command using the 'help --commands' command. The error was: " +
results.getError().getMessage());
case ERROR:
return
Result.error("Failed to check availability of patch command using the 'help --commands' command. The execution failed with an exit code " +
results.getExitCode());
case TIMEOUT:
return
Result.error("Failed to check availability of patch command using the 'help --commands' command. The execution timed out with the output: " +
results.getCapturedOutput());
case OK:
if (results.getCapturedOutput() == null || !(results.getCapturedOutput().contains(" patch ") || results.getCapturedOutput().contains("\npatch"))) {
return Result.error("The underlying server does not support the patch command. Cannot perform the patch operation.");
}
break;
}
if (!uniqueDeploymentRequired) {
return Result.with(null);
}
Result<MetadataFiles> metadata = MetadataFiles.getActive(referencedConfiguration);
if (metadata.failed()) {
return Result.error(metadata.errorMessage);
}
if (metadata.result == null) {
// no active deployment yet, we're cool.
return Result.with(null);
}
int destinationId = resourceDeployment.getBundleDeployment().getDestination().getId();
int activeDestinationId = metadata.result.getDestinationId();
if (destinationId != activeDestinationId) {
try {
String destName = metadata.result.getDestinationName();
return Result.error("The destination \"" + destName +
"\" already deployed one or more patches to this server. You can deploy to a server using only a single destination.");
} catch (Exception e) {
//k, we just failed to read the name of the destination but we still know that the user
//tries to use a different destination, so let's just return a slightly less user-friendly
//error message.
return Result.error("The destination with id " + destinationId +
" already handles patch deployments to this server. You can deploy to a server using only a single destination.");
}
}
return Result.with(null);
}
/**
* Store the last deployed patch ID at this point in time. This information is stored for each bundle resource
* deployment individually.
*
* @return the error message or null if everything went fine.
*/
private String storeState(ServerControl control, BundleResourceDeployment rd, Configuration referencedConfiguration,
List<PatchDetails> historyBeforeDeployment) {
Result<List<PatchDetails>> history = getPatchHistory(control, "deployment");
if (history.failed()) {
return history.errorMessage;
}
Result<MetadataFiles> metadataFiles = MetadataFiles.forDeployment(rd, referencedConfiguration);
if (metadataFiles.failed()) {
return metadataFiles.errorMessage;
}
Result<Void> saveDestinationName = metadataFiles.result.saveDestinationName(
rd.getBundleDeployment().getDestination().getName());
if (saveDestinationName.failed()) {
return saveDestinationName.errorMessage;
}
// if sanity check allowed going forward with deployment and we have multiple destinations, forget about
// all others...
Result<Void> saveAsActive = metadataFiles.result.saveAsActive();
if (saveAsActive.failed()) {
return saveAsActive.errorMessage;
}
BundleMetadata.DeploymentMetadata deploymentMetadata = BundleMetadata.DeploymentMetadata
.from(historyBeforeDeployment, history.result);
Result<Void> write = deploymentMetadata.persistAsNewState(rd, referencedConfiguration);
if (write.failed()) {
return write.errorMessage;
}
return null;
}
private String forgetState(BundleResourceDeployment rd, Configuration referencedConfiguration) {
Result<MetadataFiles> metadata = MetadataFiles.forDeployment(rd, referencedConfiguration);
if (metadata.failed()) {
return metadata.errorMessage;
}
metadata.result.delete();
return null;
}
private Result<List<PatchDetails>> getPatchHistory(ServerControl control, String operation) {
Result<String> json = getPatchHistoryJSON(control, operation);
if (json.errorMessage != null) {
return new Result<List<PatchDetails>>(null, json.errorMessage);
}
List<PatchDetails> installedPatches = PatchDetails.fromHistory(json.result);
return new Result<List<PatchDetails>>(installedPatches, null);
}
private Result<String> getPatchHistoryJSON(ServerControl control, String operation) {
ProcessExecutionResults results = control.cli().disconnected(true).executeCliCommand("patch history");
switch (handleExecutionResults(results, null, null, false)) {
case EXECUTION_ERROR:
return new Result<String>(null,
"Failed to determine the patch history while doing a " + operation + ": " +
results.getError().getMessage());
case TIMEOUT:
return new Result<String>(null,
"Timed out while determining patch history for a " + operation + ". Output was: " +
results.getCapturedOutput());
case ERROR:
return new Result<String>(null,
"Failed to determine the patch history for a " + operation + ". Returned error code was: " +
results.getExitCode() + "\nOutput was: " + results.getCapturedOutput());
}
return new Result<String>(results.getCapturedOutput(), null);
}
private Result<String[]> getPids(BundleResourceDeployment rd, String operation) {
PropertySimple patchType = rd.getBundleDeployment().getConfiguration().getSimple("patchType");
boolean isBundle = patchType != null && "patch-bundle".equals(patchType.getStringValue());
PropertySimple patchIdProp = rd.getBundleDeployment().getConfiguration()
.getSimple("patchId");
PropertySimple allPatchIdsProp = rd.getBundleDeployment().getConfiguration()
.getSimple("allPatchIds");
String[] pids;
if (isBundle) {
if (allPatchIdsProp == null || allPatchIdsProp.getStringValue() == null) {
return new Result<String[]>(null,
"Could not determine the list of patch ids from the bundle configuration while performing " +
operation);
}
pids = allPatchIdsProp.getStringValue().split("#");
//we need to return the pids in the same order as patch history - i.e. in the reversed deployment order
Collections.reverse(Arrays.asList(pids));
} else {
if (patchIdProp == null || patchIdProp.getStringValue() == null) {
return new Result<String[]>(null,
"Could not determine the list of patch ids from the bundle configuration while performing " +
operation);
}
pids = new String[1];
pids[0] = patchIdProp.getStringValue();
}
return new Result<String[]>(pids, null);
}
private boolean isTakeOver(Configuration deploymentConfiguration) {
PropertySimple takeOver = deploymentConfiguration.getSimple("takeOver");
if (takeOver == null) {
return false;
}
Boolean value = takeOver.getBooleanValue();
return value != null && value;
}
}