org.apache.ambari.server.state.UpgradeHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ambari.server.state.UpgradeHelper.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.state;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.controller.internal.StageResourceProvider;
import org.apache.ambari.server.controller.predicate.AndPredicate;
import org.apache.ambari.server.controller.spi.ClusterController;
import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
import org.apache.ambari.server.controller.spi.NoSuchResourceException;
import org.apache.ambari.server.controller.spi.Predicate;
import org.apache.ambari.server.controller.spi.QueryResponse;
import org.apache.ambari.server.controller.spi.Request;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.controller.spi.SystemException;
import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
import org.apache.ambari.server.controller.utilities.ClusterControllerHelper;
import org.apache.ambari.server.controller.utilities.PredicateBuilder;
import org.apache.ambari.server.controller.utilities.PropertyHelper;
import org.apache.ambari.server.stack.HostsType;
import org.apache.ambari.server.stack.MasterHostResolver;
import org.apache.ambari.server.state.stack.UpgradePack;
import org.apache.ambari.server.state.stack.UpgradePack.ProcessingComponent;
import org.apache.ambari.server.state.stack.upgrade.Direction;
import org.apache.ambari.server.state.stack.upgrade.Grouping;
import org.apache.ambari.server.state.stack.upgrade.ManualTask;
import org.apache.ambari.server.state.stack.upgrade.StageWrapper;
import org.apache.ambari.server.state.stack.upgrade.StageWrapperBuilder;
import org.apache.ambari.server.state.stack.upgrade.Task;
import org.apache.ambari.server.state.stack.upgrade.Task.Type;
import org.apache.ambari.server.state.stack.upgrade.TaskWrapper;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;

/**
 * Class to assist with upgrading a cluster.
 */
@Singleton
public class UpgradeHelper {

    private static final Logger LOG = LoggerFactory.getLogger(UpgradeHelper.class);

    /**
     * Matches on placeholder values such as
     *
     * <pre>
     * {{host}}
     * </pre>
     * and
     * <pre>
     * {{hdfs-site/foo}}
     * </pre>
     */
    private static final Pattern PLACEHOLDER_REGEX = Pattern.compile("(\\{\\{.*?\\}\\})");

    /**
     * Enum used to define placeholder text for replacement
     */
    private static enum Placeholder {
        /**
         * No placeholder defined
         */
        OTHER(""),
        /**
         * A placeholder token that represents all of the hosts that a component is
         * deployed on. This can be used for cases where text needs to be rendered
         * with all of the hosts mentioned by their FQDN.
         */
        HOST_ALL("hosts.all"),
        /**
         * A placeholder token that represents a single, active master that a
         * component is deployed on. This can be used for cases where text needs to eb
         * rendered with a single master host FQDN inserted.
         */
        HOST_MASTER("hosts.master"),
        /**
         * The version that the stack is being upgraded or downgraded to, such as
         * {@code 2.2.1.0-1234}.
         */
        VERSION("version"),
        /**
         * The lower case of the {@link Direction} value.
         */
        DIRECTION_TEXT("direction.text"),
        /**
         * The proper case of the {@link Direction} value.
         */
        DIRECTION_TEXT_PROPER("direction.text.proper"),
        /**
         * The past tense of the {@link Direction} value.
         */
        DIRECTION_PAST("direction.past"),
        /**
         * The proper past tense of the {@link Direction} value.
         */
        DIRECTION_PAST_PROPER("direction.past.proper"),
        /**
         * The plural tense of the {@link Direction} value.
         */
        DIRECTION_PLURAL("direction.plural"),
        /**
         * The proper plural tense of the {@link Direction} value.
         */
        DIRECTION_PLURAL_PROPER("direction.plural.proper"),
        /**
         * The verbal noun of the {@link Direction} value.
         */
        DIRECTION_VERB("direction.verb"),
        /**
         * The proper verbal noun of the {@link Direction} value.
         */
        DIRECTION_VERB_PROPER("direction.verb.proper"),
        /**
         * Unhealthy hosts if they are specified.
         */
        UNHEALTHY_HOSTS("hosts.unhealthy");

        private String pattern;

        private Placeholder(String key) {
            pattern = "{{" + key + "}}";
        }

        static Placeholder find(String pattern) {
            for (Placeholder p : values()) {
                if (p.pattern.equals(pattern)) {
                    return p;
                }
            }

            return OTHER;
        }
    }

    /**
     * Used to render parameter placeholders in {@link ManualTask}s after the
     * {@link StageWrapperBuilder} has finished building out all of the stages.
     */
    @Inject
    private Provider<ConfigHelper> m_configHelper;

    @Inject
    private Provider<AmbariMetaInfo> m_ambariMetaInfo;

