/* * JBoss, Home of Professional Open Source. * Copyright 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 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 java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.jboss.as.controller.logging.ControllerLogger; import org.jboss.dmr.ModelNode; import org.jboss.msc.service.AbstractServiceListener; import org.jboss.msc.service.ServiceContainer; import org.jboss.msc.service.ServiceController; import org.jboss.msc.service.ServiceListener; import org.jboss.msc.service.ServiceName; import org.jboss.msc.service.StabilityMonitor; import org.jboss.msc.service.StartException; /** * Tracks the status of a service installed by an {@link OperationStepHandler}, recording a failure desription * if the service has a problems starting. * * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> * @author <a href="mailto:ropalka@redhat.com">Richard Opalka</a> * * @author Brian Stansberry (c) 2014 Red Hat Inc. */ @SuppressWarnings("deprecation") class ServiceVerificationHelper extends AbstractServiceListener<Object> implements ServiceListener<Object>, OperationStepHandler { private final StabilityMonitor monitor = new StabilityMonitor(); @Override public void listenerAdded(ServiceController<?> controller) { monitor.addController(controller); controller.removeListener(this); } StabilityMonitor getMonitor() { return monitor; } public synchronized void execute(final OperationContext context, final ModelNode operation) { final Set<ServiceController<?>> failed = new HashSet<ServiceController<?>>(); final Set<ServiceController<?>> problems = new HashSet<ServiceController<?>>(); try { monitor.awaitStability(failed, problems); } catch (InterruptedException e) { Thread.currentThread().interrupt(); context.getFailureDescription().set(ControllerLogger.ROOT_LOGGER.operationCancelled()); context.completeStep(OperationContext.RollbackHandler.NOOP_ROLLBACK_HANDLER); return; } finally { monitor.clear(); } if (!failed.isEmpty() || !problems.isEmpty()) { Set<ServiceController<?>> missingTransitive = null; final ModelNode failureDescription = context.getFailureDescription(); Set<ServiceName> unavailableServices = new HashSet<>(); // generate a list of failedServices ModelNode failedList = null; for (ServiceController<?> controller : failed) { if (failedList == null) { failedList = failureDescription.get(ControllerLogger.ROOT_LOGGER.failedServices()); } ServiceName serviceName = controller.getName(); unavailableServices.add(serviceName); failedList.get(serviceName.getCanonicalName()).set(getServiceFailureDescription(controller.getStartException())); } // generate lists of problems and missing services List<String> problemList = new ArrayList<>(); for (ServiceController<?> controller : problems) { Set<ServiceName> immediatelyUnavailable = controller.getImmediateUnavailableDependencies(); if (!immediatelyUnavailable.isEmpty()) { StringBuilder missing = new StringBuilder(); for (Iterator<ServiceName> i = immediatelyUnavailable.iterator(); i.hasNext(); ) { ServiceName missingSvc = i.next(); unavailableServices.add(missingSvc); missing.append(missingSvc.getCanonicalName()); if (i.hasNext()) { missing.append(", "); } } final StringBuilder problem = new StringBuilder(); problem.append(controller.getName().getCanonicalName()); problem.append(" ").append(ControllerLogger.ROOT_LOGGER.servicesMissing(missing)); problemList.add(problem.toString()); } else { if (missingTransitive == null) { missingTransitive = new HashSet<>(); } missingTransitive.add(controller); } } // print out missing services reportUnavailableRequiredServices(unavailableServices, failureDescription); // print out list of services depending on missing services reportImmediateDependants(problemList, failureDescription); if (missingTransitive != null) { // See if any other services are known to the service container as being missing and aren't already // tracked by this SVH as failed or directly missing. If any are found, that is some additional // info to the user, so report that SortedSet<ServiceName> allMissing = findAllMissingServices(missingTransitive, unavailableServices); if (!allMissing.isEmpty()) { ModelNode missingTransitiveDesc = failureDescription.get(ControllerLogger.ROOT_LOGGER.missingTransitiveDependencyProblem()); ModelNode missingTransitiveDeps = missingTransitiveDesc.get(ControllerLogger.ROOT_LOGGER.missingTransitiveDependents()); Set<ServiceName> sortedNames = new TreeSet<>(); for (ServiceController<?> serviceController : missingTransitive) { sortedNames.add(serviceController.getName()); } for (ServiceName serviceName : sortedNames) { missingTransitiveDeps.add(serviceName.getCanonicalName()); } ModelNode allMissingList = missingTransitiveDesc.get(ControllerLogger.ROOT_LOGGER.missingTransitiveDependencies()); for (ServiceName serviceName : allMissing) { allMissingList.add(serviceName.getCanonicalName()); } } } if (context.isRollbackOnRuntimeFailure()) { context.setRollbackOnly(); } } } private static void reportUnavailableRequiredServices(Set<ServiceName> unavailableServices, ModelNode failureDescription) { if (!unavailableServices.isEmpty()) { ModelNode requiredServicesNode = failureDescription.get(ControllerLogger.ROOT_LOGGER.missingRequiredServices()); for (ServiceName serviceName : unavailableServices) { requiredServicesNode.add(serviceName.getCanonicalName()); } } } private static void reportImmediateDependants(List<String> problemList, ModelNode failureDescription) { ModelNode problemListNode = failureDescription.get(ControllerLogger.ROOT_LOGGER.servicesMissingDependencies()); for (String problem: problemList) { problemListNode.add(problem); } } private static ModelNode getServiceFailureDescription(final StartException exception) { final ModelNode result = new ModelNode(); if (exception != null) { StringBuilder sb = new StringBuilder(exception.toString()); Throwable cause = exception.getCause(); while (cause != null) { sb.append("\n Caused by: "); sb.append(cause.toString()); cause = cause.getCause(); } result.set(sb.toString()); } return result; } private static SortedSet<ServiceName> findAllMissingServices(Set<ServiceController<?>> missingTransitive, Set<ServiceName> alreadyTracked) { // Check all relevant service containers. This is a bit silly since in reality there // should only be one that is associated with every SC that is passed in, // but I'm being anal and vaguely future-proofing a bit Set<ServiceContainer> examined = new HashSet<ServiceContainer>(); SortedSet<ServiceName> allMissingServices = new TreeSet<ServiceName>(); for (ServiceController<?> controller : missingTransitive) { ServiceContainer container = controller.getServiceContainer(); if (examined.add(container)) { allMissingServices.addAll(findAllMissingServices(container)); } } Set<ServiceName> retain = new HashSet<>(allMissingServices); retain.removeAll(alreadyTracked); if (retain.size() == 0) { allMissingServices.clear(); } return allMissingServices; } private static Set<ServiceName> findAllMissingServices(ServiceContainer container) { Set<ServiceName> result = new HashSet<ServiceName>(); for (ServiceName serviceName : container.getServiceNames()) { ServiceController<?> controller = container.getService(serviceName); if (controller != null && controller.getMode() != ServiceController.Mode.NEVER && controller.getMode() != ServiceController.Mode.REMOVE && controller.getSubstate() == ServiceController.Substate.PROBLEM) { result.addAll(controller.getImmediateUnavailableDependencies()); } } return result; } static ModelNode extractFailedServicesDescription(ModelNode failureDescription) { return extractIfPresent(ControllerLogger.ROOT_LOGGER.failedServices(), failureDescription); } static ModelNode extractMissingServicesDescription(ModelNode failureDescription) { return extractIfPresent(ControllerLogger.ROOT_LOGGER.servicesMissingDependencies(), failureDescription); } static ModelNode extractTransitiveDependencyProblemDescription(ModelNode failureDescription) { return extractIfPresent(ControllerLogger.ROOT_LOGGER.missingTransitiveDependencyProblem(), failureDescription); } private static ModelNode extractIfPresent(String key, ModelNode modelNode) { ModelNode result = null; if (modelNode.hasDefined(key)) { result = modelNode.get(key); } return result; } }