-
Notifications
You must be signed in to change notification settings - Fork 116
add support for limiting job concurrency based on one or more build parameters #9
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
base: master
Are you sure you want to change the base?
Changes from all commits
766475a
931f183
75a1598
07d2291
65bc248
05b8ed7
0be7eff
d8d2017
8319b20
f4c0ce2
ed78fdb
805556e
a01b866
801200e
77ad29d
3421a91
7520a2c
12b7aac
1a32b7d
cc0da5a
5284d6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| *.iml | ||
| *.ipr | ||
| *.iws | ||
| .idea | ||
| target | ||
| work | ||
| # eclipse => | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,16 @@ | ||
| Work in progress on a plugin to dynamically allocate labels in order | ||
| to allow for throttling the number of concurrent builds of a project | ||
| allowed to run on a given node at one time. | ||
|
|
||
| Contributing | ||
| ------------ | ||
|
|
||
| ### Run / Debug cycle: | ||
|
|
||
| Execute `mvn hpi:run`. This will compile the plugin and launch a Jenkins instance on http://localhost:8080 | ||
|
|
||
| ### Create Package (.hpi): | ||
|
|
||
| Execute `mvn hpi:hpi`. This will create `throttle-concurrent.hpi` in the `target/` directory | ||
|
|
||
| For other mvn targets, see: https://jenkins-ci.org/maven-hpi-plugin/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,8 @@ public class ThrottleJobProperty extends JobProperty<AbstractProject<?,?>> { | |
| private List<String> categories; | ||
| private boolean throttleEnabled; | ||
| private String throttleOption; | ||
| private boolean limitOneJobWithMatchingParams; | ||
| private String limitOneJobByParams; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Store a list instead of the string to avoid multiple parsings. |
||
|
|
||
| /** | ||
| * Store a config version so we're able to migrate config on various | ||
|
|
@@ -49,12 +51,16 @@ public ThrottleJobProperty(Integer maxConcurrentPerNode, | |
| Integer maxConcurrentTotal, | ||
| List<String> categories, | ||
| boolean throttleEnabled, | ||
| String throttleOption) { | ||
| String throttleOption, | ||
| boolean limitOneJobWithMatchingParams, | ||
| String limitOneJobByParams) { | ||
| this.maxConcurrentPerNode = maxConcurrentPerNode == null ? 0 : maxConcurrentPerNode; | ||
| this.maxConcurrentTotal = maxConcurrentTotal == null ? 0 : maxConcurrentTotal; | ||
| this.categories = categories; | ||
| this.throttleEnabled = throttleEnabled; | ||
| this.throttleOption = throttleOption; | ||
| this.limitOneJobWithMatchingParams = limitOneJobWithMatchingParams; | ||
| this.limitOneJobByParams = limitOneJobByParams; | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -83,6 +89,7 @@ public Object readResolve() { | |
| maxConcurrentTotal = 0; | ||
| } | ||
| } | ||
|
|
||
| configVersion = 1L; | ||
|
|
||
| return this; | ||
|
|
@@ -109,6 +116,14 @@ public boolean getThrottleEnabled() { | |
| return throttleEnabled; | ||
| } | ||
|
|
||
| public boolean getlimitOneJobWithMatchingParams() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAIK, Stapler requires |
||
| return limitOneJobWithMatchingParams; | ||
| } | ||
|
|
||
| public String getLimitOneJobByParams() { | ||
| return limitOneJobByParams; | ||
| } | ||
|
|
||
| public String getThrottleOption() { | ||
| return throttleOption; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,16 +4,23 @@ | |
| import hudson.matrix.MatrixConfiguration; | ||
| import hudson.matrix.MatrixProject; | ||
| import hudson.model.AbstractProject; | ||
| import hudson.model.ParameterValue; | ||
| import hudson.model.Computer; | ||
| import hudson.model.Executor; | ||
| import hudson.model.Hudson; | ||
| import hudson.model.Node; | ||
| import hudson.model.Queue; | ||
| import hudson.model.Queue.Task; | ||
| import hudson.model.queue.WorkUnit; | ||
| import hudson.model.labels.LabelAtom; | ||
| import hudson.model.queue.CauseOfBlockage; | ||
| import hudson.model.queue.QueueTaskDispatcher; | ||
| import hudson.model.Action; | ||
| import hudson.model.ParametersAction; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import java.util.logging.Level; | ||
|
|
@@ -23,7 +30,8 @@ | |
| public class ThrottleQueueTaskDispatcher extends QueueTaskDispatcher { | ||
|
|
||
| @Override | ||
| public CauseOfBlockage canTake(Node node, Task task) { | ||
| public CauseOfBlockage canTake(Node node, Queue.BuildableItem item) { | ||
| Task task = item.task; | ||
| if (task instanceof MatrixConfiguration) { | ||
| return null; | ||
| } | ||
|
|
@@ -32,7 +40,6 @@ public CauseOfBlockage canTake(Node node, Task task) { | |
| if (tjp!=null && tjp.getThrottleEnabled()) { | ||
| CauseOfBlockage cause = canRun(task, tjp); | ||
| if (cause != null) return cause; | ||
|
|
||
| if (tjp.getThrottleOption().equals("project")) { | ||
| if (tjp.getMaxConcurrentPerNode().intValue() > 0) { | ||
| int maxConcurrentPerNode = tjp.getMaxConcurrentPerNode().intValue(); | ||
|
|
@@ -83,10 +90,14 @@ else if (tjp.getThrottleOption().equals("category")) { | |
| return null; | ||
| } | ||
|
|
||
| // @Override on jenkins 4.127+ , but still compatible with 1.399 | ||
| // @Override on jenkins 1.427+ , but still compatible with 1.399 | ||
| public CauseOfBlockage canRun(Queue.Item item) { | ||
| ThrottleJobProperty tjp = getThrottleJobProperty(item.task); | ||
| if (tjp!=null && tjp.getThrottleEnabled()) { | ||
| if (tjp.getlimitOneJobWithMatchingParams() && isAnotherBuildWithSameParametersRunningOnAnyNode(item)) { | ||
| LOGGER.info("A build with matching parameters is already running."); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isAnotherBuildWithSameParametersRunningOnAnyNode() has its internal logging of conflicts |
||
| return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_OnlyOneWithMatchingParameters()); | ||
| } | ||
| return canRun(item.task, tjp); | ||
| } | ||
| return null; | ||
|
|
@@ -147,6 +158,98 @@ else if (tjp.getThrottleOption().equals("category")) { | |
| return null; | ||
| } | ||
|
|
||
| private boolean isAnotherBuildWithSameParametersRunningOnAnyNode(Queue.Item item) { | ||
| if (isAnotherBuildWithSameParametersRunningOnNode(Hudson.getInstance(), item)) { | ||
| return true; | ||
| } | ||
|
|
||
| for (Node node : Hudson.getInstance().getNodes()) { | ||
| if (isAnotherBuildWithSameParametersRunningOnNode(node, item)) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| private boolean isAnotherBuildWithSameParametersRunningOnNode(Node node, Queue.Item item) { | ||
| ThrottleJobProperty tjp = getThrottleJobProperty(item.task); | ||
| Computer computer = node.toComputer(); | ||
| List<String> paramsToCompare = new ArrayList<String>(); | ||
| List<ParameterValue> itemParams = getParametersFromQueueItem(item); | ||
|
|
||
| if (tjp.getLimitOneJobByParams().length() > 0) { | ||
| paramsToCompare = Arrays.asList(tjp.getLimitOneJobByParams().split(",")); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It makes sense to add the parameter validation to UI form.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest to calculate the array and save it to a transient field once (on save/load) to decrease the performance impact |
||
| itemParams = doFilterParams(paramsToCompare, itemParams); | ||
| } | ||
|
|
||
| if (computer != null) { | ||
| for (Executor exec : computer.getExecutors()) { | ||
| if (item != null && item.task != null) { | ||
| // TODO: refactor into a nameEquals helper method | ||
| if (exec.getCurrentExecutable() != null && | ||
| exec.getCurrentExecutable().getParent() != null && | ||
| exec.getCurrentExecutable().getParent().getOwnerTask() != null && | ||
| exec.getCurrentExecutable().getParent().getOwnerTask().getName().equals(item.task.getName())) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| List<ParameterValue> executingUnitParams = getParametersFromWorkUnit(exec.getCurrentWorkUnit()); | ||
| executingUnitParams = doFilterParams(paramsToCompare, executingUnitParams); | ||
|
|
||
| if (executingUnitParams.containsAll(itemParams)) { | ||
| LOGGER.info("build (" + exec.getCurrentWorkUnit() + ") with identical parameters (" + | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I propose to decrease the logging level. Seems that the log message will totally spam logs on conflicts |
||
| executingUnitParams + ") is already running."); | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| // takes a String array containing a list of params, a List of ParameterValue objects | ||
| // and returns a new List<ParameterValue> with only the desired params in the list. | ||
| private List<ParameterValue> doFilterParams(List<String> params, List<ParameterValue> OriginalParams) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use Sun/Oracle Java code style to prevent complains from FindBugs and other utilities |
||
| if (params.isEmpty()) { | ||
| return OriginalParams; | ||
| } | ||
|
|
||
| List<ParameterValue> newParams = new ArrayList<ParameterValue>(); | ||
|
|
||
| for (ParameterValue p : OriginalParams) { | ||
| if (params.contains(p.getName())) { | ||
| newParams.add(p); | ||
| } | ||
| } | ||
| return newParams; | ||
| } | ||
|
|
||
| public List<ParameterValue> getParametersFromWorkUnit(WorkUnit unit) { | ||
| List<ParameterValue> paramsList = new ArrayList<ParameterValue>(); | ||
|
|
||
| if (unit != null && unit.context != null && unit.context.actions != null) { | ||
| List<Action> actions = unit.context.actions; | ||
| for (Action action : actions) { | ||
| if (action instanceof ParametersAction) { | ||
| ParametersAction params = (ParametersAction) action; | ||
| if (params != null) { | ||
| paramsList = params.getParameters(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return paramsList; | ||
| } | ||
|
|
||
| public List<ParameterValue> getParametersFromQueueItem(Queue.Item item) { | ||
| List<ParameterValue> paramsList = new ArrayList<ParameterValue>(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid the creation of objects, will be overridden soon. The else clause in |
||
|
|
||
| ParametersAction params = item.getAction(ParametersAction.class); | ||
| if (params != null) { | ||
| paramsList = params.getParameters(); | ||
| } | ||
| return paramsList; | ||
| } | ||
|
|
||
|
|
||
| private ThrottleJobProperty getThrottleJobProperty(Task task) { | ||
| if (task instanceof AbstractProject) { | ||
| AbstractProject<?,?> p = (AbstractProject<?,?>) task; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| 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 | ||
| ThrottleQueueTaskDispatcher.OnlyOneWithMatchingParameters=A build with matching parameters is already running |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <div> | ||
| <p>If this box is checked, only one instance of the job with matching parameters will be allowed to run at a given time. | ||
| Other instances of this job with different parameters will be allowed to run concurrently.</p> | ||
| <p>Optionally, provide a comma-separated list of parameters to use when comparing jobs. If blank, all parameters | ||
| must match for a job to be limited to one running instance.</p> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need to change the version? I don't see real dependencies from Jenkins core.
I also recommend to avoid 1.509.4 at least. See https://groups.google.com/forum/#!topic/jenkinsci-dev/9fMrUsVh6zU
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure, possibly. It's been a while since we started this patch and
I don't recall if we bumped version for a reason.
Would the proper approach to testing this be to simply revert the version
in the pom.xml and run the test suite?
thanks
On Fri, Apr 4, 2014 at 10:46 AM, Oleg Nenashev [email protected]:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test suites don't provide enough test coverage. BTW, it is better than nothing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Throttling by parameter doesnt work on 1.424 I will go through each LTS release between 1.424 and 1.509. Starting with 1.424.6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LTS 1.447 builds and works. Although the helpful message does not show up in the queue
LTS 1.466 builds and works and has the helpful message in the queue