| /** |
| * 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.serveraction.upgrades; |
| |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.concurrent.ConcurrentMap; |
| |
| import org.apache.ambari.server.AmbariException; |
| import org.apache.ambari.server.Role; |
| import org.apache.ambari.server.RoleCommand; |
| import org.apache.ambari.server.actionmanager.HostRoleCommand; |
| import org.apache.ambari.server.actionmanager.HostRoleStatus; |
| import org.apache.ambari.server.actionmanager.ServiceComponentHostEventWrapper; |
| import org.apache.ambari.server.agent.CommandReport; |
| import org.apache.ambari.server.api.services.AmbariMetaInfo; |
| import org.apache.ambari.server.metadata.ActionMetadata; |
| import org.apache.ambari.server.orm.dao.HostRoleCommandDAO; |
| import org.apache.ambari.server.orm.dao.UpgradeDAO; |
| import org.apache.ambari.server.orm.entities.HostRoleCommandEntity; |
| import org.apache.ambari.server.orm.entities.UpgradeGroupEntity; |
| import org.apache.ambari.server.orm.entities.UpgradeItemEntity; |
| import org.apache.ambari.server.serveraction.AbstractServerAction; |
| import org.apache.ambari.server.state.Cluster; |
| import org.apache.ambari.server.state.Clusters; |
| import org.apache.ambari.server.state.ServiceComponentHostEvent; |
| import org.apache.ambari.server.state.StackId; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.gson.Gson; |
| import com.google.inject.Inject; |
| |
| /** |
| * The {@link AutoSkipFailedSummaryAction} is used to check if any |
| * {@link HostRoleCommand}s were skipped automatically after they failed during |
| * an upgrade. This will be automatically marked as |
| * {@link HostRoleStatus#COMPLETED} if there are no skipped failures. Otherwise |
| * it will be placed into {@link HostRoleStatus#HOLDING}. |
| */ |
| public class AutoSkipFailedSummaryAction extends AbstractServerAction { |
| |
| /** |
| * Logger. |
| */ |
| private static final Logger LOG = LoggerFactory.getLogger(AutoSkipFailedSummaryAction.class); |
| |
| /** |
| * The standard output template message. |
| */ |
| private static final String FAILURE_STD_OUT_TEMPLATE = "There were {0} skipped failure(s) that must be addressed before you can proceed. Please resolve each failure before continuing with the upgrade."; |
| |
| private static final String SKIPPED_SERVICE_CHECK = "service_check"; |
| private static final String SKIPPED_HOST_COMPONENT = "host_component"; |
| private static final String SKIPPED = "skipped"; |
| private static final String FAILURES = "failures"; |
| |
| /** |
| * Used to lookup the {@link UpgradeGroupEntity}. |
| */ |
| @Inject |
| private UpgradeDAO m_upgradeDAO; |
| |
| /** |
| * Used to lookup the tasks that need to be checked for |
| * {@link HostRoleStatus#SKIPPED_FAILED}. |
| */ |
| @Inject |
| private HostRoleCommandDAO m_hostRoleCommandDAO; |
| |
| /** |
| * Used for writing structured out. |
| */ |
| @Inject |
| private Gson m_gson; |
| |
| /** |
| * Used to look up service check name -> service name bindings |
| */ |
| @Inject |
| private ActionMetadata actionMetadata; |
| |
| @Inject |
| private AmbariMetaInfo ambariMetaInfo; |
| |
| @Inject |
| private Clusters clusters; |
| |
| /** |
| * A mapping of host -> Map<key,info> for each failure. |
| */ |
| private Map<String, Object> m_structuredFailures = new HashMap<>(); |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public CommandReport execute(ConcurrentMap<String, Object> requestSharedDataContext) |
| throws AmbariException, InterruptedException { |
| |
| HostRoleCommand hostRoleCommand = getHostRoleCommand(); |
| long requestId = hostRoleCommand.getRequestId(); |
| long stageId = hostRoleCommand.getStageId(); |
| |
| String clusterName = hostRoleCommand.getExecutionCommandWrapper().getExecutionCommand().getClusterName(); |
| Cluster cluster = clusters.getCluster(clusterName); |
| StackId stackId = cluster.getDesiredStackVersion(); |
| |
| // use the host role command to get to the parent upgrade group |
| UpgradeItemEntity upgradeItem = m_upgradeDAO.findUpgradeItemByRequestAndStage(requestId,stageId); |
| UpgradeGroupEntity upgradeGroup = upgradeItem.getGroupEntity(); |
| |
| // find all of the stages in this group |
| long upgradeGroupId = upgradeGroup.getId(); |
| UpgradeGroupEntity upgradeGroupEntity = m_upgradeDAO.findUpgradeGroup(upgradeGroupId); |
| List<UpgradeItemEntity> groupUpgradeItems = upgradeGroupEntity.getItems(); |
| TreeSet<Long> stageIds = new TreeSet<>(); |
| for (UpgradeItemEntity groupUpgradeItem : groupUpgradeItems) { |
| stageIds.add(groupUpgradeItem.getStageId()); |
| } |
| |
| // for every stage, find all tasks that have been SKIPPED_FAILED - we use a |
| // bit of trickery here since within any given request, the stage ID are |
| // always sequential. This allows us to make a simple query instead of some |
| // overly complex IN or NESTED SELECT query |
| long minStageId = stageIds.first(); |
| long maxStageId = stageIds.last(); |
| |
| List<HostRoleCommandEntity> skippedTasks = m_hostRoleCommandDAO.findByStatusBetweenStages( |
| hostRoleCommand.getRequestId(), |
| HostRoleStatus.SKIPPED_FAILED, minStageId, maxStageId); |
| |
| if (skippedTasks.isEmpty()) { |
| return createCommandReport(0, HostRoleStatus.COMPLETED, "{}", |
| "There were no skipped failures", null); |
| } |
| |
| StringBuilder buffer = new StringBuilder("The following steps failed but were automatically skipped:\n"); |
| Set<String> skippedCategories = new HashSet<>(); |
| Map<String, Object> skippedFailures = new HashMap<>(); |
| |
| Set<String> skippedServiceChecks = new HashSet<>(); |
| Map<String, Object> hostComponents= new HashMap<>(); |
| |
| // Internal representation for failed host components |
| // To avoid duplicates |
| // Format: <hostname, Set<Role>> |
| Map<String, Set<Role>> publishedHostComponents= new HashMap<>(); |
| |
| for (HostRoleCommandEntity skippedTask : skippedTasks) { |
| try { |
| String skippedCategory; |
| if (skippedTask.getRoleCommand().equals(RoleCommand.SERVICE_CHECK)) { |
| skippedCategory = SKIPPED_SERVICE_CHECK; |
| |
| String serviceCheckActionName = skippedTask.getRole().toString(); |
| String service = actionMetadata.getServiceNameByServiceCheckAction(serviceCheckActionName); |
| skippedServiceChecks.add(service); |
| |
| skippedFailures.put(SKIPPED_SERVICE_CHECK, skippedServiceChecks); |
| m_structuredFailures.put(FAILURES, skippedFailures); |
| } else { |
| skippedCategory = SKIPPED_HOST_COMPONENT; |
| |
| String hostName = skippedTask.getHostName(); |
| if (null != hostName) { |
| List<Object> failures = (List<Object>) hostComponents.get(hostName); |
| if (null == failures) { |
| failures = new ArrayList<>(); |
| hostComponents.put(hostName, failures); |
| |
| publishedHostComponents.put(hostName, new HashSet<Role>()); |
| } |
| |
| Set<Role> publishedHostComponentsOnHost = publishedHostComponents.get(hostName); |
| Role role = skippedTask.getRole(); |
| if (! publishedHostComponentsOnHost.contains(role)) { |
| HashMap<String, String> details = new HashMap<>(); |
| String service = ambariMetaInfo.getComponentToService( |
| stackId.getStackName(), stackId.getStackVersion(), role.toString()); |
| |
| details.put("service", service); |
| details.put("component", role.toString()); |
| failures.add(details); |
| } |
| } |
| |
| skippedFailures.put(SKIPPED_HOST_COMPONENT, hostComponents); |
| m_structuredFailures.put(FAILURES, skippedFailures); |
| } |
| |
| skippedCategories.add(skippedCategory); |
| |
| ServiceComponentHostEventWrapper eventWrapper = new ServiceComponentHostEventWrapper( |
| skippedTask.getEvent()); |
| |
| ServiceComponentHostEvent event = eventWrapper.getEvent(); |
| |
| buffer.append(event.getServiceComponentName()); |
| if (null != event.getHostName()) { |
| buffer.append(" on "); |
| buffer.append(event.getHostName()); |
| } |
| |
| buffer.append(": "); |
| buffer.append(skippedTask.getCommandDetail()); |
| buffer.append("\n"); |
| } catch (Exception exception) { |
| LOG.warn("Unable to extract failure information for {}", skippedTask); |
| buffer.append(": "); |
| buffer.append(skippedTask); |
| } |
| } |
| |
| m_structuredFailures.put(SKIPPED, skippedCategories); |
| |
| String structuredOutput = m_gson.toJson(m_structuredFailures); |
| String standardOutput = MessageFormat.format(FAILURE_STD_OUT_TEMPLATE, skippedTasks.size()); |
| String standardError = buffer.toString(); |
| |
| return createCommandReport(0, HostRoleStatus.HOLDING, structuredOutput, standardOutput, |
| standardError); |
| } |
| } |