blob: a07d0e6e6acb7eee82350651c7d5ab7459699141 [file] [log] [blame]
/**
* 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.io.PrintWriter;
import java.io.StringWriter;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.actionmanager.HostRoleStatus;
import org.apache.ambari.server.agent.CommandReport;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.orm.dao.ClusterVersionDAO;
import org.apache.ambari.server.orm.dao.HostComponentStateDAO;
import org.apache.ambari.server.orm.dao.HostVersionDAO;
import org.apache.ambari.server.orm.dao.ServiceComponentDesiredStateDAO;
import org.apache.ambari.server.orm.dao.StackDAO;
import org.apache.ambari.server.orm.dao.UpgradeDAO;
import org.apache.ambari.server.orm.entities.ClusterVersionEntity;
import org.apache.ambari.server.orm.entities.HostComponentStateEntity;
import org.apache.ambari.server.orm.entities.HostEntity;
import org.apache.ambari.server.orm.entities.HostVersionEntity;
import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
import org.apache.ambari.server.orm.entities.ServiceComponentDesiredStateEntity;
import org.apache.ambari.server.orm.entities.ServiceComponentHistoryEntity;
import org.apache.ambari.server.orm.entities.StackEntity;
import org.apache.ambari.server.orm.entities.UpgradeEntity;
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.ComponentInfo;
import org.apache.ambari.server.state.RepositoryVersionState;
import org.apache.ambari.server.state.Service;
import org.apache.ambari.server.state.ServiceComponent;
import org.apache.ambari.server.state.ServiceComponentHost;
import org.apache.ambari.server.state.StackId;
import org.apache.ambari.server.state.UpgradeState;
import org.apache.ambari.server.state.stack.upgrade.Direction;
import org.apache.ambari.server.state.svccomphost.ServiceComponentHostSummary;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrBuilder;
import com.google.inject.Inject;
/**
* Action that represents finalizing the Upgrade by completing any database changes.
*/
public class FinalizeUpgradeAction extends AbstractServerAction {
public static final String CLUSTER_NAME_KEY = "cluster_name";
public static final String UPGRADE_DIRECTION_KEY = "upgrade_direction";
public static final String VERSION_KEY = "version";
public static final String REQUEST_ID = "request_id";
public static final String PREVIOUS_UPGRADE_NOT_COMPLETED_MSG = "It is possible that a previous upgrade was not finalized. " +
"For this reason, Ambari will not remove any configs. Please ensure that all database records are correct.";
/**
* The original "current" stack of the cluster before the upgrade started.
* This is the same regardless of whether the current direction is
* {@link Direction#UPGRADE} or {@link Direction#DOWNGRADE}.
*/
public static final String ORIGINAL_STACK_KEY = "original_stack";
/**
* The target upgrade stack before the upgrade started. This is the same
* regardless of whether the current direction is {@link Direction#UPGRADE} or
* {@link Direction#DOWNGRADE}.
*/
public static final String TARGET_STACK_KEY = "target_stack";
/**
* The Cluster that this ServerAction implementation is executing on
*/
@Inject
protected Clusters clusters;
@Inject
private ClusterVersionDAO clusterVersionDAO;
@Inject
private HostVersionDAO hostVersionDAO;
@Inject
private HostComponentStateDAO hostComponentStateDAO;
/**
* Gets {@link StackEntity} instances from {@link StackId}.
*/
@Inject
private StackDAO stackDAO;
/**
* Gets desired state entities for service components.
*/
@Inject
private ServiceComponentDesiredStateDAO serviceComponentDesiredStateDAO;
/**
* Gets {@link UpgradeEntity} instances.
*/
@Inject
private UpgradeDAO upgradeDAO;
@Inject
private AmbariMetaInfo ambariMetaInfo;
@Override
public CommandReport execute(ConcurrentMap<String, Object> requestSharedDataContext)
throws AmbariException, InterruptedException {
Map<String, String> commandParams = getExecutionCommand().getCommandParams();
boolean isDowngrade = commandParams.containsKey(UPGRADE_DIRECTION_KEY) &&
"downgrade".equals(commandParams.get(UPGRADE_DIRECTION_KEY).toLowerCase());
String version = commandParams.get(VERSION_KEY);
StackId originalStackId = new StackId(commandParams.get(ORIGINAL_STACK_KEY));
StackId targetStackId = new StackId(commandParams.get(TARGET_STACK_KEY));
String clusterName = getExecutionCommand().getClusterName();
if (isDowngrade) {
return finalizeDowngrade(clusterName, originalStackId, targetStackId, version);
} else {
return finalizeUpgrade(clusterName, version, commandParams);
}
}
/**
* Execution path for upgrade.
* @param clusterName the name of the cluster the upgrade is for
* @param version the target version of the upgrade
* @return the command report
*/
private CommandReport finalizeUpgrade(String clusterName, String version,
Map<String, String> commandParams)
throws AmbariException, InterruptedException {
StringBuilder outSB = new StringBuilder();
StringBuilder errSB = new StringBuilder();
try {
outSB.append(MessageFormat.format("Begin finalizing the upgrade of cluster {0} to version {1}\n", clusterName, version));
Cluster cluster = clusters.getCluster(clusterName);
StackId clusterDesiredStackId = cluster.getDesiredStackVersion();
StackId clusterCurrentStackId = cluster.getCurrentStackVersion();
ClusterVersionEntity upgradingClusterVersion = clusterVersionDAO.findByClusterAndStackAndVersion(
clusterName, clusterDesiredStackId, version);
if (upgradingClusterVersion == null) {
throw new AmbariException(MessageFormat.format(
"Cluster stack version {0} not found", version));
}
// Validate that all of the hosts with a version in the cluster have the
// version being upgraded to, and it is in an allowed state.
List<HostVersionEntity> hostVersions = hostVersionDAO.findByClusterStackAndVersion(
clusterName, clusterDesiredStackId, version);
// Will include hosts whose state is INSTALLED
Set<HostVersionEntity> hostVersionsAllowed = new HashSet<HostVersionEntity>();
Set<String> hostsWithoutCorrectVersionState = new HashSet<String>();
Set<String> hostsToUpdate = new HashSet<String>();
// It is important to only iterate over the hosts with a version, as
// opposed to all hosts, since some hosts may only have components that do
// not advertise a version, such as AMBARI_METRICS.
for (HostVersionEntity hostVersion : hostVersions) {
boolean hostHasCorrectVersionState = false;
RepositoryVersionState hostVersionState = hostVersion.getState();
switch( hostVersionState ){
case CURRENT:{
// if the state is correct, then do nothing
hostHasCorrectVersionState = true;
break;
}
case NOT_REQUIRED:
case INSTALLED:{
// It is possible that the host version has a state of INSTALLED and it
// never changed if the host only has components that do not advertise a
// version.
HostEntity host = hostVersion.getHostEntity();
ServiceComponentHostSummary hostSummary = new ServiceComponentHostSummary(ambariMetaInfo,
host, clusterDesiredStackId);
// if all components have finished advertising their version, then
// this host can be considered upgraded
if (hostSummary.haveAllComponentsFinishedAdvertisingVersion()) {
// mark this as upgraded
hostHasCorrectVersionState = true;
} else {
hostsWithoutCorrectVersionState.add(hostVersion.getHostName());
}
break;
}
default: {
// all other states are not allowed
hostsWithoutCorrectVersionState.add(hostVersion.getHostName());
break;
}
}
// keep track of this host version in order to transition it correctly
if (hostHasCorrectVersionState) {
hostVersionsAllowed.add(hostVersion);
hostsToUpdate.add(hostVersion.getHostName());
}
}
// throw an exception if there are hosts which are not not fully upgraded
if (hostsWithoutCorrectVersionState.size() > 0) {
String message = String.format("The following %d host(s) have not been upgraded to version %s. " +
"Please install and upgrade the Stack Version on those hosts and try again.\nHosts: %s\n",
hostsWithoutCorrectVersionState.size(),
version,
StringUtils.join(hostsWithoutCorrectVersionState, ", "));
outSB.append(message);
throw new AmbariException(message);
}
// iterate through all host components and make sure that they are on the
// correct version; if they are not, then this will throw an exception
List<InfoTuple> errors = checkHostComponentVersions(cluster, version, clusterDesiredStackId);
if (! errors.isEmpty()) {
StrBuilder messageBuff = new StrBuilder(
String.format(
"The following %d host component(s) "
+ "have not been upgraded to version %s. Please install and upgrade "
+ "the Stack Version on those hosts and try again.\nHost components:\n",
errors.size(), version));
for (InfoTuple error : errors) {
messageBuff.append(String.format("%s on host %s\n", error.componentName, error.hostName));
}
throw new AmbariException(messageBuff.toString());
}
// we're guaranteed to be ready transition to upgraded now; ensure that
// the transition will be allowed if the cluster state is not upgraded
upgradingClusterVersion = clusterVersionDAO.findByClusterAndStackAndVersion(clusterName,
clusterDesiredStackId, version);
if (RepositoryVersionState.INSTALLING == upgradingClusterVersion.getState()) {
cluster.transitionClusterVersion(clusterDesiredStackId, version, RepositoryVersionState.INSTALLED);
upgradingClusterVersion = clusterVersionDAO.findByClusterAndStackAndVersion(
clusterName, clusterDesiredStackId, version);
}
// we cannot finalize since the cluster was not ready to move into the
// upgraded state
if (RepositoryVersionState.INSTALLED != upgradingClusterVersion.getState()) {
throw new AmbariException(String.format("The cluster stack version state %s is not allowed to transition directly into %s",
upgradingClusterVersion.getState(), RepositoryVersionState.CURRENT.toString()));
}
outSB.append(
String.format("Finalizing the upgraded state of host components in %d host(s).\n",
hostVersionsAllowed.size()));
// Reset the upgrade state
for (HostVersionEntity hostVersion : hostVersionsAllowed) {
Collection<HostComponentStateEntity> hostComponentStates = hostComponentStateDAO.findByHost(hostVersion.getHostName());
for (HostComponentStateEntity hostComponentStateEntity: hostComponentStates) {
hostComponentStateEntity.setUpgradeState(UpgradeState.NONE);
hostComponentStateDAO.merge(hostComponentStateEntity);
}
}
// Impacts all hosts that have a version
outSB.append(
String.format("Finalizing the version for %d host(s).\n", hostVersionsAllowed.size()));
cluster.mapHostVersions(hostsToUpdate, upgradingClusterVersion, RepositoryVersionState.CURRENT);
// Reset upgrade state
cluster.setUpgradeEntity(null);
// transitioning the cluster into CURRENT will update the current/desired
// stack values
outSB.append(String.format("Finalizing the version for cluster %s.\n", clusterName));
cluster.transitionClusterVersion(clusterDesiredStackId, version,
RepositoryVersionState.CURRENT);
if (commandParams.containsKey(REQUEST_ID)) {
String requestId = commandParams.get(REQUEST_ID);
UpgradeEntity upgradeEntity = upgradeDAO.findUpgradeByRequestId(Long.valueOf(requestId));
if (null != upgradeEntity) {
outSB.append("Creating upgrade history.\n");
writeComponentHistory(cluster, upgradeEntity, clusterCurrentStackId,
clusterDesiredStackId);
} else {
String warning = String.format(
"Unable to create upgrade history because no upgrade could be found for request with ID %s\n",
requestId);
outSB.append(warning);
}
}
outSB.append("Upgrade was successful!\n");
return createCommandReport(0, HostRoleStatus.COMPLETED, "{}", outSB.toString(), errSB.toString());
} catch (Exception e) {
errSB.append(e.getMessage());
return createCommandReport(-1, HostRoleStatus.FAILED, "{}", outSB.toString(), errSB.toString());
}
}
/**
* Execution path for downgrade.
*
* @param clusterName
* the name of the cluster the downgrade is for
* @paran originalStackId the stack ID of the cluster before the upgrade.
* @paran targetStackId the stack ID that was desired for this upgrade.
* @param version
* the target version of the downgrade
* @return the command report
*/
private CommandReport finalizeDowngrade(String clusterName,
StackId originalStackId, StackId targetStackId, String version)
throws AmbariException, InterruptedException {
StringBuilder out = new StringBuilder();
StringBuilder err = new StringBuilder();
try {
Cluster cluster = clusters.getCluster(clusterName);
StackId currentClusterStackId = cluster.getCurrentStackVersion();
// Safety check that the cluster's stack (from clusterstate's current_stack_id) is equivalent to the
// cluster's CURRENT repo version's stack. This is to avoid deleting configs from the target stack if the customer
// ended up modifying their database manually after a stack upgrade and forgot to call "Save DB State".
ClusterVersionEntity currentClusterVersion = cluster.getCurrentClusterVersion();
RepositoryVersionEntity currentRepoVersion = currentClusterVersion.getRepositoryVersion();
StackId currentRepoStackId = currentRepoVersion.getStackId();
if (!currentRepoStackId.equals(originalStackId)) {
String msg = String.format("The stack of Cluster %s's CURRENT repo version is %s, yet the original stack id from " +
"the Stack Upgrade has a different value of %s. %s",
clusterName, currentRepoStackId.getStackId(), originalStackId.getStackId(), PREVIOUS_UPGRADE_NOT_COMPLETED_MSG);
out.append(msg);
err.append(msg);
throw new AmbariException("The source target stack doesn't match the cluster's CURRENT repo version's stack.");
}
// This was a cross-stack upgrade, meaning that configurations were created that now need to be removed.
if (!originalStackId.equals(targetStackId)) {
out.append(String.format("Will remove configs since the original stack %s differs from the target stack %s " +
"that Ambari just downgraded from.", originalStackId.getStackId(), targetStackId.getStackId()));
cluster.removeConfigurations(targetStackId);
}
// !!! find and make sure the cluster_version EXCEPT current are set back
out.append(String.format("Searching for current version for %s\n",
clusterName));
ClusterVersionEntity clusterVersion = clusterVersionDAO.findByClusterAndStateCurrent(clusterName);
if (null == clusterVersion) {
throw new AmbariException("Could not find current cluster version");
}
out.append(String.format("Comparing downgrade version %s to current cluster version %s\n",
version,
clusterVersion.getRepositoryVersion().getVersion()));
if (!version.equals(clusterVersion.getRepositoryVersion().getVersion())) {
throw new AmbariException(
String.format("Downgrade version %s is not the current cluster version of %s",
version, clusterVersion.getRepositoryVersion().getVersion()));
} else {
out.append(String.format("Downgrade version is the same as current. Searching " +
"for cluster versions that do not match %s\n", version));
}
Set<String> badVersions = new HashSet<String>();
// update the cluster version
for (ClusterVersionEntity cve : clusterVersionDAO.findByCluster(clusterName)) {
switch (cve.getState()) {
case INSTALL_FAILED:
case INSTALLED:
case INSTALLING: {
badVersions.add(cve.getRepositoryVersion().getVersion());
cve.setState(RepositoryVersionState.INSTALLED);
clusterVersionDAO.merge(cve);
break;
}
default:
break;
}
}
out.append(String.format("Found %d other version(s) not matching downgrade: %s\n",
badVersions.size(), StringUtils.join(badVersions, ", ")));
Set<String> badHosts = new HashSet<String>();
for (String badVersion : badVersions) {
List<HostVersionEntity> hostVersions = hostVersionDAO.findByClusterStackAndVersion(
clusterName, targetStackId, badVersion);
for (HostVersionEntity hostVersion : hostVersions) {
badHosts.add(hostVersion.getHostName());
hostVersion.setState(RepositoryVersionState.INSTALLED);
hostVersionDAO.merge(hostVersion);
}
}
out.append(String.format("Found %d hosts not matching downgrade version: %s\n",
badHosts.size(), version));
for (String badHost : badHosts) {
List<HostComponentStateEntity> hostComponentStates = hostComponentStateDAO.findByHost(badHost);
for (HostComponentStateEntity hostComponentState : hostComponentStates) {
hostComponentState.setUpgradeState(UpgradeState.NONE);
hostComponentStateDAO.merge(hostComponentState);
}
}
// ensure that when downgrading, we set the desired back to the
// original value
cluster.setDesiredStackVersion(currentClusterStackId);
// Reset upgrade state
cluster.setUpgradeEntity(null);
return createCommandReport(0, HostRoleStatus.COMPLETED, "{}",
out.toString(), err.toString());
} catch (Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
err.append(sw.toString());
return createCommandReport(-1, HostRoleStatus.FAILED, "{}",
out.toString(), err.toString());
}
}
/**
* Confirms that all host components that are able to provide hdp version,
* have been upgraded to the target version.
* @param cluster the cluster the upgrade is for
* @param desiredVersion the target version of the upgrade
* @param targetStackId the target stack id for meta-info lookup
* @return the list of {@link InfoTuple} objects of host components in error
*/
protected List<InfoTuple> checkHostComponentVersions(Cluster cluster, String desiredVersion, StackId targetStackId)
throws AmbariException {
ArrayList<InfoTuple> errors = new ArrayList<InfoTuple>();
for (Service service : cluster.getServices().values()) {
for (ServiceComponent serviceComponent : service.getServiceComponents().values()) {
for (ServiceComponentHost serviceComponentHost : serviceComponent.getServiceComponentHosts().values()) {
ComponentInfo componentInfo = ambariMetaInfo.getComponent(targetStackId.getStackName(),
targetStackId.getStackVersion(), service.getName(), serviceComponent.getName());
if (!componentInfo.isVersionAdvertised()) {
StackId desired = serviceComponentHost.getDesiredStackVersion();
StackId actual = serviceComponentHost.getStackVersion();
if (!desired.equals(actual)) {
serviceComponentHost.setStackVersion(desired);
}
} else if (componentInfo.isVersionAdvertised()
&& !serviceComponentHost.getVersion().equals(desiredVersion)) {
errors.add(new InfoTuple(
service.getName(), serviceComponent.getName(),
serviceComponentHost.getHostName(), serviceComponentHost.getVersion()));
}
}
}
}
return errors;
}
private void writeComponentHistory(Cluster cluster, UpgradeEntity upgradeEntity,
StackId fromStackId, StackId toStackId) {
StackEntity fromStack = stackDAO.find(fromStackId.getStackName(), fromStackId.getStackVersion());
StackEntity toStack = stackDAO.find(toStackId.getStackName(), toStackId.getStackVersion());
// for every service component, if it was included in the upgrade then
// create a historical entry
for (Service service : cluster.getServices().values()) {
for (ServiceComponent serviceComponent : service.getServiceComponents().values()) {
if (serviceComponent.isVersionAdvertised()) {
// create the historical entry
ServiceComponentHistoryEntity historyEntity = new ServiceComponentHistoryEntity();
historyEntity.setUpgrade(upgradeEntity);
historyEntity.setFromStack(fromStack);
historyEntity.setToStack(toStack);
// get the service component
ServiceComponentDesiredStateEntity desiredStateEntity = serviceComponentDesiredStateDAO.findByName(
cluster.getClusterId(), serviceComponent.getServiceName(),
serviceComponent.getName());
// add the history to the component and save
desiredStateEntity.addHistory(historyEntity);
serviceComponentDesiredStateDAO.merge(desiredStateEntity);
}
}
}
}
protected static class InfoTuple {
protected final String serviceName;
protected final String componentName;
protected final String hostName;
protected final String currentVersion;
protected InfoTuple(String service, String component, String host, String version) {
serviceName = service;
componentName = component;
hostName = host;
currentVersion = version;
}
}
}