/* * 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.COMPOSITE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST; 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.SERVER; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STEPS; import java.util.Collections; import java.util.HashSet; 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.logging.ControllerLogger; import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration; import org.jboss.as.controller.registry.OperationEntry; import org.jboss.as.domain.controller.LocalHostControllerInfo; import org.jboss.as.domain.controller.logging.DomainControllerLogger; import org.jboss.dmr.ModelNode; /** * Encapsulates routing information for an operation executed against a host controller. * * @author Brian Stansberry (c) 2011 Red Hat Inc. */ class OperationRouting { static OperationRouting determineRouting(OperationContext context, ModelNode operation, final LocalHostControllerInfo localHostControllerInfo, Set<String> hostNames) throws OperationFailedException { final ImmutableManagementResourceRegistration rootRegistration = context.getRootResourceRegistration(); return determineRouting(operation, localHostControllerInfo, rootRegistration, hostNames); } private static OperationRouting determineRouting(final ModelNode operation, final LocalHostControllerInfo localHostControllerInfo, final ImmutableManagementResourceRegistration rootRegistration, Set<String> hostNames) throws OperationFailedException { final PathAddress address = PathAddress.pathAddress(operation.get(OP_ADDR)); final String operationName = operation.require(OP).asString(); final Set<OperationEntry.Flag> operationFlags = resolveOperationFlags(address, operationName, rootRegistration); return determineRouting(operation, address, operationName, operationFlags, localHostControllerInfo, rootRegistration, hostNames); } private static Set<OperationEntry.Flag> resolveOperationFlags(final PathAddress address, final String operationName, final ImmutableManagementResourceRegistration rootRegistration) throws OperationFailedException { Set<OperationEntry.Flag> result = null; boolean validAddress = false; OperationEntry ope = rootRegistration.getOperationEntry(address, operationName); if (ope != null) { return ope.getFlags(); } ImmutableManagementResourceRegistration targetReg = rootRegistration.getSubModel(address); if (targetReg != null) { validAddress = true; OperationEntry opE = targetReg.getOperationEntry(PathAddress.EMPTY_ADDRESS, operationName); result = opE == null ? null : opE.getFlags(); } if (result == null) { // Throw appropriate exception if (validAddress) { // Bad operation name exception throw new OperationFailedException(ControllerLogger.ROOT_LOGGER.noHandlerForOperation(operationName, address)); } else { // Bad address exception throw new OperationFailedException(ControllerLogger.ROOT_LOGGER.noSuchResourceType(address)); } } return result; } private static OperationRouting determineRouting(final ModelNode operation, final PathAddress address, final String operationName, final Set<OperationEntry.Flag> operationFlags, final LocalHostControllerInfo localHostControllerInfo, final ImmutableManagementResourceRegistration rootRegistration, Set<String> hostNames) throws OperationFailedException { OperationRouting routing = null; Set<String> targetHost = null; boolean compositeOp = false; if (address.size() > 0) { PathElement first = address.getElement(0); if (HOST.equals(first.getKey())) { if (first.isMultiTarget()) { if (first.isWildcard()) { targetHost = new HashSet<>(); targetHost.addAll(hostNames); targetHost.add(localHostControllerInfo.getLocalHostName()); } else { targetHost = new HashSet<>(); Collections.addAll(targetHost, first.getSegments()); } } else { targetHost = Collections.singleton(first.getValue()); } } } else { compositeOp = COMPOSITE.equals(operationName); } if (targetHost != null) { // Check for read-only flags. But note they will only exist for addresses on this host, // as we have no accurate flags for ops registered on remote hosts if(operationFlags.contains(OperationEntry.Flag.READ_ONLY) && !operationFlags.contains(OperationEntry.Flag.DOMAIN_PUSH_TO_SERVERS)) { routing = new OperationRouting(targetHost, false); } // Check if the target is an actual server else if(address.size() > 1) { PathElement first = address.getElement(1); if (SERVER.equals(first.getKey())) { // A direct request to a server is not handled via two-phase handling // even if the request is for a write op. A write-op to a server // is illegal anyway, so there is no reason to handle it two-phase routing = new OperationRouting(targetHost, false); } } if (routing == null) { if(operationFlags.contains(OperationEntry.Flag.HOST_CONTROLLER_ONLY)) { routing = new OperationRouting(targetHost, false); } else { // We can't rely on the check for read-only flags above to tell us whether // remote ops are two step. But, we can treat ops solely directed at remote hosts // as non-two step, as we don't need two step execution on this host for such ops boolean twoStep = targetHost.contains(localHostControllerInfo.getLocalHostName()); routing = new OperationRouting(targetHost, twoStep); } } } else if (compositeOp) { // Recurse into the steps to see what's required if (operation.hasDefined(STEPS)) { Set<String> allHosts = new HashSet<String>(); boolean fwdToAllHosts = false; boolean twoStep = false; for (ModelNode step : operation.get(STEPS).asList()) { OperationRouting stepRouting = determineRouting(step, localHostControllerInfo, rootRegistration, hostNames); if (stepRouting.isTwoStep()) { twoStep = true; // Make sure we don't loose the information that we have to execute the operation on all hosts fwdToAllHosts = fwdToAllHosts || stepRouting.getHosts().isEmpty(); } // if (!localHostControllerInfo.isMasterDomainController()) { // fwdToAllHosts = fwdToAllHosts || stepRouting.getHosts().isEmpty(); // } allHosts.addAll(stepRouting.getHosts()); } if (fwdToAllHosts) { routing = new OperationRouting(true); } else { routing = new OperationRouting(allHosts, twoStep); } } else { // empty; this will be an error but don't deal with it here // Let our DomainModel deal with it routing = new OperationRouting(localHostControllerInfo); } } else { // Domain level operation if (operationFlags.contains(OperationEntry.Flag.READ_ONLY) && !operationFlags.contains(OperationEntry.Flag.DOMAIN_PUSH_TO_SERVERS)) { // Direct read of domain model routing = new OperationRouting(localHostControllerInfo); } else if (!localHostControllerInfo.isMasterDomainController()) { // Route to master routing = new OperationRouting(); } else if (operationFlags.contains(OperationEntry.Flag.MASTER_HOST_CONTROLLER_ONLY)) { // Deployment ops should be executed on the master DC only routing = new OperationRouting(localHostControllerInfo); } } if (routing == null) { // Write operation to the model or a read that needs to be pushed to servers; everyone gets it routing = new OperationRouting(true); } DomainControllerLogger.HOST_CONTROLLER_LOGGER.tracef("Routing for operation %s is %s", operation, routing); return routing; } private final Set<String> hosts = new HashSet<String>(); private final boolean twoStep; /** Constructor for domain-level requests where we are not master */ private OperationRouting() { twoStep = false; } /** Constructor for multi-host ops */ private OperationRouting(final boolean twoStep) { this.twoStep = twoStep; } /** * Constructor for a non-two-step request routed to this host * * @param localHostControllerInfo information describing this host */ private OperationRouting(LocalHostControllerInfo localHostControllerInfo) { this.hosts.add(localHostControllerInfo.getLocalHostName()); this.twoStep = false; } /** * Constructor for a request routed to a single host * * @param hosts the name of the hosts * @param twoStep true if a two-step execution is needed */ private OperationRouting(Set<String> hosts, boolean twoStep) { this.hosts.addAll(hosts); this.twoStep = twoStep; } public Set<String> getHosts() { return hosts; } public String getSingleHost() { return hosts.size() == 1 ? hosts.iterator().next() : null; } public boolean isTwoStep() { return twoStep; } public boolean isLocalOnly(final String localHostName) { return hosts.size() == 1 && hosts.contains(localHostName); } public boolean isLocalCallNeeded(final String localHostName) { return hosts.size() == 0 || hosts.contains(localHostName); } @Override public String toString() { return "OperationRouting{" + "hosts=" + hosts + ", twoStep=" + twoStep + '}'; } }