com.sonyericsson.hudson.plugins.gerrit.trigger.dependency.DependencyQueueTaskDispatcher.java Source code

Java tutorial

Introduction

Here is the source code for com.sonyericsson.hudson.plugins.gerrit.trigger.dependency.DependencyQueueTaskDispatcher.java

Source

/*
 * The MIT License
 *
 * Copyright 2014 Smartmatic International Corporation. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.sonyericsson.hudson.plugins.gerrit.trigger.dependency;

import hudson.Extension;
import hudson.ExtensionList;
import hudson.model.Cause;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.Queue;
import hudson.model.Run;
import hudson.model.queue.QueueTaskDispatcher;
import hudson.model.queue.CauseOfBlockage;
import jenkins.model.Jenkins;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.Collections;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sonymobile.tools.gerrit.gerritevents.GerritDefaultValues;
import com.sonymobile.tools.gerrit.gerritevents.GerritEventListener;
import com.sonymobile.tools.gerrit.gerritevents.dto.events.GerritTriggeredEvent;
import com.sonymobile.tools.gerrit.gerritevents.dto.GerritEvent;
import com.sonymobile.tools.gerrit.gerritevents.GerritHandler;
import com.sonyericsson.hudson.plugins.gerrit.trigger.events.lifecycle.GerritEventLifecycleListener;
import com.sonyericsson.hudson.plugins.gerrit.trigger.events.lifecycle.GerritEventLifecycle;
import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritCause;
import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritTrigger;
import com.sonyericsson.hudson.plugins.gerrit.trigger.gerritnotifier.ToGerritRunListener;
import com.sonyericsson.hudson.plugins.gerrit.trigger.PluginImpl;

/**
 * Blocks builds from running until the projects on which they depend have finished building.
 * This applies on a per-event basis, so for each event, the plugin will wait for
 * dependency projects (i.e., projects on which it depends) which also trigger for the same
 * event, to finish building before building a dependent project.
 *
 * @author Yannick Brhon <yannick.brehon@smartmatic.com>
 */
