/*
* 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.domain.controller.operations.coordination;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CALLER_TYPE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.COMPOSITE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.GROUP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.IGNORED;
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.RUNNING_SERVER;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER_CONFIG;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STEPS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.USER;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.extension.ExtensionRegistry;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.controller.registry.Resource;
import org.jboss.as.domain.controller.ServerIdentity;
import org.jboss.as.host.controller.IgnoredNonAffectedServerGroupsUtil;
import org.jboss.as.host.controller.IgnoredNonAffectedServerGroupsUtil.ServerConfigInfo;
import org.jboss.as.host.controller.ignored.IgnoredDomainResourceRegistry;
import org.jboss.dmr.ModelNode;
/**
* Support class for the execution of an operation on an individual host controller.
*
* @author Brian Stansberry (c) 2011 Red Hat Inc.
*/
interface HostControllerExecutionSupport {
/**
* Gets the operation (if any) that should be run on the host controller itself
* @return the operation to run on the host controller, or {@code null}
*/
ModelNode getDomainOperation();
/**
* Gets the operations that should be run on the servers managed by the host controller.
*
* @param provider provider of server operations this object can delegate to if needed
*
* @return map of servers to the operation they should execute. Will not be {@code null} but may be empty
*/
Map<ServerIdentity, ModelNode> getServerOps(ServerOperationProvider provider);
/**
* Gets the result of this operation (if any) on this host controller, along with any operations
* needed to effect the operation on the servers managed by this host controller, in the
* format expected by the host controller that is coordinating overall execution across the domain.
*
* @param resultNode node to which the result should be attached
*
* @return the formatted result
*/
ModelNode getFormattedDomainResult(ModelNode resultNode);
/**
* Returns whether the operation puts the host in the reload-required state.
*
* @return {@code true} if the operation puts the host in the reload-required state
*/
boolean isReloadRequired();
/**
* Callback for then the controller transaction has completed.
*
* @param rollback Whether the transaction rolled back or not. {@code true} indicates it was rolled back; {@code false}
* indicates it was committed.
*/
void complete(boolean rollback);
/**
* Provider of server level operations necessary to effect a given domain or host level operation on the servers
* managed by this host controller.
*/
interface ServerOperationProvider {
/**
* Gets the server level operations necessary to effect a given domain or host level operation on the servers.
*
* @param domainOp the domain or host level operation
* @param address the address of the domain level operation
*
* @return map of servers to the operation they should execute. Will not be {@code null} but may be empty
*/
Map<Set<ServerIdentity>, ModelNode> getServerOperations(ModelNode domainOp, PathAddress address);
}
/** Provides a reference to a ModelNode representation of the domain model to {@link Factory} */
interface DomainModelProvider {
/**
* Gets the domain model resource
* @return the resource. Cannot be {@code null}
*/
Resource getDomainModel();
}
/** Provides a factory method for creating {@link HostControllerExecutionSupport} instances */
class Factory {
/**
* Create a HostControllerExecutionSupport for a given operation.
*
*
* @param context
* @param operation the operation
* @param hostName the name of the host executing the operation
* @param domainModelProvider source for the domain model
* @param ignoredDomainResourceRegistry registry of resource addresses that should be ignored
* @throws OperationFailedException
*
* @return the HostControllerExecutionSupport
*/
public static HostControllerExecutionSupport create(OperationContext context, final ModelNode operation,
final String hostName,
final DomainModelProvider domainModelProvider,
final IgnoredDomainResourceRegistry ignoredDomainResourceRegistry,
final boolean isRemoteDomainControllerIgnoreUnaffectedConfiguration,
final ExtensionRegistry extensionRegistry) throws OperationFailedException {
String targetHost = null;
PathElement runningServerTarget = null;
ModelNode runningServerOp = null;
final PathAddress address = PathAddress.pathAddress(operation.get(OP_ADDR));
if (address.size() > 0) {
PathElement first = address.getElement(0);
if (HOST.equals(first.getKey()) && !first.isMultiTarget()) {
targetHost = first.getValue();
if (address.size() > 1 && RUNNING_SERVER.equals(address.getElement(1).getKey())) {
runningServerTarget = address.getElement(1);
ModelNode relativeAddress = new ModelNode().setEmptyList();
for (int i = 2; i < address.size(); i++) {
PathElement element = address.getElement(i);
relativeAddress.add(element.getKey(), element.getValue());
}
runningServerOp = operation.clone();
runningServerOp.get(OP_ADDR).set(relativeAddress);
}
}
}
HostControllerExecutionSupport result;
if (targetHost != null && !hostName.equals(targetHost)) {
// HostControllerExecutionSupport representing another host
result = new IgnoredOpExecutionSupport(ignoredDomainResourceRegistry);
}
else if (runningServerTarget != null) {
// HostControllerExecutionSupport representing a server op
final Resource domainModel = domainModelProvider.getDomainModel();
final Resource hostModel = domainModel.getChild(PathElement.pathElement(HOST, targetHost));
if (runningServerTarget.isMultiTarget()) {
return new DomainOpExecutionSupport(ignoredDomainResourceRegistry, operation, PathAddress.EMPTY_ADDRESS);
} else {
final String serverName = runningServerTarget.getValue();
// TODO prevent NPE
final String serverGroup = hostModel.getChild(PathElement.pathElement(SERVER_CONFIG, serverName)).getModel().require(GROUP).asString();
final ServerIdentity serverIdentity = new ServerIdentity(targetHost, serverGroup, serverName);
result = new DirectServerOpExecutionSupport(ignoredDomainResourceRegistry, serverIdentity, runningServerOp);
}
}
else if (COMPOSITE.equals(operation.require(OP).asString())) {
// Recurse into the steps to see what's required
if (operation.hasDefined(STEPS)) {
List<HostControllerExecutionSupport> parsedSteps = new ArrayList<HostControllerExecutionSupport>();
for (ModelNode step : operation.get(STEPS).asList()) {
//Remove the caller-type=user header
if (operation.hasDefined(OPERATION_HEADERS) && operation.get(OPERATION_HEADERS).hasDefined(CALLER_TYPE) && operation.get(OPERATION_HEADERS, CALLER_TYPE).asString().equals(USER)) {
step = step.clone();
step.get(OPERATION_HEADERS, CALLER_TYPE).set(USER);
}
parsedSteps.add(create(context, step, hostName, domainModelProvider, ignoredDomainResourceRegistry, isRemoteDomainControllerIgnoreUnaffectedConfiguration, extensionRegistry));
}
result = new MultiStepOpExecutionSupport(ignoredDomainResourceRegistry, parsedSteps);
}
else {
// Will fail later
result = new DomainOpExecutionSupport(ignoredDomainResourceRegistry, operation, address);
}
}
else if (targetHost == null && isResourceExcluded(context, ignoredDomainResourceRegistry, isRemoteDomainControllerIgnoreUnaffectedConfiguration, domainModelProvider, hostName, address, extensionRegistry, operation)) {
result = new IgnoredOpExecutionSupport(ignoredDomainResourceRegistry);
}
else {
result = new DomainOpExecutionSupport(ignoredDomainResourceRegistry, operation, address);
}
return result;
}
private static boolean isResourceExcluded(OperationContext context, IgnoredDomainResourceRegistry ignoredDomainResourceRegistry, boolean isRemoteDomainControllerIgnoreUnaffectedConfiguration, DomainModelProvider domainModelProvider, String hostName, PathAddress address, ExtensionRegistry extensionRegistry, ModelNode operation) {
if (ignoredDomainResourceRegistry.getIgnoredClonedProfileRegistry().checkIgnoredProfileClone(operation)) {
return true;
}
if (ignoredDomainResourceRegistry.isResourceExcluded(address)) {
return true;
}
if (isRemoteDomainControllerIgnoreUnaffectedConfiguration) {
IgnoredNonAffectedServerGroupsUtil util = IgnoredNonAffectedServerGroupsUtil.create(extensionRegistry);
Set<ServerConfigInfo> serverConfigs = util.getServerConfigsOnSlave(domainModelProvider.getDomainModel().getChild(PathElement.pathElement(HOST, hostName)));
return util.ignoreOperation(domainModelProvider.getDomainModel(), serverConfigs, address);
}
return false;
}
private abstract static class AbstractOpExecutionSupport implements HostControllerExecutionSupport {
private final IgnoredDomainResourceRegistry.IgnoredClonedProfileRegistry ignoredClonedProfileRegistry;
private AbstractOpExecutionSupport(final IgnoredDomainResourceRegistry ignoredDomainResourceRegistry) {
this.ignoredClonedProfileRegistry = ignoredDomainResourceRegistry.getIgnoredClonedProfileRegistry();
}
@Override
public boolean isReloadRequired() {
return ignoredClonedProfileRegistry.isReloadRequired();
}
@Override
public void complete(boolean rollback) {
ignoredClonedProfileRegistry.complete(rollback);
}
}
private static class IgnoredOpExecutionSupport extends SimpleOpExecutionSupport {
private IgnoredOpExecutionSupport(final IgnoredDomainResourceRegistry ignoredDomainResourceRegistry) {
super(ignoredDomainResourceRegistry);
}
@Override
public ModelNode getDomainOperation() {
return null;
}
@Override
public Map<ServerIdentity, ModelNode> getServerOps(ServerOperationProvider provider) {
return Collections.emptyMap();
}
}
private static class DirectServerOpExecutionSupport extends SimpleOpExecutionSupport {
private Map<ServerIdentity, ModelNode> serverOps;
private DirectServerOpExecutionSupport(final IgnoredDomainResourceRegistry ignoredDomainResourceRegistry,
final ServerIdentity serverIdentity, ModelNode serverOp) {
super(ignoredDomainResourceRegistry);
this.serverOps = Collections.singletonMap(serverIdentity, serverOp);
}
@Override
public ModelNode getDomainOperation() {
return null;
}
@Override
public Map<ServerIdentity, ModelNode> getServerOps(ServerOperationProvider provider) {
return serverOps;
}
}
private static class DomainOpExecutionSupport extends SimpleOpExecutionSupport {
private final ModelNode domainOp;
private final PathAddress domainOpAddress;
private DomainOpExecutionSupport(final IgnoredDomainResourceRegistry ignoredDomainResourceRegistry,
ModelNode domainOp, final PathAddress domainOpAddress) {
super(ignoredDomainResourceRegistry);
this.domainOp = domainOp;
this.domainOpAddress = domainOpAddress;
}
@Override
public ModelNode getDomainOperation() {
return domainOp;
}
@Override
public Map<ServerIdentity, ModelNode> getServerOps(ServerOperationProvider provider) {
// TODO change ServerOperationResolver to just provide the unbundled map
Map<Set<ServerIdentity>, ModelNode> bundled = provider.getServerOperations(domainOp, domainOpAddress);
Map<ServerIdentity, ModelNode> unbundled = new HashMap<ServerIdentity, ModelNode>();
for (Map.Entry<Set<ServerIdentity>, ModelNode> entry : bundled.entrySet()) {
ModelNode op = entry.getValue();
for (ServerIdentity id : entry.getKey()) {
unbundled.put(id, op);
}
}
return unbundled;
}
}
private abstract static class SimpleOpExecutionSupport extends AbstractOpExecutionSupport {
private SimpleOpExecutionSupport(final IgnoredDomainResourceRegistry ignoredDomainResourceRegistry) {
super(ignoredDomainResourceRegistry);
}
@Override
public ModelNode getFormattedDomainResult(ModelNode resultNode) {
return resultNode.clone();
}
}
private static class MultiStepOpExecutionSupport extends AbstractOpExecutionSupport {
private final List<HostControllerExecutionSupport> steps;
private MultiStepOpExecutionSupport(final IgnoredDomainResourceRegistry ignoredDomainResourceRegistry,
final List<HostControllerExecutionSupport> steps) {
super(ignoredDomainResourceRegistry);
this.steps = steps;
}
public Map<ServerIdentity, ModelNode> getServerOps(ServerOperationProvider provider) {
final Map<ServerIdentity, ModelNode> result = new HashMap<ServerIdentity, ModelNode>();
int stepNum = 1;
for (HostControllerExecutionSupport step : steps) {
String stepLabel = "step-" + stepNum++;
Map<ServerIdentity, ModelNode> stepResults = step.getServerOps(provider);
for (Map.Entry<ServerIdentity, ModelNode> entry : stepResults.entrySet()) {
ModelNode serverNode = result.get(entry.getKey());
if (serverNode == null) {
serverNode = new ModelNode();
result.put(entry.getKey(), serverNode);
}
serverNode.get(stepLabel).set(entry.getValue());
}
}
return result;
}
@Override
public ModelNode getDomainOperation() {
List<ModelNode> domainSteps = new ArrayList<ModelNode>();
for (HostControllerExecutionSupport step : steps) {
ModelNode stepNode = step.getDomainOperation();
if (stepNode != null) {
domainSteps.add(stepNode);
}
}
if (domainSteps.size() == 0) {
//Nothing to do, return null
return null;
}
//
ModelNode stepsParam = new ModelNode();
for (ModelNode stepNode : domainSteps) {
stepsParam.add(stepNode);
}
ModelNode result = Util.getEmptyOperation(COMPOSITE, new ModelNode());
result.get(STEPS).set(stepsParam);
return result;
}
@Override
public ModelNode getFormattedDomainResult(ModelNode resultNode) {
ModelNode allSteps = new ModelNode();
int resultStep = 0;
for (int i = 0; i < steps.size(); i++) {
HostControllerExecutionSupport po = steps.get(i);
if (po.getDomainOperation() != null) {
String label = "step-" + (++resultStep);
ModelNode stepResponseNode = resultNode.get(label);
ModelNode formattedStepResponseNode;
if (po instanceof MultiStepOpExecutionSupport) {
formattedStepResponseNode = stepResponseNode.clone();
ModelNode stepResultNode = stepResponseNode.get(RESULT);
formattedStepResponseNode.get(RESULT).set(po.getFormattedDomainResult(stepResultNode));
} else {
formattedStepResponseNode = po.getFormattedDomainResult(stepResponseNode);
}
allSteps.get("step-" + (i + 1)).set(formattedStepResponseNode);
}
else {
allSteps.get("step-" + (i + 1), OUTCOME).set(IGNORED);
}
}
return allSteps;
}
}
}
}