    /**
     * Generates a list of UpgradeGroupHolder items that are used to execute either
     * an upgrade or a downgrade.
     *
     * @param upgradePack
     *          the upgrade pack
     * @param context
     *          the context that wraps key fields required to perform an upgrade
     * @return the list of holders
     */
    public List<UpgradeGroupHolder> createSequence(UpgradePack upgradePack, UpgradeContext context)
            throws AmbariException {

        context.setAmbariMetaInfo(m_ambariMetaInfo.get());
        Cluster cluster = context.getCluster();
        MasterHostResolver mhr = context.getResolver();

        Map<String, Map<String, ProcessingComponent>> allTasks = upgradePack.getTasks();
        List<UpgradeGroupHolder> groups = new ArrayList<UpgradeGroupHolder>();

        for (Grouping group : upgradePack.getGroups(context.getDirection())) {

            UpgradeGroupHolder groupHolder = new UpgradeGroupHolder();
            groupHolder.name = group.name;
            groupHolder.title = group.title;
            groupHolder.skippable = group.skippable;
            groupHolder.allowRetry = group.allowRetry;

            // !!! all downgrades are skippable
            if (context.getDirection().isDowngrade()) {
                groupHolder.skippable = true;
            }

            StageWrapperBuilder builder = group.getBuilder();

            List<UpgradePack.OrderService> services = group.services;

            if (context.getDirection().isDowngrade() && !services.isEmpty()) {
                List<UpgradePack.OrderService> reverse = new ArrayList<UpgradePack.OrderService>(services);
                Collections.reverse(reverse);
                services = reverse;
            }

            // !!! cluster and service checks are empty here
            for (UpgradePack.OrderService service : services) {

                if (!allTasks.containsKey(service.serviceName)) {
                    continue;
                }

                for (String component : service.components) {
                    if (!allTasks.get(service.serviceName).containsKey(component)) {
                        continue;
                    }

                    HostsType hostsType = mhr.getMasterAndHosts(service.serviceName, component);
                    if (null == hostsType) {
                        continue;
                    }

                    if (!hostsType.unhealthy.isEmpty()) {
                        context.addUnhealthy(hostsType.unhealthy);
                    }

                    Service svc = cluster.getService(service.serviceName);
                    ProcessingComponent pc = allTasks.get(service.serviceName).get(component);

                    setDisplayNames(context, service.serviceName, component);

                    // Special case for NAMENODE
                    if (service.serviceName.equalsIgnoreCase("HDFS") && component.equalsIgnoreCase("NAMENODE")) {
                        // !!! revisit if needed
                        if (!hostsType.hosts.isEmpty() && hostsType.master != null && hostsType.secondary != null) {
                            // The order is important, first do the standby, then the active namenode.
                            LinkedHashSet<String> order = new LinkedHashSet<String>();

                            order.add(hostsType.secondary);
                            order.add(hostsType.master);

                            // Override the hosts with the ordered collection
                            hostsType.hosts = order;
                        }

                        builder.add(context, hostsType, service.serviceName, svc.isClientOnlyService(), pc);

                    } else {
                        builder.add(context, hostsType, service.serviceName, svc.isClientOnlyService(), pc);
                    }
                }
            }

            List<StageWrapper> proxies = builder.build(context);

            if (!proxies.isEmpty()) {
                groupHolder.items = proxies;
                postProcess(context, groupHolder);
                groups.add(groupHolder);
            }
        }

        if (LOG.isDebugEnabled()) {
            for (UpgradeGroupHolder group : groups) {
                LOG.debug(group.name);

                int i = 0;
                for (StageWrapper proxy : group.items) {
                    LOG.debug("  Stage {}", Integer.valueOf(i++));
                    int j = 0;

                    for (TaskWrapper task : proxy.getTasks()) {
                        LOG.debug("    Task {} {}", Integer.valueOf(j++), task);
                    }
                }
            }
        }

        return groups;
    }

    /**
     * Walks through the UpgradeGroupHolder and updates titles and manual tasks,
     * replacing keyword tokens needed for display purposes
     *
     * @param ctx     the upgrade context
     * @param holder  the upgrade holder
     */
    private void postProcess(UpgradeContext ctx, UpgradeGroupHolder holder) {

        holder.title = tokenReplace(ctx, holder.title, null, null);

        for (StageWrapper stageWrapper : holder.items) {
            for (TaskWrapper taskWrapper : stageWrapper.getTasks()) {
                for (Task task : taskWrapper.getTasks()) {
                    if (task.getType() == Type.MANUAL) {
                        ManualTask mt = (ManualTask) task;
                        if (null != mt.message) {
                            mt.message = tokenReplace(ctx, mt.message, taskWrapper.getService(),
                                    taskWrapper.getComponent());
                        }
                    }
                }
            }
        }
    }

