/* * 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.remote; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CANCELLED; 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.OP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME; import java.io.IOException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.jboss.as.controller.BlockingTimeout; import org.jboss.as.controller.ModelController; import org.jboss.as.controller.ModelVersion; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.ProxyController; import org.jboss.as.controller.ProxyOperationAddressTranslator; 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.logging.ControllerLogger; import org.jboss.as.protocol.mgmt.ManagementChannelHandler; import org.jboss.dmr.ModelNode; import org.jboss.threads.AsyncFuture; /** * Remote {@link ProxyController} implementation. * * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> * @author Emanuel Muckenhuber */ public class RemoteProxyController implements ProxyController { private final PathAddress pathAddress; private final ProxyOperationAddressTranslator addressTranslator; private final TransactionalProtocolClient client; private final ModelVersion targetKernelVersion; private RemoteProxyController(final TransactionalProtocolClient client, final PathAddress pathAddress, final ProxyOperationAddressTranslator addressTranslator, final ModelVersion targetKernelVersion) { this.client = client; this.pathAddress = pathAddress; this.addressTranslator = addressTranslator; this.targetKernelVersion = targetKernelVersion; } /** * Create a new remote proxy controller. * * @param client the transactional protocol client * @param pathAddress the path address * @param addressTranslator the address translator * @param targetKernelVersion the {@link ModelVersion} of the kernel management API exposed by the proxied process * @return the proxy controller */ public static RemoteProxyController create(final TransactionalProtocolClient client, final PathAddress pathAddress, final ProxyOperationAddressTranslator addressTranslator, final ModelVersion targetKernelVersion) { return new RemoteProxyController(client, pathAddress, addressTranslator, targetKernelVersion); } /** * Creates a new remote proxy controller using an existing channel. * * @param channelAssociation the channel association * @param pathAddress the address within the model of the created proxy controller * @param addressTranslator the translator to use translating the address for the remote proxy * @return the proxy controller * * @deprecated only present for test case use */ @Deprecated public static RemoteProxyController create(final ManagementChannelHandler channelAssociation, final PathAddress pathAddress, final ProxyOperationAddressTranslator addressTranslator) { final TransactionalProtocolClient client = TransactionalProtocolHandlers.createClient(channelAssociation); // the remote proxy return create(client, pathAddress, addressTranslator, ModelVersion.CURRENT); } /** * Get the underlying transactional protocol client. * * @return the protocol client */ public TransactionalProtocolClient getTransactionalProtocolClient() { return client; } /** {@inheritDoc} */ @Override public PathAddress getProxyNodeAddress() { return pathAddress; } /** {@inheritDoc} */ @Override public void execute(final ModelNode original, final OperationMessageHandler messageHandler, final ProxyOperationControl control, final OperationAttachments attachments, final BlockingTimeout blockingTimeout) { // Add blocking support to adhere to the proxy controller API contracts final CountDownLatch completed = new CountDownLatch(1); final BlockingQueue<TransactionalProtocolClient.PreparedOperation<TransactionalProtocolClient.Operation>> queue = new ArrayBlockingQueue<TransactionalProtocolClient.PreparedOperation<TransactionalProtocolClient.Operation>>(1, true); final TransactionalProtocolClient.TransactionalOperationListener<TransactionalProtocolClient.Operation> operationListener = new TransactionalProtocolClient.TransactionalOperationListener<TransactionalProtocolClient.Operation>() { @Override public void operationPrepared(TransactionalProtocolClient.PreparedOperation<TransactionalProtocolClient.Operation> prepared) { if(! queue.offer(prepared)) { prepared.rollback(); } } @Override public void operationFailed(TransactionalProtocolClient.Operation operation, ModelNode result) { try { queue.offer(new BlockingQueueOperationListener.FailedOperation<TransactionalProtocolClient.Operation>(operation, result)); } finally { // This might not be needed? completed.countDown(); } } @Override public void operationComplete(TransactionalProtocolClient.Operation operation, OperationResponse response) { try { control.operationCompleted(response); } finally { // Make sure the handler is called before commit/rollback returns completed.countDown(); } } }; AsyncFuture<OperationResponse> futureResult = null; try { // Translate the operation final PathAddress targetAddress = PathAddress.pathAddress(original.get(OP_ADDR)); final ModelNode translated = translateOperationForProxy(original, targetAddress); // Execute the operation ControllerLogger.MGMT_OP_LOGGER.tracef("Executing %s for %s", translated.get(OP).asString(), getProxyNodeAddress()); futureResult = client.execute(operationListener, translated, messageHandler, attachments); // Wait for the prepared response final TransactionalProtocolClient.PreparedOperation<TransactionalProtocolClient.Operation> prepared; if (blockingTimeout == null) { prepared = queue.take(); } else { long timeout = blockingTimeout.getProxyBlockingTimeout(targetAddress, this); prepared = queue.poll(timeout, TimeUnit.MILLISECONDS); if (prepared == null) { blockingTimeout.proxyTimeoutDetected(targetAddress); futureResult.asyncCancel(true); ModelNode response = getTimeoutResponse(translated.get(OP).asString(), timeout); control.operationFailed(response); ControllerLogger.MGMT_OP_LOGGER.info(response.get(FAILURE_DESCRIPTION).asString()); return; } } if(prepared.isFailed()) { // If the operation failed, there is nothing more to do control.operationFailed(prepared.getPreparedResult()); return; } // Send the prepared notification and wrap the OperationTransaction to block on commit/rollback final AsyncFuture cancellable = futureResult; control.operationPrepared(new ModelController.OperationTransaction() { @Override public void commit() { prepared.commit(); awaitCompletion(); } @Override public void rollback() { prepared.rollback(); awaitCompletion(); } private void awaitCompletion() { try { // Await the completed notification if (blockingTimeout == null) { completed.await(); } else { long timeout = blockingTimeout.getProxyBlockingTimeout(targetAddress, RemoteProxyController.this); if (!completed.await(timeout, TimeUnit.MILLISECONDS)) { cancellable.asyncCancel(true); blockingTimeout.proxyTimeoutDetected(targetAddress); ControllerLogger.MGMT_OP_LOGGER.timeoutAwaitingFinalResponse(translated.get(OP).asString(), getProxyNodeAddress(), timeout); } } } catch (InterruptedException e) { cancellable.asyncCancel(true); ControllerLogger.MGMT_OP_LOGGER.interruptedAwaitingFinalResponse(translated.get(OP).asString(), getProxyNodeAddress()); Thread.currentThread().interrupt(); } catch (Exception e) { // ignore } } }, prepared.getPreparedResult()); } catch (InterruptedException e) { if (futureResult != null) { // it won't be null, as IE can only be thrown after it's assigned ControllerLogger.MGMT_OP_LOGGER.interruptedAwaitingInitialResponse(original.get(OP).asString(), getProxyNodeAddress()); // Cancel the operation futureResult.asyncCancel(true); } control.operationFailed(getCancelledResponse()); Thread.currentThread().interrupt(); } catch (IOException e) { final ModelNode result = new ModelNode(); result.get(OUTCOME).set(FAILED); result.get(FAILURE_DESCRIPTION).set(e.getLocalizedMessage()); // Notify the proxy control that the operation failed control.operationFailed(result); } } @Override public ModelVersion getKernelModelVersion() { return targetKernelVersion; } /** * Translate the operation address. * * @param op the operation * @return the new operation */ public ModelNode translateOperationForProxy(final ModelNode op) { return translateOperationForProxy(op, PathAddress.pathAddress(op.get(OP_ADDR))); } private ModelNode translateOperationForProxy(final ModelNode op, PathAddress targetAddress) { final PathAddress translated = addressTranslator.translateAddress(targetAddress); if (targetAddress.equals(translated)) { return op; } final ModelNode proxyOp = op.clone(); proxyOp.get(OP_ADDR).set(translated.toModelNode()); return proxyOp; } private static ModelNode getCancelledResponse() { ModelNode result = new ModelNode(); result.get(OUTCOME).set(CANCELLED); result.get(FAILURE_DESCRIPTION).set(ControllerLogger.ROOT_LOGGER.operationCancelled()); return result; } private ModelNode getTimeoutResponse(String operation, long timeout) { ModelNode response = new ModelNode(); response.get(OUTCOME).set(FAILED); response.get(FAILURE_DESCRIPTION).set(ControllerLogger.ROOT_LOGGER.proxiedOperationTimedOut(operation, pathAddress, timeout)); return response; } }