├── src ├── main │ ├── webapp │ │ └── images │ │ │ ├── original.jpg │ │ │ ├── icon-16x16.jpg │ │ │ ├── icon-32x32.jpg │ │ │ ├── icon-64x64.jpg │ │ │ ├── original-rotated.jpg │ │ │ └── icon-rotated-64x64.jpg │ ├── resources │ │ ├── org │ │ │ └── terracotta │ │ │ │ └── jenkins │ │ │ │ └── plugins │ │ │ │ └── acceleratedbuildnow │ │ │ │ ├── AcceleratedBuildNowAction │ │ │ │ └── index.jelly │ │ │ │ ├── AcceleratedBuildNowBadgeAction │ │ │ │ └── badge.jelly │ │ │ │ └── AcceleratedBuildNowVictimBadgeAction │ │ │ │ └── badge.jelly │ │ └── index.jelly │ └── java │ │ └── org │ │ └── terracotta │ │ └── jenkins │ │ └── plugins │ │ └── acceleratedbuildnow │ │ ├── AcceleratedBuildNowPlugin.java │ │ ├── AcceleratedBuildNowActionFactory.java │ │ ├── AcceleratedBuildNowComparator.java │ │ ├── AcceleratedBuildNowBadgeAction.java │ │ ├── AcceleratedBuildNowVictimBadgeAction.java │ │ ├── AcceleratedBuildNowSorter.java │ │ └── AcceleratedBuildNowAction.java └── test │ └── java │ └── org │ └── terracotta │ └── jenkins │ └── plugins │ └── acceleratedbuildnow │ ├── AcceleratedBuildNowSorterTest.java │ └── AcceleratedBuildNowComparatorTest.java ├── .gitignore ├── LICENSE.txt ├── README.md └── pom.xml /src/main/webapp/images/original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/accelerated-build-now-plugin/master/src/main/webapp/images/original.jpg -------------------------------------------------------------------------------- /src/main/webapp/images/icon-16x16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/accelerated-build-now-plugin/master/src/main/webapp/images/icon-16x16.jpg -------------------------------------------------------------------------------- /src/main/webapp/images/icon-32x32.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/accelerated-build-now-plugin/master/src/main/webapp/images/icon-32x32.jpg -------------------------------------------------------------------------------- /src/main/webapp/images/icon-64x64.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/accelerated-build-now-plugin/master/src/main/webapp/images/icon-64x64.jpg -------------------------------------------------------------------------------- /src/main/webapp/images/original-rotated.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/accelerated-build-now-plugin/master/src/main/webapp/images/original-rotated.jpg -------------------------------------------------------------------------------- /src/main/webapp/images/icon-rotated-64x64.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/accelerated-build-now-plugin/master/src/main/webapp/images/icon-rotated-64x64.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ 2 | .idea/ 3 | *.iml 4 | *.iws 5 | 6 | # Eclipse 7 | .classpath 8 | .project 9 | .settings/ 10 | 11 | # Maven 12 | target/ 13 | 14 | # MacOS 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /src/main/resources/org/terracotta/jenkins/plugins/acceleratedbuildnow/AcceleratedBuildNowAction/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/org/terracotta/jenkins/plugins/acceleratedbuildnow/AcceleratedBuildNowBadgeAction/badge.jelly: -------------------------------------------------------------------------------- 1 | 2 | ${it.text} 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/org/terracotta/jenkins/plugins/acceleratedbuildnow/AcceleratedBuildNowVictimBadgeAction/badge.jelly: -------------------------------------------------------------------------------- 1 | 2 | ${it.text} 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 6 |
7 | A plug-in that enables you to instantly run a job on a busy Jenkins cluster (top of the queue, can kill a build to execute now) 8 |
-------------------------------------------------------------------------------- /src/main/java/org/terracotta/jenkins/plugins/acceleratedbuildnow/AcceleratedBuildNowPlugin.java: -------------------------------------------------------------------------------- 1 | package org.terracotta.jenkins.plugins.acceleratedbuildnow; 2 | 3 | import hudson.Plugin; 4 | 5 | import java.util.logging.Logger; 6 | 7 | /** 8 | * @author Anthony Dahanne 9 | */ 10 | public class AcceleratedBuildNowPlugin extends Plugin { 11 | private final static Logger LOG = Logger.getLogger(AcceleratedBuildNowPlugin.class.getName()); 12 | 13 | public void start() throws Exception { 14 | LOG.info("Starting Accelerated Build Now Plugin"); 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2013 Terracotta Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/jenkins/plugins/acceleratedbuildnow/AcceleratedBuildNowActionFactory.java: -------------------------------------------------------------------------------- 1 | package org.terracotta.jenkins.plugins.acceleratedbuildnow; 2 | 3 | import hudson.Extension; 4 | import hudson.model.AbstractProject; 5 | import hudson.model.Action; 6 | import hudson.model.TransientProjectActionFactory; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collection; 10 | 11 | /** 12 | * @author : Anthony Dahanne 13 | */ 14 | @Extension 15 | public class AcceleratedBuildNowActionFactory extends TransientProjectActionFactory{ 16 | @Override 17 | public Collection createFor(AbstractProject target) { 18 | ArrayList actions = new ArrayList(); 19 | actions.add(new AcceleratedBuildNowAction(target)); 20 | return actions; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/jenkins/plugins/acceleratedbuildnow/AcceleratedBuildNowComparator.java: -------------------------------------------------------------------------------- 1 | package org.terracotta.jenkins.plugins.acceleratedbuildnow; 2 | 3 | import hudson.model.AbstractProject; 4 | import hudson.model.Queue; 5 | 6 | import java.util.Comparator; 7 | 8 | /** 9 | * @author : Anthony Dahanne 10 | */ 11 | public class AcceleratedBuildNowComparator implements Comparator { 12 | 13 | private final AbstractProject mostPriorityProject; 14 | 15 | public AcceleratedBuildNowComparator(AbstractProject mostPriorityProject) { 16 | this.mostPriorityProject = mostPriorityProject; 17 | } 18 | 19 | public int compare(Queue.BuildableItem buildableItem0, Queue.BuildableItem buildableItem1) { 20 | AbstractProject project0 = (AbstractProject) buildableItem0.task; 21 | AbstractProject project1 = (AbstractProject) buildableItem1.task; 22 | if(project0.equals(mostPriorityProject)) { 23 | return -1; 24 | } 25 | if(project1.equals(mostPriorityProject)) { 26 | return 1; 27 | } 28 | return 0; 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/main/java/org/terracotta/jenkins/plugins/acceleratedbuildnow/AcceleratedBuildNowBadgeAction.java: -------------------------------------------------------------------------------- 1 | package org.terracotta.jenkins.plugins.acceleratedbuildnow; 2 | 3 | import hudson.model.AbstractBuild; 4 | import hudson.model.BuildBadgeAction; 5 | import org.kohsuke.stapler.export.Exported; 6 | 7 | /** 8 | * @author : Anthony Dahanne 9 | */ 10 | public class AcceleratedBuildNowBadgeAction implements BuildBadgeAction{ 11 | private final static String ICON_PATH = "/plugin/accelerated-build-now-plugin/images/icon-64x64.jpg";; 12 | private final AbstractBuild killedBuild; 13 | 14 | public AcceleratedBuildNowBadgeAction(AbstractBuild killedBuild) { 15 | this.killedBuild = killedBuild; 16 | } 17 | 18 | @Exported 19 | public String getIconPath() { return ICON_PATH; } 20 | 21 | @Exported 22 | public String getKilledBuildUrl() { return killedBuild.getProject().getUrl(); } 23 | 24 | @Exported 25 | public String getText() { return "This build was top priority built. It aborted and re scheduled " + killedBuild.getProject().getName() + " #" + killedBuild.getNumber(); } 26 | 27 | public String getDisplayName() { 28 | return ""; 29 | } 30 | 31 | public String getIconFileName() { 32 | return ""; 33 | } 34 | 35 | public String getUrlName() { 36 | return ""; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/jenkins/plugins/acceleratedbuildnow/AcceleratedBuildNowVictimBadgeAction.java: -------------------------------------------------------------------------------- 1 | package org.terracotta.jenkins.plugins.acceleratedbuildnow; 2 | 3 | import hudson.model.AbstractBuild; 4 | import hudson.model.BuildBadgeAction; 5 | import org.kohsuke.stapler.export.Exported; 6 | 7 | /** 8 | * @author : Anthony Dahanne 9 | */ 10 | public class AcceleratedBuildNowVictimBadgeAction implements BuildBadgeAction{ 11 | private final static String ICON_PATH = "/plugin/accelerated-build-now-plugin/images/icon-rotated-64x64.jpg";; 12 | private final AbstractBuild killerBuild; 13 | 14 | public AcceleratedBuildNowVictimBadgeAction(AbstractBuild killerBuild) { 15 | this.killerBuild = killerBuild; 16 | } 17 | 18 | @Exported 19 | public String getIconPath() { return ICON_PATH; } 20 | 21 | @Exported 22 | public String getKillerBuildUrl() { return killerBuild.getProject().getUrl(); } 23 | 24 | @Exported 25 | public String getText() { return "This build was aborted and re scheduled by top priority build : " + killerBuild.getProject().getName() + " #" + killerBuild.getNumber(); } 26 | 27 | public String getDisplayName() { 28 | return ""; 29 | } 30 | 31 | public String getIconFileName() { 32 | return ""; 33 | } 34 | 35 | public String getUrlName() { 36 | return ""; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/jenkins/plugins/acceleratedbuildnow/AcceleratedBuildNowSorter.java: -------------------------------------------------------------------------------- 1 | package org.terracotta.jenkins.plugins.acceleratedbuildnow; 2 | 3 | import hudson.model.AbstractProject; 4 | import hudson.model.Queue; 5 | import hudson.model.queue.QueueSorter; 6 | 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | /** 11 | * @author : Anthony Dahanne 12 | */ 13 | public class AcceleratedBuildNowSorter extends QueueSorter { 14 | 15 | private final AbstractProject project; 16 | private final QueueSorter originalQueueSorter; 17 | private final AcceleratedBuildNowComparator comparator; 18 | 19 | public AcceleratedBuildNowSorter(AbstractProject project, QueueSorter originalQueueSorter) { 20 | this.project = project; 21 | this.originalQueueSorter = originalQueueSorter; 22 | comparator = new AcceleratedBuildNowComparator(this.project); 23 | } 24 | 25 | @Override 26 | public void sortBuildableItems(List buildables) { 27 | if(this.originalQueueSorter != null) { 28 | this.originalQueueSorter.sortBuildableItems(buildables); 29 | } 30 | Collections.sort(buildables, comparator); 31 | } 32 | 33 | public QueueSorter getOriginalQueueSorter() { 34 | return originalQueueSorter; 35 | } 36 | 37 | public AbstractProject getProject() { 38 | return project; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/jenkins/plugins/acceleratedbuildnow/AcceleratedBuildNowSorterTest.java: -------------------------------------------------------------------------------- 1 | package org.terracotta.jenkins.plugins.acceleratedbuildnow; 2 | 3 | import hudson.model.AbstractProject; 4 | import hudson.model.Queue; 5 | import hudson.model.queue.QueueSorter; 6 | import org.junit.Test; 7 | import org.mockito.Mockito; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import static org.junit.Assert.assertEquals; 13 | import static org.junit.Assert.assertTrue; 14 | import static org.mockito.Mockito.when; 15 | 16 | /** 17 | * @author : Anthony Dahanne 18 | */ 19 | public class AcceleratedBuildNowSorterTest { 20 | 21 | @Test 22 | public void shouldWrapOriginalSorterTest() { 23 | DummyQueueSorter originalQueueSorter = new DummyQueueSorter(); 24 | AcceleratedBuildNowSorter acceleratedBuildNowSorter = new AcceleratedBuildNowSorter(getAbstractProject("myProject"), originalQueueSorter); 25 | acceleratedBuildNowSorter.sortBuildableItems(new ArrayList()); 26 | assertTrue(originalQueueSorter.wasCalled()); 27 | } 28 | 29 | @Test 30 | public void shouldNotWrapAcceleratedBuildNowSorterTest() { 31 | DummyQueueSorter originalQueueSorter = new DummyQueueSorter(); 32 | AcceleratedBuildNowSorter acceleratedBuildNowSorter = new AcceleratedBuildNowSorter(getAbstractProject("myProject"), originalQueueSorter); 33 | AcceleratedBuildNowSorter acceleratedBuildNowSorter2 = new AcceleratedBuildNowSorter(getAbstractProject("myProject"), acceleratedBuildNowSorter); 34 | assertEquals(acceleratedBuildNowSorter, acceleratedBuildNowSorter2.getOriginalQueueSorter()); 35 | } 36 | 37 | 38 | private AbstractProject getAbstractProject(final String importantProject) { 39 | AbstractProject abstractProject = Mockito.mock(AbstractProject.class); 40 | when(abstractProject.getName()).thenReturn(importantProject); 41 | return abstractProject; 42 | } 43 | 44 | private class DummyQueueSorter extends QueueSorter{ 45 | private boolean wasCalled = false; 46 | 47 | @Override 48 | public void sortBuildableItems(List buildables) { 49 | wasCalled = true; 50 | } 51 | 52 | public boolean wasCalled() { 53 | return wasCalled; 54 | } 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Accelerated Build Now Plugin 2 | ============================ 3 | The Jenkins Accelerated Build Now Plugin allows Jenkins users to launch a project's build right away, even if the queue is long (moving it to the top of the queue) and even if no executor is available (killing and rescheduling builds not launched by "humans") 4 | 5 | ## How to build and test : 6 | Simply clone this repo and run mvn clean install hpi:run 7 | 8 | ## How to install : 9 | Download the [latest release](https://github.com/Terracotta-OSS/accelerated-build-now-plugin/releases) and use Jenkins installer advanced tab to upload and install it to Jenkins. 10 | 11 | ## How to use : 12 | When your Jenkins cluster is overloaded with jobs (a queue with 10+ builds, all the executors busy with nightly builds that take ages), you know you will wait ages before the job you want to run effectively starts running. 13 | Relax ! With the Accelerated Build Now Plugin, your job will run right away ! 14 | 15 | 1. The queue is full of automatically (not user launched) scheduled jobs, and the only executor available is busy ... 16 | 17 | ![Screenshot](https://raw.github.com/Terracotta-OSS/accelerated-build-now-plugin/gh-pages/screenshots/queue_is_long.png "A long queue to wait for") 18 | 19 | 2. You want your job maven-surefire to run ASAP, so you click on the Accelerated Build Now button 20 | 21 | ![Screenshot](https://raw.github.com/Terracotta-OSS/accelerated-build-now-plugin/gh-pages/screenshots/accelerated_button.png "Accelerated Build Now !") 22 | 23 | 3. Your maven-surefire job just got priorized to the top of the queue and just started running (it had to kill the quartz job, but it rescheduled it already) 24 | 25 | ![Screenshot](https://raw.github.com/Terracotta-OSS/accelerated-build-now-plugin/gh-pages/screenshots/job_running.png "Your job is running") 26 | 27 | 4. A nice rhyno badge was added to your build that got "acceleratedly built" ; if you click on it you will see the killed build got a killed rhyno badge 28 | 29 | ![Screenshot](https://raw.github.com/Terracotta-OSS/accelerated-build-now-plugin/gh-pages/screenshots/build_prioritized.png "Killer Rhyno !") 30 | 31 | ![Screenshot](https://raw.github.com/Terracotta-OSS/accelerated-build-now-plugin/gh-pages/screenshots/build_aborted.png "Killed Rhyno !") 32 | 33 | ## How does that work ? 34 | When you click on the Accelerated Build Now button, the plugin will : 35 | * make sure the queue is not empty and all the excutors are busy ( if not, it will normally schedule the build and exit) 36 | * sort the queue using a QueueSorter wrapping any existing QueueSorter (such as the Priority Sorter Plugin queue sorter) 37 | * look for any executors compatible with this job (checking labels) and running a job not scheduled by a "human" (SCM triggered, cron style triggered, etc..); if none is found, exit 38 | * if a compatible executor is found: abort the build (and re schedule it for later) and mark it as killed by the plugin, wait for the accelerated build to start, mark it as accelerated 39 | 40 | ## Authors : 41 | This plugin was developed by Terracotta, by 42 | 43 | - [Anthony Dahanne](https://github.com/anthonydahanne/) 44 | 45 | ## License 46 | Apache 2 licensed (see LICENSE.txt) 47 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | org.jenkins-ci.plugins 5 | plugin 6 | 1.523 7 | 8 | 9 | org.terracotta.jenkins.plugins 10 | accelerated-build-now-plugin 11 | 1.0.2-SNAPSHOT 12 | hpi 13 | 14 | 15 | 16 | org.mockito 17 | mockito-all 18 | 1.8.5 19 | test 20 | 21 | 22 | junit 23 | junit 24 | 4.11 25 | test 26 | 27 | 28 | org.jenkins-ci.main 29 | maven-plugin 30 | 31 | 32 | 33 | 34 | 35 | 36 | repo.jenkins-ci.org 37 | http://repo.jenkins-ci.org/public/ 38 | 39 | 40 | 41 | 42 | 43 | repo.jenkins-ci.org 44 | http://repo.jenkins-ci.org/public/ 45 | 46 | 47 | 48 | 49 | 50 | https://github.com/Terracotta-OSS/accelerated-build-now-plugin 51 | HEAD 52 | scm:git:ssh://git@github.com/Terracotta-OSS/accelerated-build-now-plugin.git 53 | scm:git:ssh://git@github.com/Terracotta-OSS/accelerated-build-now-plugin.git 54 | 55 | 56 | 57 | 70 | 71 | 72 | 73 | anthonydahanne 74 | Anthony Dahanne 75 | anthony.dahanne@gmail.com 76 | Terracotta 77 | http://www.terracotta.org 78 | 79 | 80 | 81 | https://wiki.jenkins-ci.org/display/JENKINS/Accelerated+Build+Now+Plugin 82 | 83 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/jenkins/plugins/acceleratedbuildnow/AcceleratedBuildNowComparatorTest.java: -------------------------------------------------------------------------------- 1 | package org.terracotta.jenkins.plugins.acceleratedbuildnow; 2 | 3 | import hudson.model.*; 4 | import hudson.model.Queue; 5 | import hudson.model.queue.CauseOfBlockage; 6 | import jenkins.util.NonLocalizable; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | import org.jvnet.localizer.Localizable; 10 | import org.mockito.Mockito; 11 | 12 | import java.util.*; 13 | 14 | import static org.hamcrest.Matchers.*; 15 | import static org.mockito.Mockito.when; 16 | 17 | /** 18 | * @author : Anthony Dahanne 19 | */ 20 | public class AcceleratedBuildNowComparatorTest { 21 | 22 | 23 | @Test 24 | public void sortBuildItemsTest() { 25 | AbstractProject notImportant = getAbstractProject("notImportant"); 26 | AbstractProject notImportantEither = getAbstractProject("notImportantEither"); 27 | AbstractProject importantProject = getAbstractProject("importantProject"); 28 | 29 | 30 | List buildableItemList = new ArrayList(); 31 | Queue.BuildableItem notImportantBuildableItem = new Queue.BuildableItem(new Queue.WaitingItem(Calendar.getInstance(),notImportant,new ArrayList())); 32 | buildableItemList.add(notImportantBuildableItem); 33 | Queue.BuildableItem notImportantEitherBuildableItem = new Queue.BuildableItem(new Queue.WaitingItem(Calendar.getInstance(),notImportantEither,new ArrayList())); 34 | buildableItemList.add(notImportantEitherBuildableItem); 35 | Queue.BuildableItem importantBuildableItem = new Queue.BuildableItem(new Queue.WaitingItem(Calendar.getInstance(),importantProject,new ArrayList())); 36 | buildableItemList.add(importantBuildableItem); 37 | 38 | Assert.assertThat(buildableItemList,contains(notImportantBuildableItem, notImportantEitherBuildableItem, importantBuildableItem)); 39 | 40 | Comparator acceleratedBuildNowComparator = new AcceleratedBuildNowComparator(importantProject); 41 | Collections.sort(buildableItemList, acceleratedBuildNowComparator); 42 | 43 | Assert.assertThat(buildableItemList,contains(importantBuildableItem, notImportantBuildableItem,notImportantEitherBuildableItem)); 44 | } 45 | 46 | 47 | @Test 48 | public void sortBuildItemsTest2() { 49 | AbstractProject notImportant = getAbstractProject("notImportant"); 50 | AbstractProject importantProject = getAbstractProject("importantProject"); 51 | 52 | 53 | List buildableItemList = new ArrayList(); 54 | Queue.BuildableItem importantBuildableItem = new Queue.BuildableItem(new Queue.WaitingItem(Calendar.getInstance(),importantProject,new ArrayList())); 55 | buildableItemList.add(importantBuildableItem); 56 | Queue.BuildableItem notImportantBuildableItem = new Queue.BuildableItem(new Queue.WaitingItem(Calendar.getInstance(),notImportant,new ArrayList())); 57 | buildableItemList.add(notImportantBuildableItem); 58 | 59 | Assert.assertThat(buildableItemList,contains(importantBuildableItem, notImportantBuildableItem)); 60 | 61 | Comparator acceleratedBuildNowComparator = new AcceleratedBuildNowComparator(importantProject); 62 | Collections.sort(buildableItemList, acceleratedBuildNowComparator); 63 | 64 | Assert.assertThat(buildableItemList,contains(importantBuildableItem, notImportantBuildableItem)); 65 | } 66 | 67 | 68 | private AbstractProject getAbstractProject(final String importantProject) { 69 | AbstractProject abstractProject = Mockito.mock(AbstractProject.class); 70 | when(abstractProject.getName()).thenReturn(importantProject); 71 | return abstractProject; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/jenkins/plugins/acceleratedbuildnow/AcceleratedBuildNowAction.java: -------------------------------------------------------------------------------- 1 | package org.terracotta.jenkins.plugins.acceleratedbuildnow; 2 | 3 | import hudson.maven.MavenBuild; 4 | import hudson.model.*; 5 | import hudson.model.queue.QueueSorter; 6 | import hudson.model.queue.QueueTaskFuture; 7 | import jenkins.model.Jenkins; 8 | import org.kohsuke.stapler.StaplerRequest; 9 | import org.kohsuke.stapler.StaplerResponse; 10 | 11 | import javax.servlet.ServletException; 12 | import java.io.IOException; 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Set; 16 | import java.util.concurrent.ExecutionException; 17 | import java.util.concurrent.Future; 18 | import java.util.logging.Logger; 19 | 20 | /** 21 | * This class contains the main logic of the plugin 22 | * 23 | * @author : Anthony Dahanne 24 | */ 25 | public class AcceleratedBuildNowAction implements Action { 26 | 27 | private static final Logger LOG = Logger.getLogger(AcceleratedBuildNowAction.class.getName()); 28 | private final AbstractProject project; 29 | 30 | public AcceleratedBuildNowAction(AbstractProject abstractProject) { 31 | this.project = abstractProject; 32 | } 33 | 34 | public String getDisplayName() { 35 | if (project.hasPermission(Item.BUILD)) { 36 | return "Accelerated Build Now !"; 37 | } 38 | return null; 39 | } 40 | 41 | public String getIconFileName() { 42 | if (project.hasPermission(Item.BUILD)) { 43 | return "/plugin/accelerated-build-now-plugin/images/icon-64x64.jpg"; 44 | } 45 | return null; 46 | } 47 | 48 | public String getUrlName() { 49 | if (project.hasPermission(Item.BUILD)) { 50 | return "accelerated"; 51 | } 52 | return null; 53 | } 54 | 55 | public void doBuild(final StaplerRequest request, final StaplerResponse response) throws ServletException, 56 | IOException, InterruptedException, ExecutionException { 57 | 58 | if (!project.hasPermission(Item.BUILD)) { 59 | // Jenkins is secured AND the user is not supposed to build this job 60 | response.sendRedirect(request.getContextPath() + '/' + project.getUrl()); 61 | } 62 | 63 | LOG.info("project : " + project.getName() + " needs to be built NOW !"); 64 | 65 | Label assignedLabel = project.getAssignedLabel(); 66 | 67 | // what if the user clicks repeatedly on the link ? 68 | boolean alreadyTakenCareOf = project.getLastBuild() != null && project.getLastBuild().isBuilding() || queueSorterPriorityOn(project); 69 | 70 | // what if the queue is empty and all executors are already busy ? 71 | boolean jenkinsIsFreeToBuild = Jenkins.getInstance().getQueue().isEmpty() && atLeastOneExecutorIsIdle(assignedLabel); 72 | 73 | if (alreadyTakenCareOf || jenkinsIsFreeToBuild) { 74 | LOG.info("No need for AcceleratedBuildNow plugin (already building or empty queue with idle executors"); 75 | project.scheduleBuild2(0, new Cause.UserIdCause(), new Action[0]); 76 | if(Jenkins.getInstance().getQueue().getSorter() !=null && Jenkins.getInstance().getQueue().getBuildableItems()!=null) { 77 | Jenkins.getInstance().getQueue().getSorter().sortBuildableItems(Jenkins.getInstance().getQueue().getBuildableItems()); 78 | } 79 | response.sendRedirect(request.getContextPath() + '/' + project.getUrl()); 80 | return; 81 | } 82 | 83 | QueueTaskFuture queueTaskFuture = project.scheduleBuild2(0, new Cause.UserIdCause(), new Action[0]); 84 | LOG.info("project : " + project.getName() + " is scheduled to build now !"); 85 | 86 | 87 | //replace the original queue sorter with one that will place our project build first in the queue 88 | QueueSorter originalQueueSorter = Jenkins.getInstance().getQueue().getSorter(); 89 | AcceleratedBuildNowSorter acceleratedBuildNowSorter = new AcceleratedBuildNowSorter(project, originalQueueSorter); 90 | Jenkins.getInstance().getQueue().setSorter(acceleratedBuildNowSorter); 91 | // we sort the queue so that our project is next to be built on the list 92 | Jenkins.getInstance().getQueue().getSorter().sortBuildableItems(Jenkins.getInstance().getQueue().getBuildableItems()); 93 | 94 | AbstractBuild killedBuild = null; 95 | List allItems = Jenkins.getInstance().getAllItems(AbstractProject.class); 96 | for (AbstractProject projectConsidered : allItems) { 97 | AbstractBuild lastBuild = getLastBuild(projectConsidered); 98 | 99 | if (lastBuild != null && lastBuild.isBuilding()) { 100 | if (isBuildNotTriggeredByHuman(lastBuild) && slaveRunningBuildCompatible(lastBuild, assignedLabel)) { 101 | LOG.info("project : " + lastBuild.getProject().getName() + " #" + lastBuild.getNumber() + " was not scheduled by a human, killing it right now to re schedule it later !"); 102 | Executor executor = getExecutor(lastBuild); 103 | executor.interrupt(Result.ABORTED); 104 | killedBuild = lastBuild; 105 | break; 106 | } 107 | } 108 | } 109 | 110 | if (killedBuild == null) { 111 | LOG.info("project : " + project.getName() + " could not be built : no way to build it now !"); 112 | } else { 113 | AbstractBuild projectBuild = ((Future) queueTaskFuture.getStartCondition()).get(); 114 | LOG.info("build #" + projectBuild.getNumber() + " for " + project.getName() + " was launched successfully !"); 115 | 116 | // we add a nice badge to the killer build 117 | projectBuild.getActions().add(new AcceleratedBuildNowBadgeAction(killedBuild)); 118 | 119 | // we add a nice badge to the killeD build 120 | killedBuild.getActions().add(new AcceleratedBuildNowVictimBadgeAction(projectBuild)); 121 | 122 | // we re schedule the build that got killed 123 | rescheduleKilledBuild(killedBuild, new AcceleratedBuildNowKilledCause(), originalQueueSorter); 124 | } 125 | Jenkins.getInstance().getQueue().setSorter(originalQueueSorter); 126 | 127 | response.sendRedirect(request.getContextPath() + '/' + project.getUrl()); 128 | } 129 | 130 | private Executor getExecutor(AbstractBuild lastBuild) { 131 | Executor executorConsidered = null; 132 | for (Executor executor : lastBuild.getBuiltOn().toComputer().getExecutors()) { 133 | if (lastBuild.equals(executor.getCurrentExecutable())) { 134 | executorConsidered = executor; 135 | } 136 | } 137 | return executorConsidered; 138 | } 139 | 140 | private AbstractBuild getLastBuild(AbstractProject projectConsidered) { 141 | AbstractBuild lastBuild = projectConsidered.getLastBuild(); 142 | // be careful that lastBuild can be a maven module building ! 143 | if (lastBuild instanceof MavenBuild) { 144 | lastBuild = ((MavenBuild) lastBuild).getParentBuild(); 145 | } 146 | return lastBuild; 147 | } 148 | 149 | private boolean atLeastOneExecutorIsIdle(Label assignedLabel) { 150 | 151 | int idleExecutors = 0; 152 | 153 | if (assignedLabel == null) { 154 | // no assignedLabel ? that's fine, let's find any idle executor 155 | // code based on Label.class code 156 | Set nodes = new HashSet(); 157 | Jenkins h = Jenkins.getInstance(); 158 | nodes.add(h); 159 | for (Node n : h.getNodes()) { 160 | nodes.add(n); 161 | } 162 | for (Node n : nodes) { 163 | Computer c = n.toComputer(); 164 | if (c != null && (c.isOnline() || c.isConnecting()) && c.isAcceptingTasks()) { 165 | idleExecutors += c.countIdle(); 166 | } 167 | } 168 | } else { 169 | idleExecutors = assignedLabel.getIdleExecutors(); 170 | } 171 | return idleExecutors > 0; 172 | } 173 | 174 | private boolean queueSorterPriorityOn(AbstractProject project) { 175 | QueueSorter originalQueueSorter = Jenkins.getInstance().getQueue().getSorter(); 176 | if (originalQueueSorter instanceof AcceleratedBuildNowSorter) { 177 | if (((AcceleratedBuildNowSorter) originalQueueSorter).getProject().equals(project)) { 178 | return true; 179 | } 180 | } 181 | return false; 182 | } 183 | 184 | private boolean isBuildNotTriggeredByHuman(AbstractBuild lastBuild) { 185 | return lastBuild.getCause(Cause.UserIdCause.class) == null && lastBuild.getCause(Cause.UserCause.class) == null; 186 | } 187 | 188 | private boolean slaveRunningBuildCompatible(AbstractBuild lastBuild, Label assignedLabel) { 189 | boolean contains = assignedLabel == null ? true : lastBuild.getBuiltOn().getAssignedLabels().contains(assignedLabel); 190 | if (!contains) { 191 | LOG.info("build : " + lastBuild.getNumber() + " of project " + lastBuild.getProject().getName() + " is not running on node compatible with " + assignedLabel.getName()); 192 | } 193 | return contains; 194 | } 195 | 196 | private void rescheduleKilledBuild(AbstractBuild killedBuild, Cause cause, QueueSorter originalQueueSorter) throws ExecutionException, InterruptedException { 197 | //replace the original queue sorter with one that will place our project build first in the queue 198 | AcceleratedBuildNowSorter acceleratedBuildNowSorter = new AcceleratedBuildNowSorter(project, originalQueueSorter); 199 | Jenkins.getInstance().getQueue().setSorter(acceleratedBuildNowSorter); 200 | 201 | QueueTaskFuture queueTaskFuture = killedBuild.getProject().scheduleBuild2(0, cause, new Action[0]); 202 | LOG.info("build that was killed : " + killedBuild.getProject().getName() + " #" + killedBuild.getNumber() + " is scheduled to build next !"); 203 | 204 | // we sort the queue so that our project is next to be built on the list 205 | Jenkins.getInstance().getQueue().getSorter().sortBuildableItems(Jenkins.getInstance().getQueue().getBuildableItems()); 206 | 207 | } 208 | 209 | 210 | public static class AcceleratedBuildNowKilledCause extends Cause { 211 | 212 | public AcceleratedBuildNowKilledCause() { 213 | } 214 | 215 | public String getShortDescription() { 216 | return "Started by AcceleratedBuildNow plugin"; 217 | } 218 | 219 | @Override 220 | public boolean equals(Object o) { 221 | return o instanceof AcceleratedBuildNowKilledCause; 222 | } 223 | 224 | @Override 225 | public int hashCode() { 226 | return 42; 227 | } 228 | } 229 | 230 | } 231 | --------------------------------------------------------------------------------