org.apache.ambari.server.controller.AmbariCustomCommandExecutionHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ambari.server.controller.AmbariCustomCommandExecutionHelper.java

Source

/**
 * 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.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.HOOKS_FOLDER;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.JAVA_HOME;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.JCE_NAME;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.JDK_LOCATION;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.JDK_NAME;
import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.MYSQL_JDBC_URL;
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.SERVICE_PACKAGE_FOLDER;
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_LIST;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

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.Stage;
import org.apache.ambari.server.agent.AgentCommand.AgentCommandType;
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.entities.ClusterVersionEntity;
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.ConfigHelper;
import org.apache.ambari.server.state.CustomCommandDefinition;
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.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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
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<String, String>();

    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_EXCLUDE_FILE_ONLY = "update_exclude_file_only";

    private final static String ALIGN_MAINTENANCE_STATE = "align_maintenance_state";

    @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;

    protected static final String SERVICE_CHECK_COMMAND_NAME = "SERVICE_CHECK";
    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 {

        Cluster cluster = clusters.getCluster(clusterName);
        StackId stackId = cluster.getDesiredStackVersion();

        if (componentName == null) {
            return false;
        }
        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();
    }

    private void addCustomCommandAction(final ActionExecutionContext actionExecutionContext,
            final RequestResourceFilter resourceFilter, Stage stage, Map<String, String> additionalCommandParams,
            String commandDetail, boolean retryAllowed) throws AmbariException {
        final String serviceName = resourceFilter.getServiceName();
        final String componentName = resourceFilter.getComponentName();
        final String commandName = actionExecutionContext.getActionName();

        String clusterName = stage.getClusterName();
        final Cluster cluster = clusters.getCluster(clusterName);

        Set<String> candidateHosts = new HashSet<String>(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> filteredHosts = filterUnhealthHosts(candidateHosts, actionExecutionContext, resourceFilter);

        if (!ignoredHosts.isEmpty()) {
            String message = String.format(
                    "Some hosts (%s) have been ignored " + "because components on them are in Maintenance state.",
                    ignoredHosts);
            LOG.debug(message);
        } else if (!filteredHosts.isEmpty()) {
            String message = String.format(
                    "Some hosts (%s) have been ignored " + "because they are in unknown state", filteredHosts);
            LOG.warn(message);
        } else if (candidateHosts.isEmpty()) {
            String message = "Invalid request : No hosts specified.";
            throw new AmbariException(message);
        }

        StackId stackId = cluster.getDesiredStackVersion();
        AmbariMetaInfo ambariMetaInfo = managementController.getAmbariMetaInfo();
        ServiceInfo serviceInfo = ambariMetaInfo.getService(stackId.getStackName(), stackId.getStackVersion(),
                serviceName);
        StackInfo stackInfo = ambariMetaInfo.getStack(stackId.getStackName(), stackId.getStackVersion());

        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);

            Map<String, Map<String, String>> configurations = new TreeMap<String, Map<String, String>>();
            Map<String, Map<String, Map<String, String>>> configurationAttributes = new TreeMap<String, Map<String, Map<String, String>>>();
            Map<String, Map<String, String>> configTags = managementController
                    .findConfigurationTagsWithOverrides(cluster, hostName);

            HostRoleCommand cmd = stage.getHostRoleCommand(hostName, componentName);
            if (cmd != null) {
                cmd.setCommandDetail(commandDetail);
                cmd.setCustomCommandName(commandName);
            }

            ExecutionCommand execCmd = stage.getExecutionCommandWrapper(hostName, componentName)
                    .getExecutionCommand();

            //set type background
            if (customCommandDefinition != null && customCommandDefinition.isBackground()) {
                execCmd.setCommandType(AgentCommandType.BACKGROUND_EXECUTION_COMMAND);
            }

            execCmd.setConfigurations(configurations);
            execCmd.setConfigurationAttributes(configurationAttributes);
            execCmd.setConfigurationTags(configTags);

            if (actionExecutionContext.getParameters() != null && actionExecutionContext.getParameters()
                    .containsKey(KeyNames.REFRESH_ADITIONAL_COMPONENT_TAGS)) {
                execCmd.setForceRefreshConfigTags(parseAndValidateComponentsMapping(
                        actionExecutionContext.getParameters().get(KeyNames.REFRESH_ADITIONAL_COMPONENT_TAGS)));
            }

            Map<String, String> hostLevelParams = new TreeMap<String, String>();

            hostLevelParams.put(CUSTOM_COMMAND, commandName);
            // Set parameters required for re-installing clients on restart
            hostLevelParams.put(REPO_INFO, getRepoInfo(cluster, host));

            Set<String> userSet = configHelper.getPropertyValuesWithPropertyType(stackId, PropertyType.USER,
                    cluster);
            String userList = gson.toJson(userSet);
            hostLevelParams.put(USER_LIST, userList);

            Set<String> groupSet = configHelper.getPropertyValuesWithPropertyType(stackId, PropertyType.GROUP,
                    cluster);
            String groupList = gson.toJson(groupSet);
            hostLevelParams.put(GROUP_LIST, groupList);

            execCmd.setHostLevelParams(hostLevelParams);

            Map<String, String> commandParams = new TreeMap<String, String>();
            if (additionalCommandParams != null) {
                for (String key : additionalCommandParams.keySet()) {
                    commandParams.put(key, additionalCommandParams.get(key));
                }
            }

            boolean isInstallCommand = commandName.equals(RoleCommand.INSTALL.toString());
            String commandTimeout = configs.getDefaultAgentTaskTimeout(isInstallCommand);

            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 = String.valueOf(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
            }

            commandParams.put(COMMAND_TIMEOUT, commandTimeout);

            commandParams.put(SERVICE_PACKAGE_FOLDER, serviceInfo.getServicePackageFolder());

            commandParams.put(HOOKS_FOLDER, stackInfo.getStackHooksFolder());

            ClusterVersionEntity currentClusterVersion = cluster.getCurrentClusterVersion();
            if (currentClusterVersion != null) {
                commandParams.put(KeyNames.VERSION, currentClusterVersion.getRepositoryVersion().getVersion());
            }

            execCmd.setCommandParams(commandParams);

            Map<String, String> roleParams = execCmd.getRoleParams();
            if (roleParams == null) {
                roleParams = new TreeMap<String, String>();
            }

            roleParams.put(COMPONENT_CATEGORY, componentInfo.getCategory());
            execCmd.setRoleParams(roleParams);
        }
    }

    /**
     * Splits the passed comma separated value and returns it as set.
     *
     * @param commaSeparatedTags  separated list
     *
     * @return set of items or null
     */
    private Set<String> parseAndValidateComponentsMapping(String commaSeparatedTags) {
        Set<String> retVal = null;
        if (commaSeparatedTags != null && !commaSeparatedTags.trim().isEmpty()) {
            Collections.addAll(retVal = new HashSet<String>(), commaSeparatedTags.split(","));
        }
        return retVal;
    }

    private void findHostAndAddServiceCheckAction(final ActionExecutionContext actionExecutionContext,
            final RequestResourceFilter resourceFilter, Stage stage, boolean retryAllowed) 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();
        }
        long nowTimestamp = System.currentTimeMillis();
        Map<String, String> actionParameters = actionExecutionContext.getParameters();
        final Set<String> candidateHosts;
        if (componentName != null) {
            Map<String, ServiceComponentHost> components = cluster.getService(serviceName)
                    .getServiceComponent(componentName).getServiceComponentHosts();
            if (components.isEmpty()) {
                throw new AmbariException("Hosts not found, component=" + componentName + ", service = "
                        + serviceName + ", cluster = " + clusterName);
            }
            List<String> candidateHostsList = resourceFilter.getHostNames();
            if (candidateHostsList != null && !candidateHostsList.isEmpty()) {
                candidateHosts = new HashSet<String>(candidateHostsList);
            } else {
                candidateHosts = components.keySet();
            }
        } else { // TODO: this code branch looks unreliable(taking random component)
            Map<String, ServiceComponent> components = cluster.getService(serviceName).getServiceComponents();
            if (components.isEmpty()) {
                throw new AmbariException(
                        "Components not found, service = " + serviceName + ", cluster = " + clusterName);
            }
            ServiceComponent serviceComponent = components.values().iterator().next();
            if (serviceComponent.getServiceComponentHosts().isEmpty()) {
                throw new AmbariException("Hosts not found, component=" + serviceComponent.getName()
                        + ", service = " + serviceName + ", cluster = " + clusterName);
            }
            candidateHosts = serviceComponent.getServiceComponentHosts().keySet();
        }

        // Filter hosts that are in MS
        Set<String> ignoredHosts = new HashSet<String>();

        if (!actionExecutionContext.isIgnoreMaintenance()) {
            ignoredHosts.addAll(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);
                        }
                    }));
        }

        String hostName = managementController.getHealthyHost(candidateHosts);
        if (hostName == null) {
            String msg = String.format("Unable to find a healthy host " + "amongst the provided set of "
                    + "hosts: %s. You may also see this message if "
                    + "all healthy hosts are not appropriate for service check "
                    + "due to maintenance state (these hosts are %s). ", candidateHosts, ignoredHosts);
            LOG.info(msg);
        }

        addServiceCheckAction(stage, hostName, smokeTestRole, nowTimestamp, serviceName, componentName,
                actionParameters, retryAllowed);
    }

    /**
     * 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)
            throws AmbariException {

        String clusterName = stage.getClusterName();
        Cluster cluster = clusters.getCluster(clusterName);
        StackId stackId = cluster.getDesiredStackVersion();
        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);

        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<String, Map<String, String>>();
        Map<String, Map<String, Map<String, String>>> configurationAttributes = new TreeMap<String, Map<String, Map<String, String>>>();
        Map<String, Map<String, String>> configTags = managementController
                .findConfigurationTagsWithOverrides(cluster, hostname);

        ExecutionCommand execCmd = stage.getExecutionCommandWrapper(hostname, smokeTestRole).getExecutionCommand();

        execCmd.setConfigurations(configurations);
        execCmd.setConfigurationAttributes(configurationAttributes);
        execCmd.setConfigurationTags(configTags);

        // Generate cluster host info
        execCmd.setClusterHostInfo(
                StageUtils.getClusterHostInfo(clusters.getHostsForCluster(clusterName), cluster));

        Map<String, String> commandParams = new TreeMap<String, String>();

        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
        }

        commandParams.put(COMMAND_TIMEOUT, commandTimeout);

        commandParams.put(SERVICE_PACKAGE_FOLDER, serviceInfo.getServicePackageFolder());
        commandParams.put(HOOKS_FOLDER, stackInfo.getStackHooksFolder());

        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<String>();
        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, boolean retryAllowed) 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);

        Set<String> cloneSet = new HashSet<String>(excludedHosts);
        cloneSet.retainAll(includedHosts);
        if (cloneSet.size() > 0) {
            throw new AmbariException("Same host cannot be specified for inclusion "
                    + "as well as exclusion. Hosts: " + cloneSet.toString());
        }

        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_EXCLUDE_FILE_ONLY parameter as string
                String upd_excl_file_only_str = actionExecutionContext.getParameters()
                        .get(UPDATE_EXCLUDE_FILE_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<String>(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<String>(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<String>();
        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(
                        "Decommissioning " + slaveCompType + " and marking Maintenance=ON on " + sch.getHostName());
            }
            if (filteredIncludedHosts.contains(sch.getHostName())) {
                sch.setComponentAdminState(HostComponentAdminState.INSERVICE);
                if (alignMtnState) {
                    sch.setMaintenanceState(MaintenanceState.OFF);
                }
                LOG.info("Recommissioning " + slaveCompType + " and marking Maintenance=OFF 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<RequestResourceFilter>();
            resourceFilters.add(commandFilter);

            ActionExecutionContext commandContext = new ActionExecutionContext(clusterName,
                    actionExecutionContext.getActionName(), resourceFilters);

            String clusterHostInfoJson = StageUtils.getGson().toJson(
                    StageUtils.getClusterHostInfo(clusters.getHostsForCluster(cluster.getClusterName()), cluster));

            // Reset cluster host info as it has changed
            stage.setClusterHostInfo(clusterHostInfoJson);

            Map<String, String> commandParams = new HashMap<String, String>();
            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_EXCLUDE_FILE_ONLY, "false");
                addCustomCommandAction(commandContext, commandFilter, stage, commandParams,
                        commandDetail.toString(), retryAllowed);
            }
        }
    }

    private StringBuilder getReadableDecommissionCommandDetail(ActionExecutionContext actionExecutionContext,
            Set<String> includedHosts, List<String> listOfExcludedHosts) {
        StringBuilder commandDetail = new StringBuilder();
        commandDetail.append(actionExecutionContext.getActionName());
        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()) {
            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 retryAllowed            indicates whether the the command allows retry
     *
     * @throws AmbariException if the commands can not be added
     */
    public void addExecutionCommandsToStage(ActionExecutionContext actionExecutionContext, Stage stage,
            Map<String, String> requestParams, boolean retryAllowed) throws AmbariException {

        List<RequestResourceFilter> resourceFilters = actionExecutionContext.getResourceFilters();

        for (RequestResourceFilter resourceFilter : resourceFilters) {
            LOG.debug("Received a command execution request" + ", clusterName="
                    + actionExecutionContext.getClusterName() + ", serviceName=" + resourceFilter.getServiceName()
                    + ", request=" + actionExecutionContext.toString());

            if (actionExecutionContext.getActionName().contains(SERVICE_CHECK_COMMAND_NAME)) {
                findHostAndAddServiceCheckAction(actionExecutionContext, resourceFilter, stage, retryAllowed);
            } else if (actionExecutionContext.getActionName().equals(DECOMMISSION_COMMAND_NAME)) {
                addDecommissionAction(actionExecutionContext, resourceFilter, stage, retryAllowed);
            } else if (isValidCustomCommand(actionExecutionContext, resourceFilter)) {
                String commandDetail = getReadableCustomCommandDetail(actionExecutionContext, resourceFilter);

                Map<String, String> extraParams = null;
                String componentName = (null == resourceFilter.getComponentName()) ? null
                        : resourceFilter.getComponentName().toLowerCase();

                if (null != componentName && requestParams.containsKey(componentName)) {
                    extraParams = new HashMap<String, String>();
                    extraParams.put(componentName, requestParams.get(componentName));
                }

                if (requestParams.containsKey(KeyNames.REFRESH_ADITIONAL_COMPONENT_TAGS)) {
                    actionExecutionContext.getParameters().put(KeyNames.REFRESH_ADITIONAL_COMPONENT_TAGS,
                            requestParams.get(KeyNames.REFRESH_ADITIONAL_COMPONENT_TAGS));
                }
                addCustomCommandAction(actionExecutionContext, resourceFilter, stage, extraParams, commandDetail,
                        retryAllowed);
            } else {
                throw new AmbariException("Unsupported action " + actionExecutionContext.getActionName());
            }
        }
    }

    /**
     * Get repository info given a cluster and host.
     *
     * @param cluster  the cluster
     * @param host     the host
     *
     * @return the repo info
     *
     * @throws AmbariException if the repository information can not be obtained
     */
    public String getRepoInfo(Cluster cluster, Host host) throws AmbariException {
        StackId stackId = cluster.getDesiredStackVersion();

        Map<String, List<RepositoryInfo>> repos = ambariMetaInfo.getRepository(stackId.getStackName(),
                stackId.getStackVersion());
        String repoInfo = "";

        String family = os_family.find(host.getOsType());
        if (null == family) {
            family = host.getOsFamily();
        }

        // !!! check for the most specific first
        if (repos.containsKey(host.getOsType())) {
            repoInfo = gson.toJson(repos.get(host.getOsType()));
        } else if (null != family && repos.containsKey(family)) {
            repoInfo = gson.toJson(repos.get(family));
        } else {
            LOG.warn("Could not retrieve repo information for host" + ", hostname=" + host.getHostName()
                    + ", clusterName=" + cluster.getClusterName() + ", stackInfo=" + stackId.getStackId());
        }

        return repoInfo;
    }

    /**
     * Helper method to fill execution command information.
     *
     * @param actionExecContext  the context
     * @param cluster            the cluster for the command
     *
     * @return a wrapper of the imporant JSON structures to add to a stage
     */
    public ExecuteCommandJson getCommandJson(ActionExecutionContext actionExecContext, Cluster cluster)
            throws AmbariException {

        Map<String, String> commandParamsStage = StageUtils.getCommandParamsStage(actionExecContext);
        Map<String, String> hostParamsStage = new HashMap<String, String>();
        Map<String, Set<String>> clusterHostInfo;
        String clusterHostInfoJson = "{}";

        if (null != cluster) {
            clusterHostInfo = StageUtils.getClusterHostInfo(clusters.getHostsForCluster(cluster.getClusterName()),
                    cluster);
            hostParamsStage = createDefaultHostParams(cluster);
            StackId stackId = cluster.getDesiredStackVersion();
            String componentName = null;
            String serviceName = null;
            if (actionExecContext.getOperationLevel() != null) {
                componentName = actionExecContext.getOperationLevel().getHostComponentName();
                serviceName = actionExecContext.getOperationLevel().getServiceName();
            }
            if (serviceName != null && componentName != null) {
                ComponentInfo componentInfo = ambariMetaInfo.getComponent(stackId.getStackName(),
                        stackId.getStackVersion(), serviceName, componentName);
                List<String> clientsToUpdateConfigsList = componentInfo.getClientsToUpdateConfigs();
                if (clientsToUpdateConfigsList == null) {
                    clientsToUpdateConfigsList = new ArrayList<String>();
                    clientsToUpdateConfigsList.add("*");
                }
                String clientsToUpdateConfigs = gson.toJson(clientsToUpdateConfigsList);
                hostParamsStage.put(CLIENTS_TO_UPDATE_CONFIGS, clientsToUpdateConfigs);
            }
            clusterHostInfoJson = StageUtils.getGson().toJson(clusterHostInfo);
        }

        String hostParamsStageJson = StageUtils.getGson().toJson(hostParamsStage);
        String commandParamsStageJson = StageUtils.getGson().toJson(commandParamsStage);

        return new ExecuteCommandJson(clusterHostInfoJson, commandParamsStageJson, hostParamsStageJson);
    }

    Map<String, String> createDefaultHostParams(Cluster cluster) {
        StackId stackId = cluster.getDesiredStackVersion();
        TreeMap<String, String> hostLevelParams = new TreeMap<String, String>();
        hostLevelParams.put(JDK_LOCATION, managementController.getJdkResourceUrl());
        hostLevelParams.put(JAVA_HOME, managementController.getJavaHome());
        hostLevelParams.put(JDK_NAME, managementController.getJDKName());
        hostLevelParams.put(JCE_NAME, managementController.getJCEName());
        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());

        return hostLevelParams;
    }

    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(String.format("Unknown error appears during getting service component: %s", 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> filterUnhealthHosts(Set<String> hosts, ActionExecutionContext actionExecutionContext,
            RequestResourceFilter resourceFilter) throws AmbariException {
        Set<String> removedHosts = new HashSet<String>();
        for (String hostname : hosts) {
            if (filterUnhealthHostItem(hostname, actionExecutionContext, resourceFilter)) {
                removedHosts.add(hostname);
            }
        }
        hosts.removeAll(removedHosts);
        return removedHosts;
    }
}