@Extension
public final class DependencyQueueTaskDispatcher extends QueueTaskDispatcher
        implements GerritEventLifecycleListener, GerritEventListener {

    private static final Logger logger = LoggerFactory.getLogger(DependencyQueueTaskDispatcher.class);
    private Set<GerritTriggeredEvent> currentlyTriggeringEvents;

    /**
     * Default constructor.
     */
    public DependencyQueueTaskDispatcher() {
        this(PluginImpl.getHandler_());
    }

    /**
     * Constructor use by default constructor and for unit tests.
     *
     * @param gerritHandler the handler
     */
    DependencyQueueTaskDispatcher(GerritHandler gerritHandler) {
        this.currentlyTriggeringEvents = Collections
                .newSetFromMap(new ConcurrentHashMap<GerritTriggeredEvent, Boolean>());
        if (gerritHandler == null) {
            logger.error("Gerrit Handler was not available to construct DependencyQueueTaskDispatcher");
        } else {
            gerritHandler.addListener(this);
        }
        logger.debug("Registered to gerrit events");
    }

    /**
     * Returns the registered instance of this class from the list of all listeners.
     *
     * @return the instance.
     */
    public static DependencyQueueTaskDispatcher getInstance() {
        Jenkins jenkins = Jenkins.getInstance();
        if (jenkins == null) {
            logger.error("INITIALIZATION ERROR? Could not find the Jenkins instance.");
            return null;
        }
        ExtensionList<DependencyQueueTaskDispatcher> dispatchers = jenkins
                .getExtensionList(DependencyQueueTaskDispatcher.class);
        if (dispatchers == null || dispatchers.isEmpty()) {
            logger.error("INITIALIZATION ERROR? Could not find the registered instance.");
            return null;
        }
        return dispatchers.get(0);
    }

    @Override
    public CauseOfBlockage canRun(Queue.Item item) {
        //Job check
        if (!(item.task instanceof Job)) {
            logger.debug("Not an Job instance: {}", item.task);
            return null;
        }

        GerritCause cause = getGerritCause(item);
        //Not gerrit-triggered
        if (cause == null) {
            logger.debug("Not Gerrit cause for {}", item);
            return null;
        }
        GerritTriggeredEvent event = cause.getEvent();
        //The GerritCause should contain an event, but just in case.
        if (event == null) {
            logger.debug("{} does not contain an event", item);
            return null;
        }
        //we do not block an item when it reached the buildable state: a buildable item is
        //an item for which it has already been determined it canRun, and it is only
        //waiting for an executor. Once the executor is avail, an extra check is done, but
        //we already determined in the previous canRun checks that its dependencies were done.
        if (item.isBuildable()) {
            logger.debug("{} is already buildable for {}", item, event);
            return null;
        }
        Job p = (Job) item.task;
        GerritTrigger trigger = GerritTrigger.getTrigger(p);
        //The project being checked has no Gerrit Trigger
        if (trigger == null) {
            logger.debug("Project {} does not contain a trigger", p);
            return null;
        }
        //Dependency projects in the build queue
        List<Job> dependencies = getProjectsFromString(trigger.getDependencyJobsNames(), p);
        if (dependencies == null || dependencies.isEmpty()) {
            logger.debug("No dependencies on project: {} for event {}", p, event);
            return null;
        }
        //logger.debug("We have dependencies on project {} : {}", p, trigger.getDependencyJobsNames());

        // We ensure that we wait until other jobs have been put into queue.
        // We use the default Gerrit Build Schedule Delay value
        long inQueueSince = item.getInQueueSince();
        if (System.currentTimeMillis() - inQueueSince < TimeUnit.SECONDS
                .toMillis(GerritDefaultValues.DEFAULT_BUILD_SCHEDULE_DELAY)) {
            logger.debug("We need to wait to ensure dependent jobs {} are in queue for {}", event, p);
            return new BecauseWaitingToEnsureOtherJobsAreInQueue();
        }

        /* we really should first check for other projects which will be triggered
         * for the same event, and haven't yet. Unfortunately, this requires some kind
         * of event notification - GerritEventLifecycle like - which is not available
         * at this time. Fortunately, triggering is near instant, and projects are kept
         * long enough in the queue  for us to not worry too much about this at this time.
         * We can do this check for the retrigger.all action however, which does not have a quiet
         * time, because specific code exists for it in GerritTrigger, fortunately.
         */
        if (currentlyTriggeringEvents.contains(event)) {
            logger.debug("We need to wait while {} is being triggered for {}", event, p);
            return new BecauseWaitingForOtherProjectsToTrigger();
        }

        CauseOfBlockage causeOfBlockage = getCauseOfBlockage(dependencies, event);

        if (causeOfBlockage != null) {
            return causeOfBlockage;
        } else {
            ToGerritRunListener toGerritRunListener = ToGerritRunListener.getInstance();

            if (toGerritRunListener != null) {
                List<Run> parentRuns = toGerritRunListener.getRuns(event);

                if (parentRuns == null) {
                    logger.info("All dependencies for event: {} on project: {}, are triggered in silent mode. "
                            + "Can not get list of actual dependencies", event, p);
                    return null;
                }

                if (parentRuns.isEmpty()) {
                    logger.info("Project {} has dependencies, but does not have known runs for {}", p, event);
                    return null;
                }

                List<Run> actualDependencies = new ArrayList<Run>(dependencies.size());
                for (Run run : parentRuns) {
                    if (dependencies.contains(run.getParent())) {
                        actualDependencies.add(run);
                    }
                }

                if (actualDependencies.isEmpty()) {
                    logger.info("Project {} has dependencies, but all of them are not interested in event {}", p,
                            event);
                    return null;
                }

                // TODO: returning `null` from a QueueTaskDispatcher does not mean the build will start immediately.
                // So, this line can be called multiple times. In case of performance/stability issues it makes sense to
                // mode to TransientActionFactory or maybe a QueueListener would be better.
                item.replaceAction(new GerritDependencyAction(actualDependencies));
            }

            logger.info("No active dependencies on project: {} for event: {}, it will now build", p, event);
            return null;
        }
    }

    /**
     * Gets the cause of blockage if one of dependant project was not triggered or was not finished yet.
     * @param dependencies The list of projects which need to be checked
     * @param event The event should have also caused the blocking builds.
     * @return the cause of blockage.
     */
    private CauseOfBlockage getCauseOfBlockage(List<Job> dependencies, GerritTriggeredEvent event) {
        ToGerritRunListener toGerritRunListener = ToGerritRunListener.getInstance();
        if (toGerritRunListener != null) {
            for (Job dependency : dependencies) {
                if (toGerritRunListener.isTriggered(dependency, event)) {
                    if (toGerritRunListener.isBuilding(dependency, event)) {
                        return new BecauseDependentBuildIsBuilding(dependency);
                    }
                } else {
                    GerritTrigger gerritTrigger = GerritTrigger.getTrigger(dependency);
                    if (gerritTrigger != null && gerritTrigger.isInteresting(event)) {
                        return new BecauseWaitingForOtherProjectsToTrigger();
                    }
                }
            }
        }
        return null;
    }

    /**
     * Return the GerritCause of the specific item if any, otherwise return null.
     * @param item The item
     * @return the GerritCause
     */
    private GerritCause getGerritCause(Queue.Item item) {
        for (Cause cause : item.getCauses()) {
            if (cause instanceof GerritCause) {
                return (GerritCause) cause;
            }
        }
        return null;
    }

    /**
     * Return a list of Abstract Projects from their string names.
     * @param projects The string containing the projects, comma-separated.
     * @param context The context in which to read the string
     * @return the list of projects
     */
    public static List<Job> getProjectsFromString(String projects, Item context) {
        List<Job> dependencyJobs = new ArrayList<Job>();
        if (StringUtils.isEmpty(projects)) {
            return null;
        } else {
            Jenkins jenkins = Jenkins.getInstance();
            assert jenkins != null;
            StringTokenizer tokens = new StringTokenizer(projects, ",");
            while (tokens.hasMoreTokens()) {
                String projectName = tokens.nextToken().trim();
                if (!projectName.isEmpty()) {
                    Item item = jenkins.getItem(projectName, context, Item.class);
                    if ((item != null) && (item instanceof Job)) {
                        dependencyJobs.add((Job) item);
                        logger.debug("project dependency job added : {}", item);
                    }
                }
            }
        }
        return dependencyJobs;
    }

    /**
     * Signals this event started retriggering all its projects.
     * In the meantime, no builds with dependencies should be allowed to start.
     * @param event the event triggering
     */
    public void onTriggeringAll(GerritTriggeredEvent event) {
        currentlyTriggeringEvents.add(event);
        logger.debug("Triggering all projects for {}", event);
    }

    /**
     * Signals this event is done retriggering all its projects.
     * Builds with dependencies may be allowed to start once their dependencies are built..
     * @param event the event done triggering
     */
    public void onDoneTriggeringAll(GerritTriggeredEvent event) {
        currentlyTriggeringEvents.remove(event);
        logger.debug("Done triggering all projects for {}", event);
    }

    /*
     * GerritEventListener interface
     */

    /**
     * Process lifecycle events. We register to these events
     * so we can get notified of the beginning of the scanning and end of
     * scanning.
     * @param event the event to which we subscribe.
     */
    @Override
    public void gerritEvent(GerritEvent event) {
        //we are only interested in the ManualPatchsetCreated events for now
        //as they are the only ones which have event scanning information.
        if (event instanceof GerritEventLifecycle) {
            logger.debug("registering to lifecycle");
            // Registering to get the ScanDone event.
            ((GerritEventLifecycle) event).addListener(this);
            // while this is most likely a ManualPatchSetCreated, which is
            // a GerritTriggeredEvent, we don't have a guarantee that this
            // will necessarily be the case in the future.
            if (event instanceof GerritTriggeredEvent) {
                //We only can setup this "barrier" if we are certain to be able to
                //lift it, which only happens for Lifecycle events for which we
                //will get the ScanDone.
                onTriggeringAll((GerritTriggeredEvent) event);
            }
        }
    }

    /*
     * GerritEventLifecycleListener interface
     */

    @Override
    public void triggerScanStarting(GerritEvent event) {
        // While it would make sense to call the onTriggeringAll, this event (ScanStarting)
        // is fired before the event even makes it to the gerritEvent above, meaning
        // nothing here will execute because we aren't registered yet.
    }

    @Override
    public void triggerScanDone(GerritEvent event) {
        // while this is most likely a ManualPatchSetCreated, which is
        // a GerritTriggeredEvent, we don't have a guarantee that this
        // will necessarily be the case in the future.
        logger.debug("trigger scan done");
        if (event instanceof GerritTriggeredEvent) {
            onDoneTriggeringAll((GerritTriggeredEvent) event);
        }
        // But, we do know this is a Lifecycle because this method is for lifecyle events
        ((GerritEventLifecycle) event).removeListener(this);
    }

    @Override
    public void projectTriggered(GerritEvent event, Job project) {
    }

    @Override
    public void buildStarted(GerritEvent event, Run build) {
    }

    @Override
    public void buildCompleted(GerritEvent event, Run build) {
    }

    @Override
    public void allBuildsCompleted(GerritEvent event) {
    }
}