Skip to content

Commit

Permalink
Merge pull request #18 from jenkinsci/throttle-matrix-configurations
Browse files Browse the repository at this point in the history
[JENKINS-13619] - Throttle matrix configurations
  • Loading branch information
oleg-nenashev committed Jun 1, 2014
2 parents 4fa0bb5 + 5102979 commit abd999b
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 15 deletions.
8 changes: 8 additions & 0 deletions pom.xml
Expand Up @@ -100,5 +100,13 @@ THE SOFTWARE.
<url>http://repo.jenkins-ci.org/public/</url>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>2.0.1</version>
<type>jar</type>
</dependency>
</dependencies>
</project>

@@ -1,6 +1,7 @@
package hudson.plugins.throttleconcurrents;

import hudson.Extension;
import hudson.matrix.MatrixConfiguration;
import hudson.model.AbstractDescribableImpl;
import hudson.model.AbstractProject;
import hudson.model.Descriptor;
Expand All @@ -12,6 +13,8 @@
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import hudson.Util;
import hudson.matrix.MatrixBuild;
import hudson.matrix.MatrixProject;

import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -20,6 +23,8 @@
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;

import net.sf.json.JSONObject;
Expand All @@ -37,6 +42,8 @@ public class ThrottleJobProperty extends JobProperty<AbstractProject<?,?>> {
private List<String> categories;
private boolean throttleEnabled;
private String throttleOption;
private transient boolean throttleConfiguration;
private @CheckForNull ThrottleMatrixProjectOptions matrixOptions;

/**
* Store a config version so we're able to migrate config on various
Expand All @@ -49,12 +56,15 @@ public ThrottleJobProperty(Integer maxConcurrentPerNode,
Integer maxConcurrentTotal,
List<String> categories,
boolean throttleEnabled,
String throttleOption) {
String throttleOption,
@CheckForNull ThrottleMatrixProjectOptions matrixOptions
) {
this.maxConcurrentPerNode = maxConcurrentPerNode == null ? 0 : maxConcurrentPerNode;
this.maxConcurrentTotal = maxConcurrentTotal == null ? 0 : maxConcurrentTotal;
this.categories = categories;
this.throttleEnabled = throttleEnabled;
this.throttleOption = throttleOption;
this.matrixOptions = matrixOptions;
}


Expand Down Expand Up @@ -85,6 +95,11 @@ public Object readResolve() {
}
configVersion = 1L;

// Handle the throttleConfiguration in custom builds (not released)
if (throttleConfiguration && matrixOptions == null) {
matrixOptions = new ThrottleMatrixProjectOptions(false, true);
}

return this;
}

Expand Down Expand Up @@ -131,6 +146,29 @@ public Integer getMaxConcurrentTotal() {
return maxConcurrentTotal;
}

@CheckForNull
public ThrottleMatrixProjectOptions getMatrixOptions() {
return matrixOptions;
}

/**
* Check if the build throttles {@link MatrixBuild}s.
*/
public boolean isThrottleMatrixBuilds() {
return matrixOptions != null
? matrixOptions.isThrottleMatrixBuilds()
: ThrottleMatrixProjectOptions.DEFAULT.isThrottleMatrixBuilds();
}

/**
* Check if the build throttles {@link MatrixConfiguration}s.
*/
public boolean isThrottleMatrixConfigurations() {
return matrixOptions != null
? matrixOptions.isThrottleMatrixConfigurations()
: ThrottleMatrixProjectOptions.DEFAULT.isThrottleMatrixConfigurations();
}

static List<AbstractProject<?,?>> getCategoryProjects(String category) {
assert category != null && !category.equals("");
List<AbstractProject<?,?>> categoryProjects = new ArrayList<AbstractProject<?, ?>>();
Expand All @@ -146,6 +184,11 @@ static List<AbstractProject<?,?>> getCategoryProjects(String category) {
AbstractProject<?,?> p = t.owner;
if (/* not deleted */getItem(p.getParent(), p.getName()) == p && /* has not since been reconfigured */ p.getProperty(ThrottleJobProperty.class) == t) {
categoryProjects.add(p);
if (p instanceof MatrixProject && t.isThrottleMatrixConfigurations()) {
for (MatrixConfiguration mc : ((MatrixProject)p).getActiveConfigurations()) {
categoryProjects.add((AbstractProject<?,?>)mc);
}
}
}
}
}
Expand All @@ -169,7 +212,7 @@ public static final class DescriptorImpl extends JobPropertyDescriptor {
= new HashMap<String,Map<ThrottleJobProperty,Void>>();
/** A sync object for {@link #propertiesByCategory} */
private final transient Object propertiesByCategoryLock = new Object();

public DescriptorImpl() {
super(ThrottleJobProperty.class);
synchronized(propertiesByCategoryLock) {
Expand All @@ -195,6 +238,10 @@ public String getDisplayName() {
public boolean isApplicable(Class<? extends Job> jobType) {
return AbstractProject.class.isAssignableFrom(jobType);
}

public boolean isMatrixProject(Job job) {
return job instanceof MatrixProject;
}

@Override
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
Expand Down Expand Up @@ -310,7 +357,7 @@ public List<NodeLabeledPair> getNodeLabeledPairs() {

return nodeLabeledPairs;
}

@Extension
public static class DescriptorImpl extends Descriptor<ThrottleCategory> {
@Override
Expand Down
@@ -0,0 +1,88 @@
/*
* The MIT License
*
* Copyright 2014 Oleg Nenashev, Synopsys Inc.
*
* 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 hudson.plugins.throttleconcurrents;

import hudson.Extension;
import hudson.matrix.MatrixBuild;
import hudson.matrix.MatrixConfiguration;
import hudson.model.Describable;
import hudson.model.Descriptor;
import javax.annotation.Nonnull;
import org.kohsuke.stapler.DataBoundConstructor;

/**
* Defines additional options for throttling of {@link MatrixBuild}s and
* @{@link MatrixConfiguration}s.
* This class is intended to be used inside {@link ThrottleJobProperty}.
* @author Oleg Nenashev <o.v.nenashev@gmail.com>
* @since TODO: 1.9.0?
*/
public class ThrottleMatrixProjectOptions implements Describable<ThrottleMatrixProjectOptions> {

private final boolean throttleMatrixBuilds;
private final boolean throttleMatrixConfigurations;

/**
* A default configuration, which retains the behavior from
* version 1.8.
*/
public static final ThrottleMatrixProjectOptions DEFAULT =
new ThrottleMatrixProjectOptions(true, false);

@DataBoundConstructor
public ThrottleMatrixProjectOptions(boolean throttleMatrixBuilds, boolean throttleMatrixConfigurations) {
this.throttleMatrixBuilds = throttleMatrixBuilds;
this.throttleMatrixConfigurations = throttleMatrixConfigurations;
}

public boolean isThrottleMatrixBuilds() {
return throttleMatrixBuilds;
}

public boolean isThrottleMatrixConfigurations() {
return throttleMatrixConfigurations;
}

@Extension
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

@Override
public Descriptor<ThrottleMatrixProjectOptions> getDescriptor() {
return DESCRIPTOR;
}

public static class DescriptorImpl extends Descriptor<ThrottleMatrixProjectOptions> {

@Override
public String getDisplayName() {
return Messages.ThrottleMatrixProjectOptions_DisplayName();
}

@Nonnull
public ThrottleMatrixProjectOptions getDefaults() {
return ThrottleMatrixProjectOptions.DEFAULT;
}
}
}
Expand Up @@ -18,17 +18,22 @@
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

@Extension
public class ThrottleQueueTaskDispatcher extends QueueTaskDispatcher {

@Override
public CauseOfBlockage canTake(Node node, Task task) {
if (task instanceof MatrixConfiguration) {

ThrottleJobProperty tjp = getThrottleJobProperty(task);

// Handle multi-configuration filters
if (!shouldBeThrottled(task, tjp)) {
return null;
}

ThrottleJobProperty tjp = getThrottleJobProperty(task);
if (tjp!=null && tjp.getThrottleEnabled()) {
CauseOfBlockage cause = canRun(task, tjp);
if (cause != null) return cause;
Expand Down Expand Up @@ -91,9 +96,35 @@ public CauseOfBlockage canRun(Queue.Item item) {
}
return null;
}

@Nonnull
private ThrottleMatrixProjectOptions getMatrixOptions(Task task) {
ThrottleJobProperty tjp = getThrottleJobProperty(task);
if (tjp == null) return ThrottleMatrixProjectOptions.DEFAULT;
ThrottleMatrixProjectOptions matrixOptions = tjp.getMatrixOptions();
return matrixOptions != null ? matrixOptions : ThrottleMatrixProjectOptions.DEFAULT;
}

private boolean shouldBeThrottled(@Nonnull Task task, @CheckForNull ThrottleJobProperty tjp) {
if (tjp == null) return false;
if (!tjp.getThrottleEnabled()) return false;

// Handle matrix options
ThrottleMatrixProjectOptions matrixOptions = tjp.getMatrixOptions();
if (matrixOptions == null) matrixOptions = ThrottleMatrixProjectOptions.DEFAULT;
if (!matrixOptions.isThrottleMatrixConfigurations() && task instanceof MatrixConfiguration) {
return false;
}
if (!matrixOptions.isThrottleMatrixBuilds()&& task instanceof MatrixProject) {
return false;
}

// Allow throttling by default
return true;
}

public CauseOfBlockage canRun(Task task, ThrottleJobProperty tjp) {
if (task instanceof MatrixConfiguration) {
if (!shouldBeThrottled(task, tjp)) {
return null;
}
if (Hudson.getInstance().getQueue().isPending(task)) {
Expand Down Expand Up @@ -147,6 +178,7 @@ else if (tjp.getThrottleOption().equals("category")) {
return null;
}

@CheckForNull
private ThrottleJobProperty getThrottleJobProperty(Task task) {
if (task instanceof AbstractProject) {
AbstractProject<?,?> p = (AbstractProject<?,?>) task;
Expand All @@ -170,7 +202,14 @@ private int buildsOfProjectOnNode(Node node, Task task) {
for (Executor e : computer.getExecutors()) {
runCount += buildsOnExecutor(task, e);
}
if (task instanceof MatrixProject) {

ThrottleMatrixProjectOptions matrixOptions = getMatrixOptions(task);
if ( matrixOptions.isThrottleMatrixBuilds() && task instanceof MatrixProject) {
for (Executor e : computer.getOneOffExecutors()) {
runCount += buildsOnExecutor(task, e);
}
}
if ( matrixOptions.isThrottleMatrixConfigurations() && task instanceof MatrixConfiguration) {
for (Executor e : computer.getOneOffExecutors()) {
runCount += buildsOnExecutor(task, e);
}
Expand Down
@@ -1,3 +1,5 @@
ThrottleQueueTaskDispatcher.MaxCapacityOnNode=Already running {0} builds on node
ThrottleQueueTaskDispatcher.MaxCapacityTotal=Already running {0} builds across all nodes
ThrottleQueueTaskDispatcher.BuildPending=A build is pending launch
ThrottleQueueTaskDispatcher.BuildPending=A build is pending launch

ThrottleMatrixProjectOptions.DisplayName=Additional options for Matrix projects
Expand Up @@ -29,7 +29,11 @@
</j:forEach>
</f:entry>
</j:if>
<!--Specific options for Matrix projects-->
<j:if test="${descriptor.isMatrixProject(it)}">
<f:entry>
<f:property field="matrixOptions"/>
</f:entry>
</j:if>
</f:optionalBlock>
</j:jelly>


@@ -0,0 +1,8 @@
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:p="/lib/hudson/project">
<f:entry field="throttleMatrixBuilds" title="${%throttleMatrixBuilds.title}">
<f:checkbox default="${descriptor.defaults.throttleMatrixBuilds}"/>
</f:entry>
<f:entry field="throttleMatrixConfigurations" title="${%throttleMatrixConfigurations.title}">
<f:checkbox default="${descriptor.defaults.throttleMatrixConfigurations}"/>
</f:entry>
</j:jelly>
@@ -0,0 +1,2 @@
throttleMatrixBuilds.title= Throttle Matrix master builds
throttleMatrixConfigurations.title= Throttle Matrix configuration builds
Expand Up @@ -21,11 +21,11 @@ public void testGetCategoryProjects() throws Exception {
String alpha = "alpha", beta = "beta", gamma = "gamma"; // category names
FreeStyleProject p1 = createFreeStyleProject("p1");
FreeStyleProject p2 = createFreeStyleProject("p2");
p2.addProperty(new ThrottleJobProperty(1, 1, Arrays.asList(alpha), false, THROTTLE_OPTION_CATEGORY));
p2.addProperty(new ThrottleJobProperty(1, 1, Arrays.asList(alpha), false, THROTTLE_OPTION_CATEGORY, ThrottleMatrixProjectOptions.DEFAULT));
FreeStyleProject p3 = createFreeStyleProject("p3");
p3.addProperty(new ThrottleJobProperty(1, 1, Arrays.asList(alpha, beta), true, THROTTLE_OPTION_CATEGORY));
p3.addProperty(new ThrottleJobProperty(1, 1, Arrays.asList(alpha, beta), true, THROTTLE_OPTION_CATEGORY, ThrottleMatrixProjectOptions.DEFAULT));
FreeStyleProject p4 = createFreeStyleProject("p4");
p4.addProperty(new ThrottleJobProperty(1, 1, Arrays.asList(beta, gamma), true, THROTTLE_OPTION_CATEGORY));
p4.addProperty(new ThrottleJobProperty(1, 1, Arrays.asList(beta, gamma), true, THROTTLE_OPTION_CATEGORY, ThrottleMatrixProjectOptions.DEFAULT));
// TODO when core dep ≥1.480.3, add cloudbees-folder as a test dependency so we can check jobs inside folders
assertProjects(alpha, p3);
assertProjects(beta, p3, p4);
Expand Down Expand Up @@ -61,6 +61,4 @@ private static class RejectAllAuthorizationStrategy extends AuthorizationStrateg
return super.getACL(project);
}
}


}

0 comments on commit abd999b

Please sign in to comment.