/*
* 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.host.controller;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DOMAIN_MODEL;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.IGNORED_RESOURCES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.IGNORED_RESOURCE_TYPE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATION_HEADERS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS;
import static org.jboss.as.host.controller.logging.HostControllerLogger.ROOT_LOGGER;
import java.io.DataInput;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.SSLHandshakeException;
import javax.security.sasl.SaslException;
import org.jboss.as.controller.Cancellable;
import org.jboss.as.controller.HashUtil;
import org.jboss.as.controller.ModelController;
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.PathElement;
import org.jboss.as.controller.ProxyController;
import org.jboss.as.controller.RunningMode;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.client.Operation;
import org.jboss.as.controller.client.OperationBuilder;
import org.jboss.as.controller.client.OperationMessageHandler;
import org.jboss.as.controller.client.OperationResponse;
import org.jboss.as.controller.client.impl.ExistingChannelModelControllerClient;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.extension.ExtensionRegistry;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.as.controller.registry.Resource;
import org.jboss.as.controller.remote.ResponseAttachmentInputStreamSupport;
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.domain.controller.DomainController;
import org.jboss.as.domain.controller.LocalHostControllerInfo;
import org.jboss.as.domain.controller.SlaveRegistrationException;
import org.jboss.as.domain.controller.operations.FetchMissingConfigurationHandler;
import org.jboss.as.domain.controller.operations.SyncDomainModelOperationHandler;
import org.jboss.as.domain.controller.operations.SyncServerGroupOperationHandler;
import org.jboss.as.domain.controller.operations.coordination.DomainControllerLockIdUtils;
import org.jboss.as.domain.controller.operations.deployment.SyncModelParameters;
import org.jboss.as.domain.management.SecurityRealm;
import org.jboss.as.host.controller.discovery.DiscoveryOption;
import org.jboss.as.host.controller.discovery.RemoteDomainControllerConnectionConfiguration;
import org.jboss.as.host.controller.ignored.IgnoredDomainResourceRegistry;
import org.jboss.as.host.controller.logging.HostControllerLogger;
import org.jboss.as.host.controller.mgmt.DomainControllerProtocol;
import org.jboss.as.host.controller.mgmt.DomainRemoteFileRequestAndHandler;
import org.jboss.as.host.controller.mgmt.HostControllerRegistrationHandler;
import org.jboss.as.host.controller.mgmt.HostInfo;
import org.jboss.as.protocol.ProtocolConnectionConfiguration;
import org.jboss.as.protocol.StreamUtils;
import org.jboss.as.protocol.mgmt.AbstractManagementRequest;
import org.jboss.as.protocol.mgmt.ActiveOperation;
import org.jboss.as.protocol.mgmt.FlushableDataOutput;
import org.jboss.as.protocol.mgmt.ManagementChannelHandler;
import org.jboss.as.protocol.mgmt.ManagementRequestContext;
import org.jboss.as.remoting.management.ManagementRemotingServices;
import org.jboss.as.repository.ContentReference;
import org.jboss.as.repository.ContentRepository;
import org.jboss.as.repository.HostFileRepository;
import org.jboss.as.repository.RemoteFileRequestAndHandler.CannotCreateLocalDirectoryException;
import org.jboss.as.repository.RemoteFileRequestAndHandler.DidNotReadEntireFileException;
import org.jboss.as.version.ProductConfig;
import org.jboss.dmr.ModelNode;
import org.jboss.msc.service.Service;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceTarget;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StartException;
import org.jboss.msc.service.StopContext;
import org.jboss.msc.value.InjectedValue;
import org.jboss.remoting3.Connection;
import org.jboss.remoting3.Endpoint;
import org.jboss.remoting3.RemotingOptions;
import org.jboss.threads.AsyncFuture;
import org.jboss.threads.AsyncFutureTask;
import org.wildfly.security.manager.WildFlySecurityManager;
import org.xnio.OptionMap;
import org.xnio.Options;
/**
* Establishes the connection from a slave {@link org.jboss.as.domain.controller.DomainController} to the master
* {@link org.jboss.as.domain.controller.DomainController}
*
* @author Kabir Khan
*/
public class RemoteDomainConnectionService implements MasterDomainControllerClient, Service<MasterDomainControllerClient> {
public static final String DOMAIN_CONNECTION_ID = "domain-connection-id";
private static final int CONNECTION_TIMEOUT_DEFAULT = 30000;
private static final String CONNECTION_TIMEOUT_PROPERTY = "jboss.host.domain.connection.timeout";
private static final int CONNECTION_TIMEOUT = getSystemProperty(CONNECTION_TIMEOUT_PROPERTY, CONNECTION_TIMEOUT_DEFAULT);
//private static final ModelNode APPLY_EXTENSIONS = new ModelNode();
private static final ModelNode APPLY_DOMAIN_MODEL = new ModelNode();
private static final Operation GRAB_DOMAIN_RESOURCE;
static {
// APPLY_EXTENSIONS.get(OP).set(ApplyExtensionsHandler.OPERATION_NAME);
// APPLY_EXTENSIONS.get(OPERATION_HEADERS, "execute-for-coordinator").set(true);
// APPLY_EXTENSIONS.get(OP_ADDR).setEmptyList();
// APPLY_EXTENSIONS.protect();
APPLY_DOMAIN_MODEL.get(OP).set(ModelDescriptionConstants.APPLY_REMOTE_DOMAIN_MODEL);
//FIXME this makes the op work after boot (i.e. slave connects to restarted master), but does not make the slave resync the servers
APPLY_DOMAIN_MODEL.get(OPERATION_HEADERS, "execute-for-coordinator").set(true);
APPLY_DOMAIN_MODEL.get(OP_ADDR).setEmptyList();
APPLY_DOMAIN_MODEL.protect();
ModelNode mn = new ModelNode();
mn.get(OP).set("grab-domain-resource"); // This is actually not used anywhere
mn.get(OP_ADDR).setEmptyList();
mn.protect();
GRAB_DOMAIN_RESOURCE = OperationBuilder.create(mn).build();
}
private final ExtensionRegistry extensionRegistry;
private final ModelController controller;
private final ProductConfig productConfig;
private final LocalHostControllerInfo localHostInfo;
private final RemoteFileRepository remoteFileRepository;
private final ContentRepository contentRepository;
private final IgnoredDomainResourceRegistry ignoredDomainResourceRegistry;
private final HostControllerRegistrationHandler.OperationExecutor operationExecutor;
private final DomainController domainController;
private final HostControllerEnvironment hostControllerEnvironment;
private final RunningMode runningMode;
private final File tempDir;
private final Map<String, ProxyController> serverProxies;
/** Used to invoke ModelController ops on the master */
private volatile ModelControllerClient masterProxy;
private volatile TransactionalProtocolClient txMasterProxy;
private final FutureClient futureClient = new FutureClient();
private final InjectedValue<Endpoint> endpointInjector = new InjectedValue<Endpoint>();
private final InjectedValue<SecurityRealm> securityRealmInjector = new InjectedValue<SecurityRealm>();
private final InjectedValue<ServerInventory> serverInventoryInjector = new InjectedValue<ServerInventory>();
private final InjectedValue<ScheduledExecutorService> scheduledExecutorInjector = new InjectedValue<>();
private final ExecutorService executor;
private final AtomicBoolean domainModelComplete;
private ManagementChannelHandler handler;
private volatile ResponseAttachmentInputStreamSupport responseAttachmentSupport;
private volatile RemoteDomainConnection connection;
private RemoteDomainConnectionService(final ModelController controller,
final ExtensionRegistry extensionRegistry,
final LocalHostControllerInfo localHostControllerInfo,
final RemoteFileRepository remoteFileRepository,
final ContentRepository contentRepository,
final IgnoredDomainResourceRegistry ignoredDomainResourceRegistry,
final HostControllerRegistrationHandler.OperationExecutor operationExecutor,
final DomainController domainController,
final HostControllerEnvironment hostControllerEnvironment,
final ExecutorService executor,
final RunningMode runningMode,
final Map<String, ProxyController> serverProxies,
final AtomicBoolean domainModelComplete){
this.controller = controller;
this.extensionRegistry = extensionRegistry;
this.productConfig = hostControllerEnvironment.getProductConfig();
this.localHostInfo = localHostControllerInfo;
this.remoteFileRepository = remoteFileRepository;
this.contentRepository = contentRepository;
remoteFileRepository.setRemoteFileRepositoryExecutor(remoteFileRepositoryExecutor);
this.ignoredDomainResourceRegistry = ignoredDomainResourceRegistry;
this.operationExecutor = operationExecutor;
this.domainController = domainController;
this.hostControllerEnvironment = hostControllerEnvironment;
this.executor = executor;
this.runningMode = runningMode;
this.tempDir = hostControllerEnvironment.getDomainTempDir();
this.serverProxies = serverProxies;
this.domainModelComplete = domainModelComplete;
}
static Future<MasterDomainControllerClient> install(final ServiceTarget serviceTarget,
final ModelController controller,
final ExtensionRegistry extensionRegistry,
final LocalHostControllerInfo localHostControllerInfo,
final String securityRealm,
final RemoteFileRepository remoteFileRepository,
final ContentRepository contentRepository,
final IgnoredDomainResourceRegistry ignoredDomainResourceRegistry,
final HostControllerRegistrationHandler.OperationExecutor operationExecutor,
final DomainController domainController,
final HostControllerEnvironment hostControllerEnvironment,
final ExecutorService executor,
final RunningMode currentRunningMode,
final Map<String, ProxyController> serverProxies,
final AtomicBoolean domainModelComplete) {
RemoteDomainConnectionService service = new RemoteDomainConnectionService(controller, extensionRegistry, localHostControllerInfo,
remoteFileRepository, contentRepository,
ignoredDomainResourceRegistry, operationExecutor, domainController,
hostControllerEnvironment, executor, currentRunningMode, serverProxies, domainModelComplete);
ServiceBuilder<MasterDomainControllerClient> builder = serviceTarget.addService(MasterDomainControllerClient.SERVICE_NAME, service)
.addDependency(ManagementRemotingServices.MANAGEMENT_ENDPOINT, Endpoint.class, service.endpointInjector)
.addDependency(ServerInventoryService.SERVICE_NAME, ServerInventory.class, service.serverInventoryInjector)
.addDependency(HostControllerService.HC_SCHEDULED_EXECUTOR_SERVICE_NAME, ScheduledExecutorService.class, service.scheduledExecutorInjector)
.setInitialMode(ServiceController.Mode.ACTIVE);
if (securityRealm != null) {
SecurityRealm.ServiceUtil.addDependency(builder, service.securityRealmInjector, securityRealm, false);
}
builder.install();
return service.futureClient;
}
/** {@inheritDoc} */
public synchronized void register() throws IOException {
boolean connected = false;
List<DiscoveryOption> discoveryOptions = localHostInfo.getRemoteDomainControllerDiscoveryOptions();
// Loop through discovery options
for (Iterator<DiscoveryOption> i = discoveryOptions.iterator(); i.hasNext(); ) {
DiscoveryOption discoveryOption = i.next();
final long timeout = CONNECTION_TIMEOUT;
final long endTime = System.currentTimeMillis() + timeout;
int retries = 0;
URI masterURI = null;
try {
// Determine the remote DC host and port to use
List<RemoteDomainControllerConnectionConfiguration> remoteDcConfigs = discoveryOption.discover();
while (!connected) {
IOException ex = null;
for (RemoteDomainControllerConnectionConfiguration remoteDcConfig : remoteDcConfigs) {
try {
masterURI = new URI(remoteDcConfig.getProtocol(), null, remoteDcConfig.getHost(), remoteDcConfig.getPort(), null, null, null);
connection.setUri(masterURI);
connection.connect();
connected = true;
break;
} catch (IOException e) {
// If the cause is one of the irrecoverable ones, unwrap and throw it on
rethrowIrrecoverableConnectionFailures(e);
HostControllerLogger.ROOT_LOGGER.cannotConnect(masterURI, e);
// if we're using isCachedDC, just try once then allow the poll to run in the background.
// this will allow us to start up without having to wait for retries to be exhausted.
if (hostControllerEnvironment.isUseCachedDc()) {
throw e;
}
// Something else; we can retry if time remains
ex = e;
}
}
if (ex != null) {
if (System.currentTimeMillis() > endTime) {
throw HostControllerLogger.ROOT_LOGGER.connectionToMasterTimeout(ex, retries, timeout);
}
try {
ReconnectPolicy.CONNECT.wait(retries);
retries++;
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw HostControllerLogger.ROOT_LOGGER.connectionToMasterInterrupted();
}
}
}
HostControllerLogger.ROOT_LOGGER.connectedToMaster(masterURI);
setupHandler();
break;
} catch (Exception e) {
boolean moreOptions = i.hasNext();
logConnectionException(masterURI, discoveryOption, moreOptions, e);
if (!moreOptions) {
throw HostControllerLogger.ROOT_LOGGER.discoveryOptionsFailureUnableToConnect(e);
}
}
}
}
/** {@inheritDoc} */
public synchronized void unregister() {
StreamUtils.safeClose(connection);
}
@Override
public synchronized Cancellable pollForConnect() {
final Future<Connection> future = connection.reconnect();
setupHandler();
return new Cancellable() {
@Override
public boolean cancel() {
return future.cancel(true);
}
};
}
/** {@inheritDoc} */
public synchronized HostFileRepository getRemoteFileRepository() {
return remoteFileRepository;
}
@Override
public synchronized void reportServerInstability(String serverName) {
if (connection.isConnected()) {
try {
handler.executeRequest(new ControllerInstabilityNotificationRequest(serverName), null);
} catch (Exception e) {
HostControllerLogger.ROOT_LOGGER.failedReportingServerInstabilityToMaster(e, serverName);
}
}
}
@Override
public ModelNode execute(ModelNode operation) throws IOException {
return execute(operation, OperationMessageHandler.logging);
}
@Override
public ModelNode execute(Operation operation) throws IOException {
return masterProxy.execute(operation, OperationMessageHandler.logging);
}
@Override
public ModelNode execute(ModelNode operation, OperationMessageHandler messageHandler) throws IOException {
return masterProxy.execute(operation, messageHandler);
}
@Override
public ModelNode execute(Operation operation, OperationMessageHandler messageHandler) throws IOException {
return masterProxy.execute(operation, messageHandler);
}
@Override
public AsyncFuture<ModelNode> executeAsync(ModelNode operation, OperationMessageHandler messageHandler) {
return masterProxy.executeAsync(operation, messageHandler);
}
@Override
public AsyncFuture<ModelNode> executeAsync(Operation operation, OperationMessageHandler messageHandler) {
return masterProxy.executeAsync(operation, messageHandler);
}
@Override
public OperationResponse executeOperation(Operation operation, OperationMessageHandler messageHandler) throws IOException {
return masterProxy.executeOperation(operation, messageHandler);
}
@Override
public AsyncFuture<OperationResponse> executeOperationAsync(Operation operation, OperationMessageHandler messageHandler) {
return masterProxy.executeOperationAsync(operation, messageHandler);
}
@Override
public void fetchAndSyncMissingConfiguration(final OperationContext context, final Resource original) throws OperationFailedException {
final TransactionalProtocolClient client = txMasterProxy;
context.addStep(new OperationStepHandler() {
@Override
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
// Create the operation to get the required configuration from the master
final ModelNode fetchContentOp = new ModelNode();
fetchContentOp.get(OP).set(FetchMissingConfigurationHandler.OPERATION_NAME);
fetchContentOp.get(OP_ADDR).setEmptyList();
// This is based on the configured server-configs and required server-groups and socket-bindings
final PathElement hostElement = PathElement.pathElement(HOST, localHostInfo.getLocalHostName());
final Resource hostModel = context.readResourceFromRoot(PathAddress.pathAddress(hostElement));
// Add the information about which parts of configuration are required and ignored
IgnoredNonAffectedServerGroupsUtil.addServerGroupsToModel(hostModel, fetchContentOp);
final ModelNode ignoredModel = ignoredDomainResourceRegistry.getIgnoredResourcesAsModel();
if (ignoredModel.hasDefined(IGNORED_RESOURCE_TYPE)) {
fetchContentOp.get(IGNORED_RESOURCES).set(ignoredModel.require(IGNORED_RESOURCE_TYPE));
}
// Attach the operation id, in case it got executed through the master
final Integer domainControllerLock = context.getAttachment(DomainControllerLockIdUtils.DOMAIN_CONTROLLER_LOCK_ID_ATTACHMENT);
if (domainControllerLock != null) {
fetchContentOp.get(OPERATION_HEADERS, DomainControllerLockIdUtils.DOMAIN_CONTROLLER_LOCK_ID).set(domainControllerLock);
}
// execute the operation blocking
final TransactionalProtocolClient.PreparedOperation<TransactionalProtocolClient.Operation> preparedOperation;
try {
preparedOperation = TransactionalProtocolHandlers.executeBlocking(fetchContentOp, client);
} catch (IOException e) {
throw new OperationFailedException(e);
} catch (InterruptedException e) {
throw ControllerLogger.ROOT_LOGGER.operationCancelledAsynchronously();
}
// Process the prepared result, note: this won't include outcome yet
final ModelNode result = preparedOperation.getPreparedResult();
if (preparedOperation.isFailed()) {
final ModelNode prepared = preparedOperation.getPreparedResult();
if (prepared.hasDefined(FAILURE_DESCRIPTION)) {
throw new OperationFailedException(prepared.get(FAILURE_DESCRIPTION).asString());
} else {
throw new OperationFailedException(prepared);
}
} else if (result.get(FAILURE_DESCRIPTION).isDefined()) {
preparedOperation.rollback();
throw new OperationFailedException(result.get(FAILURE_DESCRIPTION).asString());
}
final ModelNode syncOperation = new ModelNode();
syncOperation.get(OP).set("calculate-diff-and-sync");
syncOperation.get(OP_ADDR).setEmptyList();
syncOperation.get(DOMAIN_MODEL).set(result.get(RESULT));
// Execute the handler to synchronize the model
SyncModelParameters parameters =
new SyncModelParameters(domainController, ignoredDomainResourceRegistry,
hostControllerEnvironment, extensionRegistry, operationExecutor, false, serverProxies,
remoteFileRepository, contentRepository);
final SyncServerGroupOperationHandler handler =
new SyncServerGroupOperationHandler(localHostInfo.getLocalHostName(), original, parameters);
context.addStep(syncOperation, handler, OperationContext.Stage.MODEL, true);
// Complete the remote tx on the master depending on the outcome
// This cannot be executed as another step
// If this is not called the lock on the master will not be released and result in a deadlock
context.completeStep(new OperationContext.ResultHandler() {
@Override
public void handleResult(OperationContext.ResultAction resultAction, OperationContext context, ModelNode operation) {
if (resultAction == OperationContext.ResultAction.KEEP) {
preparedOperation.commit();
} else {
preparedOperation.rollback();
}
}
});
}
}, OperationContext.Stage.MODEL, true);
}
@Override
public void close() throws IOException {
throw HostControllerLogger.ROOT_LOGGER.closeShouldBeManagedByService();
}
/** {@inheritDoc} */
@Override
public synchronized void start(StartContext context) throws StartException {
final RemoteDomainConnection connection;
final ManagementChannelHandler handler;
try {
ScheduledExecutorService scheduledExecutorService = scheduledExecutorInjector.getValue();
this.responseAttachmentSupport = new ResponseAttachmentInputStreamSupport(scheduledExecutorService);
final OptionMap options = OptionMap.builder()
.set(RemotingOptions.HEARTBEAT_INTERVAL, 15000)
.set(Options.READ_TIMEOUT, 45000)
.getMap();
// Gather the required information to connect to the remote DC
// The URI will be set later when looping through discovery options when registering with
// or reconnecting to the remote DC.
final ProtocolConnectionConfiguration configuration = ProtocolConnectionConfiguration.create(endpointInjector.getValue(), options);
final SecurityRealm realm = securityRealmInjector.getOptionalValue();
// Create the remote domain channel strategy
connection = new RemoteDomainConnection(localHostInfo.getLocalHostName(), configuration, realm,
localHostInfo.getRemoteDomainControllerUsername(),
localHostInfo.getRemoteDomainControllerDiscoveryOptions(), executor, scheduledExecutorService,
new RemoteDomainConnection.HostRegistrationCallback() {
/**
* Calculates the metadata required when connecting to the master {@link org.jboss.as.domain.controller.DomainController}
* This value does not require locking during use, as {@link org.jboss.as.host.controller.mgmt.HostInfo createLocalHostHostInfo()} uses
* '/domain-controller', which is read-only, and '/core-service=ignored-resources/ignored-resource-type' which has handlers on
* add, remove and write-attribute that will place the host into reload-required.
* @return
*/
@Override
public ModelNode createLocalHostInfo() {
return HostInfo.createLocalHostHostInfo(localHostInfo, productConfig, ignoredDomainResourceRegistry, ReadRootResourceHandler.grabDomainResource(operationExecutor).getChildren(HOST).iterator().next());
}
@Override
public ModelNode resolveSubsystemVersions(ModelNode extensions) {
return resolveSubsystems(extensions.asList());
}
@Override
public boolean applyDomainModel(final List<ModelNode> bootOperations) {
// Apply the model..
final HostInfo info = HostInfo.fromModelNode(createLocalHostInfo());
return applyRemoteDomainModel(bootOperations, info);
}
@Override
public void registrationComplete(ManagementChannelHandler handler) {
RemoteDomainConnectionService.this.domainModelComplete.set(true);
}
}, runningMode);
// Setup the management channel handler
handler = connection.getChannelHandler();
handler.getAttachments().attach(ManagementChannelHandler.TEMP_DIR, tempDir);
} catch (Exception e) {
throw new StartException(e);
} finally {
futureClient.setClient(this);
}
this.connection = connection;
this.handler = handler;
}
/**
* Resolve the subsystem versions.
*
* @param extensions the extensions to install
* @return the subsystem versions
*/
private ModelNode resolveSubsystems(final List<ModelNode> extensions) {
HostControllerLogger.ROOT_LOGGER.debug("Applying extensions provided by master");
final ModelNode result = operationExecutor.installSlaveExtensions(extensions);
if (!SUCCESS.equals(result.get(OUTCOME).asString())) {
throw HostControllerLogger.ROOT_LOGGER.failedToAddExtensions(result.get(FAILURE_DESCRIPTION));
}
final ModelNode subsystems = new ModelNode();
for (final ModelNode extension : extensions) {
extensionRegistry.recordSubsystemVersions(extension.asString(), subsystems);
}
return subsystems;
}
/**
* Apply the remote domain model to the local host controller.
*
* @param bootOperations the result of the remote read-domain-model op
* @return {@code true} if the model was applied successfully, {@code false} otherwise
*/
private boolean applyRemoteDomainModel(final List<ModelNode> bootOperations, final HostInfo hostInfo) {
try {
HostControllerLogger.ROOT_LOGGER.debug("Applying domain level boot operations provided by master");
SyncModelParameters parameters =
new SyncModelParameters(domainController, ignoredDomainResourceRegistry,
hostControllerEnvironment, extensionRegistry, operationExecutor, true, serverProxies, remoteFileRepository, contentRepository);
final SyncDomainModelOperationHandler handler =
new SyncDomainModelOperationHandler(hostInfo, parameters);
final ModelNode operation = APPLY_DOMAIN_MODEL.clone();
operation.get(DOMAIN_MODEL).set(bootOperations);
final ModelNode result = operationExecutor.execute(OperationBuilder.create(operation).build(), OperationMessageHandler.DISCARD, ModelController.OperationTransactionControl.COMMIT, handler);
final String outcome = result.get(OUTCOME).asString();
final boolean success = SUCCESS.equals(outcome);
if (!success) {
ModelNode failureDesc = result.hasDefined(FAILURE_DESCRIPTION) ? result.get(FAILURE_DESCRIPTION) : new ModelNode();
HostControllerLogger.ROOT_LOGGER.failedToApplyDomainConfig(outcome, failureDesc);
return false;
} else {
return true;
}
} catch (Exception e) {
HostControllerLogger.ROOT_LOGGER.failedToApplyDomainConfig(e);
return false;
}
}
/** {@inheritDoc} */
@Override
public synchronized void stop(final StopContext context) {
Runnable r = new Runnable() {
@Override
public void run() {
try {
StreamUtils.safeClose(connection);
responseAttachmentSupport.shutdown();
} finally {
context.complete();
}
}
};
try {
executor.execute(r);
} catch (RejectedExecutionException e) {
r.run();
} finally {
context.asynchronous();
}
}
/** {@inheritDoc} */
@Override
public synchronized MasterDomainControllerClient getValue() throws IllegalStateException, IllegalArgumentException {
return this;
}
/**
* Analyzes a failure thrown connecting to the master for causes that indicate
* some problem not likely to be resolved by immediately retrying. If found,
* throws an exception highlighting the underlying cause. If the cause is not
* one of the ones understood by this method, the method returns normally.
*
* @throws org.jboss.as.domain.controller.SlaveRegistrationException if the remote HC rejected the request
* @throws IllegalStateException for other failures understood by this method
*/
static void rethrowIrrecoverableConnectionFailures(IOException e) throws SlaveRegistrationException {
Throwable cause = e;
while ((cause = cause.getCause()) != null) {
if (cause instanceof SaslException) {
throw HostControllerLogger.ROOT_LOGGER.authenticationFailureUnableToConnect(cause);
} else if (cause instanceof SSLHandshakeException) {
throw HostControllerLogger.ROOT_LOGGER.sslFailureUnableToConnect(cause);
} else if (cause instanceof SlaveRegistrationException) {
throw (SlaveRegistrationException) cause;
}
}
}
/**
* Handles logging tasks related to a failure to connect to a remote HC.
* @param uri the URI at which the connection attempt was made. Can be {@code null} indicating a failure to discover the HC
* @param discoveryOption the {@code DiscoveryOption} used to determine {@code uri}
* @param moreOptions {@code true} if there are more untried discovery options
* @param e the exception
*/
static void logConnectionException(URI uri, DiscoveryOption discoveryOption, boolean moreOptions, Exception e) {
if (uri == null) {
HostControllerLogger.ROOT_LOGGER.failedDiscoveringMaster(discoveryOption, e);
} else {
HostControllerLogger.ROOT_LOGGER.cannotConnect(uri, e);
}
if (!moreOptions) {
// All discovery options have been exhausted
HostControllerLogger.ROOT_LOGGER.noDiscoveryOptionsLeft();
}
}
private class GetFileRequest extends AbstractManagementRequest<File, Void> {
private final byte rootId;
private final String filePath;
private final HostFileRepository localFileRepository;
private GetFileRequest(final byte rootId, final String filePath, final HostFileRepository localFileRepository) {
this.rootId = rootId;
this.filePath = filePath;
this.localFileRepository = localFileRepository;
}
@Override
public byte getOperationType() {
return DomainControllerProtocol.GET_FILE_REQUEST;
}
@Override
protected void sendRequest(ActiveOperation.ResultHandler<File> resultHandler, ManagementRequestContext<Void> context, FlushableDataOutput output) throws IOException {
output.write(DomainControllerProtocol.PARAM_HOST_ID);
output.writeUTF(localHostInfo.getLocalHostName());
DomainRemoteFileRequestAndHandler.INSTANCE.sendRequest(output, rootId, filePath);
}
@Override
public void handleRequest(DataInput input, ActiveOperation.ResultHandler<File> resultHandler, ManagementRequestContext<Void> context) throws IOException {
final File localPath;
switch (rootId) {
case DomainControllerProtocol.PARAM_ROOT_ID_FILE: {
localPath = localFileRepository.getFile(filePath);
break;
}
case DomainControllerProtocol.PARAM_ROOT_ID_CONFIGURATION: {
localPath = localFileRepository.getConfigurationFile(filePath);
break;
}
case DomainControllerProtocol.PARAM_ROOT_ID_DEPLOYMENT: {
byte[] hash = HashUtil.hexStringToByteArray(filePath);
localPath = localFileRepository.getDeploymentRoot(new ContentReference(filePath, hash));
break;
}
default: {
localPath = null;
}
}
try {
DomainRemoteFileRequestAndHandler.INSTANCE.handleResponse(input, localPath, ROOT_LOGGER, resultHandler, context);
} catch (CannotCreateLocalDirectoryException e) {
throw HostControllerLogger.ROOT_LOGGER.cannotCreateLocalDirectory(e.getDir());
} catch (DidNotReadEntireFileException e) {
throw HostControllerLogger.ROOT_LOGGER.didNotReadEntireFile(e.getMissing());
}
}
}
static class RemoteFileRepository implements HostFileRepository {
private final HostFileRepository localFileRepository;
private volatile RemoteFileRepositoryExecutor remoteFileRepositoryExecutor;
RemoteFileRepository(final HostFileRepository localFileRepository) {
this.localFileRepository = localFileRepository;
}
@Override
public final File getFile(String relativePath) {
return getFile(relativePath, DomainControllerProtocol.PARAM_ROOT_ID_FILE);
}
@Override
public final File getConfigurationFile(String relativePath) {
return getFile(relativePath, DomainControllerProtocol.PARAM_ROOT_ID_CONFIGURATION);
}
@Override
public final File[] getDeploymentFiles(ContentReference reference) {
final File root = getDeploymentRoot(reference);
return root.listFiles();
}
@Override
public File getDeploymentRoot(ContentReference reference) {
File file = localFileRepository.getDeploymentRoot(reference);
if(! file.exists()) {
return getFile(reference.getHexHash(), DomainControllerProtocol.PARAM_ROOT_ID_DEPLOYMENT);
}
return file;
}
private File getFile(final String relativePath, final byte repoId) {
return remoteFileRepositoryExecutor.getFile(relativePath, repoId, localFileRepository);
}
void setRemoteFileRepositoryExecutor(RemoteFileRepositoryExecutor remoteFileRepositoryExecutor) {
this.remoteFileRepositoryExecutor = remoteFileRepositoryExecutor;
}
@Override
public void deleteDeployment(ContentReference reference) {
localFileRepository.deleteDeployment(reference);
}
}
interface RemoteFileRepositoryExecutor {
File getFile(final String relativePath, final byte repoId, HostFileRepository localFileRepository);
}
private final RemoteFileRepositoryExecutor remoteFileRepositoryExecutor = new RemoteFileRepositoryExecutor() {
public File getFile(final String relativePath, final byte repoId, HostFileRepository localFileRepository) {
if(connection.isConnected()) {
try {
return handler.executeRequest(new GetFileRequest(repoId, relativePath, localFileRepository), null).getResult().get();
} catch (Exception e) {
throw HostControllerLogger.ROOT_LOGGER.failedToGetFileFromRemoteRepository(e);
}
} else {
final File file = localFileRepository.getFile(relativePath);
// using --cached-dc and the DC is unavailable, make sure the content exists locally.
if (localHostInfo.isUsingCachedDc()) {
if (! file.exists()) {
throw HostControllerLogger.ROOT_LOGGER.failedToGetFileFromRemoteRepository(new RuntimeException("Content hash " + relativePath + " not found."));
}
}
return file;
}
}
};
private void setupHandler() {
// Setup the transaction protocol handler
handler.addHandlerFactory(new TransactionalProtocolOperationHandler(controller, handler, responseAttachmentSupport));
// Use the existing channel strategy
masterProxy = ExistingChannelModelControllerClient.createAndAdd(handler);
txMasterProxy = TransactionalProtocolHandlers.createClient(handler);
}
private class FutureClient extends AsyncFutureTask<MasterDomainControllerClient>{
protected FutureClient() {
super(null);
}
private void setClient(MasterDomainControllerClient client) {
super.setResult(client);
}
}
private static int getSystemProperty(final String name, final int defaultValue) {
final String value = WildFlySecurityManager.getPropertyPrivileged(name, null);
try {
return value == null ? defaultValue : Integer.parseInt(value);
} catch (NumberFormatException ignored) {
return defaultValue;
}
}
private static class ReadRootResourceHandler implements OperationStepHandler {
private Resource resource;
static Resource grabDomainResource(HostControllerRegistrationHandler.OperationExecutor executor) {
ReadRootResourceHandler handler = new ReadRootResourceHandler();
executor.execute(GRAB_DOMAIN_RESOURCE, OperationMessageHandler.DISCARD, ModelController.OperationTransactionControl.COMMIT, handler);
return handler.resource;
}
@Override
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
resource = context.readResourceFromRoot(PathAddress.EMPTY_ADDRESS);
}
}
private class ControllerInstabilityNotificationRequest extends AbstractManagementRequest<Void, Void> {
private final String server;
private ControllerInstabilityNotificationRequest(String server) {
this.server = server;
}
@Override
public byte getOperationType() {
return DomainControllerProtocol.SERVER_INSTABILITY_REQUEST;
}
@Override
protected void sendRequest(ActiveOperation.ResultHandler<Void> resultHandler, ManagementRequestContext<Void> context, FlushableDataOutput output) throws IOException {
output.write(DomainControllerProtocol.PARAM_SERVER_ID);
output.writeUTF(server);
output.write(DomainControllerProtocol.PARAM_HOST_ID);
output.writeUTF(localHostInfo.getLocalHostName());
}
@Override
public void handleRequest(DataInput input, ActiveOperation.ResultHandler<Void> resultHandler, ManagementRequestContext<Void> context) throws IOException {
resultHandler.done(null);
}
}
}