/* * JBoss, Home of Professional Open Source. * Copyright 2014, 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.test.integration.management.extension.blocker; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM; import java.util.Set; import java.util.logging.Logger; import org.jboss.as.controller.AbstractAddStepHandler; import org.jboss.as.controller.AttributeDefinition; import org.jboss.as.controller.Extension; import org.jboss.as.controller.ExtensionContext; import org.jboss.as.controller.ModelOnlyRemoveStepHandler; import org.jboss.as.controller.ModelOnlyWriteAttributeHandler; import org.jboss.as.controller.ModelVersion; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationDefinition; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.OperationStepHandler; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.PathElement; import org.jboss.as.controller.ProcessType; import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; import org.jboss.as.controller.SimpleOperationDefinitionBuilder; import org.jboss.as.controller.SimpleResourceDefinition; import org.jboss.as.controller.SubsystemRegistration; import org.jboss.as.controller.client.helpers.MeasurementUnit; import org.jboss.as.controller.descriptions.NonResolvingResourceDescriptionResolver; import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler; import org.jboss.as.controller.operations.validation.EnumValidator; import org.jboss.as.controller.parsing.ExtensionParsingContext; import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.controller.registry.Resource; import org.jboss.as.server.ServerEnvironment; import org.jboss.as.test.integration.management.extension.EmptySubsystemParser; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import org.jboss.msc.service.Service; import org.jboss.msc.service.ServiceName; import org.jboss.msc.service.StartContext; import org.jboss.msc.service.StartException; import org.jboss.msc.service.StopContext; /** * Extension that can block threads. * * @author Brian Stansberry (c) 2014 Red Hat Inc. */ public class BlockerExtension implements Extension { public static final String MODULE_NAME = "org.wildfly.extension.blocker-test"; public static final String SUBSYSTEM_NAME = "blocker-test"; public static final AttributeDefinition CALLER = SimpleAttributeDefinitionBuilder.create("caller", ModelType.STRING, true) .setDefaultValue(new ModelNode("unknown")).build(); public static final AttributeDefinition TARGET_HOST = SimpleAttributeDefinitionBuilder.create(HOST, ModelType.STRING, true).build(); public static final AttributeDefinition TARGET_SERVER = SimpleAttributeDefinitionBuilder.create(SERVER, ModelType.STRING, true).build(); public static final AttributeDefinition BLOCK_POINT = SimpleAttributeDefinitionBuilder.create("block-point", ModelType.STRING) .setValidator(EnumValidator.create(BlockPoint.class, false, false)) .build(); public static final AttributeDefinition BLOCK_TIME = SimpleAttributeDefinitionBuilder.create("block-time", ModelType.LONG, true) .setDefaultValue(new ModelNode(20000)) .setMeasurementUnit(MeasurementUnit.MILLISECONDS) .build(); public static final AttributeDefinition FOO = SimpleAttributeDefinitionBuilder.create("foo", ModelType.BOOLEAN, true).build(); public static final String REGISTERED_MESSAGE = "Registered blocker-test operations"; private static final EmptySubsystemParser PARSER = new EmptySubsystemParser("urn:wildfly:extension:blocker-test:1.0"); private static final Logger log = Logger.getLogger(BlockerExtension.class.getCanonicalName()); @Override public void initialize(ExtensionContext context) { SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, ModelVersion.create(1)); subsystem.registerSubsystemModel(new BlockerSubsystemResourceDefinition(context.getProcessType() == ProcessType.HOST_CONTROLLER)); subsystem.registerXMLElementWriter(PARSER); } @Override public void initializeParsers(ExtensionParsingContext context) { context.setSubsystemXmlMapping(SUBSYSTEM_NAME, PARSER.getNamespace(), PARSER); } private static class BlockerSubsystemResourceDefinition extends SimpleResourceDefinition { private final boolean forHost; private BlockerSubsystemResourceDefinition(boolean forHost) { super(PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME), new NonResolvingResourceDescriptionResolver(), new AbstractAddStepHandler(), ModelOnlyRemoveStepHandler.INSTANCE); this.forHost = forHost; } @Override public void registerOperations(ManagementResourceRegistration resourceRegistration) { super.registerOperations(resourceRegistration); resourceRegistration.registerOperationHandler(BlockHandler.DEFINITION, new BlockHandler()); if (forHost) { resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE); } // Don't remove this as some tests check for it in the log log.info(REGISTERED_MESSAGE); } @Override public void registerAttributes(ManagementResourceRegistration resourceRegistration) { super.registerAttributes(resourceRegistration); resourceRegistration.registerReadWriteAttribute(FOO, null, new ModelOnlyWriteAttributeHandler(FOO)); } } public enum BlockPoint { MODEL, RUNTIME, SERVICE_START, SERVICE_STOP, VERIFY, COMMIT, ROLLBACK } private static class BlockHandler implements OperationStepHandler { private static final OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder("block", new NonResolvingResourceDescriptionResolver()) .setParameters(CALLER, TARGET_HOST, TARGET_SERVER, BLOCK_POINT, BLOCK_TIME) .setRuntimeOnly() .build(); @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { ModelNode targetServer = TARGET_SERVER.resolveModelAttribute(context, operation); ModelNode targetHost = TARGET_HOST.resolveModelAttribute(context, operation); final BlockPoint blockPoint = BlockPoint.valueOf(BLOCK_POINT.resolveModelAttribute(context, operation).asString()); log.info("block requested by " + CALLER.resolveModelAttribute(context, operation).asString() + " for " + targetHost.asString() + "/" + targetServer.asString() + "(" + blockPoint + ")"); boolean forMe = false; if (context.getProcessType() == ProcessType.STANDALONE_SERVER) { forMe = true; } else { context.readResourceForUpdate(PathAddress.EMPTY_ADDRESS); // To help with WFCORE-263 testing, get the exclusive lock on this process Resource rootResource = context.readResourceFromRoot(PathAddress.EMPTY_ADDRESS); if (targetServer.isDefined()) { if (context.getProcessType().isServer()) { String name = System.getProperty(ServerEnvironment.SERVER_NAME); forMe = targetServer.asString().equals(name); } } else if (context.getProcessType() == ProcessType.HOST_CONTROLLER) { Set<String> hosts = rootResource.getChildrenNames(HOST); String name; if (hosts.size() > 1) { name = "master"; } else { name = hosts.iterator().next(); } if (!targetHost.isDefined()) { throw new OperationFailedException("target-host required"); } forMe = targetHost.asString().equals(name); } } if (forMe) { final long blockTime = BLOCK_TIME.resolveModelAttribute(context, operation).asLong(); log.info("will block at " + blockPoint + " for " + blockTime); switch (blockPoint) { case MODEL: { block(blockTime); break; } case RUNTIME: { context.addStep(new BlockStep(blockTime), OperationContext.Stage.RUNTIME); break; } case SERVICE_START: { context.addStep(new OperationStepHandler() { @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { BlockingService service = new BlockingService(blockTime, true); context.getServiceTarget().addService(BlockingService.SERVICE_NAME, service).install(); context.completeStep(new OperationContext.ResultHandler() { @Override public void handleResult(OperationContext.ResultAction resultAction, OperationContext context, ModelNode operation) { log.info("BlockingService step completed: result = " + resultAction); context.removeService(BlockingService.SERVICE_NAME); } }); } }, OperationContext.Stage.RUNTIME); break; } //This is used by PreparedResponseTestCase where we only add the service which will be stopped by stopping/reloading the server in the test. //This might not be the original intent of the BLockerExtension so be careful if you want to change it. case SERVICE_STOP: { context.addStep(new OperationStepHandler() { @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { BlockingService service = new BlockingService(blockTime, false); context.getServiceTarget().addService(BlockingService.SERVICE_NAME, service).install(); } }, OperationContext.Stage.RUNTIME); break; } case VERIFY: { context.addStep(new BlockStep(blockTime), OperationContext.Stage.VERIFY); break; } case ROLLBACK: context.addStep(new OperationStepHandler() { @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { context.getFailureDescription().set("rollback"); context.setRollbackOnly(); } }, OperationContext.Stage.MODEL); break; case COMMIT: break; default: throw new IllegalStateException(blockPoint.toString()); } context.completeStep(new OperationContext.ResultHandler() { @Override public void handleResult(OperationContext.ResultAction resultAction, OperationContext context, ModelNode operation) { if ((blockPoint == BlockPoint.COMMIT && resultAction == OperationContext.ResultAction.KEEP) || (blockPoint == BlockPoint.ROLLBACK && resultAction == OperationContext.ResultAction.ROLLBACK)) { block(blockTime); } } }); } } private static void block(long time) { try { log.info("blocking"); Thread.sleep(time); } catch (InterruptedException e) { log.info("interrupted"); throw new RuntimeException(e); } } private static class BlockStep implements OperationStepHandler { private final long blockTime; private BlockStep(long blockTime) { this.blockTime = blockTime; } @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { block(blockTime); } } } private static class BlockingService implements Service<BlockingService> { private static final ServiceName SERVICE_NAME = ServiceName.of("jboss", "test", "blocking-service"); private final long blockTime; private final boolean blockStart; private final Object waitObject = new Object(); private BlockingService(long blockTime, boolean blockStart) { this.blockTime = blockTime; this.blockStart = blockStart; } @Override public void start(final StartContext context) throws StartException { if (blockStart) { // Runnable r = new Runnable() { // @Override // public void run() { try { synchronized (waitObject) { log.info("BlockService blocking in start"); waitObject.wait(blockTime); } context.complete(); } catch (InterruptedException e) { log.info("BlockService interrupted"); // context.failed(new StartException(e)); throw new StartException(e); } // } // }; // Thread thread = new Thread(r); // thread.start(); // context.asynchronous(); } } @Override public void stop(final StopContext context) { if (!blockStart) { try { synchronized (waitObject) { log.info("BlockService blocking in stop"); waitObject.wait(blockTime); } context.complete(); } catch (InterruptedException e) { log.info("BlockService interrupted"); Thread.currentThread().interrupt(); throw new RuntimeException(e); } } else { synchronized (waitObject) { log.info("BlockService Stopping"); waitObject.notifyAll(); } } } @Override public BlockingService getValue() throws IllegalStateException, IllegalArgumentException { return this; } } }