/* * Copyright (C) 2015 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 library 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 library 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ package org.jboss.as.controller.operations.global; import static org.jboss.as.controller.client.helpers.ClientConstants.RESULT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ACCESS_CONTROL; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DOMAIN_ORGANIZATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FILE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ORGANIZATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PRODUCT_NAME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PRODUCT_VERSION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import org.jboss.as.controller.AttributeMarshaller; import org.jboss.as.controller.ObjectTypeAttributeDefinition; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.OperationStepHandler; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.PathElement; import org.jboss.as.controller.SimpleAttributeDefinition; import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; import org.jboss.as.controller.SimpleOperationDefinition; import org.jboss.as.controller.SimpleOperationDefinitionBuilder; import org.jboss.as.controller.descriptions.common.ControllerResolver; import org.jboss.as.controller.logging.ControllerLogger; import org.jboss.as.controller.operations.common.Util; import org.jboss.as.controller.registry.OperationEntry; import org.jboss.as.controller.registry.Resource; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; /** * Global operation to build the installation summary of a server or a domain. * * @author <a href="mailto:ehugonne@redhat.com">Emmanuel Hugonnet</a> (c) 2015 Red Hat, inc. */ public class GlobalInstallationReportHandler extends GlobalOperationHandlers.AbstractMultiTargetHandler { public static final String OPERATION_NAME = "product-info"; public static final String SUB_OPERATION_NAME = "report"; public static final String ARCH = "host-cpu-arch"; public static final String AVAILABLE_PROCESSORS = "host-core-count"; public static final String CPU = "host-cpu"; public static final String FORMAT = "format"; public static final String HOSTNAME = "host-name"; public static final String INSTANCE_ID = "instance-identifier"; public static final String JAVA_VERSION = "java-version"; public static final String JVM = "jvm"; public static final String JVM_HOME = "java-home"; public static final String JVM_VENDOR = "jvm-vendor"; public static final String JVM_VERSION = "jvm-version"; public static final String NODE_NAME = "node-name"; public static final String OS = "host-operating-system"; public static final String PRODUCT_COMMUNITY_IDENTIFIER = "product-community-identifier"; public static final String PRODUCT_HOME = "product-home"; public static final String PRODUCT_INSTALLATION_DATE = "installation-date"; public static final String PRODUCT_LAST_UPDATE = "last-update-date"; public static final String STANDALONE_DOMAIN_IDENTIFIER = "standalone-or-domain-identifier"; public static final String SUMMARY = "summary"; /** * Type of PRODUCT_COMMUNITY_IDENTIFIER */ public static final String PRODUCT_TYPE = "Product"; public static final String PROJECT_TYPE = "Project"; /** * Supported FORMATs */ public static final String XML_FORMAT = "xml"; public static final String JSON_FORMAT = "json"; public static final SimpleAttributeDefinition JVM_DEFINITION = ObjectTypeAttributeDefinition.Builder.of(JVM, SimpleAttributeDefinitionBuilder.create(JAVA_VERSION, ModelType.STRING, true).build(), SimpleAttributeDefinitionBuilder.create(JVM_VERSION, ModelType.STRING, true).build(), SimpleAttributeDefinitionBuilder.create(JVM_VENDOR, ModelType.STRING, true).build(), SimpleAttributeDefinitionBuilder.create(JVM_HOME, ModelType.STRING, true).build() ).setAllowNull(false).setAttributeMarshaller(AttributeMarshaller.ELEMENT_ONLY_OBJECT).build(); public static final SimpleAttributeDefinition CPU_DEFINITION = ObjectTypeAttributeDefinition.Builder.of(CPU, SimpleAttributeDefinitionBuilder.create(ARCH, ModelType.STRING, true).build(), SimpleAttributeDefinitionBuilder.create(AVAILABLE_PROCESSORS, ModelType.INT, true).setDefaultValue(new ModelNode(1)).build() ).setAllowNull(false).setAttributeMarshaller(AttributeMarshaller.ATTRIBUTE_OBJECT).build(); public static final SimpleAttributeDefinition SUMMARY_DEFINITION = new ObjectTypeAttributeDefinition.Builder(SUMMARY, SimpleAttributeDefinitionBuilder.create(NODE_NAME, ModelType.STRING, true).build(), SimpleAttributeDefinitionBuilder.create(HOSTNAME, ModelType.STRING, true).build(), SimpleAttributeDefinitionBuilder.create(INSTANCE_ID, ModelType.STRING, true).build(), SimpleAttributeDefinitionBuilder.create(PRODUCT_NAME, ModelType.STRING, true).build(), SimpleAttributeDefinitionBuilder.create(PRODUCT_COMMUNITY_IDENTIFIER, ModelType.STRING, false) .setDefaultValue(new ModelNode(PROJECT_TYPE)) .setAllowedValues(PRODUCT_TYPE, PROJECT_TYPE).build(), SimpleAttributeDefinitionBuilder.create(PRODUCT_VERSION, ModelType.STRING, true).build(), SimpleAttributeDefinitionBuilder.create(PRODUCT_HOME, ModelType.STRING, true).build(), SimpleAttributeDefinitionBuilder.create(PRODUCT_INSTALLATION_DATE, ModelType.STRING, true).build(), SimpleAttributeDefinitionBuilder.create(PRODUCT_LAST_UPDATE, ModelType.STRING, true).build(), SimpleAttributeDefinitionBuilder.create(OS, ModelType.STRING, false).build(), JVM_DEFINITION, CPU_DEFINITION).setAttributeMarshaller(AttributeMarshaller.ELEMENT_ONLY_OBJECT).build(); public static final SimpleAttributeDefinition CREATE_REPORT_DEFINITION = SimpleAttributeDefinitionBuilder.create(FILE, ModelType.BOOLEAN, true) .setDefaultValue(new ModelNode(false)).build(); public static final SimpleAttributeDefinition FILE_FORMAT_DEFINITION = SimpleAttributeDefinitionBuilder.create(FORMAT, ModelType.STRING, true) .setDefaultValue(new ModelNode(XML_FORMAT)) .setAllowedValues(XML_FORMAT, JSON_FORMAT).build(); public static final GlobalInstallationReportHandler INSTANCE = new GlobalInstallationReportHandler(); public static final SimpleOperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(OPERATION_NAME, ControllerResolver.getResolver("global")) .setRuntimeOnly() .setReadOnly() .setReplyType(ModelType.LIST) .setReplyParameters(SUMMARY_DEFINITION) .build(); @Override void doExecute(OperationContext context, ModelNode operation, FilteredData filteredData, boolean ignoreMissingResource) throws OperationFailedException { final Map<String, GlobalOperationHandlers.AvailableResponse> servers = new HashMap<>(); ModelNode rootModel = context.readResourceFromRoot(PathAddress.EMPTY_ADDRESS,false).getModel(); final String defaultOrganization = (rootModel.hasDefined(DOMAIN_ORGANIZATION)) ? rootModel.get(DOMAIN_ORGANIZATION).asString() : null; final Map<String, String> serverOrganizations = new HashMap<>(); final ReportAssemblyHandler assemblyHandler = new ReportAssemblyHandler(servers, serverOrganizations, filteredData, ignoreMissingResource); if (null != context.getProcessType()) { switch (context.getProcessType()) { case HOST_CONTROLLER: context.addStep(new OperationStepHandler() { @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { context.addStep(assemblyHandler, OperationContext.Stage.VERIFY, true); String host = Util.getNameFromAddress(context.getCurrentAddress()); PathAddress hostAddress = context.getCurrentAddress(); ModelNode hostModel = context.readResource(PathAddress.EMPTY_ADDRESS).getModel(); final String hostOrganization = hostModel.hasDefined(ORGANIZATION) ? hostModel.get(ORGANIZATION).asString() : defaultOrganization; addHostReport(context, hostAddress, host, servers); if (hostOrganization != null) { serverOrganizations.put(hostOrganization, hostOrganization); } Set<String> hostServers = context.readResource(PathAddress.EMPTY_ADDRESS).getChildrenNames(SERVER); hostServers.stream().forEach((server) -> { String nodeName = host + ":" + server; addServerReport(context, hostAddress.append(SERVER, server), nodeName, servers); if(hostOrganization != null) { serverOrganizations.put(nodeName, hostOrganization); } }); } }, OperationContext.Stage.RUNTIME); break; case STANDALONE_SERVER: case DOMAIN_SERVER: case EMBEDDED_SERVER: case SELF_CONTAINED: context.addStep(assemblyHandler, OperationContext.Stage.VERIFY, true); addStandaloneReport(context, servers); break; default: } } } private void addStandaloneReport(OperationContext context, final Map<String, GlobalOperationHandlers.AvailableResponse> responseMap) { ModelNode reportOperation = Util.getEmptyOperation(SUB_OPERATION_NAME, PathAddress.EMPTY_ADDRESS.toModelNode()); final ModelNode response = new ModelNode(); GlobalOperationHandlers.AvailableResponse availableResponse = new GlobalOperationHandlers.AvailableResponse(response); OperationEntry entry = context.getResourceRegistration().getOperationEntry(PathAddress.EMPTY_ADDRESS, SUB_OPERATION_NAME); OperationStepHandler osh = entry.getOperationHandler(); GlobalOperationHandlers.AvailableResponseWrapper wrapper = new GlobalOperationHandlers.AvailableResponseWrapper(osh, availableResponse); context.addStep(response, reportOperation, wrapper, OperationContext.Stage.RUNTIME); responseMap.put("", availableResponse); ControllerLogger.ROOT_LOGGER.debug("We are asking the standalone server its report"); } private void addServerReport(OperationContext context, PathAddress address, String server, final Map<String, GlobalOperationHandlers.AvailableResponse> responseMap) { ModelNode reportOperation = Util.getEmptyOperation(SUB_OPERATION_NAME, address.toModelNode()); final ModelNode response = new ModelNode(); GlobalOperationHandlers.AvailableResponse availableResponse = new GlobalOperationHandlers.AvailableResponse(response); responseMap.put(server, availableResponse); OperationEntry entry = context.getRootResourceRegistration().getOperationEntry(address, SUB_OPERATION_NAME); if (entry != null) { OperationStepHandler osh = entry.getOperationHandler(); GlobalOperationHandlers.AvailableResponseWrapper wrapper = new GlobalOperationHandlers.AvailableResponseWrapper(osh, availableResponse); context.addStep(response, reportOperation, wrapper, OperationContext.Stage.RUNTIME); ControllerLogger.ROOT_LOGGER.debugf("We are asking the server %s its report", server); } } private void addHostReport(OperationContext context, PathAddress address, String host, final Map<String, GlobalOperationHandlers.AvailableResponse> responseMap) { ModelNode reportOperation = Util.getEmptyOperation(SUB_OPERATION_NAME, address.toModelNode()); final ModelNode response = new ModelNode(); GlobalOperationHandlers.AvailableResponse availableResponse = new GlobalOperationHandlers.AvailableResponse(response); responseMap.put(host, availableResponse); OperationEntry entry = context.getRootResourceRegistration().getOperationEntry(address, SUB_OPERATION_NAME); if (entry != null) { OperationStepHandler osh = entry.getOperationHandler(); GlobalOperationHandlers.AvailableResponseWrapper wrapper = new GlobalOperationHandlers.AvailableResponseWrapper(osh, availableResponse); context.addStep(response, reportOperation, wrapper, OperationContext.Stage.RUNTIME); ControllerLogger.ROOT_LOGGER.debugf("We are asking the host %s its report", host); } } public static OperationStepHandler createDomainOperation() { return new OperationStepHandler() { @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { Resource res = context.readResourceFromRoot(PathAddress.EMPTY_ADDRESS,false); Set<String> hosts = res.getChildrenNames(HOST); String hostName = hosts.iterator().next(); PathAddress address = PathAddress.pathAddress(PathElement.pathElement(HOST)); //Hacky part we are getting the handler from a real host and calling the operation on /host=* OperationEntry entry = context.getRootResourceRegistration().getOperationEntry(PathAddress.pathAddress(PathElement.pathElement(HOST, hostName)), OPERATION_NAME); ModelNode reportOperation = Util.getEmptyOperation(OPERATION_NAME, address.toModelNode()); if (operation.hasDefined(CREATE_REPORT_DEFINITION.getName())) { reportOperation.get(CREATE_REPORT_DEFINITION.getName()).set(operation.get(CREATE_REPORT_DEFINITION.getName())); if (operation.hasDefined(FILE_FORMAT_DEFINITION.getName())) { reportOperation.get(FILE_FORMAT_DEFINITION.getName()).set(operation.get(FILE_FORMAT_DEFINITION.getName())); } } if (entry != null) { OperationStepHandler osh = entry.getOperationHandler(); context.addStep(reportOperation, osh, OperationContext.Stage.MODEL); } } }; } /** * Assembles the response to a read-attribute request from the components gathered by earlier steps. */ private static class ReportAssemblyHandler implements OperationStepHandler { private final Map<String, GlobalOperationHandlers.AvailableResponse> servers; private final Map<String, String> serverOrganizations; private final FilteredData filteredData; private final boolean ignoreMissingResource; private ReportAssemblyHandler(final Map<String, GlobalOperationHandlers.AvailableResponse> servers, final Map<String, String> serverOrganizations, final FilteredData filteredData, final boolean ignoreMissingResource) { this.servers = servers; this.serverOrganizations = serverOrganizations; this.filteredData = filteredData; this.ignoreMissingResource = ignoreMissingResource; } @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { boolean record = false; ModelNode rootModel = context.readResourceFromRoot(PathAddress.EMPTY_ADDRESS,false).getModel(); String defaultOrganization = null; if(rootModel.hasDefined(ORGANIZATION)) { defaultOrganization = rootModel.get(ORGANIZATION).asString(); } String format = FILE_FORMAT_DEFINITION.resolveModelAttribute(context, operation).asString(); Map<String, ModelNode> sortedAttributes = new TreeMap<>(); boolean failed = false; for (Map.Entry<String, GlobalOperationHandlers.AvailableResponse> entry : servers.entrySet()) { GlobalOperationHandlers.AvailableResponse ar = entry.getValue(); if (ar.unavailable) { // Our target resource has disappeared handleMissingResource(context); return; } ModelNode value = ar.response; if (!value.has(FAILURE_DESCRIPTION)) { sortedAttributes.put(entry.getKey(), value.get(RESULT)); } else if (value.hasDefined(FAILURE_DESCRIPTION)) { context.getFailureDescription().set(value.get(FAILURE_DESCRIPTION)); failed = true; break; } } if (!failed) { final ModelNode result = context.getResult(); result.setEmptyList(); ReportAttacher attacher; switch(format) { case JSON_FORMAT: attacher = new JsonReportAttacher(record); break; case XML_FORMAT: default: attacher = new XMLReportAttacher(SUMMARY_DEFINITION, record, "urn:jboss:product-report:1.0", "report"); } if (sortedAttributes.size() == 1) { Entry<String, ModelNode> entry = sortedAttributes.entrySet().iterator().next(); ModelNode report = entry.getValue(); result.add(report); attacher.addReport(report); } else { for(Entry<String, ModelNode> entry : sortedAttributes.entrySet()) { ModelNode report = entry.getValue(); ModelNode summary = report.get(SUMMARY_DEFINITION.getName()); updateSummary(summary, defaultOrganization, entry.getKey()); result.add(report); attacher.addReport(report); } } attacher.attachResult(context); if (filteredData != null && filteredData.hasFilteredData()) { context.getResponseHeaders().get(ACCESS_CONTROL).set(filteredData.toModelNode()); } } } private void updateSummary(final ModelNode summary, String defaultOrganization, String nodeName) { summary.get(NODE_NAME).set(nodeName); if (!summary.hasDefined(ORGANIZATION)) { if (serverOrganizations.containsKey(nodeName)) { summary.get(ORGANIZATION).set(serverOrganizations.get(nodeName)); } else if (defaultOrganization != null) { summary.get(ORGANIZATION).set(defaultOrganization); } } } private void handleMissingResource(OperationContext context) { // Our target resource has disappeared if (context.hasResult()) { context.getResult().set(new ModelNode()); } if (!ignoreMissingResource) { throw ControllerLogger.MGMT_OP_LOGGER.managementResourceNotFound(context.getCurrentAddress()); } } } }