/* * JBoss, Home of Professional Open Source. * Copyright 2012, 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.test; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.locks.ReentrantLock; import org.jboss.as.controller.MockModelController; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.client.OperationAttachments; import org.jboss.as.controller.client.OperationMessageHandler; import org.jboss.as.controller.client.OperationResponse; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.remote.BlockingQueueOperationListener; import org.jboss.as.controller.remote.ResponseAttachmentInputStreamSupport; import org.jboss.as.controller.remote.TransactionalOperationImpl; import org.jboss.as.controller.remote.TransactionalProtocolClient; import org.jboss.as.controller.remote.TransactionalProtocolHandlers; import org.jboss.as.controller.remote.TransactionalProtocolOperationHandler; import org.jboss.as.controller.support.ChannelServer; import org.jboss.as.protocol.ProtocolConnectionConfiguration; import org.jboss.as.protocol.mgmt.ManagementChannelHandler; import org.jboss.as.protocol.mgmt.ManagementClientChannelStrategy; import org.jboss.as.protocol.mgmt.ManagementRequestHandlerFactory; import org.jboss.dmr.ModelNode; import org.jboss.remoting3.Channel; import org.jboss.remoting3.Connection; import org.jboss.remoting3.OpenListener; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.xnio.FutureResult; import org.xnio.IoFuture; import org.xnio.OptionMap; /** * Transactional protocol tests. * * @author Emanuel Muckenhuber */ public class TransactionalProtocolClientTestCase { private static final String ENDPOINT_NAME = "endpoint"; private static final String URI_SCHEME = "remote"; private static final String TEST_CHANNEL = "Test-Channel"; private static final int PORT = 32123; private static final int CLIENTS = 10; private static final ModelNode SUCCESS = new ModelNode(); private static final ModelNode FAILURE = new ModelNode(); static { SUCCESS.get(ModelDescriptionConstants.OUTCOME).set(ModelDescriptionConstants.SUCCESS); SUCCESS.get(ModelDescriptionConstants.RESULT).set("test"); FAILURE.get(ModelDescriptionConstants.OUTCOME).set(ModelDescriptionConstants.FAILED); FAILURE.get(ModelDescriptionConstants.FAILURE_DESCRIPTION).set(""); FAILURE.get(ModelDescriptionConstants.ROLLED_BACK).set(true); } private ChannelServer channelServer; private IoFuture<Connection> futureConnection; private List<Channel> channels = new ArrayList<Channel>(); // Only a single thread to be used on the client private final ExecutorService clientExecutor = Executors.newSingleThreadExecutor(); private final ExecutorService remoteExecutors = Executors.newCachedThreadPool(); private final BlockingQueue<MockController> transferQueue = new LinkedBlockingQueue<MockController>(); @Before public void startChannelServer() throws Exception { final ChannelServer.Configuration configuration = new ChannelServer.Configuration(); configuration.setEndpointName(ENDPOINT_NAME); configuration.setUriScheme(URI_SCHEME); configuration.setBindAddress(new InetSocketAddress("127.0.0.1", PORT)); channelServer = ChannelServer.create(configuration); // channelServer.addChannelOpenListener(TEST_CHANNEL, new OpenListener() { @Override public void channelOpened(final Channel channel) { final MockController controller = new MockController(); final ManagementClientChannelStrategy strategy = ManagementClientChannelStrategy.create(channel); final ManagementChannelHandler channels = new ManagementChannelHandler(strategy, remoteExecutors); final ManagementRequestHandlerFactory handlerFactory = new TransactionalProtocolOperationHandler(controller, channels, new ResponseAttachmentInputStreamSupport()); channels.addHandlerFactory(handlerFactory); transferQueue.offer(controller); channel.addCloseHandler(channels); channel.receiveMessage(channels.getReceiver()); } @Override public void registrationTerminated() { // } }); final ProtocolConnectionConfiguration connectionConfig = ProtocolConnectionConfiguration.create(channelServer.getEndpoint(), new URI("" + URI_SCHEME + "://127.0.0.1:" + PORT + "")); connectionConfig.setEndpoint(channelServer.getEndpoint()); // futureConnection = connectionConfig.getEndpoint().getConnection(connectionConfig.getUri()); } @After public void stopChannels() throws Exception { for(final Channel channel : channels) { channel.close(); } channelServer.close(); channelServer = null; } @Test public void testSimpleRequest() throws Exception { // final TestUpdateWrapper update = createTestClient(0); final TransactionalProtocolClient client = update.getClient(); final BlockingOperationListener listener = new BlockingOperationListener(); client.execute(listener, update); // final TransactionalProtocolClient.PreparedOperation<TestUpdateWrapper> prepared = listener.retrievePreparedOperation(); assert ! prepared.isFailed(); assert ! prepared.isDone(); // Commit prepared.commit(); // Block until we have the result final ModelNode result = prepared.getFinalResult().get().getResponseNode(); assert result.hasDefined(ModelDescriptionConstants.OUTCOME); assert prepared.isDone(); } @Test public void testCancelBeforePrepared() throws Exception { final BlockingOperationListener listener = new BlockingOperationListener(); final CountDownLatch latch = new CountDownLatch(1); final TestOperationHandler handler = new TestOperationHandler() { @Override public void execute(ModelNode operation, OperationMessageHandler handler, OperationAttachments attachments) throws Exception { try { synchronized (this) { latch.countDown(); wait(); } } catch (InterruptedException e) { // } } }; // final TestUpdateWrapper wrapper = createTestClient(0, handler); final Future<OperationResponse> futureResult = wrapper.execute(listener); latch.await(); // Now the server side should for latch to countDown futureResult.cancel(false); // OperationContext.ResultAction action = null; while(action == null) { action = wrapper.getResultAction(); Thread.sleep(15); } wrapper.assertResultAction(OperationContext.ResultAction.ROLLBACK); final ModelNode result = futureResult.get().getResponseNode(); Assert.assertEquals(FAILURE, result); } @Test public void testClosePrepared() throws Exception { final BlockingOperationListener listener = new BlockingOperationListener(); final TestOperationHandler handler = new TestOperationHandler() { @Override public void execute(ModelNode operation, OperationMessageHandler handler, OperationAttachments attachments) throws Exception { // } }; final TestUpdateWrapper wrapper = createTestClient(0, handler); final Future<OperationResponse> futureResult = wrapper.execute(listener); listener.retrievePreparedOperation(); futureConnection.get().close(); try { futureResult.get(); Assert.fail(); } catch (CancellationException expected) { // } } @Test public void testConcurrentGroup() throws Exception { // final BlockingOperationListener listener = new BlockingOperationListener(CLIENTS); final List<TestUpdateWrapper> wrappers = createTestClients(CLIENTS); // First execute all operations for(final TestUpdateWrapper update : wrappers) { final TransactionalProtocolClient client = update.getClient(); client.execute(listener, update); } // Now wait for all operations to be prepared final List<TransactionalProtocolClient.PreparedOperation<TestUpdateWrapper>> preparedOps = new ArrayList<TransactionalProtocolClient.PreparedOperation<TestUpdateWrapper>>(); for(int i = 0; i < CLIENTS; i++) { // Wait for 10 prepared results final TransactionalProtocolClient.PreparedOperation<TestUpdateWrapper> prepared = listener.retrievePreparedOperation(); assert ! prepared.isFailed(); assert ! prepared.isDone(); // Check that the controller is locked in the prepared state final MockController controller = prepared.getOperation().getController(); assert controller.lock.isLocked(); // Commit all prepared.commit(); preparedOps.add(prepared); } final ModelNode result = new ModelNode(); // Now we just need to get the final results for(final TransactionalProtocolClient.PreparedOperation<TestUpdateWrapper> prepared : preparedOps) { // Block until we have the result final ModelNode finalResult = prepared.getFinalResult().get().getResponseNode(); final MockController controller = prepared.getOperation().getController(); assert prepared.isDone(); assert ! controller.lock.isLocked(); final int id = prepared.getOperation().getId(); result.get("" + id).set(finalResult); } for(int i = 0; i < CLIENTS; i++) { assert result.hasDefined("" + i); assert result.get("" + i).hasDefined(ModelDescriptionConstants.OUTCOME); } } @Test public void testSequentialGroup() throws Exception { // final ModelNode result = new ModelNode(); final BlockingOperationListener listener = new BlockingOperationListener(CLIENTS); final List<TestUpdateWrapper> wrappers = createTestClients(CLIENTS); for(final TestUpdateWrapper update : wrappers) { // Execute a single operation final TransactionalProtocolClient client = update.getClient(); client.execute(listener, update); // Wait for the operation to reach the prepared state final TransactionalProtocolClient.PreparedOperation<TestUpdateWrapper> prepared = listener.retrievePreparedOperation(); assert ! prepared.isFailed(); assert ! prepared.isDone(); // Check that the controller is locked in the prepared state final MockController controller = update.getController(); assert controller.lock.isLocked(); // Commit prepared.commit(); // Block until we have the result final ModelNode finalResult = prepared.getFinalResult().get().getResponseNode(); assert prepared.isDone(); assert ! controller.lock.isLocked(); final int id = prepared.getOperation().getId(); result.get("" + id).set(finalResult); // onto to the next operation } for(int i = 0; i < CLIENTS; i++) { assert result.hasDefined("" + i); assert result.get("" + i).hasDefined(ModelDescriptionConstants.OUTCOME); } } /** * Create a bunch of test operation clients. * * @param count the amount of clients to be created * @return the create clients * @throws Exception */ List<TestUpdateWrapper> createTestClients(final int count) throws Exception { final List<TestUpdateWrapper> wrappers = new ArrayList<TestUpdateWrapper>(); for(int j = 0; j < count; j++) { wrappers.add(createTestClient(j)); } return wrappers; } TestUpdateWrapper createTestClient(final int id) throws Exception { return createTestClient(id, null); } /** * Create a single test client. * * @param id the client id * @return the client * @throws Exception */ TestUpdateWrapper createTestClient(final int id, final TestOperationHandler handler) throws Exception { final TransactionalProtocolClient client = createClient(); final MockController controller = transferQueue.take(); controller.handler = handler; return new TestUpdateWrapper(id, client, controller); } /** * Create the protocol client to talk to the remote controller. * * @return the client * @throws Exception */ TransactionalProtocolClient createClient() throws Exception { final Connection connection = futureConnection.get(); final IoFuture<Channel> channelIoFuture = connection.openChannel(TEST_CHANNEL, OptionMap.EMPTY); return createClient(channelIoFuture.get()); } /** * Create the protocol client to talk to the remote controller. * * @param channel the remoting channel * @return the client * @throws Exception */ TransactionalProtocolClient createClient(final Channel channel) { channels.add(channel); final ManagementClientChannelStrategy strategy = ManagementClientChannelStrategy.create(channel); final ManagementChannelHandler channelAssociation = new ManagementChannelHandler(strategy, clientExecutor); final TransactionalProtocolClient client = TransactionalProtocolHandlers.createClient(channelAssociation); channel.addCloseHandler(channelAssociation); channel.receiveMessage(channelAssociation.getReceiver()); return client; } /** * A basic blocking operation listener implementation */ static class BlockingOperationListener extends BlockingQueueOperationListener<TestUpdateWrapper> { BlockingOperationListener() { this(1); } BlockingOperationListener(int size) { super(new ArrayBlockingQueue<TransactionalProtocolClient.PreparedOperation<TestUpdateWrapper>>(size, true)); } } /** * Operation wrapper. */ static class TestUpdateWrapper extends TransactionalOperationImpl { private final int id; private final MockController controller; private final TransactionalProtocolClient client; TestUpdateWrapper(final int id, final TransactionalProtocolClient client, final MockController controller) { super(SUCCESS, OperationMessageHandler.DISCARD, OperationAttachments.EMPTY); this.id = id; this.client = client; this.controller = controller; } public int getId() { return id; } public TransactionalProtocolClient getClient() { return client; } public MockController getController() { return controller; } OperationContext.ResultAction getResultAction() { return controller.getAction(); } void assertResultAction(final OperationContext.ResultAction expected) { Assert.assertEquals(expected, getResultAction()); // controller.action = null; } Future<OperationResponse> execute(TransactionalProtocolClient.TransactionalOperationListener<TestUpdateWrapper> listener) throws IOException { return client.execute(listener, this); } } /** * A mock controller */ private static class MockController extends MockModelController { private final ReentrantLock lock = new ReentrantLock(); private final FutureResult<OperationContext.ResultAction> action = new FutureResult<>(); private TestOperationHandler handler; OperationContext.ResultAction getAction() { try { return action.getIoFuture().getInterruptibly(); } catch (Exception e) { throw new RuntimeException(e); } } @Override public ModelNode execute(final ModelNode operation, final OperationMessageHandler messageHandler, final OperationTransactionControl control, final OperationAttachments attachments) { lock.lock(); try { if(handler != null) { try { handler.execute(operation, messageHandler, attachments); } catch (Exception e) { throw new RuntimeException(e); } } control.operationPrepared(new OperationTransaction() { @Override public void commit() { action.setResult(OperationContext.ResultAction.KEEP); } @Override public void rollback() { action.setResult(OperationContext.ResultAction.ROLLBACK); } }, SUCCESS); return getAction() == OperationContext.ResultAction.KEEP ? SUCCESS : FAILURE; } finally { lock.unlock(); } } } private interface TestOperationHandler { void execute(ModelNode operation, OperationMessageHandler handler, OperationAttachments attachments) throws Exception; } }