Skip to content

Commit

Permalink
Merge pull request #5 from ssogabe/JENKINS-9260.
Browse files Browse the repository at this point in the history
Jenkins 9260 promote plugin should provide ability to select slave node to run
  • Loading branch information
ssogabe committed May 4, 2011
2 parents 47e71b8 + 0250946 commit 2abeca2
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 3 deletions.
76 changes: 76 additions & 0 deletions src/main/java/hudson/plugins/promoted_builds/JobPropertyImpl.java
@@ -1,10 +1,12 @@
package hudson.plugins.promoted_builds;

import antlr.ANTLRException;
import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.AutoCompletionCandidates;
import hudson.model.BuildListener;
import hudson.model.Descriptor;
import hudson.model.Failure;
Expand All @@ -15,6 +17,7 @@
import hudson.model.Job;
import hudson.model.JobProperty;
import hudson.model.JobPropertyDescriptor;
import hudson.model.Label;
import hudson.tasks.BuildStep;
import hudson.util.FormValidation;
import net.sf.json.JSONArray;
Expand All @@ -29,6 +32,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.kohsuke.stapler.QueryParameter;

/**
Expand Down Expand Up @@ -326,6 +330,78 @@ public FormValidation doCheckName(@QueryParameter String name) {

return FormValidation.ok();
}

public FormValidation doCheckLabelString(@QueryParameter String value) {
if (Util.fixEmpty(value)==null)
return FormValidation.ok(); // nothing typed yet
try {
Label.parseExpression(value);
} catch (ANTLRException e) {
return FormValidation.error(e,
Messages.JobPropertyImpl_LabelString_InvalidBooleanExpression(e.getMessage()));
}
// TODO: if there's an atom in the expression that is empty, report it
if (Hudson.getInstance().getLabel(value).isEmpty())
return FormValidation.warning(Messages.JobPropertyImpl_LabelString_NoMatch());
return FormValidation.ok();
}

public AutoCompletionCandidates doAutoCompleteAssignedLabelString(@QueryParameter String value) {
AutoCompletionCandidates c = new AutoCompletionCandidates();
Set<Label> labels = Hudson.getInstance().getLabels();
List<String> queries = new AutoCompleteSeeder(value).getSeeds();

for (String term : queries) {
for (Label l : labels) {
if (l.getName().startsWith(term)) {
c.add(l.getName());
}
}
}
return c;
}

/**
* Utility class for taking the current input value and computing a list
* of potential terms to match against the list of defined labels.
*/
static class AutoCompleteSeeder {

private String source;
private Pattern quoteMatcher = Pattern.compile("(\\\"?)(.+?)(\\\"?+)(\\s*)");

AutoCompleteSeeder(String source) {
this.source = source;
}

List<String> getSeeds() {
ArrayList<String> terms = new ArrayList();
boolean trailingQuote = source.endsWith("\"");
boolean leadingQuote = source.startsWith("\"");
boolean trailingSpace = source.endsWith(" ");

if (trailingQuote || (trailingSpace && !leadingQuote)) {
terms.add("");
} else {
if (leadingQuote) {
int quote = source.lastIndexOf('"');
if (quote == 0) {
terms.add(source.substring(1));
} else {
terms.add("");
}
} else {
int space = source.lastIndexOf(' ');
if (space > -1) {
terms.add(source.substring(space + 1));
} else {
terms.add(source);
}
}
}

return terms;
}
}
}
}
38 changes: 35 additions & 3 deletions src/main/java/hudson/plugins/promoted_builds/PromotionProcess.java
@@ -1,5 +1,7 @@
package hudson.plugins.promoted_builds;

import antlr.ANTLRException;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
Expand All @@ -17,6 +19,8 @@
import hudson.model.Queue.Item;
import hudson.model.Run;
import hudson.model.Saveable;
import hudson.model.labels.LabelAtom;
import hudson.model.labels.LabelExpression;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
Expand Down Expand Up @@ -48,7 +52,12 @@ public final class PromotionProcess extends AbstractProject<PromotionProcess,Pro
* and ${rootURL}/plugin/promoted-builds/icons/32x32/, e.g. <code>"star-gold"</code>.
*/
public String icon;


/**
* The label that promotion process can be run on.
*/
public String assignedLabel;

private List<BuildStep> buildSteps = new ArrayList<BuildStep>();

/*package*/ PromotionProcess(JobPropertyImpl property, String name) {
Expand All @@ -62,6 +71,12 @@ public final class PromotionProcess extends AbstractProject<PromotionProcess,Pro
buildSteps = (List)Descriptor.newInstancesFromHeteroList(
req, c, "buildStep", (List) PromotionProcess.getAll());
icon = c.getString("icon");
if (c.has("hasAssignedLabel")) {
JSONObject j = c.getJSONObject("hasAssignedLabel");
assignedLabel = Util.fixEmptyAndTrim(j.getString("labelString"));
} else {
assignedLabel = null;
}
save();
}

