blob: 586e98c973344e517dad49eb85cc8858684e5562 [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.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.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.OperatingSystemEntity;
import org.apache.ambari.server.orm.entities.RepositoryEntity;
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.RepositoryVersionState;
import org.apache.ambari.server.state.StackId;
import org.apache.ambari.server.state.UpgradeState;
import org.apache.ambari.server.state.svccomphost.ServiceComponentHostSummary;
import org.apache.commons.lang.StringUtils;
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";
/**
* The Cluster that this ServerAction implementation is executing on
*/
@Inject
private Clusters clusters = null;
@Inject
private ClusterVersionDAO clusterVersionDAO;
@Inject
private HostVersionDAO hostVersionDAO;
@Inject
private HostComponentStateDAO hostComponentStateDAO;
@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);
String clusterName = getExecutionCommand().getClusterName();
if (isDowngrade) {
return executeDowngrade(clusterName, version);
} else {
return executeUpgrade(clusterName, version);
}
}
/**
* 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 executeUpgrade(String clusterName, String version)
throws AmbariException, InterruptedException {
Set<RepositoryVersionState> allowedStates = new HashSet<RepositoryVersionState>();
allowedStates.add(RepositoryVersionState.UPGRADED);
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 stack = cluster.getCurrentStackVersion();
String stackId = stack.getStackId();
ClusterVersionEntity upgradingClusterVersion = clusterVersionDAO.findByClusterAndStackAndVersion(clusterName,
stackId, version);
if (upgradingClusterVersion == null) {
throw new AmbariException(String.format("Cluster stack version %s 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, stackId, version);
// Will include hosts whose state is UPGRADED, and potentially INSTALLED
Set<HostVersionEntity> hostVersionsAllowed = new HashSet<HostVersionEntity>();
Set<String> hostsWithoutCorrectVersionState = new HashSet<String>();
Set<String> hostsToUpdate = new HashSet<String>();
// If true, then the cluster version is still in UPGRADING and allowed to transition to UPGRADED, and then CURRENT
boolean atLeastOneHostInInstalledState = false;
// 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 isStateCorrect = false;
if (allowedStates.contains(hostVersion.getState())) {
isStateCorrect = true;
} else {
if (hostVersion.getState() == RepositoryVersionState.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, stack);
if (hostSummary.haveAllComponentsFinishedAdvertisingVersion()){
isStateCorrect = true;
atLeastOneHostInInstalledState = true;
}
}
}
if (isStateCorrect) {
hostVersionsAllowed.add(hostVersion);
hostsToUpdate.add(hostVersion.getHostName());
} else {
hostsWithoutCorrectVersionState.add(hostVersion.getHostName());
}
}
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);
}
// May need to first transition to UPGRADED
if (atLeastOneHostInInstalledState) {
cluster.transitionClusterVersion(stackId, version, RepositoryVersionState.UPGRADED);
upgradingClusterVersion = clusterVersionDAO.findByClusterAndStackAndVersion(clusterName,
stackId, version);
}
if (!allowedStates.contains(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("Will finalize 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);
}
}
outSB.append(String.format("Will finalize the version for %d host(s).\n", hostVersionsAllowed.size()));
// Impacts all hosts that have a version
cluster.mapHostVersions(hostsToUpdate, upgradingClusterVersion, RepositoryVersionState.CURRENT);
outSB.append(String.format("Will finalize the version for cluster %s.\n", clusterName));
cluster.transitionClusterVersion(stackId, version, RepositoryVersionState.CURRENT);
// !!! update the stack-defined repo url
for (OperatingSystemEntity ose : upgradingClusterVersion.getRepositoryVersion().getOperatingSystems()) {
for (RepositoryEntity re : ose.getRepositories()) {
ambariMetaInfo.updateRepoBaseURL(
upgradingClusterVersion.getRepositoryVersion().getStackName(),
upgradingClusterVersion.getRepositoryVersion().getStackVersion(),
ose.getOsType(),
re.getRepositoryId(),
re.getBaseUrl());
}
}
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
* @param version the target version of the downgrade
* @return the command report
*/
private CommandReport executeDowngrade(String clusterName, String version)
throws AmbariException, InterruptedException {
StringBuilder out = new StringBuilder();
StringBuilder err = new StringBuilder();
try {
Cluster cluster = clusters.getCluster(clusterName);
StackId stackId = cluster.getDesiredStackVersion();
// !!! 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 UPGRADE_FAILED:
case UPGRADED:
case UPGRADING: {
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, stackId.getStackId(), 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);
}
}
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());
}
}
}