/* * JBoss, Home of Professional Open Source. * Copyright 2012, 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.ignored; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CLONE; 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.PROFILE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOVE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.TO_PROFILE; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.PathElement; import org.jboss.as.controller.SimpleResourceDefinition; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.controller.registry.Resource; import org.jboss.as.domain.controller.LocalHostControllerInfo; import org.jboss.as.host.controller.descriptions.HostResolver; import org.jboss.dmr.ModelNode; /** * Registry for excluded domain-level resources. To be used by slave Host Controllers to ignore requests * for particular resources that the host cannot understand. This is a mechanism to allow hosts running earlier * AS releases to function as slaves in domains whose master is in a later release. * * * @author Brian Stansberry (c) 2011 Red Hat Inc. */ public class IgnoredDomainResourceRegistry { private final LocalHostControllerInfo localHostControllerInfo; private volatile IgnoredDomainResourceRoot rootResource; private IgnoredClonedProfileRegistry ignoredClonedProfileRegistry = new IgnoredClonedProfileRegistry(); public IgnoredDomainResourceRegistry(LocalHostControllerInfo localHostControllerInfo) { this.localHostControllerInfo = localHostControllerInfo; } /** * Returns whether this host should ignore operations from the master domain controller that target * the given address. * * @param address the resource address. Cannot be {@code null} * * @return {@code true} if the operation should be ignored; {@code false} otherwise */ public boolean isResourceExcluded(final PathAddress address) { if (!localHostControllerInfo.isMasterDomainController() && address.size() > 0) { IgnoredDomainResourceRoot root = this.rootResource; PathElement firstElement = address.getElement(0); IgnoreDomainResourceTypeResource typeResource = root == null ? null : root.getChildInternal(firstElement.getKey()); if (typeResource != null) { if (typeResource.hasName(firstElement.getValue())) { return true; } } } return false; } public void registerResources(final ManagementResourceRegistration parentRegistration) { parentRegistration.registerSubModel(new ResourceDefinition()); } public Resource.ResourceEntry getRootResource() { IgnoredDomainResourceRoot root = new IgnoredDomainResourceRoot(this); this.rootResource = root; return root; } public ModelNode getIgnoredResourcesAsModel() { IgnoredDomainResourceRoot root = this.rootResource; ModelNode model = (root == null ? new ModelNode() : Resource.Tools.readModel(root)); return model; } void publish(IgnoredDomainResourceRoot root) { this.rootResource = root; } boolean isMaster() { return localHostControllerInfo.isMasterDomainController(); } public IgnoredClonedProfileRegistry getIgnoredClonedProfileRegistry() { return ignoredClonedProfileRegistry; } private class ResourceDefinition extends SimpleResourceDefinition { public ResourceDefinition() { super(IgnoredDomainResourceRoot.PATH_ELEMENT, HostResolver.getResolver(ModelDescriptionConstants.IGNORED_RESOURCES)); } @Override public void registerChildren(ManagementResourceRegistration resourceRegistration) { resourceRegistration.registerSubModel(new IgnoredDomainTypeResourceDefinition()); } } /** * <p>This class is for internal use only.</p> * * <p>The main purpose of this registry is to deal with the situation where a profile is explicitly ignored on the slave, * and that profile is cloned. So say that the {@code ignored} profile is ignored on the slave, and a call comes to * {@code /profile=ignore:clone(to-profile=new)}. Since the {@code ignored} profile is ignored on the slave, the * {@code clone} operation never gets called, so no {@code new} profile gets created on the slave. Hence, we need * to ignore all subsequent operation invocations on the slave for the {@code new} profile. This class maintains the * runtime registry of profiles resulting from clone operations on profiles that were ignored on this slave.</p> * * <p>The situation above where the {@code new} profile does not get created on the slave due to the {@ignored} profile * being explicitly ignored on the slave, will result in the server being put into the {@code reload-required} state * since according to the explicit ignores it should really be part of the slave's domain model, and a reload of the * slave will download that. * * <p>If the {@new} profile was also explicitly ignored, we do not add it to the runtime registry and do not put the * server into the {@code reload-required} state. The settings in the slave model deal with the ignores for us in * that case.</p> * * <p>Finally in the example above, a call to {@code /profile=new:remove} will remove the {@code new} profile from the * runtime registry of profiles resulting from clone operations on profiles that were ignored on this slave.</p> * * <p>The registry is transactional, using a transaction local copy of the changes made, and should be published/rolled back * on transaction completion.</p> */ public class IgnoredClonedProfileRegistry { private volatile Set<String> ignoredClonedProfiles = Collections.synchronizedSet(new HashSet<>()); private volatile Set<String> currentTxIgnoredClonedProfiles; private volatile boolean reloadRequired; private IgnoredClonedProfileRegistry() { } /** * Checks if an operation should be ignored, and updates the runtime registry and host state as required. Use * {@link #isReloadRequired()} to check if the host state should be set to {@code reload-required}. * * @param operation the operation to check * @return whether the operation should be ignored */ public boolean checkIgnoredProfileClone(ModelNode operation) { if (!localHostControllerInfo.isMasterDomainController()) { PathAddress addr = PathAddress.pathAddress(operation.get(OP_ADDR)); if (addr.size() > 0) { PathElement first = addr.getElement(0); if (first.getKey().equals(PROFILE)) { String name = operation.get(OP).asString(); if (name.equals(CLONE) && isResourceExcluded(addr)) { //We are cloning an ignored profile final String profileName = operation.get(TO_PROFILE).asString(); if (!isResourceExcluded(PathAddress.pathAddress(PROFILE, profileName))) { //The new profile is not explicitly ignored, so add the new profile to the runtime registry //and indicate that the host should be put into the reload-required state. getIgnoredClonedProfiles(true).add(profileName); reloadRequired = true; return true; } } else if (name.equals(REMOVE) && addr.size() == 1) { //We are removing a profile, remove it from the runtime registry if it is there return getIgnoredClonedProfiles(true).remove(first.getValue()); } else { //Ignore depending on if it is in the runtime registry return getIgnoredClonedProfiles(false).contains(first.getValue()); } } } } return false; } /** * Callback for starting applying a fresh domain model from the DC. This will clear the runtime registry */ public void initializeModelSync() { if (!localHostControllerInfo.isMasterDomainController()) { //We are resyncing the model, so clear the runtime registry getIgnoredClonedProfiles(true).clear(); } } /** * Callback for when the controller transaction completes. This will publish the changes to the runtime registry * if the transaction was committed, and roll them back if it was rolled back. * * @param rollback {@code true} if the changes should be rolled back, {@code false} if they should be committed. */ public void complete(boolean rollback) { if (!localHostControllerInfo.isMasterDomainController()) { if (!rollback) { if (currentTxIgnoredClonedProfiles != null) { ignoredClonedProfiles = currentTxIgnoredClonedProfiles; } } currentTxIgnoredClonedProfiles = null; reloadRequired = false; } } /** * Check if the changes to the registry should cause the slave to be put into the {@code reload-required} state. * * @return {@code true} if the host should be put into the {@code reload-required} state, {@code false} otherwise. */ public boolean isReloadRequired() { return reloadRequired; } private Set<String> getIgnoredClonedProfiles(boolean write) { if (currentTxIgnoredClonedProfiles != null) { return currentTxIgnoredClonedProfiles; } if (write) { currentTxIgnoredClonedProfiles = Collections.synchronizedSet(new HashSet<>(ignoredClonedProfiles)); return currentTxIgnoredClonedProfiles; } return ignoredClonedProfiles; } } }