/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ambari.server.state.stack.upgrade; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlType; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.api.services.AmbariMetaInfo; import org.apache.ambari.server.stack.HostsType; import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.state.CommandScriptDefinition; import org.apache.ambari.server.state.Service; import org.apache.ambari.server.state.ServiceInfo; import org.apache.ambari.server.state.StackId; import org.apache.ambari.server.state.UpgradeContext; import org.apache.ambari.server.state.stack.UpgradePack.ProcessingComponent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Grouping that is used to create stages that are service checks for a cluster. */ @XmlType(name="service-check") public class ServiceCheckGrouping extends Grouping { private static Logger LOG = LoggerFactory.getLogger(ServiceCheckGrouping.class); /** * During a Rolling Upgrade, the priority services are ran first, then the remaining services in the cluster. * During a Stop-and-Start Upgrade, only the priority services are ran. */ @XmlElementWrapper(name="priority") @XmlElement(name="service") private Set<String> priorityServices = new LinkedHashSet<>(); /** * During a Rolling Upgrade, exclude certain services. */ @XmlElementWrapper(name="exclude") @XmlElement(name="service") private Set<String> excludeServices = new HashSet<>(); /** * {@inheritDoc} */ @Override public ServiceCheckBuilder getBuilder() { return new ServiceCheckBuilder(this); } /** * @return the set of service names that should be given priority */ public Set<String> getPriorities() { return priorityServices; } /** * @return the set of service names that should be excluded */ public Set<String> getExcluded() { return excludeServices; } /** * Used to build stages for service check groupings. */ public class ServiceCheckBuilder extends StageWrapperBuilder { private Cluster m_cluster; private AmbariMetaInfo m_metaInfo; /** * Constructor. * * @param grouping * the upgrade/downgrade grouping (not {@code null}). */ protected ServiceCheckBuilder(Grouping grouping) { super(grouping); } /** * {@inheritDoc} */ @Override public void add(UpgradeContext ctx, HostsType hostsType, String service, boolean clientOnly, ProcessingComponent pc, Map<String, String> params) { // !!! nothing to do here } /** * {@inheritDoc} */ @Override public List<StageWrapper> build(UpgradeContext upgradeContext, List<StageWrapper> stageWrappers) { m_cluster = upgradeContext.getCluster(); m_metaInfo = upgradeContext.getAmbariMetaInfo(); List<StageWrapper> result = new ArrayList<>(stageWrappers); if (upgradeContext.getDirection().isDowngrade()) { return result; } Map<String, Service> serviceMap = m_cluster.getServices(); Set<String> clusterServices = new LinkedHashSet<>(serviceMap.keySet()); // create stages for the priorities for (String service : priorityServices) { if (checkServiceValidity(upgradeContext, service, serviceMap)) { StageWrapper wrapper = new StageWrapper( StageWrapper.Type.SERVICE_CHECK, "Service Check " + upgradeContext.getServiceDisplay(service), new TaskWrapper(service, "", Collections.<String>emptySet(), new ServiceCheckTask())); result.add(wrapper); clusterServices.remove(service); } } if (upgradeContext.getType() == UpgradeType.ROLLING) { // During Rolling Upgrade, create stages for everything else, as long it is valid for (String service : clusterServices) { if (excludeServices.contains(service)) { continue; } if (checkServiceValidity(upgradeContext, service, serviceMap)) { StageWrapper wrapper = new StageWrapper( StageWrapper.Type.SERVICE_CHECK, "Service Check " + upgradeContext.getServiceDisplay(service), new TaskWrapper(service, "", Collections.<String>emptySet(), new ServiceCheckTask())); result.add(wrapper); } } } return result; } /** * Checks if the service is valid for a service check * @param ctx the upgrade context to set the display name * @param service the name of the service to check * @param clusterServices the map of available services for a cluster * @return {@code true} if the service is valid and can execute a service check */ private boolean checkServiceValidity(UpgradeContext ctx, String service, Map<String, Service> clusterServices) { if (clusterServices.containsKey(service)) { Service svc = clusterServices.get(service); if (null != svc) { // Services that only have clients such as Pig can still have service check scripts. StackId stackId = m_cluster.getDesiredStackVersion(); try { ServiceInfo si = m_metaInfo.getService(stackId.getStackName(), stackId.getStackVersion(), service); CommandScriptDefinition script = si.getCommandScript(); if (null != script && null != script.getScript() && !script.getScript().isEmpty()) { ctx.setServiceDisplay(service, si.getDisplayName()); return true; } } catch (AmbariException e) { LOG.error("Could not determine if service " + service + " can run a service check. Exception: " + e.getMessage()); } } } return false; } } /** * Attempts to merge all the service check groupings. This merges the excluded list and * the priorities. The priorities are merged in an order specific manner. */ public void merge(Iterator<Grouping> iterator) throws AmbariException { List<String> priorities = new ArrayList<>(); priorities.addAll(getPriorities()); Map<String, Set<String>> skippedPriorities = new HashMap<>(); while (iterator.hasNext()) { Grouping next = iterator.next(); if (!(next instanceof ServiceCheckGrouping)) { throw new AmbariException("Invalid group type " + next.getClass().getSimpleName() + " expected service check group"); } ServiceCheckGrouping checkGroup = (ServiceCheckGrouping) next; getExcluded().addAll(checkGroup.getExcluded()); boolean added = addPriorities(priorities, checkGroup.getPriorities(), checkGroup.addAfterGroupEntry); if (added) { addSkippedPriorities(priorities, skippedPriorities, checkGroup.getPriorities()); } else { // store these services until later if (skippedPriorities.containsKey(checkGroup.addAfterGroupEntry)) { Set<String> tmp = skippedPriorities.get(checkGroup.addAfterGroupEntry); tmp.addAll(checkGroup.getPriorities()); } else { skippedPriorities.put(checkGroup.addAfterGroupEntry, checkGroup.getPriorities()); } } } getPriorities().clear(); getPriorities().addAll(priorities); } /** * Add the given child priorities if the service they are supposed to come after have been added. */ private boolean addPriorities(List<String> priorities, Set<String> childPriorities, String after) { if (after == null) { priorities.addAll(childPriorities); return true; } else { // Check the current priorities, if the "after" priority is there then add these for (int index = priorities.size() - 1; index >= 0; index--) { String priority = priorities.get(index); if (after.equals(priority)) { priorities.addAll(index + 1, childPriorities); return true; } } } return false; } /** * Add the skipped priorities if the services they are supposed to come after have been added */ private void addSkippedPriorities(List<String> priorities, Map<String, Set<String>> skippedPriorites, Set<String> prioritiesJustAdded) { for (String priority : prioritiesJustAdded) { if (skippedPriorites.containsKey(priority)) { Set<String> prioritiesToAdd = skippedPriorites.remove(priority); addPriorities(priorities, prioritiesToAdd, priority); addSkippedPriorities(priorities, skippedPriorites, prioritiesToAdd); } } } }