/* * 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.domain.controller.operations; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.EXTENSION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.GROUP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INCLUDES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROFILE; 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.SUBSYSTEM; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.PathElement; 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.transform.Transformers; import org.jboss.as.host.controller.IgnoredNonAffectedServerGroupsUtil; import org.jboss.as.host.controller.mgmt.HostInfo; import org.jboss.dmr.ModelNode; /** * Utility for the DC operation handlers to describe the missing resources for the slave hosts which are * set up to ignore domain config which does not affect their servers * * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> * @author Emanuel Muckenhuber */ public class ReadMasterDomainModelUtil { public static final String DOMAIN_RESOURCE_ADDRESS = "domain-resource-address"; public static final String DOMAIN_RESOURCE_MODEL = "domain-resource-model"; public static final String DOMAIN_RESOURCE_PROPERTIES = "domain-resource-properties"; public static final String ORDERED_CHILD_TYPES_PROPERTY = "ordered-child-types"; private final Set<PathElement> newRootResources = new HashSet<>(); private volatile List<ModelNode> describedResources; private ReadMasterDomainModelUtil() { } /** * Used to read the domain model when a slave host connects to the DC * * @param transformers the transformers for the host * @param transformationInputs parameters for the transformation * @param ignoredTransformationRegistry registry of resources ignored by the transformation target * @param domainRoot the root resource for the domain resource tree * @return a read master domain model util instance */ static ReadMasterDomainModelUtil readMasterDomainResourcesForInitialConnect(final Transformers transformers, final Transformers.TransformationInputs transformationInputs, final Transformers.ResourceIgnoredTransformationRegistry ignoredTransformationRegistry, final Resource domainRoot) throws OperationFailedException { Resource transformedResource = transformers.transformRootResource(transformationInputs, domainRoot, ignoredTransformationRegistry); ReadMasterDomainModelUtil util = new ReadMasterDomainModelUtil(); util.describedResources = util.describeAsNodeList(PathAddress.EMPTY_ADDRESS, transformedResource, false); return util; } /** * Gets a list of the resources for the slave's ApplyXXXXHandlers. Although the format might appear * similar as the operations generated at boot-time this description is only useful * to create the resource tree and cannot be used to invoke any operation. * * @return the resources */ public List<ModelNode> getDescribedResources(){ return describedResources; } /** * Describe the model as a list of resources with their address and model, which * the HC can directly apply to create the model. Although the format might appear * similar as the operations generated at boot-time this description is only useful * to create the resource tree and cannot be used to invoke any operation. * * @param rootAddress the address of the root resource being described * @param resource the root resource * @return the list of resources */ private List<ModelNode> describeAsNodeList(PathAddress rootAddress, final Resource resource, boolean isRuntimeChange) { final List<ModelNode> list = new ArrayList<ModelNode>(); describe(rootAddress, resource, list, isRuntimeChange); return list; } private void describe(final PathAddress base, final Resource resource, List<ModelNode> nodes, boolean isRuntimeChange) { if (resource.isProxy() || resource.isRuntime()) { return; // ignore runtime and proxies } else if (base.size() >= 1 && base.getElement(0).getKey().equals(ModelDescriptionConstants.HOST)) { return; // ignore hosts } if (base.size() == 1) { newRootResources.add(base.getLastElement()); } final ModelNode description = new ModelNode(); description.get(DOMAIN_RESOURCE_ADDRESS).set(base.toModelNode()); description.get(DOMAIN_RESOURCE_MODEL).set(resource.getModel()); Set<String> orderedChildren = resource.getOrderedChildTypes(); if (orderedChildren.size() > 0) { ModelNode orderedChildTypes = description.get(DOMAIN_RESOURCE_PROPERTIES, ORDERED_CHILD_TYPES_PROPERTY); for (String type : orderedChildren) { orderedChildTypes.add(type); } } nodes.add(description); for (final String childType : resource.getChildTypes()) { for (final Resource.ResourceEntry entry : resource.getChildren(childType)) { describe(base.append(entry.getPathElement()), entry, nodes, isRuntimeChange); } } } /** * Create a resource based on the result of the {@code ReadMasterDomainModelHandler}. * * @param result the operation result * @param extensions set to track extensions * @return the resource */ static Resource createResourceFromDomainModelOp(final ModelNode result, final Set<String> extensions) { final Resource root = Resource.Factory.create(); for (ModelNode model : result.asList()) { final PathAddress resourceAddress = PathAddress.pathAddress(model.require(DOMAIN_RESOURCE_ADDRESS)); if (resourceAddress.size() == 1) { final PathElement element = resourceAddress.getElement(0); if (element.getKey().equals(EXTENSION)) { if (!extensions.contains(element.getValue())) { extensions.add(element.getValue()); } } } Resource resource = root; final Iterator<PathElement> i = resourceAddress.iterator(); if (!i.hasNext()) { //Those are root attributes resource.getModel().set(model.require(DOMAIN_RESOURCE_MODEL)); } while (i.hasNext()) { final PathElement e = i.next(); if (resource.hasChild(e)) { resource = resource.getChild(e); } else { /* { "domain-resource-address" => [ ("profile" => "test"), ("subsystem" => "test") ], "domain-resource-model" => {}, "domain-resource-properties" => {"ordered-child-types" => ["ordered-child"]} }*/ final Resource nr; if (model.hasDefined(DOMAIN_RESOURCE_PROPERTIES, ORDERED_CHILD_TYPES_PROPERTY)) { List<ModelNode> list = model.get(DOMAIN_RESOURCE_PROPERTIES, ORDERED_CHILD_TYPES_PROPERTY).asList(); Set<String> orderedChildTypes = new HashSet<String>(list.size()); for (ModelNode type : list) { orderedChildTypes.add(type.asString()); } nr = Resource.Factory.create(false, orderedChildTypes); } else { nr = Resource.Factory.create(); } resource.registerChild(e, nr); resource = nr; } if (!i.hasNext()) { resource.getModel().set(model.require(DOMAIN_RESOURCE_MODEL)); } } } return root; } /** * Process the host info and determine which configuration elements are required on the slave host. * * @param hostInfo the host info * @param root the model root * @param extensionRegistry the extension registry * @return */ public static RequiredConfigurationHolder populateHostResolutionContext(final HostInfo hostInfo, final Resource root, final ExtensionRegistry extensionRegistry) { final RequiredConfigurationHolder rc = new RequiredConfigurationHolder(); for (IgnoredNonAffectedServerGroupsUtil.ServerConfigInfo info : hostInfo.getServerConfigInfos()) { processServerConfig(root, rc, info, extensionRegistry); } return rc; } /** * Determine the relevant pieces of configuration which need to be included when processing the domain model. * * @param root the resource root * @param requiredConfigurationHolder the resolution context * @param serverConfig the server config * @param extensionRegistry the extension registry */ static void processServerConfig(final Resource root, final RequiredConfigurationHolder requiredConfigurationHolder, final IgnoredNonAffectedServerGroupsUtil.ServerConfigInfo serverConfig, final ExtensionRegistry extensionRegistry) { final Set<String> serverGroups = requiredConfigurationHolder.serverGroups; final Set<String> socketBindings = requiredConfigurationHolder.socketBindings; String sbg = serverConfig.getSocketBindingGroup(); if (sbg != null && !socketBindings.contains(sbg)) { processSocketBindingGroup(root, sbg, requiredConfigurationHolder); } final String groupName = serverConfig.getServerGroup(); final PathElement groupElement = PathElement.pathElement(SERVER_GROUP, groupName); // Also check the root, since this also gets executed on the slave which may not have the server-group configured yet if (!serverGroups.contains(groupName) && root.hasChild(groupElement)) { final Resource serverGroup = root.getChild(groupElement); final ModelNode groupModel = serverGroup.getModel(); serverGroups.add(groupName); // Include the socket binding groups if (groupModel.hasDefined(SOCKET_BINDING_GROUP)) { final String socketBindingGroup = groupModel.get(SOCKET_BINDING_GROUP).asString(); processSocketBindingGroup(root, socketBindingGroup, requiredConfigurationHolder); } final String profileName = groupModel.get(PROFILE).asString(); processProfile(root, profileName, requiredConfigurationHolder, extensionRegistry); } } static void processHostModel(final RequiredConfigurationHolder holder, final Resource domain, final Resource hostModel, ExtensionRegistry extensionRegistry) { final Set<String> serverGroups = holder.serverGroups; for (final Resource.ResourceEntry entry : hostModel.getChildren(SERVER_CONFIG)) { final ModelNode model = entry.getModel(); final String serverGroup = model.get(GROUP).asString(); if (!serverGroups.contains(serverGroup)) { serverGroups.add(serverGroup); } if (model.hasDefined(SOCKET_BINDING_GROUP)) { final String socketBindingGroup = model.get(SOCKET_BINDING_GROUP).asString(); processSocketBindingGroup(domain, socketBindingGroup, holder); } // Always process the server group, since it may be different between the current vs. original model processServerGroup(holder, serverGroup, domain, extensionRegistry); } } private static void processServerGroup(final RequiredConfigurationHolder holder, final String group, final Resource domain, ExtensionRegistry extensionRegistry) { final PathElement groupElement = PathElement.pathElement(SERVER_GROUP, group); if (!domain.hasChild(groupElement)) { return; } final Resource serverGroup = domain.getChild(groupElement); final ModelNode model = serverGroup.getModel(); if (model.hasDefined(SOCKET_BINDING_GROUP)) { final String socketBindingGroup = model.get(SOCKET_BINDING_GROUP).asString(); processSocketBindingGroup(domain, socketBindingGroup, holder); } final String profile = model.get(PROFILE).asString(); processProfile(domain, profile, holder, extensionRegistry); } private static void processProfile(final Resource domain, final String profile, final RequiredConfigurationHolder holder, final ExtensionRegistry extensionRegistry) { final Set<String> profiles = holder.profiles; final Set<String> extensions = holder.extensions; if (profiles.contains(profile)) { return; } profiles.add(profile); final PathElement profileElement = PathElement.pathElement(PROFILE, profile); if (domain.hasChild(profileElement)) { final Resource resource = domain.getChild(profileElement); final Set<String> subsystems = new HashSet<>(); final Set<String> availableExtensions = extensionRegistry.getExtensionModuleNames(); for (final Resource.ResourceEntry subsystem : resource.getChildren(SUBSYSTEM)) { subsystems.add(subsystem.getName()); } for (final String extension : availableExtensions) { if (extensions.contains(extension)) { // Skip already processed extensions continue; } for (final String subsystem : extensionRegistry.getAvailableSubsystems(extension).keySet()) { if (subsystems.contains(subsystem)) { extensions.add(extension); } } } if (resource.getModel().hasDefined(INCLUDES)) { for (final ModelNode include : resource.getModel().get(INCLUDES).asList()) { processProfile(domain, include.asString(), holder, extensionRegistry); } } } } private static void processSocketBindingGroup(final Resource domain, final String socketBindingGroup, final RequiredConfigurationHolder holder) { final Set<String> socketBindingGroups = holder.socketBindings; if (socketBindingGroups.contains(socketBindingGroup)) { return; } socketBindingGroups.add(socketBindingGroup); final PathElement socketBindingGroupElement = PathElement.pathElement(SOCKET_BINDING_GROUP, socketBindingGroup); if (domain.hasChild(socketBindingGroupElement)) { final Resource resource = domain.getChild(socketBindingGroupElement); if (resource.getModel().hasDefined(INCLUDES)) { for (final ModelNode include : resource.getModel().get(INCLUDES).asList()) { processSocketBindingGroup(domain, include.asString(), holder); } } } ControllerLogger.ROOT_LOGGER.tracef("Recorded need for socket-binding-group %s", socketBindingGroup); } /** * Create the ResourceIgnoredTransformationRegistry for connection/reconnection process. * * @param hostInfo the host info * @param rc the resolution context * @return */ public static Transformers.ResourceIgnoredTransformationRegistry createHostIgnoredRegistry(final HostInfo hostInfo, final RequiredConfigurationHolder rc) { return new Transformers.ResourceIgnoredTransformationRegistry() { @Override public boolean isResourceTransformationIgnored(PathAddress address) { if (hostInfo.isResourceTransformationIgnored(address)) { return true; } if (address.size() == 1 && hostInfo.isIgnoreUnaffectedConfig()) { final PathElement element = address.getElement(0); final String type = element.getKey(); switch (type) { case ModelDescriptionConstants.EXTENSION: // Don't ignore extensions for now return false; // if (local) { // return false; // Always include all local extensions // } else if (!rc.getExtensions().contains(element.getValue())) { // return true; // } // break; case PROFILE: if (!rc.getProfiles().contains(element.getValue())) { return true; } break; case SERVER_GROUP: if (!rc.getServerGroups().contains(element.getValue())) { return true; } break; case SOCKET_BINDING_GROUP: if (!rc.getSocketBindings().contains(element.getValue())) { return true; } break; } } return false; } }; } /** * Create the ResourceIgnoredTransformationRegistry when fetching missing content, only including relevant pieces * to a server-config. * * @param rc the resolution context * @param delegate the delegate ignored resource transformation registry for manually ignored resources * @return */ public static Transformers.ResourceIgnoredTransformationRegistry createServerIgnoredRegistry(final RequiredConfigurationHolder rc, final Transformers.ResourceIgnoredTransformationRegistry delegate) { return new Transformers.ResourceIgnoredTransformationRegistry() { @Override public boolean isResourceTransformationIgnored(PathAddress address) { final int length = address.size(); if (length == 0) { return false; } else if (length >= 1) { if (delegate.isResourceTransformationIgnored(address)) { return true; } final PathElement element = address.getElement(0); final String type = element.getKey(); switch (type) { case ModelDescriptionConstants.EXTENSION: // Don't ignore extensions for now return false; // if (local) { // return false; // Always include all local extensions // } else if (rc.getExtensions().contains(element.getValue())) { // return false; // } // break; case ModelDescriptionConstants.PROFILE: if (rc.getProfiles().contains(element.getValue())) { return false; } break; case ModelDescriptionConstants.SERVER_GROUP: if (rc.getServerGroups().contains(element.getValue())) { return false; } break; case ModelDescriptionConstants.SOCKET_BINDING_GROUP: if (rc.getSocketBindings().contains(element.getValue())) { return false; } break; } } return true; } }; } public static class RequiredConfigurationHolder { private final Set<String> extensions = new HashSet<>(); private final Set<String> profiles = new HashSet<>(); private final Set<String> serverGroups = new HashSet<>(); private final Set<String> socketBindings = new HashSet<>(); public Set<String> getExtensions() { return extensions; } public Set<String> getProfiles() { return profiles; } public Set<String> getServerGroups() { return serverGroups; } public Set<String> getSocketBindings() { return socketBindings; } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("RequiredConfigurationHolder{"); builder.append("extensions=").append(extensions); builder.append("profiles=").append(profiles).append(", "); builder.append("server-groups=").append(serverGroups).append(", "); builder.append("socket-bindings=").append(socketBindings).append("}"); return builder.toString(); } } }