Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JENKINS-12092] Block job by category #25

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,22 @@
import hudson.matrix.MatrixProject;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;

import jenkins.model.Jenkins;
import net.sf.json.JSONObject;

import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
Expand Down Expand Up @@ -322,9 +325,10 @@ public static final class ThrottleCategory extends AbstractDescribableImpl<Throt
private Integer maxConcurrentPerNode;
private Integer maxConcurrentTotal;
private String categoryName;
private String blockingCategories;
private List<String> blockingCategoriesList = new ArrayList<String>();
private List<NodeLabeledPair> nodeLabeledPairs;

@DataBoundConstructor
public ThrottleCategory(String categoryName,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please save the previous constructor as well. Otherwise the change affects the binary compatibility with previous versions

Integer maxConcurrentPerNode,
Integer maxConcurrentTotal,
Expand All @@ -335,7 +339,26 @@ public ThrottleCategory(String categoryName,
this.nodeLabeledPairs =
nodeLabeledPairs == null ? new ArrayList<NodeLabeledPair>() : nodeLabeledPairs;
}

@DataBoundConstructor
public ThrottleCategory(String categoryName,
Integer maxConcurrentPerNode,
Integer maxConcurrentTotal,
String blockingCategories,
List<NodeLabeledPair> nodeLabeledPairs) {
this(categoryName, maxConcurrentPerNode, maxConcurrentTotal, nodeLabeledPairs);
this.blockingCategories = blockingCategories;
convertCategoriesToList(blockingCategories);
}

private void convertCategoriesToList(String categoriesString) {
String[] catArray = StringUtils.split(categoriesString, ", ");
catArray = StringUtils.stripAll(catArray);
if (catArray != null) {
Collections.addAll(blockingCategoriesList, catArray);
}
}

public Integer getMaxConcurrentPerNode() {
if (maxConcurrentPerNode == null)
maxConcurrentPerNode = 0;
Expand All @@ -354,6 +377,17 @@ public String getCategoryName() {
return categoryName;
}

public String getBlockingCategories() {
return blockingCategories;
}

public List<String> getBlockingCategoriesList() {
if (blockingCategoriesList == null)
blockingCategoriesList = new ArrayList<String>();

return blockingCategoriesList;
}

public List<NodeLabeledPair> getNodeLabeledPairs() {
if (nodeLabeledPairs == null)
nodeLabeledPairs = new ArrayList<NodeLabeledPair>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package hudson.plugins.throttleconcurrents;


import hudson.Extension;
import hudson.matrix.MatrixConfiguration;
import hudson.matrix.MatrixProject;
Expand All @@ -13,14 +14,18 @@
import hudson.model.labels.LabelAtom;
import hudson.model.queue.CauseOfBlockage;
import hudson.model.queue.QueueTaskDispatcher;
import hudson.plugins.throttleconcurrents.ThrottleJobProperty.ThrottleCategory;

import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

import org.apache.commons.lang.StringUtils;

@Extension
public class ThrottleQueueTaskDispatcher extends QueueTaskDispatcher {

Expand Down Expand Up @@ -153,6 +158,11 @@ else if (tjp.getThrottleOption().equals("category")) {

// Double check category itself isn't null
if (category != null) {
// Check if this job is blocked by category
if (isBlockedByCategories(category.getBlockingCategoriesList())) {
return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_BlockedByCategory());
}

if (category.getMaxConcurrentTotal().intValue() > 0) {
int maxConcurrentTotal = category.getMaxConcurrentTotal().intValue();
int totalRunCount = 0;
Expand All @@ -178,6 +188,32 @@ else if (tjp.getThrottleOption().equals("category")) {
return null;
}

private boolean isBlockedByCategories(List<String> categoryNames) {
for (String catName : categoryNames) {
if (areProjectsInCategoryBuilding(catName)) {
return true;
}
}
return false;
}

private boolean areProjectsInCategoryBuilding(String categoryName) {
List<AbstractProject<?, ?>> projectsInCategory = ThrottleJobProperty.getCategoryProjects(categoryName);
for (AbstractProject<?, ?> project : projectsInCategory) {
if (isProjectBuilding(project)) {
return true;
}
}
return false;
}

private boolean isProjectBuilding(AbstractProject<?, ?> project) {
if (project.isBuilding() || project.isInQueue()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

project.isBuilding() is unreliable for parallel runs; project.isInQueue() causes a full queue lookup with a serious performance impact on cores till 1.610

return true;
}
return false;
}

@CheckForNull
private ThrottleJobProperty getThrottleJobProperty(Task task) {
if (task instanceof AbstractProject) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
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.BlockedByCategory=Build is blocked by jobs in another category

ThrottleMatrixProjectOptions.DisplayName=Additional options for Matrix projects
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
<f:entry title="${%Maximum Concurrent Builds Per Node}" field="maxConcurrentPerNode">
<f:textbox />
</f:entry>
<f:entry title="${%Blocking Categories}" field="blockingCategories">
<f:textbox />
</f:entry>
</table>
<f:repeatable field="nodeLabeledPairs" add="${%Add Maximum Per Labeled Node}" minimum="0" header="${%Maximum Per Labeled Node}">
<table width="100%">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ public class ThrottleCategoryTest
private static final String testCategoryName = "aCategory";

@Test
public void shouldGetEmptyNodeLabeledPairsListUponInitialNull()
public void shouldGetEmptyValuesUponInitialNull()
{
ThrottleJobProperty.ThrottleCategory category =
new ThrottleJobProperty.ThrottleCategory(testCategoryName, 0, 0, null);
new ThrottleJobProperty.ThrottleCategory(testCategoryName, 0, 0, null, null);
assertTrue("nodeLabeledPairs shall be empty", category.getNodeLabeledPairs().isEmpty());
assertTrue("blockingCategories shall be empty", category.getBlockingCategoriesList().isEmpty());
assertNull("blockingCategories shall be empty", category.getBlockingCategories());
}

@Test
Expand All @@ -47,7 +49,7 @@ public void shouldGetNonEmptyNodeLabeledPairsListThatWasSet()
Integer expectedMax = new Integer(1);

ThrottleJobProperty.ThrottleCategory category =
new ThrottleJobProperty.ThrottleCategory(testCategoryName, 0, 0, null);
new ThrottleJobProperty.ThrottleCategory(testCategoryName, 0, 0, "", null);
List<ThrottleJobProperty.NodeLabeledPair> nodeLabeledPairs = category.getNodeLabeledPairs();
nodeLabeledPairs.add(new ThrottleJobProperty.NodeLabeledPair(expectedLabel, expectedMax));

Expand All @@ -59,4 +61,26 @@ public void shouldGetNonEmptyNodeLabeledPairsListThatWasSet()
assertEquals("maxConcurrentPerNodeLabeled "+actualMax+" does not match expected "+expectedMax,
expectedMax, actualMax);
}

@Test
public void shouldGetNonEmptyBlockCategoriesListThatWasCreated()
{
ThrottleJobProperty.ThrottleCategory categoryOne = new ThrottleJobProperty.ThrottleCategory(testCategoryName, 0, 0, "catA", null);
assertEquals("blockingCategories should have one entry", 1, categoryOne.getBlockingCategoriesList().size());
assertEquals("blockingCategory name should match", categoryOne.getBlockingCategoriesList().get(0), "catA");

ThrottleJobProperty.ThrottleCategory categoryTwo = new ThrottleJobProperty.ThrottleCategory(testCategoryName, 0, 0, "catA, ", null);
assertEquals("blockingCategories should have one entry", 1, categoryTwo.getBlockingCategoriesList().size());
assertEquals("blockingCategory name should match", categoryTwo.getBlockingCategoriesList().get(0), "catA");

ThrottleJobProperty.ThrottleCategory categoryThree = new ThrottleJobProperty.ThrottleCategory(testCategoryName, 0, 0, "catA,catB", null);
assertEquals("blockingCategories should not be empty", 2, categoryThree.getBlockingCategoriesList().size());
assertEquals("blockingCategory name should match", categoryThree.getBlockingCategoriesList().get(0), "catA");
assertEquals("blockingCategory name should match", categoryThree.getBlockingCategoriesList().get(1), "catB");

ThrottleJobProperty.ThrottleCategory categoryFour = new ThrottleJobProperty.ThrottleCategory(testCategoryName, 0, 0, "catA, catB,", null);
assertEquals("blockingCategories should not be empty", 2, categoryFour.getBlockingCategoriesList().size());
assertEquals("blockingCategory name should match", categoryFour.getBlockingCategoriesList().get(0), "catA");
assertEquals("blockingCategory name should match", categoryFour.getBlockingCategoriesList().get(1), "catB");
}
}