Expand Down Expand Up @@ -100,7 +115,7 @@ public PromotionCondition getPromotionCondition(String promotionClassName) {

return null;
}

public DescribableList<Publisher, Descriptor<Publisher>> getPublishersList() {
// TODO: extract from the buildsSteps field? Or should I separate builders and publishers?
return new DescribableList<Publisher,Descriptor<Publisher>>(this);
Expand All @@ -114,10 +129,27 @@ public List<BuildStep> getBuildSteps() {
return buildSteps;
}

/**
* Gets the textual representation of the assigned label as it was entered by the user.
*/
@Override
public String getAssignedLabelString() {
if (assignedLabel == null) return null;
try {
LabelExpression.parseExpression(assignedLabel);
return assignedLabel;
} catch (ANTLRException e) {
// must be old label or host name that includes whitespace or other unsafe chars
return LabelAtom.escape(assignedLabel);
}
}

@Override public Label getAssignedLabel() {
// Really would like to run on the exact node that the promoted build ran on,
// not just the same label.. but at least this works if job is tied to one node:
return getOwner().getAssignedLabel();
if (assignedLabel == null) return getOwner().getAssignedLabel();

return Hudson.getInstance().getLabel(assignedLabel);
}

@Override public JDK getJDK() {
Expand Down
Expand Up @@ -18,6 +18,15 @@
<f:option selected="${s.icon=='star-red'}" value="star-red">Red star</f:option>
</select>
</f:entry>

<f:optionalBlock name="hasAssignedLabel" title="${%Restrict where this promotiom process can be run}"
checked="${s.assignedLabelString!=null}">
<f:entry title="${%Label Expression}" field="labelString"
description="If not set, the label of the promoted build will be used.">
<f:textbox autoCompleteDelimChar=" " value="${s.assignedLabelString}" />
</f:entry>
</f:optionalBlock>

<f:section title="Criteria">
<f:nested>
<table style="width:100%">
Expand Down
@@ -0,0 +1,53 @@
<div>
If you want to always run this promotion process on a specific node/slave, just specify its name.
If not specified, the same label which the promoted build used can be used.
This works well when you have a small number of nodes.

<p>
As the size of the cluster grows, it becomes useful not to tie projects to specific slaves,
as it hurts resource utilization when slaves may come and go. For such situation, assign labels
to slaves to classify their capabilities and characteristics, and specify a boolean expression
over those labels to decide where to run.

<h3>Valid Operators</h3>
<p>
The following operators are supported, in the order of precedence.
<dl>
<dt>(expr)</dt>
<dd>parenthesis</dd>

<dt>!expr</dt>
<dd>negation</dd>

<dt>expr&amp;&amp;expr</dt>
<dd>
and
</dd>

<dt>expr||expr</dt>
<dd>
or
</dd>

<dt>a -> b</dt>
<dd>
"implies" operator. Equivalent to <tt>!a|b</tt>.
For example, <tt>windows->x64</tt> could be thought of as "if run on a Windows slave,
that slave must be 64bit." It still allows Jenkins to run this build on linux.
</dd>

<dt>a &lt;-> b</dt>
<dd>
"if and only if" operator. Equivalent to <tt>a&amp;&amp;b || !a&amp;&amp;!b</tt>.
For example, <tt>windows->sfbay</tt> could be thought of as "if run on a Windows slave,
that slave must be in the SF bay area, but if not on Windows, it must not be in the bay area."
</dd>
</dl>
<p>
All operators are left-associative (i.e., a->b->c &lt;-> (a->b)->c )
An expression can contain whitespace for better readability, and it'll be ignored.

<p>
Label names or slave names can be quoted if they contain unsafe characters. For example,
<tt>"jenkins-solaris (Solaris)" || "Windows 2008"</tt>
</div>
Expand Up @@ -22,7 +22,12 @@

PromotionProcess.PermalinkDisplayName=Latest promotion:{0}
JobPropertyImpl.ValidateRequired=Required
JobPropertyImpl.LabelString.InvalidBooleanExpression=\
Invalid boolean expression: {0}
JobPropertyImpl.LabelString.NoMatch=\
There's no slave/cloud that matches this assignment
KeepBuildForEverAction.descriptor.displayName=Keep Build Forever
KeepBuildForEverAction.console.notPromotion=This build is not a promotion, how did we get here? Not keeping build.
KeepBuildForEverAction.console.promotionNotGoodEnough=Promotion build result [{0}] is not good enough. Not keeping build.
KeepBuildForEverAction.console.keepingBuild=Marking build to keep forever.

0 comments on commit 2abeca2

Please sign in to comment.