/* * Copyright (C) 2014 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; import java.util.Set; import org.jboss.as.controller.access.Action; import org.jboss.as.controller.access.AuthorizationResult; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILED_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILED_SERVICES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MISSING_TRANSITIVE_DEPENDENCY_PROBLEMS; 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.POSSIBLE_CAUSES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVICES_MISSING_DEPENDENCIES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVICES_MISSING_TRANSITIVE_DEPENDENCIES; import java.util.HashSet; import org.jboss.as.controller.access.management.AuthorizedAddress; import org.jboss.as.controller.descriptions.common.ControllerResolver; import org.jboss.as.controller.registry.OperationEntry; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; /** * Collects boot errors. * @author <a href="mailto:ehugonne@redhat.com">Emmanuel Hugonnet</a> (c) 2014 Red Hat, inc. */ public class BootErrorCollector { private static final String COMPLETE_OP = "real-operation"; private final ModelNode errors; private final OperationStepHandler listBootErrorsHandler; public BootErrorCollector() { errors = new ModelNode(); errors.setEmptyList(); listBootErrorsHandler = new ListBootErrorsHandler(this); } void addFailureDescription(final ModelNode operation, final ModelNode failureDescription) { assert operation != null; assert failureDescription != null; ModelNode error = new ModelNode(); // for security reasons failure.get(FAILED).set(operation.clone()); ModelNode failedOperation = error.get(FAILED_OPERATION); failedOperation.get(OP).set(operation.get(OP)); ModelNode opAddr = operation.get(OP_ADDR); if (!opAddr.isDefined()) { opAddr.setEmptyList(); } failedOperation.get(OP_ADDR).set(opAddr); error.get(FAILURE_DESCRIPTION).set(failureDescription.asString()); ModelNode report = ServiceVerificationHelper.extractFailedServicesDescription(failureDescription); if (report != null) { error.get(FAILED_SERVICES).set(report); } report = ServiceVerificationHelper.extractMissingServicesDescription(failureDescription); if (report != null) { error.get(SERVICES_MISSING_DEPENDENCIES).set(report); } report = ServiceVerificationHelper.extractTransitiveDependencyProblemDescription(failureDescription); if (report != null) { error.get(MISSING_TRANSITIVE_DEPENDENCY_PROBLEMS).set(report); } error.get(COMPLETE_OP).set(operation); synchronized (errors) { errors.add(error); } } private ModelNode getErrors() { synchronized (errors) { return errors.clone(); } } public OperationStepHandler getReadBootErrorsHandler() { return this.listBootErrorsHandler; } public static class ListBootErrorsHandler implements OperationStepHandler { private static final String OPERATION_NAME = "read-boot-errors"; private final BootErrorCollector errors; private static final AttributeDefinition OP_DEFINITION = ObjectTypeAttributeDefinition.Builder.of(FAILED_OPERATION, SimpleAttributeDefinitionBuilder.create(OP, ModelType.STRING, false).build(), SimpleListAttributeDefinition.Builder.of(OP_ADDR, SimpleAttributeDefinitionBuilder.create("element", ModelType.PROPERTY, false).build()) .build()) .setAllowNull(false) .build(); private static final AttributeDefinition FAILURE_MESSAGE = SimpleAttributeDefinitionBuilder.create(FAILURE_DESCRIPTION, ModelType.STRING, false).build(); private static final AttributeDefinition FAILED_SVC_AD = SimpleListAttributeDefinition.Builder.of(FAILED_SERVICES, SimpleAttributeDefinitionBuilder.create("element", ModelType.STRING, false).build()) .setAllowNull(true) .build(); private static final AttributeDefinition MISSING_DEPS_AD = SimpleListAttributeDefinition.Builder.of(SERVICES_MISSING_DEPENDENCIES, SimpleAttributeDefinitionBuilder.create("element", ModelType.STRING, false).build()) .setAllowNull(true) .build(); private static final AttributeDefinition AFFECTED_AD = SimpleListAttributeDefinition.Builder.of(SERVICES_MISSING_TRANSITIVE_DEPENDENCIES, SimpleAttributeDefinitionBuilder.create("element", ModelType.STRING, false).build()) .build(); private static final AttributeDefinition CAUSE_AD = SimpleListAttributeDefinition.Builder.of(POSSIBLE_CAUSES, SimpleAttributeDefinitionBuilder.create("element", ModelType.STRING, false).build()) .build(); private static final AttributeDefinition TRANSITIVE_AD = ObjectTypeAttributeDefinition.Builder.of(MISSING_TRANSITIVE_DEPENDENCY_PROBLEMS, AFFECTED_AD, CAUSE_AD) .setAllowNull(true) .build(); public static final SimpleOperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(OPERATION_NAME, ControllerResolver.getResolver("errors")) .setReadOnly() .setRuntimeOnly() .setReplyType(ModelType.LIST) .setReplyParameters(OP_DEFINITION, FAILURE_MESSAGE, FAILED_SVC_AD, MISSING_DEPS_AD, TRANSITIVE_AD).build(); ListBootErrorsHandler(final BootErrorCollector errors) { this.errors = errors; } @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { context.addStep(new OperationStepHandler() { @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { ModelNode bootErrors = new ModelNode().setEmptyList(); ModelNode errorsNode = errors.getErrors(); for (ModelNode bootError : errorsNode.asList()) { secureOperationAddress(context, bootError); bootErrors.add(bootError); } context.getResult().set(bootErrors); } }, OperationContext.Stage.RUNTIME); } private void secureOperationAddress(OperationContext context, ModelNode bootError) throws OperationFailedException { if (bootError.hasDefined(FAILED_OPERATION)) { ModelNode failedOperation = bootError.get(FAILED_OPERATION); ModelNode address = failedOperation.get(OP_ADDR); ModelNode fakeOperation = new ModelNode(); fakeOperation.get(OP).set(READ_RESOURCE_OPERATION); fakeOperation.get(OP_ADDR).set(address); AuthorizedAddress authorizedAddress = AuthorizedAddress.authorizeAddress(context, fakeOperation); if(authorizedAddress.isElided()) { failedOperation.get(OP_ADDR).set(authorizedAddress.getAddress()); } if(bootError.has(FAILURE_DESCRIPTION) && !canReadFailureDescription(context, bootError)) { bootError.get(FAILURE_DESCRIPTION).set(new ModelNode()); } } bootError.remove(COMPLETE_OP); } private boolean canReadFailureDescription(OperationContext context, ModelNode bootError) { ModelNode completeOPeration = bootError.get(COMPLETE_OP); OperationEntry operationEntry = context.getRootResourceRegistration().getOperationEntry( PathAddress.pathAddress(completeOPeration.get(OP_ADDR)), completeOPeration.get(OP).asString()); Set<Action.ActionEffect> effects = getEffects(operationEntry); return context.authorize(bootError.get(COMPLETE_OP), effects).getDecision() == AuthorizationResult.Decision.PERMIT; } Set<Action.ActionEffect> getEffects(OperationEntry operationEntry) { Set<Action.ActionEffect> effects = new HashSet<>(5); effects.add(Action.ActionEffect.ADDRESS); if(operationEntry != null) { effects.add(Action.ActionEffect.READ_RUNTIME); if (!operationEntry.getFlags().contains(OperationEntry.Flag.RUNTIME_ONLY)) { effects.add(Action.ActionEffect.READ_CONFIG); } if(!operationEntry.getFlags().contains(OperationEntry.Flag.READ_ONLY)) { effects.add(Action.ActionEffect.WRITE_RUNTIME); if(!operationEntry.getFlags().contains(OperationEntry.Flag.RUNTIME_ONLY)) { effects.add(Action.ActionEffect.WRITE_CONFIG); } } } return effects; } } }