/* * JBoss, Home of Professional Open Source. * Copyright 2013, 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; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT_OVERLAY; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DOMAIN_CONTROLLER; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FULL_REPLACE_DEPLOYMENT; 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.NAME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROFILE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOTE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOVE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER_CONFIG; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER_GROUP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SOCKET_BINDING_GROUP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.UPLOAD_DEPLOYMENT_BYTES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.UPLOAD_DEPLOYMENT_STREAM; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.UPLOAD_DEPLOYMENT_URL; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.jboss.as.controller.access.HostEffect; import org.jboss.as.controller.access.ServerGroupEffect; import org.jboss.as.controller.registry.Resource; import org.jboss.dmr.ModelNode; /** * Tracks what server groups are associated with various model resources. * * @author Brian Stansberry (c) 2013 Red Hat Inc. */ class HostServerGroupTracker { private static final Set<String> UPLOAD_OPS = Collections.unmodifiableSet( new HashSet<String>(Arrays.asList(UPLOAD_DEPLOYMENT_BYTES, UPLOAD_DEPLOYMENT_STREAM, UPLOAD_DEPLOYMENT_URL))); static class HostServerGroupEffect implements HostEffect, ServerGroupEffect { private static final Set<String> EMPTY = Collections.emptySet(); private final PathAddress address; private final Set<String> serverGroupEffects; private final Set<String> hostEffects; private final boolean unassigned; private final boolean groupAdd; private final boolean groupRemove; private final boolean serverEffect; /** Creates an HSGE for an address in the domain-wide tree that CANNOT be mapped to server groups.*/ private static HostServerGroupEffect forDomainGlobal(PathAddress address) { return new HostServerGroupEffect(address, (Set<String>) null, null, false, false, false, false); } /** Creates an HSGE for an address in the domain-wide tree that IS mapped to server groups.*/ private static HostServerGroupEffect forDomain(PathAddress address, Set<String> serverGroupEffects) { return new HostServerGroupEffect(address, serverGroupEffects == null ? EMPTY : serverGroupEffects, null, false, false, false, false); } /** Creates an HSGE for an address in the domain-wide tree that CAN be mapped to server groups but currently IS NOT mapped.*/ private static HostServerGroupEffect forUnassignedDomain(PathAddress address) { return new HostServerGroupEffect(address, EMPTY, null, false, true, false, false); } /** Creates an HSGE for an address in a server-group tree */ private static HostServerGroupEffect forServerGroup(PathAddress address, String serverGroup, boolean groupAdd, boolean groupRemove) { return new HostServerGroupEffect(address, Collections.singleton(serverGroup), null, false, false, groupAdd, groupRemove); } /** Creates an HSGE for the local host-tree resource (excluding server and server-config subtress for a host * that HAS been mapped to one or more server groups */ private static HostServerGroupEffect forMappedHost(PathAddress address, Set<String> serverGroupEffects, String hostEffect) { return new HostServerGroupEffect(address, serverGroupEffects, hostEffect, false, false, false, false); } /** Creates an HSGE for an address on a remote host. We don't need to worry the op doing writes or impacting * unallowed server groups in this process; those will be attempted and authorized in the remote process. */ private static HostServerGroupEffect forNonLocalHost(PathAddress address, String hostEffect) { return new HostServerGroupEffect(address, (Set<String>) null, hostEffect, false, false, false, false); } /** Creates an HSGE for the local host-tree resource (excluding server and server-config subtress for a host * that HAS NOT been mapped to any server groups */ private static HostServerGroupEffect forUnassignedHost(PathAddress address, String hostEffect) { return new HostServerGroupEffect(address, (Set<String>) null, hostEffect, false, true, false, false); } /** Creates an HSGE for an address that points to /host=xxx/server-config=* and not to a specific server-config */ private static HostServerGroupEffect forWildCardServerConfig(PathAddress address, String hostEffect) { return new HostServerGroupEffect(address, EMPTY, hostEffect, false, true, false, false); } /** Creates an HSGE for an address that points to a domain server resource, or a non-wildcard server-config resource*/ static HostServerGroupEffect forServer(PathAddress address, String serverGroupEffect, String hostEffect) { assert serverGroupEffect != null : "serverGroupEffect is null"; return new HostServerGroupEffect(address, Collections.singleton(serverGroupEffect), hostEffect, true, false, false, false); } /** Creates an HSGE for a local host address that points to an unknown server or server-config resource */ private static HostServerGroupEffect forUnknownLocalServer(PathAddress address, String hostEffect) { return new HostServerGroupEffect(address, (Set<String>) null, hostEffect, false, false, false, false); } private HostServerGroupEffect(PathAddress address, Set<String> serverGroupEffects, String hostEffect, boolean serverEffect, boolean unassigned, boolean groupAdd, boolean groupRemove) { this.address = address; this.serverGroupEffects = serverGroupEffects; this.serverEffect = serverEffect; this.unassigned = unassigned; this.hostEffects = hostEffect == null ? null : Collections.singleton(hostEffect); this.groupAdd = groupAdd; this.groupRemove = groupRemove; } @Override public PathAddress getResourceAddress() { return address; } @Override public boolean isServerGroupEffectGlobal() { return serverGroupEffects == null; } @Override public boolean isServerGroupEffectUnassigned() { return unassigned; } @Override public Set<String> getAffectedServerGroups() { return serverGroupEffects; } @Override public boolean isServerGroupAdd() { return groupAdd; } @Override public boolean isServerGroupRemove() { return groupRemove; } @Override public boolean isHostEffectGlobal() { return hostEffects == null; } @Override public boolean isServerEffect() { return serverEffect; } @Override public Set<String> getAffectedHosts() { return hostEffects; } } private boolean requiresMapping = true; private final Map<String, Set<String>> profilesToGroups = new HashMap<String, Set<String>>(); private final Map<String, Set<String>> socketsToGroups = new HashMap<String, Set<String>>(); private final Map<String, Set<String>> deploymentsToGroups = new HashMap<String, Set<String>>(); private final Map<String, Set<String>> overlaysToGroups = new HashMap<String, Set<String>>(); private final Map<String, Set<String>> hostsToGroups = new HashMap<String, Set<String>>(); HostServerGroupEffect getHostServerGroupEffects(PathAddress address, ModelNode operation, Resource root) { final int addrSize = address.size(); if (addrSize > 0) { PathElement firstElement = address.getElement(0); String type = firstElement.getKey(); // Not a switch to ease EAP 6 backport if (HOST.equals(type)) { String hostName = firstElement.getValue(); if (addrSize > 1) { PathElement secondElement = address.getElement(1); String lvlone = secondElement.getKey(); if (SERVER_CONFIG.equals(lvlone) || SERVER.equals(lvlone)) { Resource hostResource = root.getChild(firstElement); if (hostResource != null) { String serverGroup = null; Resource serverConfig = hostResource.getChild(PathElement.pathElement(SERVER_CONFIG, secondElement.getValue())); if (serverConfig != null) { // may be null if it's a wildcard or server-config not created yet (bad address or add op) ModelNode model = serverConfig.getModel(); if (model.hasDefined(GROUP)) { serverGroup = model.get(GROUP).asString(); } } if (serverGroup == null && address.size() == 2 && SERVER_CONFIG.equals(lvlone)) { if (secondElement.isWildcard()) { // https://issues.jboss.org/browse/WFLY-2299 return HostServerGroupEffect.forWildCardServerConfig(address, hostName); } else if (ADD.equals(operation.require(OP).asString())) { serverGroup = operation.get(GROUP).asString(); } } if (serverGroup != null) { return HostServerGroupEffect.forServer(address, serverGroup, hostName); } // else maybe it's a bad address // We checked it's not a server-config add so assume it's a read and just provide the // forUnknownLocalServer response, which will be acceptable for a read for any server group scoped role // Presumably the request will fail for reasons unrelated to authorization return HostServerGroupEffect.forUnknownLocalServer(address, hostName); } // else not the local host. Can only be a read on this process, so just use the forNonLocalHost response, // which will be acceptable for a read for any server group scoped role return HostServerGroupEffect.forNonLocalHost(address, hostName); } } return getHostEffect(address, hostName, root); } else if (PROFILE.equals(type)) { return getMappableDomainEffect(address, firstElement.getValue(), profilesToGroups, root); } else if (SOCKET_BINDING_GROUP.equals(type)) { return getMappableDomainEffect(address, firstElement.getValue(), socketsToGroups, root); } else if (SERVER_GROUP.equals(type)) { // WFLY-2190 make add/remove global. So, s-g-s-r can't remove its own server group // and can't add it. This helps the console, but since there ideally would be validation // that all groups mapped to a s-g-s-r actually exist, it's reasonable to say the group // must exist (so no need for an :add) and can't be removed String opName = operation.require(OP).asString(); if (addrSize > 1 || (!ADD.equals(opName) && !REMOVE.equals(opName))) { return HostServerGroupEffect.forServerGroup(address, firstElement.getValue(), false, false); } else { boolean add = ADD.equals(opName); return HostServerGroupEffect.forServerGroup(address, firstElement.getValue(), add, !add); } } else if (DEPLOYMENT.equals(type)) { return getMappableDomainEffect(address, firstElement.getValue(), deploymentsToGroups, root); } else if (DEPLOYMENT_OVERLAY.equals(type)) { return getMappableDomainEffect(address, firstElement.getValue(), overlaysToGroups, root); } } else { // WFLY-1916 -- need special handling for deployment related ops String opName = operation.require(OP).asString(); if (FULL_REPLACE_DEPLOYMENT.equals(opName)) { // The name of the deployment being replaced is what matters if (operation.hasDefined(NAME)) { return getMappableDomainEffect(address, operation.get(NAME).asString(), deploymentsToGroups, root); } } else if (UPLOAD_OPS.contains(opName)) { // Treat this like an unmapped deployment return HostServerGroupEffect.forUnassignedDomain(address); } } return HostServerGroupEffect.forDomainGlobal(address); } synchronized void invalidate() { requiresMapping = true; profilesToGroups.clear(); socketsToGroups.clear(); deploymentsToGroups.clear(); overlaysToGroups.clear(); hostsToGroups.clear(); } /** Creates an appropriate HSGE for a domain-wide resource of a type that is mappable to server groups */ private synchronized HostServerGroupEffect getMappableDomainEffect(PathAddress address, String key, Map<String, Set<String>> map, Resource root) { if (requiresMapping) { map(root); requiresMapping = false; } Set<String> mapped = map.get(key); return mapped != null ? HostServerGroupEffect.forDomain(address, mapped) : HostServerGroupEffect.forUnassignedDomain(address); } /** Creates an appropriate HSGE for resources in the host tree, excluding the server and server-config subtrees */ private synchronized HostServerGroupEffect getHostEffect(PathAddress address, String host, Resource root) { if (requiresMapping) { map(root); requiresMapping = false; } Set<String> mapped = hostsToGroups.get(host); if (mapped == null) { // Unassigned host. Treat like an unassigned profile or socket-binding-group; // i.e. available to all server group scoped roles. // Except -- WFLY-2085 -- the master HC is not open to all s-g-s-rs Resource hostResource = root.getChild(PathElement.pathElement(HOST, host)); if (hostResource != null) { ModelNode dcModel = hostResource.getModel().get(DOMAIN_CONTROLLER); if (!dcModel.hasDefined(REMOTE)) { mapped = Collections.emptySet(); // prevents returning HostServerGroupEffect.forUnassignedHost(address, host) } } } return mapped == null ? HostServerGroupEffect.forUnassignedHost(address, host) : HostServerGroupEffect.forMappedHost(address, mapped, host); } /** Only call with monitor for 'this' held */ private void map(Resource root) { for (Resource.ResourceEntry serverGroup : root.getChildren(SERVER_GROUP)) { String serverGroupName = serverGroup.getName(); ModelNode serverGroupModel = serverGroup.getModel(); String profile = serverGroupModel.require(PROFILE).asString(); store(serverGroupName, profile, profilesToGroups); String socketBindingGroup = serverGroupModel.require(SOCKET_BINDING_GROUP).asString(); store(serverGroupName, socketBindingGroup, socketsToGroups); for (Resource.ResourceEntry deployment : serverGroup.getChildren(DEPLOYMENT)) { store(serverGroupName, deployment.getName(), deploymentsToGroups); } for (Resource.ResourceEntry overlay : serverGroup.getChildren(DEPLOYMENT_OVERLAY)) { store(serverGroupName, overlay.getName(), overlaysToGroups); } } for (Resource.ResourceEntry host : root.getChildren(HOST)) { String hostName = host.getPathElement().getValue(); for (Resource.ResourceEntry serverConfig : host.getChildren(SERVER_CONFIG)) { ModelNode serverConfigModel = serverConfig.getModel(); String serverGroupName = serverConfigModel.require(GROUP).asString(); store(serverGroupName, hostName, hostsToGroups); } } } private static void store(String serverGroup, String key, Map<String, Set<String>> map) { Set<String> set = map.get(key); if (set == null) { set = new HashSet<String>(); map.put(key, set); } set.add(serverGroup); } }