/* * JBoss, Home of Professional Open Source. * Copyright 2011, 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.controller; import static org.jboss.as.controller.logging.ControllerLogger.MGMT_OP_LOGGER; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import org.jboss.as.controller.client.OperationResponse; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.logging.ControllerLogger; import org.jboss.as.controller.operations.common.Util; import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration; import org.jboss.as.controller.registry.Resource; import org.jboss.dmr.ModelNode; import org.wildfly.security.auth.server.SecurityDomain; import org.wildfly.security.auth.server.SecurityRealm; /** * Special handler that executes subsystem boot operations in parallel. * * @author Brian Stansberry (c) 2011 Red Hat Inc. */ public class ParallelBootOperationStepHandler implements OperationStepHandler { private final Executor executor; private final ImmutableManagementResourceRegistration rootRegistration; private final ControlledProcessState processState; private final OperationStepHandler extraValidationStepHandler; private final ModelControllerImpl controller; private final int operationId; private final Map<String, List<ParsedBootOp>> opsBySubsystem = new LinkedHashMap<String, List<ParsedBootOp>>(); private ParsedBootOp ourOp; ParallelBootOperationStepHandler(final ExecutorService executorService, final ImmutableManagementResourceRegistration rootRegistration, final ControlledProcessState processState, final ModelControllerImpl controller, final int operationId, final OperationStepHandler extraValidationStepHandler) { this.executor = executorService; this.rootRegistration = rootRegistration; this.processState = processState; this.controller = controller; this.operationId = operationId; this.extraValidationStepHandler = extraValidationStepHandler; } boolean addSubsystemOperation(final ParsedBootOp parsedOp) { final String subsystemName = getSubsystemName(parsedOp.address); if (subsystemName != null) { List<ParsedBootOp> list = opsBySubsystem.get(subsystemName); if (list == null) { list = new ArrayList<ParsedBootOp>(); opsBySubsystem.put(subsystemName, list); } list.add(parsedOp); getParsedBootOp().addChildOperation(parsedOp); } return subsystemName != null; } ParsedBootOp getParsedBootOp() { if (ourOp == null) { ModelNode op = Util.getEmptyOperation("parallel-subsystem-boot", new ModelNode().setEmptyList()); ourOp = new ParsedBootOp(op, this); } return ourOp; } private String getSubsystemName(final PathAddress address) { String key = null; if (address.size() > 0 && ModelDescriptionConstants.SUBSYSTEM.equals(address.getElement(0).getKey())) { key = address.getElement(0).getValue(); } return key; } @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { if (!context.isNormalServer()) { throw ControllerLogger.ROOT_LOGGER.fullServerBootRequired(getClass()); } long start = System.currentTimeMillis(); // Make sure the lock has been taken context.getResourceRegistrationForUpdate(); final Resource rootResource = context.readResourceForUpdate(PathAddress.EMPTY_ADDRESS); context.acquireControllerLock(); final Map<String, List<ParsedBootOp>> runtimeOpsBySubsystem = new LinkedHashMap<String, List<ParsedBootOp>>(); final Map<String, ParallelBootTransactionControl> transactionControls = new LinkedHashMap<String, ParallelBootTransactionControl>(); final CountDownLatch preparedLatch = new CountDownLatch(opsBySubsystem.size()); final CountDownLatch committedLatch = new CountDownLatch(1); final CountDownLatch completeLatch = new CountDownLatch(opsBySubsystem.size()); final Thread controllingThread = Thread.currentThread(); if (!(context instanceof AbstractOperationContext)) { throw ControllerLogger.ROOT_LOGGER.operationContextIsNotAbstractOperationContext(); } for (Map.Entry<String, List<ParsedBootOp>> entry : opsBySubsystem.entrySet()) { String subsystemName = entry.getKey(); List<ParsedBootOp> subsystemRuntimeOps = new ArrayList<ParsedBootOp>(); runtimeOpsBySubsystem.put(subsystemName, subsystemRuntimeOps); final ParallelBootTransactionControl txControl = new ParallelBootTransactionControl(preparedLatch, committedLatch, completeLatch); transactionControls.put(entry.getKey(), txControl); // Execute the subsystem's ops in another thread ParallelBootTask subsystemTask = new ParallelBootTask(subsystemName, entry.getValue(), (OperationContextImpl)context, txControl, subsystemRuntimeOps, controllingThread, controller, operationId); executor.execute(subsystemTask); } // Wait for all subsystem ops to complete try { preparedLatch.await(); // See if all subsystems succeeded; if not report a failure to context checkForSubsystemFailures(context, transactionControls, OperationContext.Stage.MODEL); // Add any logging subsystem steps so we get logging early in the boot List<ParsedBootOp> loggingOps = runtimeOpsBySubsystem.remove("logging"); if (loggingOps != null) { for (ParsedBootOp loggingOp : loggingOps) { context.addStep(loggingOp.response, loggingOp.operation, loggingOp.handler, OperationContext.Stage.RUNTIME); } } // AS7-2561 // The parallel execution will have added the subsystems to their parent resource in random order. // We need to restore the order that came in the XML. final Map<String, Resource> subsystemResources = new LinkedHashMap<String, Resource>(); for (String subsystemName : opsBySubsystem.keySet()) { final Resource resource = rootResource.removeChild(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, subsystemName)); if (resource != null) { subsystemResources.put(subsystemName, resource); } } for (Map.Entry<String, Resource> entry : subsystemResources.entrySet()) { rootResource.registerChild(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, entry.getKey()), entry.getValue()); } // Add step to execute all the runtime ops recorded by the other subsystem tasks context.addStep(getRuntimeStep(runtimeOpsBySubsystem), OperationContext.Stage.RUNTIME); } catch (InterruptedException e) { context.getFailureDescription().set(new ModelNode().set(ControllerLogger.ROOT_LOGGER.subsystemBootInterrupted())); Thread.currentThread().interrupt(); } if (MGMT_OP_LOGGER.isDebugEnabled()) { long elapsed = System.currentTimeMillis() - start; MGMT_OP_LOGGER.debugf("Ran subsystem model operations in [%d] ms", elapsed); } // Continue boot context.completeStep(new OperationContext.ResultHandler() { @Override public void handleResult(OperationContext.ResultAction resultAction, OperationContext context, ModelNode operation) { // Tell all the subsystem tasks the result of the operations notifySubsystemTransactions(transactionControls, resultAction == OperationContext.ResultAction.ROLLBACK, committedLatch, OperationContext.Stage.RUNTIME); // Make sure all the subsystems have completed the out path before we return try { completeLatch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); } private void checkForSubsystemFailures(OperationContext context, Map<String, ParallelBootTransactionControl> transactionControls, OperationContext.Stage stage) { boolean failureRecorded = false; for (Map.Entry<String, ParallelBootTransactionControl> entry : transactionControls.entrySet()) { ParallelBootTransactionControl txControl = entry.getValue(); if (txControl.transaction == null) { String failureDesc; if (txControl.response.getResponseNode().hasDefined(ModelDescriptionConstants.FAILURE_DESCRIPTION)) { failureDesc = txControl.response.getResponseNode().get(ModelDescriptionConstants.FAILURE_DESCRIPTION).toString(); } else { failureDesc = ControllerLogger.ROOT_LOGGER.subsystemBootOperationFailed(entry.getKey()); } MGMT_OP_LOGGER.error(failureDesc); if (!failureRecorded) { context.getFailureDescription().set(failureDesc); failureRecorded = true; } } else { MGMT_OP_LOGGER.debugf("Stage %s boot ops for subsystem %s succeeded", stage, entry.getKey()); } } } private void notifySubsystemTransactions(final Map<String, ParallelBootTransactionControl> transactionControls, final boolean rollback, final CountDownLatch committedLatch, final OperationContext.Stage stage) { for (Map.Entry<String, ParallelBootTransactionControl> entry : transactionControls.entrySet()) { ParallelBootTransactionControl txControl = entry.getValue(); if (txControl.transaction != null) { if (!rollback) { txControl.transaction.commit(); MGMT_OP_LOGGER.debugf("Committed transaction for %s subsystem %s stage boot operations", entry.getKey(), stage); } else { txControl.transaction.rollback(); MGMT_OP_LOGGER.debugf("Rolled back transaction for %s subsystem %s stage boot operations", entry.getKey(), stage); } } } committedLatch.countDown(); } private OperationStepHandler getRuntimeStep(final Map<String, List<ParsedBootOp>> runtimeOpsBySubsystem) { return new OperationStepHandler() { @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { long start = System.currentTimeMillis(); // make sure the registry lock is held context.getServiceRegistry(true); final Map<String, ParallelBootTransactionControl> transactionControls = new LinkedHashMap<String, ParallelBootTransactionControl>(); final CountDownLatch preparedLatch = new CountDownLatch(runtimeOpsBySubsystem.size()); final CountDownLatch committedLatch = new CountDownLatch(1); final CountDownLatch completeLatch = new CountDownLatch(runtimeOpsBySubsystem.size()); final Thread controllingThread = Thread.currentThread(); if (!(context instanceof AbstractOperationContext)) { throw ControllerLogger.ROOT_LOGGER.operationContextIsNotAbstractOperationContext(); } for (Map.Entry<String, List<ParsedBootOp>> entry : runtimeOpsBySubsystem.entrySet()) { String subsystemName = entry.getKey(); final ParallelBootTransactionControl txControl = new ParallelBootTransactionControl(preparedLatch, committedLatch, completeLatch); transactionControls.put(subsystemName, txControl); // Execute the subsystem's ops in another thread ParallelBootTask subsystemTask = new ParallelBootTask(subsystemName, entry.getValue(), (OperationContextImpl)context, txControl, null, controllingThread, controller, operationId); executor.execute(subsystemTask); } // Wait for all subsystem ops to complete try { preparedLatch.await(); // See if all subsystems succeeded; if not report a failure to context checkForSubsystemFailures(context, transactionControls, OperationContext.Stage.RUNTIME); } catch (InterruptedException e) { context.getFailureDescription().set(new ModelNode().set(ControllerLogger.ROOT_LOGGER.subsystemBootInterrupted())); Thread.currentThread().interrupt(); } if (MGMT_OP_LOGGER.isDebugEnabled()) { long elapsed = System.currentTimeMillis() - start; MGMT_OP_LOGGER.debugf("Ran subsystem runtime operations in [%d] ms", elapsed); } // Continue boot context.completeStep(new OperationContext.ResultHandler() { @Override public void handleResult(OperationContext.ResultAction resultAction, OperationContext context, ModelNode operation) { // Tell all the subsystem tasks the result of the operations notifySubsystemTransactions(transactionControls, resultAction == OperationContext.ResultAction.ROLLBACK, committedLatch, OperationContext.Stage.MODEL); // Make sure all the subsystems have completed the out path before we return try { completeLatch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); } }; } private class ParallelBootTask implements Runnable { private final String subsystemName; private final List<ParsedBootOp> bootOperations; private final OperationContextImpl primaryContext; private final OperationContext.Stage executionStage; private final ParallelBootTransactionControl transactionControl; private final List<ParsedBootOp> runtimeOps; private final Thread controllingThread; private final ModelControllerImpl controller; private final int lockId; public ParallelBootTask(final String subsystemName, final List<ParsedBootOp> bootOperations, final OperationContextImpl primaryContext, final ParallelBootTransactionControl transactionControl, final List<ParsedBootOp> runtimeOps, final Thread controllingThread, final ModelControllerImpl controller, final int lockId) { this.subsystemName = subsystemName; this.bootOperations = bootOperations; this.primaryContext = primaryContext; this.executionStage = primaryContext.getCurrentStage(); this.transactionControl = transactionControl; this.runtimeOps = runtimeOps; this.controllingThread = controllingThread; this.controller = controller; this.lockId = lockId; } @Override public void run() { boolean interrupted = false; ParallelBootOperationContext operationContext = null; try { // TODO Elytron - We probably need a way to stop repeating this. final SecurityDomain bootSecurityDomain = SecurityDomain.builder() .setDefaultRealmName("Empty") .addRealm("Empty", SecurityRealm.EMPTY_REALM).build() .build(); if(bootOperations == null || bootOperations.isEmpty()) { transactionControl.operationPrepared(new ModelController.OperationTransaction() { @Override public void commit() {} @Override public void rollback() {} }, new ModelNode()); return; } operationContext = new ParallelBootOperationContext(transactionControl, processState, primaryContext, runtimeOps, controllingThread, controller, lockId, controller.getAuditLogger(), controller.getManagementModel().getRootResource(), extraValidationStepHandler, bootSecurityDomain::getAnonymousSecurityIdentity); for (ParsedBootOp op : bootOperations) { final OperationStepHandler osh = op.handler == null ? rootRegistration.getOperationHandler(op.address, op.operationName) : op.handler; operationContext.addStep(op.response, op.operation, osh, executionStage); } operationContext.executeOperation(); } catch (Throwable t) { interrupted = (t instanceof InterruptedException); MGMT_OP_LOGGER.failedSubsystemBootOperations(t, subsystemName); if (!transactionControl.signalled) { ModelNode failure = new ModelNode(); failure.get(ModelDescriptionConstants.SUCCESS).set(false); failure.get(ModelDescriptionConstants.FAILURE_DESCRIPTION).set(t.toString()); transactionControl.operationFailed(failure); } } finally { if (!transactionControl.signalled) { for (ParsedBootOp op : bootOperations) { if (op.response.hasDefined(ModelDescriptionConstants.SUCCESS) && !op.response.get(ModelDescriptionConstants.SUCCESS).asBoolean()) { transactionControl.operationFailed(op.response); break; } } if (!transactionControl.signalled) { ModelNode failure = new ModelNode(); failure.get(ModelDescriptionConstants.SUCCESS).set(false); failure.get(ModelDescriptionConstants.FAILURE_DESCRIPTION).set(ControllerLogger.ROOT_LOGGER.subsystemBootOperationFailedExecuting(subsystemName)); transactionControl.operationFailed(failure); } } else { transactionControl.operationCompleted(transactionControl.response); } if (operationContext != null) { operationContext.close(); } if (interrupted) { Thread.currentThread().interrupt(); } } } } private static class ParallelBootTransactionControl implements ProxyController.ProxyOperationControl { private final CountDownLatch preparedLatch; private final CountDownLatch committedLatch; private final CountDownLatch completeLatch; private OperationResponse response; private ModelController.OperationTransaction transaction; private boolean signalled; public ParallelBootTransactionControl(CountDownLatch preparedLatch, CountDownLatch committedLatch, CountDownLatch completeLatch) { this.preparedLatch = preparedLatch; this.committedLatch = committedLatch; this.completeLatch = completeLatch; } @Override public void operationFailed(ModelNode response) { if (!signalled) { this.response = OperationResponse.Factory.createSimple(response); preparedLatch.countDown(); completeLatch.countDown(); signalled = true; } } @Override public void operationPrepared(ModelController.OperationTransaction transaction, ModelNode result) { if (!signalled) { this.transaction = transaction; preparedLatch.countDown(); signalled = true; try { committedLatch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw ControllerLogger.ROOT_LOGGER.transactionInterrupted(); } } } @Override public void operationCompleted(OperationResponse response) { this.response = response; completeLatch.countDown(); } } }