Package com.sonyericsson.hudson.plugins.gerrit.trigger.dependency

Source Code of com.sonyericsson.hudson.plugins.gerrit.trigger.dependency.DependencyQueueTaskDispatcher

/*
* 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.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Cause;
import hudson.model.Item;
import hudson.model.Queue;
import hudson.model.queue.QueueTaskDispatcher;
import hudson.model.queue.CauseOfBlockage;
import hudson.Util;
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.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 Bréhon <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.getInstance().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() {
        ExtensionList<DependencyQueueTaskDispatcher> dispatchers =
                Jenkins.getInstance().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) {
        //AbstractProject check
        if (!(item.task instanceof AbstractProject)) {
            logger.debug("Not an abstract project: {}", item.task);
            return null;
        }

        GerritCause cause = getGerritCause(item);
        //Not gerrit-triggered
        if (cause == null) {
            logger.debug("Not a gerrit cause: {}", cause);
            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");
            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("Item is already buildable");
            return null;
        }
        AbstractProject p = (AbstractProject)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");
            return null;
        }
        //Dependency projects in the build queue
        List<AbstractProject> dependencies = getProjectsFromString(trigger.getDependencyJobsNames(),
                (Item)p);
        if ((dependencies == null) || (dependencies.size() == 0)) {
            logger.debug("No dependencies on project: {}", p);
            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", event);
            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", event);
            return new BecauseWaitingForOtherProjectsToTrigger();
        }


        List<AbstractProject> blockingProjects = getBlockingDependencyProjects(dependencies, event);

        if (blockingProjects.size() > 0) {
            return new BecauseDependentBuildIsBuilding(blockingProjects);
        } else {
            logger.info("No active dependencies on project: {} , it will now build", p);
            return null;
        }
    }

    /**
     * Gets the subset of projects which have a building element needing to complete for the same event.
     * @param dependencies The list of projects which need to be checked
     * @param event The event should have also caused the blocking builds.
     * @return the sublist of dependencies which need to be completed before this event is resolved.
     */
    protected List<AbstractProject> getBlockingDependencyProjects(List<AbstractProject> dependencies,
            GerritTriggeredEvent event) {
        List<AbstractProject> blockingProjects = new ArrayList<AbstractProject>();
        ToGerritRunListener toGerritRunListener = ToGerritRunListener.getInstance();
        if (toGerritRunListener != null) {
            for (AbstractProject dependency : dependencies) {
                if (toGerritRunListener.isProjectTriggeredAndIncomplete(dependency, event)) {
                    blockingProjects.add(dependency);
                }
            }
        }
        return blockingProjects;
    }

    /**
     * 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<AbstractProject> getProjectsFromString(String projects, Item context) {
        List<AbstractProject> dependencyJobs = new ArrayList<AbstractProject>();
        if ((projects == null) || projects.equals("")) {
            return null;
        } else {
            StringTokenizer tokens = new StringTokenizer(Util.fixNull(projects), ",");
            while (tokens.hasMoreTokens()) {
                String projectName = tokens.nextToken().trim();
                if (!projectName.equals("")) {
                    Item item = Jenkins.getInstance().getItem(projectName, context, Item.class);
                    if ((item != null) && (item instanceof AbstractProject)) {
                        dependencyJobs.add((AbstractProject)item);
                        logger.debug("project dependency job added : {}", (AbstractProject)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, AbstractProject project) {
    }

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

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

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

Related Classes of com.sonyericsson.hudson.plugins.gerrit.trigger.dependency.DependencyQueueTaskDispatcher

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.