blob: d0dd7e0d9cb07862f35345184af21f0437c8e4a9 [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.controller;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.AGENT_STACK_RETRY_COUNT;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.AGENT_STACK_RETRY_ON_UNAVAILABILITY;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.CLIENTS_TO_UPDATE_CONFIGS;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.COMMAND_TIMEOUT;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.COMPONENT_CATEGORY;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.CUSTOM_COMMAND;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.DB_DRIVER_FILENAME;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.DB_NAME;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.GROUP_LIST;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.HOST_SYS_PREPPED;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.JDK_LOCATION;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.MYSQL_JDBC_URL;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.NOT_MANAGED_HDFS_PATH_LIST;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.ORACLE_JDBC_URL;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.REPO_INFO;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.SCRIPT;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.SCRIPT_TYPE;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.STACK_NAME;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.STACK_VERSION;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.USER_GROUPS;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.USER_LIST;
import static org.apache.ambari.server.controller.internal.RequestResourceProvider.HAS_RESOURCE_FILTERS;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import org.apache.ambari.annotations.Experimental;
import org.apache.ambari.annotations.ExperimentalFeature;
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.Stage;
import org.apache.ambari.server.agent.AgentCommand.AgentCommandType;
import org.apache.ambari.server.agent.CommandRepository;
import org.apache.ambari.server.agent.ExecutionCommand;
import org.apache.ambari.server.agent.ExecutionCommand.KeyNames;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.configuration.Configuration;
import org.apache.ambari.server.controller.internal.RequestOperationLevel;
import org.apache.ambari.server.controller.internal.RequestResourceFilter;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.metadata.ActionMetadata;
import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
import org.apache.ambari.server.orm.entities.OperatingSystemEntity;
import org.apache.ambari.server.orm.entities.RepositoryEntity;
import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
import org.apache.ambari.server.state.Cluster;
import org.apache.ambari.server.state.Clusters;
import org.apache.ambari.server.state.CommandScriptDefinition;
import org.apache.ambari.server.state.ComponentInfo;
import org.apache.ambari.server.state.Config;
import org.apache.ambari.server.state.ConfigHelper;
import org.apache.ambari.server.state.CustomCommandDefinition;
import org.apache.ambari.server.state.DesiredConfig;
import org.apache.ambari.server.state.Host;
import org.apache.ambari.server.state.HostComponentAdminState;
import org.apache.ambari.server.state.HostState;
import org.apache.ambari.server.state.MaintenanceState;
import org.apache.ambari.server.state.PropertyInfo;
import org.apache.ambari.server.state.PropertyInfo.PropertyType;
import org.apache.ambari.server.state.RepositoryInfo;
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.ServiceInfo;
import org.apache.ambari.server.state.StackId;
import org.apache.ambari.server.state.StackInfo;
import org.apache.ambari.server.state.State;
import org.apache.ambari.server.state.stack.OsFamily;
import org.apache.ambari.server.state.svccomphost.ServiceComponentHostOpInProgressEvent;
import org.apache.ambari.server.utils.StageUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import com.google.inject.Singleton;
/**
* Helper class containing logic to process custom command execution requests .
* This class has special support needed for SERVICE_CHECK and DECOMMISSION.
* These commands are not pass through as Ambari has specific persistence requirements.
*/
@Singleton
public class AmbariCustomCommandExecutionHelper {
private final static Logger LOG = LoggerFactory.getLogger(
AmbariCustomCommandExecutionHelper.class);
// TODO: Remove the hard-coded mapping when stack definition indicates which slave types can be decommissioned
public static final Map<String, String> masterToSlaveMappingForDecom = new HashMap<>();
static {
masterToSlaveMappingForDecom.put("NAMENODE", "DATANODE");
masterToSlaveMappingForDecom.put("RESOURCEMANAGER", "NODEMANAGER");
masterToSlaveMappingForDecom.put("HBASE_MASTER", "HBASE_REGIONSERVER");
masterToSlaveMappingForDecom.put("JOBTRACKER", "TASKTRACKER");
}
public final static String DECOM_INCLUDED_HOSTS = "included_hosts";
public final static String DECOM_EXCLUDED_HOSTS = "excluded_hosts";
public final static String DECOM_SLAVE_COMPONENT = "slave_type";
public final static String HBASE_MARK_DRAINING_ONLY = "mark_draining_only";
public final static String UPDATE_FILES_ONLY = "update_files_only";
public final static String IS_ADD_OR_DELETE_SLAVE_REQUEST = "is_add_or_delete_slave_request";
private final static String ALIGN_MAINTENANCE_STATE = "align_maintenance_state";
public final static int MIN_STRICT_SERVICE_CHECK_TIMEOUT = 120;
@Inject
private ActionMetadata actionMetadata;
@Inject
private Clusters clusters;
@Inject
private AmbariManagementController managementController;
@Inject
private Gson gson;
@Inject
private Configuration configs;
@Inject
private AmbariMetaInfo ambariMetaInfo;
@Inject
private ConfigHelper configHelper;
@Inject
private MaintenanceStateHelper maintenanceStateHelper;
@Inject
private OsFamily os_family;
@Inject
private HostRoleCommandDAO hostRoleCommandDAO;
private Map<String, Map<String, Map<String, String>>> configCredentialsForService = new HashMap<>();
protected static final String SERVICE_CHECK_COMMAND_NAME = "SERVICE_CHECK";
protected static final String START_COMMAND_NAME = "START";
protected static final String RESTART_COMMAND_NAME = "RESTART";
protected static final String INSTALL_COMMAND_NAME = "INSTALL";
public static final String DECOMMISSION_COMMAND_NAME = "DECOMMISSION";
private Boolean isServiceCheckCommand(String command, String service) {
List<String> actions = actionMetadata.getActions(service);
return !(actions == null || actions.size() == 0) && actions.contains(command);
}
private Boolean isValidCustomCommand(String clusterName,
String serviceName, String componentName, String commandName)
throws AmbariException {
if (componentName == null) {
return false;
}
Cluster cluster = clusters.getCluster(clusterName);
Service service = cluster.getService(serviceName);
ServiceComponent component = service.getServiceComponent(componentName);
StackId stackId = component.getDesiredStackId();
ComponentInfo componentInfo = ambariMetaInfo.getComponent(
stackId.getStackName(), stackId.getStackVersion(),
serviceName, componentName);
return !(!componentInfo.isCustomCommand(commandName) &&
!actionMetadata.isDefaultHostComponentCommand(commandName));
}
private Boolean isValidCustomCommand(ActionExecutionContext
actionExecutionContext, RequestResourceFilter resourceFilter)
throws AmbariException {
String clusterName = actionExecutionContext.getClusterName();
String serviceName = resourceFilter.getServiceName();
String componentName = resourceFilter.getComponentName();
String commandName = actionExecutionContext.getActionName();
if (componentName == null) {
return false;
}
return isValidCustomCommand(clusterName, serviceName, componentName, commandName);
}
private Boolean isValidCustomCommand(ExecuteActionRequest actionRequest,
RequestResourceFilter resourceFilter) throws AmbariException {
String clusterName = actionRequest.getClusterName();
String serviceName = resourceFilter.getServiceName();
String componentName = resourceFilter.getComponentName();
String commandName = actionRequest.getCommandName();
if (componentName == null) {
return false;
}
return isValidCustomCommand(clusterName, serviceName, componentName, commandName);
}
private String getReadableCustomCommandDetail(ActionExecutionContext
actionRequest, RequestResourceFilter resourceFilter) {
StringBuilder sb = new StringBuilder();
sb.append(actionRequest.getActionName());
if (resourceFilter.getServiceName() != null
&& !resourceFilter.getServiceName().equals("")) {
sb.append(" ");
sb.append(resourceFilter.getServiceName());
}
if (resourceFilter.getComponentName() != null
&& !resourceFilter.getComponentName().equals("")) {
sb.append("/");
sb.append(resourceFilter.getComponentName());
}
return sb.toString();
}
/**
* Called during the start/stop/restart of services, plus custom commands during Stack Upgrade.
* @param actionExecutionContext Execution Context
* @param resourceFilter Resource Filter
* @param stage Command stage
* @param additionalCommandParams Additional command params to add the the stage
* @param commandDetail String for the command detail
* @throws AmbariException
*/
private void addCustomCommandAction(final ActionExecutionContext actionExecutionContext,
final RequestResourceFilter resourceFilter, Stage stage, Map<String, String> additionalCommandParams,
String commandDetail, Map<String, String> requestParams) throws AmbariException {
final String serviceName = resourceFilter.getServiceName();
final String componentName = resourceFilter.getComponentName();
final String commandName = actionExecutionContext.getActionName();
boolean retryAllowed = actionExecutionContext.isRetryAllowed();
boolean autoSkipFailure = actionExecutionContext.isFailureAutoSkipped();
String clusterName = stage.getClusterName();
final Cluster cluster = clusters.getCluster(clusterName);
// start with all hosts
Set<String> candidateHosts = new HashSet<>(resourceFilter.getHostNames());
// Filter hosts that are in MS
Set<String> ignoredHosts = maintenanceStateHelper.filterHostsInMaintenanceState(
candidateHosts, new MaintenanceStateHelper.HostPredicate() {
@Override
public boolean shouldHostBeRemoved(final String hostname)
throws AmbariException {
return !maintenanceStateHelper.isOperationAllowed(
cluster, actionExecutionContext.getOperationLevel(),
resourceFilter, serviceName, componentName, hostname);
}
}
);
// Filter unhealthy hosts
Set<String> unhealthyHosts = getUnhealthyHosts(candidateHosts, actionExecutionContext, resourceFilter);
// log excluded hosts
if (!ignoredHosts.isEmpty()) {
if( LOG.isDebugEnabled() ){
LOG.debug(
"While building the {} custom command for {}/{}, the following hosts were excluded: unhealthy[{}], maintenance[{}]",
commandName, serviceName, componentName, StringUtils.join(unhealthyHosts, ','),
StringUtils.join(ignoredHosts, ','));
}
} else if (!unhealthyHosts.isEmpty()) {
if (LOG.isDebugEnabled()) {
LOG.debug(
"While building the {} custom command for {}/{}, the following hosts were excluded: unhealthy[{}], maintenance[{}]",
commandName, serviceName, componentName, StringUtils.join(unhealthyHosts, ','),
StringUtils.join(ignoredHosts, ','));
}
} else if (candidateHosts.isEmpty()) {
String message = MessageFormat.format(
"While building the {0} custom command for {1}/{2}, there were no healthy eligible hosts",
commandName, serviceName, componentName);
throw new AmbariException(message);
}
Service service = cluster.getService(serviceName);
StackId stackId = service.getDesiredStackId();
AmbariMetaInfo ambariMetaInfo = managementController.getAmbariMetaInfo();
ServiceInfo serviceInfo = ambariMetaInfo.getService(service);
StackInfo stackInfo = ambariMetaInfo.getStack(stackId);
CustomCommandDefinition customCommandDefinition = null;
ComponentInfo ci = serviceInfo.getComponentByName(componentName);
if(ci != null){
customCommandDefinition = ci.getCustomCommandByName(commandName);
}
long nowTimestamp = System.currentTimeMillis();
for (String hostName : candidateHosts) {
Host host = clusters.getHost(hostName);
stage.addHostRoleExecutionCommand(hostName, Role.valueOf(componentName),
RoleCommand.CUSTOM_COMMAND,
new ServiceComponentHostOpInProgressEvent(componentName, hostName, nowTimestamp),
cluster.getClusterName(), serviceName, retryAllowed, autoSkipFailure);
Map<String, Map<String, String>> configurations =
new TreeMap<>();
Map<String, Map<String, Map<String, String>>> configurationAttributes =
new TreeMap<>();
Map<String, Map<String, String>> configTags = new TreeMap<>();
ExecutionCommand execCmd = stage.getExecutionCommandWrapper(hostName,
componentName).getExecutionCommand();
// if the command should fetch brand new configuration tags before
// execution, then we don't need to fetch them now
if(actionExecutionContext.getParameters() != null && actionExecutionContext.getParameters().containsKey(KeyNames.REFRESH_CONFIG_TAGS_BEFORE_EXECUTION)){
execCmd.setForceRefreshConfigTagsBeforeExecution(true);
}
// when building complex orchestration ahead of time (such as when
// performing ugprades), fetching configuration tags can take a very long
// time - if it's not needed, then don't do it
if (!execCmd.getForceRefreshConfigTagsBeforeExecution()) {
configTags = managementController.findConfigurationTagsWithOverrides(cluster, hostName);
}
HostRoleCommand cmd = stage.getHostRoleCommand(hostName, componentName);
if (cmd != null) {
cmd.setCommandDetail(commandDetail);
cmd.setCustomCommandName(commandName);
if (customCommandDefinition != null){
cmd.setOpsDisplayName(customCommandDefinition.getOpsDisplayName());
}
}
//set type background
if(customCommandDefinition != null && customCommandDefinition.isBackground()){
cmd.setBackgroundCommand(true);
execCmd.setCommandType(AgentCommandType.BACKGROUND_EXECUTION_COMMAND);
}
execCmd.setComponentVersions(cluster);
execCmd.setConfigurations(configurations);
execCmd.setConfigurationAttributes(configurationAttributes);
execCmd.setConfigurationTags(configTags);
// Get the value of credential store enabled from the DB
Service clusterService = cluster.getService(serviceName);
execCmd.setCredentialStoreEnabled(String.valueOf(clusterService.isCredentialStoreEnabled()));
ServiceComponent component = null;
if (StringUtils.isNotBlank(componentName)) {
component = clusterService.getServiceComponent(componentName);
}
// Get the map of service config type to password properties for the service
Map<String, Map<String, String>> configCredentials;
configCredentials = configCredentialsForService.get(clusterService.getName());
if (configCredentials == null) {
configCredentials = configHelper.getCredentialStoreEnabledProperties(stackId, clusterService);
configCredentialsForService.put(clusterService.getName(), configCredentials);
}
execCmd.setConfigurationCredentials(configCredentials);
Map<String, String> hostLevelParams = new TreeMap<>();
hostLevelParams.put(CUSTOM_COMMAND, commandName);
// Set parameters required for re-installing clients on restart
hostLevelParams.put(REPO_INFO, getRepoInfo(cluster, component, host));
hostLevelParams.put(STACK_NAME, stackId.getStackName());
hostLevelParams.put(STACK_VERSION, stackId.getStackVersion());
Map<String, DesiredConfig> desiredConfigs = cluster.getDesiredConfigs();
Set<String> userSet = configHelper.getPropertyValuesWithPropertyType(stackId, PropertyType.USER, cluster, desiredConfigs);
String userList = gson.toJson(userSet);
hostLevelParams.put(USER_LIST, userList);
//Create a user_group mapping and send it as part of the hostLevelParams
Map<String, Set<String>> userGroupsMap = configHelper.createUserGroupsMap(
stackId, cluster, desiredConfigs);
String userGroups = gson.toJson(userGroupsMap);
hostLevelParams.put(USER_GROUPS, userGroups);
Set<String> groupSet = configHelper.getPropertyValuesWithPropertyType(stackId, PropertyType.GROUP, cluster, desiredConfigs);
String groupList = gson.toJson(groupSet);
hostLevelParams.put(GROUP_LIST, groupList);
Map<PropertyInfo, String> notManagedHdfsPathMap = configHelper.getPropertiesWithPropertyType(stackId, PropertyType.NOT_MANAGED_HDFS_PATH, cluster, desiredConfigs);
Set<String> notManagedHdfsPathSet = configHelper.filterInvalidPropertyValues(notManagedHdfsPathMap, NOT_MANAGED_HDFS_PATH_LIST);
String notManagedHdfsPathList = gson.toJson(notManagedHdfsPathSet);
hostLevelParams.put(NOT_MANAGED_HDFS_PATH_LIST, notManagedHdfsPathList);
execCmd.setHostLevelParams(hostLevelParams);
Map<String, String> commandParams = new TreeMap<>();
if (additionalCommandParams != null) {
for (String key : additionalCommandParams.keySet()) {
commandParams.put(key, additionalCommandParams.get(key));
}
}
boolean isInstallCommand = commandName.equals(RoleCommand.INSTALL.toString());
int commandTimeout = Short.valueOf(configs.getDefaultAgentTaskTimeout(isInstallCommand)).intValue();
ComponentInfo componentInfo = ambariMetaInfo.getComponent(
stackId.getStackName(), stackId.getStackVersion(),
serviceName, componentName);
if (serviceInfo.getSchemaVersion().equals(AmbariMetaInfo.SCHEMA_VERSION_2)) {
// Service check command is not custom command
CommandScriptDefinition script = componentInfo.getCommandScript();
if (script != null) {
commandParams.put(SCRIPT, script.getScript());
commandParams.put(SCRIPT_TYPE, script.getScriptType().toString());
if (script.getTimeout() > 0) {
commandTimeout = script.getTimeout();
}
} else {
String message = String.format("Component %s has not command script " +
"defined. It is not possible to send command for " +
"this service", componentName);
throw new AmbariException(message);
}
// We don't need package/repo information to perform service check
}
// !!! the action execution context timeout is the final say, but make sure it's at least 60 seconds
if (null != actionExecutionContext.getTimeout()) {
commandTimeout = actionExecutionContext.getTimeout().intValue();
commandTimeout = Math.max(60, commandTimeout);
}
if (requestParams != null && requestParams.containsKey("context")) {
String requestContext = requestParams.get("context");
if (StringUtils.isNotEmpty(requestContext) && requestContext.toLowerCase().contains("rolling-restart")) {
Config clusterEnvConfig = cluster.getDesiredConfigByType("cluster-env");
if (clusterEnvConfig != null) {
String componentRollingRestartTimeout = clusterEnvConfig.getProperties().get("namenode_rolling_restart_timeout");
if (StringUtils.isNotEmpty(componentRollingRestartTimeout)) {
commandTimeout = Integer.parseInt(componentRollingRestartTimeout);
}
}
}
}
commandParams.put(COMMAND_TIMEOUT, "" + commandTimeout);
Map<String, String> roleParams = execCmd.getRoleParams();
if (roleParams == null) {
roleParams = new TreeMap<>();
}
// if there is a stack upgrade which is currently suspended then pass that
// information down with the command as some components may need to know
boolean isUpgradeSuspended = cluster.isUpgradeSuspended();
if (isUpgradeSuspended) {
cluster.addSuspendedUpgradeParameters(commandParams, roleParams);
}
StageUtils.useAmbariJdkInCommandParams(commandParams, configs);
roleParams.put(COMPONENT_CATEGORY, componentInfo.getCategory());
execCmd.setCommandParams(commandParams);
execCmd.setRoleParams(roleParams);
execCmd.setRepositoryFile(getCommandRepository(cluster, component, host));
// perform any server side command related logic - eg - set desired states on restart
applyCustomCommandBackendLogic(cluster, serviceName, componentName, commandName, hostName);
}
}
private void applyCustomCommandBackendLogic(Cluster cluster, String serviceName, String componentName, String commandName, String hostname) throws AmbariException {
switch (commandName) {
case "RESTART":
ServiceComponent serviceComponent = cluster.getService(serviceName).getServiceComponent(componentName);
ServiceComponentHost serviceComponentHost = serviceComponent.getServiceComponentHost(hostname);
State currentDesiredState = serviceComponentHost.getDesiredState();
if( !serviceComponent.isClientComponent()) {
if (currentDesiredState != State.STARTED) {
LOG.info("Updating desired state to {} on RESTART for {}/{} because it was {}",
State.STARTED, serviceName, componentName, currentDesiredState);
serviceComponentHost.setDesiredState(State.STARTED);
}
} else {
LOG.debug("Desired state for client components should not be updated on RESTART. Service/Component {}/{}",
serviceName, componentName);
}
break;
default:
LOG.debug("No backend operations needed for the custom command: {}", commandName);
break;
}
}
private void findHostAndAddServiceCheckAction(final ActionExecutionContext actionExecutionContext,
final RequestResourceFilter resourceFilter, Stage stage) throws AmbariException {
String clusterName = actionExecutionContext.getClusterName();
final Cluster cluster = clusters.getCluster(clusterName);
final String componentName = actionMetadata.getClient(resourceFilter.getServiceName());
final String serviceName = resourceFilter.getServiceName();
String smokeTestRole = actionMetadata.getServiceCheckAction(serviceName);
if (null == smokeTestRole) {
smokeTestRole = actionExecutionContext.getActionName();
}
Set<String> candidateHosts;
final Map<String, ServiceComponentHost> serviceHostComponents;
if (componentName != null) {
serviceHostComponents = cluster.getService(serviceName).getServiceComponent(componentName).getServiceComponentHosts();
if (serviceHostComponents.isEmpty()) {
throw new AmbariException(MessageFormat.format("No hosts found for service: {0}, component: {1} in cluster: {2}",
serviceName, componentName, clusterName));
}
// If specified a specific host, run on it as long as it contains the component.
// Otherwise, use candidates that contain the component.
List<String> candidateHostsList = resourceFilter.getHostNames();
if (candidateHostsList != null && !candidateHostsList.isEmpty()) {
candidateHosts = new HashSet<>(candidateHostsList);
// Get the intersection.
candidateHosts.retainAll(serviceHostComponents.keySet());
if (candidateHosts.isEmpty()) {
throw new AmbariException(MessageFormat.format("The resource filter for hosts does not contain components for " +
"service: {0}, component: {1} in cluster: {2}", serviceName, componentName, clusterName));
}
} else {
candidateHosts = serviceHostComponents.keySet();
}
} else {
// TODO: This code branch looks unreliable (taking random component, should prefer the clients)
Map<String, ServiceComponent> serviceComponents = cluster.getService(serviceName).getServiceComponents();
// Filter components without any HOST
Iterator<String> serviceComponentNameIterator = serviceComponents.keySet().iterator();
while (serviceComponentNameIterator.hasNext()){
String componentToCheck = serviceComponentNameIterator.next();
if (serviceComponents.get(componentToCheck).getServiceComponentHosts().isEmpty()){
serviceComponentNameIterator.remove();
}
}
if (serviceComponents.isEmpty()) {
throw new AmbariException(MessageFormat.format("Did not find any hosts with components for service: {0} in cluster: {1}",
serviceName, clusterName));
}
// Pick a random service (should prefer clients).
ServiceComponent serviceComponent = serviceComponents.values().iterator().next();
serviceHostComponents = serviceComponent.getServiceComponentHosts();
candidateHosts = serviceHostComponents.keySet();
}
// check if all hostnames are valid.
for(String candidateHostName: candidateHosts) {
ServiceComponentHost serviceComponentHost = serviceHostComponents.get(candidateHostName);
if (serviceComponentHost == null) {
throw new AmbariException("Provided hostname = "
+ candidateHostName + " is either not a valid cluster host or does not satisfy the filter condition.");
}
}
// Filter out hosts that are in maintenance mode - they should never be included in service checks
Set<String> hostsInMaintenanceMode = new HashSet<>();
if (actionExecutionContext.isMaintenanceModeHostExcluded()) {
Iterator<String> iterator = candidateHosts.iterator();
while (iterator.hasNext()) {
String candidateHostName = iterator.next();
ServiceComponentHost serviceComponentHost = serviceHostComponents.get(candidateHostName);
Host host = serviceComponentHost.getHost();
if (host.getMaintenanceState(cluster.getClusterId()) == MaintenanceState.ON) {
hostsInMaintenanceMode.add(candidateHostName);
iterator.remove();
}
}
}
// Filter out hosts that are not healthy, i.e., all hosts should be heartbeating.
// Pick one randomly. If there are none, throw an exception.
List<String> healthyHostNames = managementController.selectHealthyHosts(candidateHosts);
if (healthyHostNames.isEmpty()) {
String message = MessageFormat.format(
"While building a service check command for {0}, there were no healthy eligible hosts: unhealthy[{1}], maintenance[{2}]",
serviceName, StringUtils.join(candidateHosts, ','),
StringUtils.join(hostsInMaintenanceMode, ','));
throw new AmbariException(message);
}
String preferredHostName = selectRandomHostNameWithPreferenceOnAvailability(healthyHostNames);
long nowTimestamp = System.currentTimeMillis();
Map<String, String> actionParameters = actionExecutionContext.getParameters();
addServiceCheckAction(stage, preferredHostName, smokeTestRole, nowTimestamp, serviceName, componentName,
actionParameters, actionExecutionContext.isRetryAllowed(),
actionExecutionContext.isFailureAutoSkipped());
}
/**
* Assuming all hosts are healthy and not in maintenance mode. Rank the hosts based on availability.
* Let S = all hosts with 0 PENDING/RUNNING/QUEUED/IN-PROGRESS tasks
* Let S' be all such other hosts.
*
* If S is non-empty, pick a random host from it. If S is empty and S' is non-empty, pick a random host from S'.
* @param candidateHostNames All possible host names
* @return Random host with a preference for those that are available to process commands immediately.
*/
private String selectRandomHostNameWithPreferenceOnAvailability(List<String> candidateHostNames) throws AmbariException {
if (null == candidateHostNames || candidateHostNames.isEmpty()) {
return null;
}
if (candidateHostNames.size() == 1) {
return candidateHostNames.get(0);
}
List<String> hostsWithZeroCommands = new ArrayList<>();
List<String> hostsWithInProgressCommands = new ArrayList<>();
Map<Long, Integer> hostIdToCount = hostRoleCommandDAO.getHostIdToCountOfCommandsWithStatus(HostRoleStatus.IN_PROGRESS_STATUSES);
for (String hostName : candidateHostNames) {
Host host = clusters.getHost(hostName);
if (hostIdToCount.containsKey(host.getHostId()) && hostIdToCount.get(host.getHostId()) > 0) {
hostsWithInProgressCommands.add(hostName);
} else {
hostsWithZeroCommands.add(hostName);
}
}
List<String> preferredList = !hostsWithZeroCommands.isEmpty() ? hostsWithZeroCommands : hostsWithInProgressCommands;
if (!preferredList.isEmpty()) {
int randomIndex = new Random().nextInt(preferredList.size());
return preferredList.get(randomIndex);
}
return null;
}
/**
* Creates and populates service check EXECUTION_COMMAND for host. Not all
* EXECUTION_COMMAND parameters are populated here because they are not needed
* by service check.
*/
public void addServiceCheckAction(Stage stage, String hostname, String smokeTestRole,
long nowTimestamp, String serviceName, String componentName,
Map<String, String> actionParameters, boolean retryAllowed, boolean autoSkipFailure)
throws AmbariException {
String clusterName = stage.getClusterName();
Cluster cluster = clusters.getCluster(clusterName);
Service service = cluster.getService(serviceName);
ServiceComponent component = null;
if (null != componentName) {
component = service.getServiceComponent(componentName);
}
StackId stackId = (null != component) ? component.getDesiredStackId() : service.getDesiredStackId();
AmbariMetaInfo ambariMetaInfo = managementController.getAmbariMetaInfo();
ServiceInfo serviceInfo = ambariMetaInfo.getService(stackId.getStackName(),
stackId.getStackVersion(), serviceName);
StackInfo stackInfo = ambariMetaInfo.getStack(stackId.getStackName(),
stackId.getStackVersion());
stage.addHostRoleExecutionCommand(hostname, Role.valueOf(smokeTestRole),
RoleCommand.SERVICE_CHECK,
new ServiceComponentHostOpInProgressEvent(componentName, hostname, nowTimestamp),
cluster.getClusterName(), serviceName, retryAllowed, autoSkipFailure);
HostRoleCommand hrc = stage.getHostRoleCommand(hostname, smokeTestRole);
if (hrc != null) {
hrc.setCommandDetail(String.format("%s %s", RoleCommand.SERVICE_CHECK.toString(), serviceName));
}
// [ type -> [ key, value ] ]
Map<String, Map<String, String>> configurations =
new TreeMap<>();
Map<String, Map<String, Map<String, String>>> configurationAttributes =
new TreeMap<>();
Map<String, Map<String, String>> configTags = new TreeMap<>();
ExecutionCommand execCmd = stage.getExecutionCommandWrapper(hostname,
smokeTestRole).getExecutionCommand();
// if the command should fetch brand new configuration tags before
// execution, then we don't need to fetch them now
if(actionParameters != null && actionParameters.containsKey(KeyNames.REFRESH_CONFIG_TAGS_BEFORE_EXECUTION)){
execCmd.setForceRefreshConfigTagsBeforeExecution(true);
}
// when building complex orchestration ahead of time (such as when
// performing ugprades), fetching configuration tags can take a very long
// time - if it's not needed, then don't do it
if (!execCmd.getForceRefreshConfigTagsBeforeExecution()) {
configTags = managementController.findConfigurationTagsWithOverrides(cluster, hostname);
}
execCmd.setConfigurations(configurations);
execCmd.setConfigurationAttributes(configurationAttributes);
execCmd.setConfigurationTags(configTags);
// Generate cluster host info
execCmd.setClusterHostInfo(
StageUtils.getClusterHostInfo(cluster));
// Generate localComponents
for (ServiceComponentHost sch : cluster.getServiceComponentHosts(hostname)) {
execCmd.getLocalComponents().add(sch.getServiceComponentName());
}
Map<String, String> commandParams = new TreeMap<>();
//Propagate HCFS service type info
Map<String, ServiceInfo> serviceInfos = ambariMetaInfo.getServices(stackId.getStackName(), stackId.getStackVersion());
for (ServiceInfo serviceInfoInstance : serviceInfos.values()) {
if (serviceInfoInstance.getServiceType() != null) {
LOG.debug("Adding {} to command parameters for {}", serviceInfoInstance.getServiceType(),
serviceInfoInstance.getName());
commandParams.put("dfs_type", serviceInfoInstance.getServiceType());
break;
}
}
String commandTimeout = configs.getDefaultAgentTaskTimeout(false);
if (serviceInfo.getSchemaVersion().equals(AmbariMetaInfo.SCHEMA_VERSION_2)) {
// Service check command is not custom command
CommandScriptDefinition script = serviceInfo.getCommandScript();
if (script != null) {
commandParams.put(SCRIPT, script.getScript());
commandParams.put(SCRIPT_TYPE, script.getScriptType().toString());
if (script.getTimeout() > 0) {
commandTimeout = String.valueOf(script.getTimeout());
}
} else {
String message = String.format("Service %s has no command script " +
"defined. It is not possible to run service check" +
" for this service", serviceName);
throw new AmbariException(message);
}
// We don't need package/repo information to perform service check
}
// Try to apply overridden service check timeout value if available
Long overriddenTimeout = configs.getAgentServiceCheckTaskTimeout();
if (!overriddenTimeout.equals(Configuration.AGENT_SERVICE_CHECK_TASK_TIMEOUT.getDefaultValue())) {
commandTimeout = String.valueOf(overriddenTimeout);
}
commandParams.put(COMMAND_TIMEOUT, commandTimeout);
String checkType = configHelper.getValueFromDesiredConfigurations(cluster, ConfigHelper.CLUSTER_ENV, ConfigHelper.SERVICE_CHECK_TYPE);
if (ConfigHelper.SERVICE_CHECK_MINIMAL.equals(checkType)) {
int actualTimeout = Integer.parseInt(commandParams.get(COMMAND_TIMEOUT)) / 2;
actualTimeout = actualTimeout < MIN_STRICT_SERVICE_CHECK_TIMEOUT ? MIN_STRICT_SERVICE_CHECK_TIMEOUT : actualTimeout;
commandParams.put(COMMAND_TIMEOUT, Integer.toString(actualTimeout));
}
StageUtils.useAmbariJdkInCommandParams(commandParams, configs);
execCmd.setCommandParams(commandParams);
if (actionParameters != null) { // If defined
execCmd.setRoleParams(actionParameters);
}
}
private Set<String> getHostList(Map<String, String> cmdParameters, String key) {
Set<String> hosts = new HashSet<>();
if (cmdParameters.containsKey(key)) {
String allHosts = cmdParameters.get(key);
if (allHosts != null) {
for (String hostName : allHosts.trim().split(",")) {
hosts.add(hostName.trim());
}
}
}
return hosts;
}
/**
* Processes decommission command. Modifies the host components as needed and then
* calls into the implementation of a custom command
*/
private void addDecommissionAction(final ActionExecutionContext actionExecutionContext,
final RequestResourceFilter resourceFilter, Stage stage, ExecuteCommandJson executeCommandJson) throws AmbariException {
String clusterName = actionExecutionContext.getClusterName();
final Cluster cluster = clusters.getCluster(clusterName);
final String serviceName = resourceFilter.getServiceName();
String masterCompType = resourceFilter.getComponentName();
List<String> hosts = resourceFilter.getHostNames();
if (hosts != null && !hosts.isEmpty()) {
throw new AmbariException("Decommission command cannot be issued with " +
"target host(s) specified.");
}
//Get all hosts to be added and removed
Set<String> excludedHosts = getHostList(actionExecutionContext.getParameters(),
DECOM_EXCLUDED_HOSTS);
Set<String> includedHosts = getHostList(actionExecutionContext.getParameters(),
DECOM_INCLUDED_HOSTS);
if (actionExecutionContext.getParameters().get(IS_ADD_OR_DELETE_SLAVE_REQUEST) != null &&
actionExecutionContext.getParameters().get(IS_ADD_OR_DELETE_SLAVE_REQUEST).equalsIgnoreCase("true")) {
includedHosts = getHostList(actionExecutionContext.getParameters(), masterCompType + "_" + DECOM_INCLUDED_HOSTS);
}
Set<String> cloneSet = new HashSet<>(excludedHosts);
cloneSet.retainAll(includedHosts);
if (cloneSet.size() > 0) {
throw new AmbariException("Same host cannot be specified for inclusion " +
"as well as exclusion. Hosts: " + cloneSet);
}
Service service = cluster.getService(serviceName);
if (service == null) {
throw new AmbariException("Specified service " + serviceName +
" is not a valid/deployed service.");
}
Map<String, ServiceComponent> svcComponents = service.getServiceComponents();
if (!svcComponents.containsKey(masterCompType)) {
throw new AmbariException("Specified component " + masterCompType +
" does not belong to service " + serviceName + ".");
}
ServiceComponent masterComponent = svcComponents.get(masterCompType);
if (!masterComponent.isMasterComponent()) {
throw new AmbariException("Specified component " + masterCompType +
" is not a MASTER for service " + serviceName + ".");
}
if (!masterToSlaveMappingForDecom.containsKey(masterCompType)) {
throw new AmbariException("Decommissioning is not supported for " + masterCompType);
}
// Find the slave component
String slaveCompStr = actionExecutionContext.getParameters().get(DECOM_SLAVE_COMPONENT);
final String slaveCompType;
if (slaveCompStr == null || slaveCompStr.equals("")) {
slaveCompType = masterToSlaveMappingForDecom.get(masterCompType);
} else {
slaveCompType = slaveCompStr;
if (!masterToSlaveMappingForDecom.get(masterCompType).equals(slaveCompType)) {
throw new AmbariException("Component " + slaveCompType + " is not supported for decommissioning.");
}
}
String isDrainOnlyRequest = actionExecutionContext.getParameters().get(HBASE_MARK_DRAINING_ONLY);
if (isDrainOnlyRequest != null && !slaveCompType.equals(Role.HBASE_REGIONSERVER.name())) {
throw new AmbariException(HBASE_MARK_DRAINING_ONLY + " is not a valid parameter for " + masterCompType);
}
// Filtering hosts based on Maintenance State
MaintenanceStateHelper.HostPredicate hostPredicate
= new MaintenanceStateHelper.HostPredicate() {
@Override
public boolean shouldHostBeRemoved(final String hostname)
throws AmbariException {
//Get UPDATE_FILES_ONLY parameter as string
String upd_excl_file_only_str = actionExecutionContext.getParameters()
.get(UPDATE_FILES_ONLY);
String decom_incl_hosts_str = actionExecutionContext.getParameters()
.get(DECOM_INCLUDED_HOSTS);
if ((upd_excl_file_only_str != null &&
!upd_excl_file_only_str.trim().equals(""))){
upd_excl_file_only_str = upd_excl_file_only_str.trim();
}
boolean upd_excl_file_only = false;
//Parse of possible forms of value
if (upd_excl_file_only_str != null &&
!upd_excl_file_only_str.equals("") &&
(upd_excl_file_only_str.equals("\"true\"")
|| upd_excl_file_only_str.equals("'true'")
|| upd_excl_file_only_str.equals("true"))){
upd_excl_file_only = true;
}
// If we just clear *.exclude and component have been already removed we will skip check
if (upd_excl_file_only && decom_incl_hosts_str != null
&& !decom_incl_hosts_str.trim().equals("")) {
return upd_excl_file_only;
} else {
return !maintenanceStateHelper.isOperationAllowed(
cluster, actionExecutionContext.getOperationLevel(),
resourceFilter, serviceName, slaveCompType, hostname);
}
}
};
// Filter excluded hosts
Set<String> filteredExcludedHosts = new HashSet<>(excludedHosts);
Set<String> ignoredHosts = maintenanceStateHelper.filterHostsInMaintenanceState(
filteredExcludedHosts, hostPredicate);
if (! ignoredHosts.isEmpty()) {
String message = String.format("Some hosts (%s) from host exclude list " +
"have been ignored " +
"because components on them are in Maintenance state.",
ignoredHosts);
LOG.debug(message);
}
// Filter included hosts
Set<String> filteredIncludedHosts = new HashSet<>(includedHosts);
ignoredHosts = maintenanceStateHelper.filterHostsInMaintenanceState(
filteredIncludedHosts, hostPredicate);
if (! ignoredHosts.isEmpty()) {
String message = String.format("Some hosts (%s) from host include list " +
"have been ignored " +
"because components on them are in Maintenance state.",
ignoredHosts);
LOG.debug(message);
}
// Decommission only if the sch is in state STARTED or INSTALLED
for (ServiceComponentHost sch : svcComponents.get(slaveCompType).getServiceComponentHosts().values()) {
if (filteredExcludedHosts.contains(sch.getHostName())
&& !"true".equals(isDrainOnlyRequest)
&& sch.getState() != State.STARTED) {
throw new AmbariException("Component " + slaveCompType + " on host " + sch.getHostName() + " cannot be " +
"decommissioned as its not in STARTED state. Aborting the whole request.");
}
}
String alignMtnStateStr = actionExecutionContext.getParameters().get(ALIGN_MAINTENANCE_STATE);
boolean alignMtnState = "true".equals(alignMtnStateStr);
// Set/reset decommissioned flag on all components
List<String> listOfExcludedHosts = new ArrayList<>();
for (ServiceComponentHost sch : svcComponents.get(slaveCompType).getServiceComponentHosts().values()) {
if (filteredExcludedHosts.contains(sch.getHostName())) {
sch.setComponentAdminState(HostComponentAdminState.DECOMMISSIONED);
listOfExcludedHosts.add(sch.getHostName());
if (alignMtnState) {
sch.setMaintenanceState(MaintenanceState.ON);
LOG.info("marking Maintenance=ON on " + sch.getHostName());
}
LOG.info("Decommissioning " + slaveCompType + " on " + sch.getHostName());
}
if (filteredIncludedHosts.contains(sch.getHostName())) {
sch.setComponentAdminState(HostComponentAdminState.INSERVICE);
if (alignMtnState) {
sch.setMaintenanceState(MaintenanceState.OFF);
LOG.info("marking Maintenance=OFF on " + sch.getHostName());
}
LOG.info("Recommissioning " + slaveCompType + " on " + sch.getHostName());
}
}
// In the event there are more than one master host the following logic is applied
// -- HDFS/DN, MR1/TT, YARN/NM call refresh node on both
// -- HBASE/RS call only on one host
// Ensure host is active
Map<String, ServiceComponentHost> masterSchs = masterComponent.getServiceComponentHosts();
String primaryCandidate = null;
for (String hostName : masterSchs.keySet()) {
if (primaryCandidate == null) {
primaryCandidate = hostName;
} else {
ServiceComponentHost sch = masterSchs.get(hostName);
if (sch.getState() == State.STARTED) {
primaryCandidate = hostName;
}
}
}
StringBuilder commandDetail = getReadableDecommissionCommandDetail
(actionExecutionContext, filteredIncludedHosts, listOfExcludedHosts);
for (String hostName : masterSchs.keySet()) {
RequestResourceFilter commandFilter = new RequestResourceFilter(serviceName,
masterComponent.getName(), Collections.singletonList(hostName));
List<RequestResourceFilter> resourceFilters = new ArrayList<>();
resourceFilters.add(commandFilter);
ActionExecutionContext commandContext = new ActionExecutionContext(
clusterName, actionExecutionContext.getActionName(), resourceFilters
);
String clusterHostInfoJson = StageUtils.getGson().toJson(
StageUtils.getClusterHostInfo(cluster));
// Reset cluster host info as it has changed
if (executeCommandJson != null) {
executeCommandJson.setClusterHostInfo(clusterHostInfoJson);
}
Map<String, String> commandParams = new HashMap<>();
if (serviceName.equals(Service.Type.HBASE.name())) {
commandParams.put(DECOM_EXCLUDED_HOSTS, StringUtils.join(listOfExcludedHosts, ','));
if ((isDrainOnlyRequest != null) && isDrainOnlyRequest.equals("true")) {
commandParams.put(HBASE_MARK_DRAINING_ONLY, isDrainOnlyRequest);
} else {
commandParams.put(HBASE_MARK_DRAINING_ONLY, "false");
}
}
if (!serviceName.equals(Service.Type.HBASE.name()) || hostName.equals(primaryCandidate)) {
commandParams.put(UPDATE_FILES_ONLY, "false");
addCustomCommandAction(commandContext, commandFilter, stage, commandParams, commandDetail.toString(), null);
}
}
}
private StringBuilder getReadableDecommissionCommandDetail(
ActionExecutionContext actionExecutionContext, Set<String> includedHosts,
List<String> listOfExcludedHosts) {
StringBuilder commandDetail = new StringBuilder();
commandDetail.append(actionExecutionContext.getActionName());
if (actionExecutionContext.getParameters().containsKey(IS_ADD_OR_DELETE_SLAVE_REQUEST) &&
actionExecutionContext.getParameters().get(IS_ADD_OR_DELETE_SLAVE_REQUEST).equalsIgnoreCase("true")) {
commandDetail.append(", Update Include/Exclude Files");
return commandDetail;
}
if (listOfExcludedHosts.size() > 0) {
commandDetail.append(", Excluded: ").append(StringUtils.join(listOfExcludedHosts, ','));
}
if (includedHosts.size() > 0) {
commandDetail.append(", Included: ").append(StringUtils.join(includedHosts, ','));
}
return commandDetail;
}
/**
* Validate custom command and throw exception is invalid request.
*
* @param actionRequest the action request
*
* @throws AmbariException if the action can not be validated
*/
public void validateAction(ExecuteActionRequest actionRequest) throws AmbariException {
List<RequestResourceFilter> resourceFilters = actionRequest.getResourceFilters();
if (resourceFilters != null && resourceFilters.isEmpty() &&
actionRequest.getParameters().containsKey(HAS_RESOURCE_FILTERS) &&
actionRequest.getParameters().get(HAS_RESOURCE_FILTERS).equalsIgnoreCase("true")) {
LOG.warn("Couldn't find any resource that satisfies given resource filters");
return;
}
if (resourceFilters == null || resourceFilters.isEmpty()) {
throw new AmbariException("Command execution cannot proceed without a " +
"resource filter.");
}
for (RequestResourceFilter resourceFilter : resourceFilters) {
if (resourceFilter.getServiceName() == null
|| resourceFilter.getServiceName().isEmpty()
|| actionRequest.getCommandName() == null
|| actionRequest.getCommandName().isEmpty()) {
throw new AmbariException("Invalid resource filter : " + "cluster = "
+ actionRequest.getClusterName() + ", service = "
+ resourceFilter.getServiceName() + ", command = "
+ actionRequest.getCommandName());
}
if (!isServiceCheckCommand(actionRequest.getCommandName(), resourceFilter.getServiceName())
&& !isValidCustomCommand(actionRequest, resourceFilter)) {
throw new AmbariException(
"Unsupported action " + actionRequest.getCommandName() +
" for Service: " + resourceFilter.getServiceName()
+ " and Component: " + resourceFilter.getComponentName());
}
}
}
/**
* Other than Service_Check and Decommission all other commands are pass-through
*
* @param actionExecutionContext received request to execute a command
* @param stage the initial stage for task creation
* @param requestParams the request params
* @param executeCommandJson set of json arguments passed to the request
*
* @throws AmbariException if the commands can not be added
*/
public void addExecutionCommandsToStage(ActionExecutionContext actionExecutionContext,
Stage stage, Map<String, String> requestParams, ExecuteCommandJson executeCommandJson) throws AmbariException {
List<RequestResourceFilter> resourceFilters = actionExecutionContext.getResourceFilters();
for (RequestResourceFilter resourceFilter : resourceFilters) {
LOG.debug("Received a command execution request, clusterName={}, serviceName={}, request={}",
actionExecutionContext.getClusterName(), resourceFilter.getServiceName(), actionExecutionContext);
String actionName = actionExecutionContext.getActionName();
if (actionName.contains(SERVICE_CHECK_COMMAND_NAME)) {
findHostAndAddServiceCheckAction(actionExecutionContext, resourceFilter, stage);
} else if (actionName.equals(DECOMMISSION_COMMAND_NAME)) {
addDecommissionAction(actionExecutionContext, resourceFilter, stage, executeCommandJson);
} else if (isValidCustomCommand(actionExecutionContext, resourceFilter)) {
String commandDetail = getReadableCustomCommandDetail(actionExecutionContext, resourceFilter);
Map<String, String> extraParams = new HashMap<>();
String componentName = (null == resourceFilter.getComponentName()) ? null :
resourceFilter.getComponentName().toLowerCase();
if (null != componentName && requestParams.containsKey(componentName)) {
extraParams.put(componentName, requestParams.get(componentName));
}
// If command should be retried upon failure then add the option and also the default duration for retry
if (requestParams.containsKey(KeyNames.COMMAND_RETRY_ENABLED)) {
extraParams.put(KeyNames.COMMAND_RETRY_ENABLED, requestParams.get(KeyNames.COMMAND_RETRY_ENABLED));
String commandRetryDuration = ConfigHelper.COMMAND_RETRY_MAX_TIME_IN_SEC_DEFAULT;
if (requestParams.containsKey(KeyNames.MAX_DURATION_OF_RETRIES)) {
String commandRetryDurationStr = requestParams.get(KeyNames.MAX_DURATION_OF_RETRIES);
Integer commandRetryDurationInt = NumberUtils.toInt(commandRetryDurationStr, 0);
if (commandRetryDurationInt > 0) {
commandRetryDuration = Integer.toString(commandRetryDurationInt);
}
}
extraParams.put(KeyNames.MAX_DURATION_OF_RETRIES, commandRetryDuration);
}
// If command needs to explicitly disable STDOUT/STDERR logging
if (requestParams.containsKey(KeyNames.LOG_OUTPUT)) {
extraParams.put(KeyNames.LOG_OUTPUT, requestParams.get(KeyNames.LOG_OUTPUT));
}
if(requestParams.containsKey(KeyNames.REFRESH_CONFIG_TAGS_BEFORE_EXECUTION)){
actionExecutionContext.getParameters().put(KeyNames.REFRESH_CONFIG_TAGS_BEFORE_EXECUTION, requestParams.get(KeyNames.REFRESH_CONFIG_TAGS_BEFORE_EXECUTION));
}
RequestOperationLevel operationLevel = actionExecutionContext.getOperationLevel();
if (operationLevel != null) {
String clusterName = operationLevel.getClusterName();
String serviceName = operationLevel.getServiceName();
if (isTopologyRefreshRequired(actionName, clusterName, serviceName)) {
extraParams.put(KeyNames.REFRESH_TOPOLOGY, "True");
}
}
addCustomCommandAction(actionExecutionContext, resourceFilter, stage, extraParams, commandDetail, requestParams);
} else {
throw new AmbariException("Unsupported action " + actionName);
}
}
}
/**
* Get repository info given a cluster and host.
*
* @param cluster the cluster
* @param host the host
*
* @return the repo info
*
* @deprecated use {@link #getCommandRepository(Cluster, ServiceComponent, Host)} instead.
* @throws AmbariException if the repository information can not be obtained
*/
@Deprecated
public String getRepoInfo(Cluster cluster, ServiceComponent component, Host host) throws AmbariException {
Function<List<RepositoryInfo>, JsonArray> function = new Function<List<RepositoryInfo>, JsonArray>() {
@Override
public JsonArray apply(List<RepositoryInfo> input) {
return null == input ? null : (JsonArray) gson.toJsonTree(input);
}
};
final JsonArray gsonList = getBaseUrls(cluster, component, host, function);
if (null == gsonList) {
return "";
}
BaseUrlUpdater<JsonArray> updater = new BaseUrlUpdater<JsonArray>(gsonList) {
@Override
public JsonArray apply(final RepositoryVersionEntity rve) {
JsonArray result = new JsonArray();
for (JsonElement e : gsonList) {
JsonObject obj = e.getAsJsonObject();
String repoId = obj.has("repoId") ? obj.get("repoId").getAsString() : null;
String repoName = obj.has("repoName") ? obj.get("repoName").getAsString() : null;
String baseUrl = obj.has("baseUrl") ? obj.get("baseUrl").getAsString() : null;
String osType = obj.has("osType") ? obj.get("osType").getAsString() : null;
if (null == repoId || null == baseUrl || null == osType || null == repoName) {
continue;
}
for (OperatingSystemEntity ose : rve.getOperatingSystems()) {
if (ose.getOsType().equals(osType) && ose.isAmbariManagedRepos()) {
for (RepositoryEntity re : ose.getRepositories()) {
if (re.getName().equals(repoName) &&
!re.getBaseUrl().equals(baseUrl)) {
obj.addProperty("baseUrl", re.getBaseUrl());
}
}
result.add(e);
}
}
}
return result;
}
};
return updateBaseUrls(cluster, component, updater).toString();
}
/**
* Builds repository information for inclusion in a command. This replaces escaping json on
* a command.
*
* @param cluster the cluster
* @param host the host
* @return the command repository
* @throws AmbariException
*/
@Experimental(feature=ExperimentalFeature.PATCH_UPGRADES)
public CommandRepository getCommandRepository(final Cluster cluster, ServiceComponent component, final Host host) throws AmbariException {
final CommandRepository command = new CommandRepository();
StackId stackId = component.getDesiredStackId();
command.setRepositories(Collections.<RepositoryInfo>emptyList());
command.setStackName(stackId.getStackName());
final BaseUrlUpdater<Void> updater = new BaseUrlUpdater<Void>(null) {
@Override
public Void apply(RepositoryVersionEntity rve) {
command.setRepositoryVersionId(rve.getId());
command.setRepositoryVersion(rve.getVersion());
command.setResolved(rve.isResolved());
command.setStackName(rve.getStackName());
// !!! a repository version entity has all the repos worked out. We shouldn't use
// the stack at all.
for (OperatingSystemEntity osEntity : rve.getOperatingSystems()) {
String osEntityFamily = os_family.find(osEntity.getOsType());
if (osEntityFamily.equals(host.getOsFamily())) {
command.setRepositories(osEntity.getOsType(), osEntity.getRepositories());
if (!osEntity.isAmbariManagedRepos()) {
command.setNonManaged();
} else {
command.setUniqueSuffix(String.format("-repo-%s", rve.getId()));
}
}
}
return null;
}
};
updateBaseUrls(cluster, component, updater);
return command;
}
/**
* Executed by two different representations of repos. When we are comfortable with the new
* implementation, this may be removed and called inline in {@link #getCommandRepository(Cluster, ServiceComponent, Host)}
*
* @param cluster the cluster to isolate the stack
* @param component the component
* @param host used to resolve the family for the repositories
* @param function function that will transform the supplied repositories for specific use.
* @return <T> the type as defined by the supplied {@code function}.
* @throws AmbariException
*/
@Experimental(feature = ExperimentalFeature.PATCH_UPGRADES)
private <T> T getBaseUrls(Cluster cluster, ServiceComponent component, Host host,
Function<List<RepositoryInfo>, T> function) throws AmbariException {
String hostOsType = host.getOsType();
String hostOsFamily = host.getOsFamily();
String hostName = host.getHostName();
StackId stackId = component.getDesiredStackId();
Map<String, List<RepositoryInfo>> repos = ambariMetaInfo.getRepository(
stackId.getStackName(), stackId.getStackVersion());
String family = os_family.find(hostOsType);
if (null == family) {
family = hostOsFamily;
}
final List<RepositoryInfo> repoInfos;
// !!! check for the most specific first
if (repos.containsKey(hostOsType)) {
repoInfos = repos.get(hostOsType);
} else if (null != family && repos.containsKey(family)) {
repoInfos = repos.get(family);
} else {
repoInfos = null;
LOG.warn("Could not retrieve repo information for host"
+ ", hostname=" + hostName
+ ", clusterName=" + cluster.getClusterName()
+ ", stackInfo=" + stackId.getStackId());
}
// leave it to function implementation to handle null.
return function.apply(repoInfos);
}
/**
* Checks repo URLs against the current version for the cluster and makes
* adjustments to the Base URL when the current is different.
*
* @param <T> the result after appling the repository version, if found.
*/
@Experimental(feature = ExperimentalFeature.PATCH_UPGRADES)
private <T> T updateBaseUrls(Cluster cluster, ServiceComponent component, BaseUrlUpdater<T> function) throws AmbariException {
RepositoryVersionEntity repositoryEntity = null;
// !!! try to find the component repo first
if (null != component) {
repositoryEntity = component.getDesiredRepositoryVersion();
} else {
LOG.info("Service component not passed in, attempt to resolve the repository for cluster {}",
cluster.getClusterName());
}
if (null == repositoryEntity && null != component) {
Service service = cluster.getService(component.getServiceName());
repositoryEntity = service.getDesiredRepositoryVersion();
}
if (null == repositoryEntity) {
LOG.info("Cluster {} has no specific Repository Versions. Using stack-defined values", cluster.getClusterName());
return function.getDefault();
}
return function.apply(repositoryEntity);
}
/**
* Helper method to fill execution command information.
*
* @param actionExecContext the context
* @param cluster the cluster for the command
*
* @return a wrapper of the important JSON structures to add to a stage
*/
public ExecuteCommandJson getCommandJson(ActionExecutionContext actionExecContext,
Cluster cluster, RepositoryVersionEntity repositoryVersion, String requestContext) throws AmbariException {
Map<String, String> commandParamsStage = StageUtils.getCommandParamsStage(actionExecContext, requestContext);
Map<String, String> hostParamsStage = new HashMap<>();
Map<String, Set<String>> clusterHostInfo;
String clusterHostInfoJson = "{}";
StackId stackId = null;
if (null != repositoryVersion) {
stackId = repositoryVersion.getStackId();
}
if (null != cluster) {
clusterHostInfo = StageUtils.getClusterHostInfo(cluster);
// Important, because this runs during Stack Uprade, it needs to use the effective Stack Id.
hostParamsStage = createDefaultHostParams(cluster, repositoryVersion);
String componentName = null;
String serviceName = null;
if (actionExecContext.getOperationLevel() != null) {
componentName = actionExecContext.getOperationLevel().getHostComponentName();
serviceName = actionExecContext.getOperationLevel().getServiceName();
}
if (serviceName != null && componentName != null && null != stackId) {
Service service = cluster.getService(serviceName);
ServiceComponent component = service.getServiceComponent(componentName);
stackId = component.getDesiredStackId();
ComponentInfo componentInfo = ambariMetaInfo.getComponent(
stackId.getStackName(), stackId.getStackVersion(),
serviceName, componentName);
List<String> clientsToUpdateConfigsList = componentInfo.getClientsToUpdateConfigs();
if (clientsToUpdateConfigsList == null) {
clientsToUpdateConfigsList = new ArrayList<>();
clientsToUpdateConfigsList.add("*");
}
String clientsToUpdateConfigs = gson.toJson(clientsToUpdateConfigsList);
hostParamsStage.put(CLIENTS_TO_UPDATE_CONFIGS, clientsToUpdateConfigs);
}
clusterHostInfoJson = StageUtils.getGson().toJson(clusterHostInfo);
//Propogate HCFS service type info to command params
if (null != stackId) {
Map<String, ServiceInfo> serviceInfos = ambariMetaInfo.getServices(stackId.getStackName(),
stackId.getStackVersion());
for (ServiceInfo serviceInfoInstance : serviceInfos.values()) {
if (serviceInfoInstance.getServiceType() != null) {
LOG.debug("Adding {} to command parameters for {}",
serviceInfoInstance.getServiceType(), serviceInfoInstance.getName());
commandParamsStage.put("dfs_type", serviceInfoInstance.getServiceType());
break;
}
}
}
}
String hostParamsStageJson = StageUtils.getGson().toJson(hostParamsStage);
String commandParamsStageJson = StageUtils.getGson().toJson(commandParamsStage);
return new ExecuteCommandJson(clusterHostInfoJson, commandParamsStageJson,
hostParamsStageJson);
}
Map<String, String> createDefaultHostParams(Cluster cluster, RepositoryVersionEntity repositoryVersion) throws AmbariException {
return createDefaultHostParams(cluster, repositoryVersion.getStackId());
}
Map<String, String> createDefaultHostParams(Cluster cluster, StackId stackId) throws AmbariException {
TreeMap<String, String> hostLevelParams = new TreeMap<>();
StageUtils.useStackJdkIfExists(hostLevelParams, configs);
hostLevelParams.put(JDK_LOCATION, managementController.getJdkResourceUrl());
hostLevelParams.put(STACK_NAME, stackId.getStackName());
hostLevelParams.put(STACK_VERSION, stackId.getStackVersion());
hostLevelParams.put(DB_NAME, managementController.getServerDB());
hostLevelParams.put(MYSQL_JDBC_URL, managementController.getMysqljdbcUrl());
hostLevelParams.put(ORACLE_JDBC_URL, managementController.getOjdbcUrl());
hostLevelParams.put(DB_DRIVER_FILENAME, configs.getMySQLJarName());
hostLevelParams.putAll(managementController.getRcaParameters());
hostLevelParams.put(HOST_SYS_PREPPED, configs.areHostsSysPrepped());
hostLevelParams.put(AGENT_STACK_RETRY_ON_UNAVAILABILITY, configs.isAgentStackRetryOnInstallEnabled());
hostLevelParams.put(AGENT_STACK_RETRY_COUNT, configs.getAgentStackRetryOnInstallCount());
Map<String, DesiredConfig> desiredConfigs = cluster.getDesiredConfigs();
Map<PropertyInfo, String> notManagedHdfsPathMap = configHelper.getPropertiesWithPropertyType(stackId, PropertyType.NOT_MANAGED_HDFS_PATH, cluster, desiredConfigs);
Set<String> notManagedHdfsPathSet = configHelper.filterInvalidPropertyValues(notManagedHdfsPathMap, NOT_MANAGED_HDFS_PATH_LIST);
String notManagedHdfsPathList = gson.toJson(notManagedHdfsPathSet);
hostLevelParams.put(NOT_MANAGED_HDFS_PATH_LIST, notManagedHdfsPathList);
for (Map.Entry<String, String> dbConnectorName : configs.getDatabaseConnectorNames().entrySet()) {
hostLevelParams.put(dbConnectorName.getKey(), dbConnectorName.getValue());
}
for (Map.Entry<String, String> previousDBConnectorName : configs.getPreviousDatabaseConnectorNames().entrySet()) {
hostLevelParams.put(previousDBConnectorName.getKey(), previousDBConnectorName.getValue());
}
return hostLevelParams;
}
/**
* Determine whether or not the action should trigger a topology refresh.
*
* @param actionName the action name (i.e. START, RESTART)
* @param clusterName the cluster name
* @param serviceName the service name
*
* @return true if a topology refresh is required for the action
*/
public boolean isTopologyRefreshRequired(String actionName, String clusterName, String serviceName)
throws AmbariException {
if (actionName.equals(START_COMMAND_NAME) || actionName.equals(RESTART_COMMAND_NAME)) {
Cluster cluster = clusters.getCluster(clusterName);
StackId stackId = null;
if (serviceName != null) {
try {
Service service = cluster.getService(serviceName);
stackId = service.getDesiredStackId();
} catch (AmbariException e) {
LOG.debug("Could not load service {}, skipping topology check", serviceName);
}
}
if (stackId == null) {
stackId = cluster.getDesiredStackVersion();
}
AmbariMetaInfo ambariMetaInfo = managementController.getAmbariMetaInfo();
StackInfo stack = ambariMetaInfo.getStack(stackId.getStackName(), stackId.getStackVersion());
if (stack != null) {
ServiceInfo serviceInfo = stack.getService(serviceName);
if (serviceInfo != null) {
// if there is a chance that this action was triggered by a change in rack info then we want to
// force a topology refresh
// TODO : we may be able to be smarter about this and only refresh when the rack info has definitely changed
Boolean restartRequiredAfterRackChange = serviceInfo.isRestartRequiredAfterRackChange();
if (restartRequiredAfterRackChange != null && restartRequiredAfterRackChange) {
return true;
}
}
}
}
return false;
}
private ServiceComponent getServiceComponent ( ActionExecutionContext actionExecutionContext,
RequestResourceFilter resourceFilter){
try {
Cluster cluster = clusters.getCluster(actionExecutionContext.getClusterName());
Service service = cluster.getService(resourceFilter.getServiceName());
return service.getServiceComponent(resourceFilter.getComponentName());
} catch (Exception e) {
LOG.debug("Unknown error appears during getting service component: {}", e.getMessage());
}
return null;
}
/**
* Filter host according to status of host/host components
* @param hostname Host name to check
* @param actionExecutionContext Received request to execute a command
* @param resourceFilter Resource filter
* @return True if host need to be filtered, False if Not
* @throws AmbariException
*/
private boolean filterUnhealthHostItem(String hostname,
ActionExecutionContext actionExecutionContext,
RequestResourceFilter resourceFilter) throws AmbariException {
RequestOperationLevel operationLevel = actionExecutionContext.getOperationLevel();
ServiceComponent serviceComponent = getServiceComponent(actionExecutionContext, resourceFilter);
if (serviceComponent != null && operationLevel != null
&& operationLevel.getLevel() == Resource.Type.Service // compare operation is allowed only for Service operation level
&& actionExecutionContext.getResourceFilters().size() > 1 // Check if operation was started in a chain
&& !serviceComponent.isMasterComponent()
){
return !(clusters.getHost(hostname).getState() == HostState.HEALTHY);
} else if (serviceComponent != null && operationLevel != null
&& operationLevel.getLevel() == Resource.Type.Host // compare operation is allowed only for host component operation level
&& actionExecutionContext.getResourceFilters().size() > 1 // Check if operation was started in a chain
&& serviceComponent.getServiceComponentHosts().containsKey(hostname) // Check if host is assigned to host component
&& !serviceComponent.isMasterComponent()
){
State hostState = serviceComponent.getServiceComponentHosts().get(hostname).getState();
return hostState == State.UNKNOWN;
}
return false;
}
/**
* Filter hosts according to status of host/host components
* @param hosts Host name set to filter
* @param actionExecutionContext Received request to execute a command
* @param resourceFilter Resource filter
* @return Set of excluded hosts
* @throws AmbariException
*/
private Set<String> getUnhealthyHosts(Set<String> hosts,
ActionExecutionContext actionExecutionContext,
RequestResourceFilter resourceFilter) throws AmbariException {
Set<String> removedHosts = new HashSet<>();
for (String hostname : hosts) {
if (filterUnhealthHostItem(hostname, actionExecutionContext, resourceFilter)){
removedHosts.add(hostname);
}
}
hosts.removeAll(removedHosts);
return removedHosts;
}
/**
* Class that is used to update base urls. There are two implementations of this - when we no
* longer are sure the deprecated repo info can be removed, so too can this class.
*/
@Experimental(feature=ExperimentalFeature.PATCH_UPGRADES)
abstract static class BaseUrlUpdater<T> implements Function<RepositoryVersionEntity, T> {
private T m_default;
private BaseUrlUpdater(T defaultValue) {
m_default = defaultValue;
}
private T getDefault() {
return m_default;
}
}
}