/* * JBoss, Home of Professional Open Source. * Copyright 2013, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.patching.tool; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CORE_SERVICE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILED; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INPUT_STREAM_INDEX; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS; import java.io.File; import java.io.IOException; import java.util.List; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.PathElement; import org.jboss.as.controller.client.ModelControllerClient; import org.jboss.as.controller.client.Operation; import org.jboss.as.controller.client.OperationBuilder; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.patching.Constants; import org.jboss.as.patching.ContentConflictsException; import org.jboss.as.patching.PatchInfo; import org.jboss.as.patching.PatchingException; import org.jboss.as.patching.VerbosePatchInfo; import org.jboss.as.patching.installation.PatchableTarget.TargetInfo; import org.jboss.as.patching.logging.PatchLogger; import org.jboss.as.patching.metadata.ContentItem; import org.jboss.as.patching.metadata.ContentType; import org.jboss.as.patching.metadata.Patch; import org.jboss.as.patching.metadata.PatchElement; import org.jboss.as.patching.tool.PatchingHistory.Entry; import org.jboss.dmr.ModelNode; /** * @author Emanuel Muckenhuber */ public abstract class PatchOperationTarget { static final PathElement CORE_SERVICES = PathElement.pathElement(CORE_SERVICE, "patching"); /** * Create a local target. * * @param jbossHome the jboss home * @param moduleRoots the module roots * @param bundlesRoots the bundle roots * @return the local target * @throws IOException */ public static final PatchOperationTarget createLocal(final File jbossHome, List<File> moduleRoots, List<File> bundlesRoots) throws IOException { final PatchTool tool = PatchTool.Factory.createLocalTool(jbossHome, moduleRoots, bundlesRoots); return new LocalPatchOperationTarget(tool); } /** * Create a standalone target. * * @param controllerClient the connected controller client to a standalone instance. * @return the remote target */ public static final PatchOperationTarget createStandalone(final ModelControllerClient controllerClient) { final PathAddress address = PathAddress.EMPTY_ADDRESS.append(CORE_SERVICES); return new RemotePatchOperationTarget(address, controllerClient); } /** * Create a host target. * * @param hostName the host name * @param client the connected controller client to the master host. * @return the remote target */ public static final PatchOperationTarget createHost(final String hostName, final ModelControllerClient client) { final PathElement host = PathElement.pathElement(HOST, hostName); final PathAddress address = PathAddress.EMPTY_ADDRESS.append(host, CORE_SERVICES); return new RemotePatchOperationTarget(address, client); } // protected abstract ModelNode streams() throws PatchingException; protected abstract ModelNode info() throws PatchingException; protected abstract ModelNode info(String streamName) throws PatchingException; protected abstract ModelNode info(String patchId, boolean verbose) throws PatchingException; protected abstract ModelNode info(String streamName, String patchId, boolean verbose) throws PatchingException; protected abstract ModelNode history() throws PatchingException; protected abstract ModelNode history(String streamName) throws PatchingException; protected abstract ModelNode applyPatch(final File file, final ContentPolicyBuilderImpl builder) throws PatchingException; protected abstract ModelNode rollback(final String patchId, final ContentPolicyBuilderImpl builder, boolean rollbackTo, final boolean restoreConfiguration) throws PatchingException; protected abstract ModelNode rollback(final String streamName, final String patchId, final ContentPolicyBuilderImpl builder, boolean rollbackTo, final boolean restoreConfiguration) throws PatchingException; protected abstract ModelNode rollbackLast(final ContentPolicyBuilderImpl builder, final boolean restoreConfiguration) throws PatchingException; protected abstract ModelNode rollbackLast(final String streamName, final ContentPolicyBuilderImpl builder, final boolean restoreConfiguration) throws PatchingException; protected static class LocalPatchOperationTarget extends PatchOperationTarget { private final PatchTool tool; public LocalPatchOperationTarget(PatchTool tool) { this.tool = tool; } @Override protected ModelNode streams() throws PatchingException { final List<String> streams = tool.getPatchStreams(); final ModelNode result = new ModelNode(); result.get(OUTCOME).set(SUCCESS); final ModelNode list = result.get(RESULT).setEmptyList(); for(final String stream : streams) { list.add(stream); } return result; } @Override protected ModelNode info() throws PatchingException { return info(null); } @Override protected ModelNode info(String streamName) throws PatchingException { final PatchInfo info = tool.getPatchInfo(streamName); final ModelNode response = new ModelNode(); response.get(OUTCOME).set(SUCCESS); final ModelNode result = response.get(RESULT); result.get(Constants.VERSION).set(info.getVersion()); result.get(Constants.CUMULATIVE).set(info.getCumulativePatchID()); result.get(Constants.PATCHES).setEmptyList(); for(final String patch : info.getPatchIDs()) { result.get(Constants.PATCHES).add(patch); } if(info instanceof VerbosePatchInfo) { final VerbosePatchInfo vInfo = (VerbosePatchInfo) info; if(vInfo.hasLayers()) { final ModelNode layersNode = result.get(Constants.LAYER); for(String name : vInfo.getLayerNames()) { final TargetInfo layerInfo = vInfo.getLayerInfo(name); final ModelNode layerNode = layersNode.get(name); layerNode.get(Constants.CUMULATIVE).set(layerInfo.getCumulativePatchID()); final ModelNode patchesNode = layerNode.get(Constants.PATCHES).setEmptyList(); if(!layerInfo.getPatchIDs().isEmpty()) { for(String patchId : layerInfo.getPatchIDs()) { patchesNode.add(patchId); } } } } if(vInfo.hasAddOns()) { final ModelNode layerNode = result.get(Constants.ADD_ON); for(String name : vInfo.getAddOnNames()) { final TargetInfo layerInfo = vInfo.getAddOnInfo(name); layerNode.get(name, Constants.CUMULATIVE).set(layerInfo.getCumulativePatchID()); final ModelNode patchesNode = layerNode.get(Constants.PATCHES).setEmptyList(); if(!layerInfo.getPatchIDs().isEmpty()) { for(String patchId : layerInfo.getPatchIDs()) { patchesNode.add(patchId); } } } } } return response; } @Override protected ModelNode history() { return history(null); } @Override protected ModelNode history(String streamName) { final ModelNode result = new ModelNode(); result.get(OUTCOME).set(SUCCESS); try { result.get(RESULT).set(tool.getPatchingHistory(streamName).getHistory()); } catch (PatchingException e) { return formatFailedResponse(e); } return result; } @Override protected ModelNode applyPatch(final File file, final ContentPolicyBuilderImpl builder) { final ContentVerificationPolicy policy = builder.createPolicy(); ModelNode result = new ModelNode(); try { PatchingResult apply = tool.applyPatch(file, policy); apply.commit(); result.get(OUTCOME).set(SUCCESS); result.get(RESULT).setEmptyObject(); } catch (PatchingException e) { return formatFailedResponse(e); } return result; } @Override protected ModelNode rollback(final String patchId, final ContentPolicyBuilderImpl builder, boolean rollbackTo, boolean resetConfiguration) { return rollback(null, patchId, builder, rollbackTo, resetConfiguration); } @Override protected ModelNode rollback(final String streamName, final String patchId, final ContentPolicyBuilderImpl builder, boolean rollbackTo, boolean resetConfiguration) { final ContentVerificationPolicy policy = builder.createPolicy(); ModelNode result = new ModelNode(); try { PatchingResult rollback = tool.rollback(streamName, patchId, policy, rollbackTo, resetConfiguration); rollback.commit(); result.get(OUTCOME).set(SUCCESS); result.get(RESULT).setEmptyObject(); } catch (PatchingException e) { return formatFailedResponse(e); } return result; } @Override protected ModelNode rollbackLast(final ContentPolicyBuilderImpl builder, boolean restoreConfiguration) { return rollbackLast(null, builder, restoreConfiguration); } @Override protected ModelNode rollbackLast(final String streamName, final ContentPolicyBuilderImpl builder, boolean restoreConfiguration) { final ContentVerificationPolicy policy = builder.createPolicy(); ModelNode result = new ModelNode(); try { PatchingResult rollback = tool.rollbackLast(streamName, policy, restoreConfiguration); rollback.commit(); result.get(OUTCOME).set(SUCCESS); result.get(RESULT).setEmptyObject(); } catch (PatchingException e) { return formatFailedResponse(e); } return result; } @Override protected ModelNode info(String patchId, boolean verbose) throws PatchingException { return info(null, patchId, verbose); } @Override protected ModelNode info(String streamName, String patchId, boolean verbose) throws PatchingException { if(patchId == null) { throw new IllegalArgumentException("patchId is null"); } final PatchingHistory history = tool.getPatchingHistory(streamName); try { final PatchingHistory.Iterator iterator = history.iterator(); while(iterator.hasNext()) { final Entry next = iterator.next(); if(patchId.equals(next.getPatchId())) { final ModelNode response = new ModelNode(); response.get(OUTCOME).set(SUCCESS); final ModelNode result = response.get(RESULT); result.get(Constants.PATCH_ID).set(next.getPatchId()); result.get(Constants.TYPE).set(next.getType().getName()); final Patch metadata = next.getMetadata(); result.get(Constants.IDENTITY_NAME).set(metadata.getIdentity().getName()); result.get(Constants.IDENTITY_VERSION).set(metadata.getIdentity().getVersion()); result.get(Constants.DESCRIPTION).set(next.getMetadata().getDescription()); if (next.getMetadata().getLink() != null) { result.get(Constants.LINK).set(next.getMetadata().getLink()); } if (verbose) { final ModelNode elements = result.get(Constants.ELEMENTS).setEmptyList(); for(PatchElement e : metadata.getElements()) { final ModelNode element = new ModelNode(); element.get(Constants.PATCH_ID).set(e.getId()); element.get(Constants.NAME).set(e.getProvider().getName()); element.get(Constants.TYPE).set(e.getProvider().isAddOn() ? Constants.ADD_ON : Constants.LAYER); element.get(Constants.DESCRIPTION).set(e.getDescription()); elements.add(element); } } return response; } } } catch (PatchingException e) { return formatFailedResponse(e); } return formatFailedResponse(PatchLogger.ROOT_LOGGER.patchNotFoundInHistory(patchId).getLocalizedMessage()); } } protected static class RemotePatchOperationTarget extends PatchOperationTarget { private final PathAddress address; private final ModelControllerClient client; public RemotePatchOperationTarget(PathAddress address, ModelControllerClient client) { this.address = address; this.client = client; } @Override protected ModelNode streams() throws PatchingException { final ModelNode operation = new ModelNode(); operation.get(ModelDescriptionConstants.OP_ADDR).set(address.toModelNode()); operation.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.READ_CHILDREN_NAMES_OPERATION); operation.get(ModelDescriptionConstants.CHILD_TYPE).set(Constants.PATCH_STREAM); return executeOp(operation); } @Override protected ModelNode info() throws PatchingException { return info(null); } @Override protected ModelNode info(String streamName) throws PatchingException { final ModelNode operation = new ModelNode(); operation.get(ModelDescriptionConstants.OP).set(Constants.PATCH_INFO); operation.get(ModelDescriptionConstants.OP_ADDR).set(address.toModelNode()); if(streamName != null) { operation.get(ModelDescriptionConstants.OP_ADDR).add(Constants.PATCH_STREAM, streamName); } operation.get(Constants.VERBOSE).set(true); return executeOp(operation); } @Override protected ModelNode history() throws PatchingException { return history(null); } @Override protected ModelNode history(String streamName) throws PatchingException { final ModelNode operation = new ModelNode(); operation.get(OP).set(Constants.SHOW_HISTORY); operation.get(OP_ADDR).set(address.toModelNode()); if(streamName != null) { operation.get(ModelDescriptionConstants.OP_ADDR).add(Constants.PATCH_STREAM, streamName); } return executeOp(operation); } @Override protected ModelNode applyPatch(final File file, final ContentPolicyBuilderImpl policyBuilder) throws PatchingException { final ModelNode operation = createOperation(Constants.PATCH, address.toModelNode(), policyBuilder); operation.get(INPUT_STREAM_INDEX).set(0); final OperationBuilder operationBuilder = OperationBuilder.create(operation); operationBuilder.addFileAsAttachment(file); return executeOp(operationBuilder.build()); } @Override protected ModelNode rollback(String patchId, ContentPolicyBuilderImpl builder, boolean rollbackTo, boolean resetConfiguration) throws PatchingException { return rollback(null, patchId, builder, rollbackTo, resetConfiguration); } @Override protected ModelNode rollback(String streamName, String patchId, ContentPolicyBuilderImpl builder, boolean rollbackTo, boolean resetConfiguration) throws PatchingException { final ModelNode operation = createOperation(Constants.ROLLBACK, address.toModelNode(), builder); operation.get(Constants.PATCH_ID).set(patchId); operation.get(Constants.RESET_CONFIGURATION).set(resetConfiguration); operation.get(Constants.ROLLBACK_TO).set(rollbackTo); if(streamName != null) { operation.get(ModelDescriptionConstants.OP_ADDR).add(Constants.PATCH_STREAM, streamName); } return executeOp(operation); } @Override protected ModelNode rollbackLast(ContentPolicyBuilderImpl builder, boolean restoreConfiguration) throws PatchingException { return rollbackLast(null, builder, restoreConfiguration); } @Override protected ModelNode rollbackLast(String streamName, ContentPolicyBuilderImpl builder, boolean restoreConfiguration) throws PatchingException { final ModelNode operation = createOperation(Constants.ROLLBACK_LAST, address.toModelNode(), builder); operation.get(Constants.RESET_CONFIGURATION).set(restoreConfiguration); if(streamName != null) { operation.get(ModelDescriptionConstants.OP_ADDR).add(Constants.PATCH_STREAM, streamName); } return executeOp(operation); } @Override protected ModelNode info(String patchId, boolean verbose) throws PatchingException { return info(null, patchId, verbose); } @Override protected ModelNode info(String streamName, String patchId, boolean verbose) throws PatchingException { final ModelNode operation = new ModelNode(); operation.get(ModelDescriptionConstants.OP).set(Constants.PATCH_INFO); operation.get(ModelDescriptionConstants.OP_ADDR).set(address.toModelNode()); operation.get(Constants.PATCH_ID).set(patchId); if(streamName != null) { operation.get(ModelDescriptionConstants.OP_ADDR).add(Constants.PATCH_STREAM, streamName); } if(verbose) { operation.get(Constants.VERBOSE).set(true); } return executeOp(operation); } protected ModelNode executeOp(ModelNode operation) throws PatchingException { try { return client.execute(operation); } catch (IOException e) { throw new PatchingException("Failed to execute operation " + operation, e); } } protected ModelNode executeOp(Operation operation) throws PatchingException { try { return client.execute(operation); } catch (IOException e) { throw new PatchingException("Failed to execute operation " + operation.getOperation(), e); } } } static ModelNode formatFailedResponse(final String msg) { final ModelNode result = new ModelNode(); result.get(OUTCOME).set(FAILED); result.get(FAILURE_DESCRIPTION, Constants.MESSAGE).set(msg); return result; } static ModelNode formatFailedResponse(final PatchingException e) { final ModelNode result = new ModelNode(); result.get(OUTCOME).set(FAILED); formatFailedResponse(e, result.get(FAILURE_DESCRIPTION)); return result; } public static void formatFailedResponse(final PatchingException e, final ModelNode failureDescription) { if(e instanceof ContentConflictsException) { failureDescription.get(Constants.MESSAGE).set(PatchLogger.ROOT_LOGGER.detectedConflicts()); final ModelNode conflicts = failureDescription.get(Constants.CONFLICTS); for(final ContentItem item : ((ContentConflictsException)e).getConflicts()) { final ContentType type = item.getContentType(); switch (type) { case BUNDLE: conflicts.get(Constants.BUNDLES).add(item.getRelativePath()); break; case MODULE: conflicts.get(Constants.MODULES).add(item.getRelativePath()); break; case MISC: conflicts.get(Constants.MISC).add(item.getRelativePath()); break; } } } else { failureDescription.set(e.getLocalizedMessage()); } } static ModelNode createOperation(final String operationName, final ModelNode addr, final ContentPolicyBuilderImpl builder) { final ModelNode operation = new ModelNode(); operation.get(ModelDescriptionConstants.OP).set(operationName); operation.get(ModelDescriptionConstants.OP_ADDR).set(addr); // Process the policy operation.get(Constants.OVERRIDE_MODULES).set(builder.ignoreModulesChanges); operation.get(Constants.OVERRIDE_ALL).set(builder.overrideAll); if(! builder.override.isEmpty()) { for(final String o : builder.override) { operation.get(Constants.OVERRIDE).add(o); } } if(! builder.preserve.isEmpty()) { for(final String p : builder.preserve) { operation.get(Constants.PRESERVE).add(p); } } return operation; } }