/*
* 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.server.operations;
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.SHUTDOWN;
import java.util.EnumSet;
import org.jboss.as.controller.ControlledProcessState;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.OperationStepHandler;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleOperationDefinition;
import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
import org.jboss.as.controller.access.Action;
import org.jboss.as.controller.access.AuthorizationResult;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.as.process.ExitCodes;
import org.jboss.as.server.SystemExiter;
import org.jboss.as.server.controller.descriptions.ServerDescriptions;
import org.jboss.as.server.logging.ServerLogger;
import org.jboss.as.server.suspend.OperationListener;
import org.jboss.as.server.suspend.SuspendController;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceRegistry;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jboss.as.controller.client.helpers.MeasurementUnit;
/**
* Handler that shuts down the standalone server.
*
* @author Jason T. Greene
*/
public class ServerShutdownHandler implements OperationStepHandler {
protected static final SimpleAttributeDefinition RESTART = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.RESTART, ModelType.BOOLEAN)
.setDefaultValue(new ModelNode(false))
.setAllowNull(true)
.build();
protected static final SimpleAttributeDefinition TIMEOUT = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.TIMEOUT, ModelType.INT)
.setDefaultValue(new ModelNode(0))
.setAllowNull(true)
.setMeasurementUnit(MeasurementUnit.SECONDS)
.build();
public static final SimpleOperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(ModelDescriptionConstants.SHUTDOWN, ServerDescriptions.getResourceDescriptionResolver())
.setParameters(RESTART, TIMEOUT)
.setRuntimeOnly()
.build();
private final ControlledProcessState processState;
public ServerShutdownHandler(ControlledProcessState processState) {
this.processState = processState;
}
/**
* {@inheritDoc}
*/
@Override
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
final boolean restart = RESTART.resolveModelAttribute(context, operation).asBoolean();
final int timeout = TIMEOUT.resolveModelAttribute(context, operation).asInt(); //in seconds, need to convert to ms
// Acquire the controller lock to prevent new write ops and wait until current ones are done
context.acquireControllerLock();
context.addStep(new OperationStepHandler() {
@Override
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
// WFLY-2741 -- DO NOT call context.getServiceRegistry(true) as that will trigger blocking for
// service container stability and one use case for this op is to recover from a
// messed up service container from a previous op. Instead just ask for authorization.
// Note that we already have the exclusive lock, so we are just skipping waiting for stability.
// If another op that is a step in a composite step with this op needs to modify the container
// it will have to wait for container stability, so skipping this only matters for the case
// where this step is the only runtime change.
// context.getServiceRegistry(true);
AuthorizationResult authorizationResult = context.authorize(operation, EnumSet.of(Action.ActionEffect.WRITE_RUNTIME));
if (authorizationResult.getDecision() == AuthorizationResult.Decision.DENY) {
throw ControllerLogger.ACCESS_LOGGER.unauthorized(operation.get(OP).asString(),
PathAddress.pathAddress(operation.get(OP_ADDR)), authorizationResult.getExplanation());
}
context.completeStep(new OperationContext.ResultHandler() {
@Override
public void handleResult(OperationContext.ResultAction resultAction, OperationContext context, ModelNode operation) {
if(resultAction == OperationContext.ResultAction.KEEP) {
//even if the timeout is zero we still pause the server
//to stop new requests being accepted as it is shutting down
final ShutdownAction shutdown = new ShutdownAction(getOperationName(operation), restart);
final ServiceRegistry registry = context.getServiceRegistry(false);
final ServiceController<SuspendController> suspendControllerServiceController = (ServiceController<SuspendController>) registry.getRequiredService(SuspendController.SERVICE_NAME);
final SuspendController suspendController = suspendControllerServiceController.getValue();
OperationListener listener = new OperationListener() {
@Override
public void suspendStarted() {
}
@Override
public void complete() {
suspendController.removeListener(this);
shutdown.shutdown();
}
@Override
public void cancelled() {
suspendController.removeListener(this);
shutdown.cancel();
}
@Override
public void timeout() {
suspendController.removeListener(this);
shutdown.shutdown();
}
};
suspendController.addListener(listener);
suspendController.suspend(timeout > 0 ? timeout * 1000 : timeout);
if(timeout == 0) {
shutdown.shutdown();
}
}
}
});
}
}, OperationContext.Stage.RUNTIME);
}
private static String getOperationName(ModelNode op) {
return op.hasDefined(OP) ? op.get(OP).asString() : SHUTDOWN;
}
private final class ShutdownAction extends AtomicBoolean {
private final String op;
private final boolean restart;
private ShutdownAction(String op, boolean restart) {
this.op = op;
this.restart = restart;
}
void cancel() {
compareAndSet(false, true);
}
void shutdown() {
if(compareAndSet(false, true)) {
processState.setStopping();
final Thread thread = new Thread(new Runnable() {
public void run() {
int exitCode = restart ? ExitCodes.RESTART_PROCESS_FROM_STARTUP_SCRIPT : ExitCodes.NORMAL;
SystemExiter.logAndExit(new SystemExiter.ExitLogger() {
@Override
public void logExit() {
ServerLogger.ROOT_LOGGER.shuttingDownInResponseToManagementRequest(op);
}
}, exitCode);
}
});
// The intention is that this shutdown is graceful, and so the client gets a reply.
// At the time of writing we did not yet have graceful shutdown.
thread.setName("Management Triggered Shutdown");
thread.start();
}
}
}
}