    /**
     * @param ctx       the upgrade context
     * @param source    the source string to replace tokens on
     * @param service   the service name if required
     * @param component the component name if required
     * @return the source string with tokens replaced, if any are found
     */
    private String tokenReplace(UpgradeContext ctx, String source, String service, String component) {
        Cluster cluster = ctx.getCluster();
        MasterHostResolver mhr = ctx.getResolver();
        String version = ctx.getVersion();

        String result = source;

        List<String> tokens = new ArrayList<String>(5);
        Matcher matcher = PLACEHOLDER_REGEX.matcher(source);
        while (matcher.find()) {
            tokens.add(matcher.group(1));
        }

        // iterate through all of the matched tokens
        for (String token : tokens) {
            String value = null;

            Placeholder p = Placeholder.find(token);
            switch (p) {
            case HOST_ALL: {
                if (null != service && null != component) {
                    HostsType hostsType = mhr.getMasterAndHosts(service, component);

                    if (null != hostsType) {
                        value = StringUtils.join(hostsType.hosts, ", ");
                    }
                }
                break;
            }
            case HOST_MASTER: {
                if (null != service && null != component) {
                    HostsType hostsType = mhr.getMasterAndHosts(service, component);

                    if (null != hostsType) {
                        value = hostsType.master;
                    }
                }
                break;
            }
            case VERSION:
                value = version;
                break;
            case DIRECTION_VERB:
            case DIRECTION_VERB_PROPER:
                value = ctx.getDirection().getVerb(p == Placeholder.DIRECTION_VERB_PROPER);
                break;
            case DIRECTION_PAST:
            case DIRECTION_PAST_PROPER:
                value = ctx.getDirection().getPast(p == Placeholder.DIRECTION_PAST_PROPER);
                break;
            case DIRECTION_PLURAL:
            case DIRECTION_PLURAL_PROPER:
                value = ctx.getDirection().getPlural(p == Placeholder.DIRECTION_PLURAL_PROPER);
                break;
            case DIRECTION_TEXT:
            case DIRECTION_TEXT_PROPER:
                value = ctx.getDirection().getText(p == Placeholder.DIRECTION_TEXT_PROPER);
                break;
            case UNHEALTHY_HOSTS:
                value = StringUtils.join(ctx.getUnhealthy().keySet(), ", ");
                break;
            default:
                value = m_configHelper.get().getPlaceholderValueFromDesiredConfigurations(cluster, token);
                break;
            }

            // replace the token in the message with the value
            if (null != value) {
                result = result.replace(token, value);
            }
        }

        return result;
    }

    /**
     * Short-lived objects that hold information about upgrade groups
     */
    public static class UpgradeGroupHolder {
        /**
         * The name
         */
        public String name;
        /**
         * The title
         */
        public String title;

        /**
         * Indicate whether retry is allowed for the stages in this group.
         */
        public boolean allowRetry = true;

        /**
         * Indicates whether the stages in this group are skippable on failure.  If a
         * stage is skippable, a failed result can be skipped without failing the entire upgrade.
         */
        public boolean skippable = false;

        /**
         * List of stages for the group
         */
        public List<StageWrapper> items = new ArrayList<StageWrapper>();
    }

    /**
     * Gets a set of Stages resources to aggregate an UpgradeItem with Stage.
     *
     * @param clusterName the cluster name
     * @param requestId the request id containing the stages
     * @param stageIds the list of stages to fetch
     * @return the list of Stage resources
     * @throws UnsupportedPropertyException
     * @throws NoSuchResourceException
     * @throws NoSuchParentResourceException
     * @throws SystemException
     */
    // !!! FIXME this feels very wrong
    public Set<Resource> getStageResources(String clusterName, Long requestId, List<Long> stageIds)
            throws UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException,
            SystemException {
        ClusterController clusterController = ClusterControllerHelper.getClusterController();

        Request request = PropertyHelper.getReadRequest();

        Predicate p1 = new PredicateBuilder().property(StageResourceProvider.STAGE_CLUSTER_NAME).equals(clusterName)
                .toPredicate();
        Predicate p2 = new PredicateBuilder().property(StageResourceProvider.STAGE_REQUEST_ID).equals(requestId)
                .toPredicate();
        Predicate p3 = null;

        if (1 == stageIds.size()) {
            p3 = new PredicateBuilder().property(StageResourceProvider.STAGE_STAGE_ID).equals(stageIds.get(0))
                    .toPredicate();
        } else if (stageIds.size() > 0) {
            PredicateBuilder pb = new PredicateBuilder();

            int i = 0;
            for (Long stageId : stageIds) {
                if (i++ < stageIds.size() - 1) {
                    pb = pb.property(StageResourceProvider.STAGE_STAGE_ID).equals(stageId).or();
                } else {
                    pb.property(StageResourceProvider.STAGE_STAGE_ID).equals(stageId);
                }
            }

            p3 = pb.toPredicate();
        }

        QueryResponse response = clusterController.getResources(Resource.Type.Stage, request,
                new AndPredicate(p1, p2, p3));

        return response.getResources();
    }

    /**
     * Helper to set service and component display names on the context
     * @param context   the context to update
     * @param service   the service name
     * @param component the component name
     */
    private void setDisplayNames(UpgradeContext context, String service, String component) {
        StackId stackId = context.getCluster().getDesiredStackVersion();
        try {
            ServiceInfo serviceInfo = m_ambariMetaInfo.get().getService(stackId.getStackName(),
                    stackId.getStackVersion(), service);
            context.setServiceDisplay(service, serviceInfo.getDisplayName());

            ComponentInfo compInfo = serviceInfo.getComponentByName(component);
            context.setComponentDisplay(service, component, compInfo.getDisplayName());

        } catch (AmbariException e) {
            LOG.debug("Could not get service detail", e);
        }

    }

}