提交 004c75c3 编写于 作者: K kohsuke

First stab at boolean expression over labels.

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@33617 71c3de6d-444a-0410-be80-ed276b4c234a
上级 09afdea4
......@@ -54,6 +54,18 @@ THE SOFTWARE.
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-injector</artifactId>
<version>1.0</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.kohsuke.stapler</groupId>
<artifactId>maven-stapler-plugin</artifactId>
......@@ -380,6 +392,13 @@ THE SOFTWARE.
<scope>test</scope>
</dependency>
<dependency><!-- compile-time only dependency to generate synthetic methods -->
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-injector</artifactId>
<version>1.0</version>
<optional>true</optional>
</dependency>
<dependency><!-- until we get this version through Stapler -->
<groupId>org.kohsuke.stapler</groupId>
<artifactId>json-lib</artifactId>
......
......@@ -32,35 +32,31 @@ import hudson.Util;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
import hudson.diagnosis.OldDataMonitor;
import hudson.slaves.WorkspaceList;
import hudson.model.Cause.LegacyCodeCause;
import hudson.model.Cause.UserCause;
import hudson.model.Cause.RemoteCause;
import hudson.model.Cause.UserCause;
import hudson.model.Descriptor.FormException;
import hudson.model.Fingerprint.RangeSet;
import hudson.model.RunMap.Constructor;
import hudson.model.Queue.WaitingItem;
import hudson.model.Queue.Executable;
import hudson.model.Queue.WaitingItem;
import hudson.model.RunMap.Constructor;
import hudson.model.queue.CauseOfBlockage;
import hudson.scm.ChangeLogSet;
import hudson.scm.ChangeLogSet.Entry;
import hudson.scm.NullSCM;
import hudson.scm.SCM;
import hudson.scm.SCMS;
import hudson.scm.PollingResult;
import hudson.scm.SCM;
import hudson.scm.SCMRevisionState;
import static hudson.scm.PollingResult.NO_CHANGES;
import static hudson.scm.PollingResult.BUILD_NOW;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import hudson.scm.SCMS;
import hudson.search.SearchIndexBuilder;
import hudson.security.Permission;
import hudson.slaves.WorkspaceList;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildTrigger;
import hudson.tasks.BuildWrapperDescriptor;
import hudson.tasks.Mailer;
import hudson.tasks.Publisher;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildWrapperDescriptor;
import hudson.triggers.SCMTrigger;
import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;
......@@ -72,14 +68,14 @@ import hudson.widgets.HistoryWidget;
import net.sf.json.JSONObject;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.stapler.ForwardToView;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.ForwardToView;
import javax.servlet.ServletException;
import java.io.File;
......@@ -102,6 +98,9 @@ import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import static hudson.scm.PollingResult.*;
import static javax.servlet.http.HttpServletResponse.*;
/**
* Base implementation of {@link Job}s that build software.
*
......@@ -286,6 +285,14 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
return Hudson.getInstance().getLabel(assignedNode);
}
/**
* Gets the textual representation of the assigned label as it was entered by the user.
*/
public String getAssignedLabelString() {
if (canRoam) return null;
return assignedNode;
}
/**
* Sets the assigned label.
*/
......@@ -1488,11 +1495,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
if(req.getParameter("hasSlaveAffinity")!=null) {
canRoam = false;
assignedNode = req.getParameter("slave");
if(assignedNode !=null) {
if(Hudson.getInstance().getLabel(assignedNode).isEmpty())
assignedNode = null; // no such label
}
assignedNode = req.getParameter("_.assignedLabelString");
} else {
canRoam = true;
assignedNode = null;
......@@ -1673,6 +1676,13 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
public boolean isApplicable(Descriptor descriptor) {
return true;
}
public FormValidation doCheckAssignedLabelString(@QueryParameter String value) {
// TODO: we can do a lot better than this
if (Hudson.getInstance().getLabel(value).isEmpty())
return FormValidation.warning("There's no slave/cloud that matches this assignment");
return FormValidation.ok();
}
}
/**
......
......@@ -66,6 +66,7 @@ import hudson.lifecycle.Lifecycle;
import hudson.logging.LogRecorderManager;
import hudson.lifecycle.RestartNotSupportedException;
import hudson.model.Descriptor.FormException;
import hudson.model.label.LabelAtom;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.SCMListener;
import hudson.model.listeners.SaveableListener;
......@@ -1357,24 +1358,36 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
return new ComputerSet();
}
/**
* Gets the label that exists on this system by the name.
*
* @return null if no name is null.
* @see Label#parse(String)
* @return null if name is null.
* @see Label#parseExpression(String) (String)
*/
public Label getLabel(String name) {
if(name==null) return null;
public Label getLabel(String expr) {
if(expr==null) return null;
while(true) {
Label l = labels.get(name);
Label l = labels.get(expr);
if(l!=null)
return l;
// non-existent
labels.putIfAbsent(name,new Label(name));
labels.putIfAbsent(expr,Label.parseExpression(expr));
}
}
/**
* Returns the label atom of the given name.
*/
public LabelAtom getLabelAtom(String name) {
if(name==null) return null;
Label l = getLabel(name);
if (l instanceof LabelAtom)
return (LabelAtom)l;
return null;
}
/**
* Gets all the active labels in the current system.
*/
......@@ -1387,6 +1400,15 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
return r;
}
public Set<LabelAtom> getLabelAtoms() {
Set<LabelAtom> r = new TreeSet<LabelAtom>();
for (Label l : labels.values()) {
if(!l.isEmpty() && l instanceof LabelAtom)
r.add((LabelAtom)l);
}
return r;
}
public Queue getQueue() {
return queue;
}
......@@ -2087,8 +2109,8 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
}
@Override
public Label getSelfLabel() {
return getLabel("master");
public LabelAtom getSelfLabel() {
return getLabelAtom("master");
}
public Computer createComputer() {
......
......@@ -25,8 +25,12 @@ package hudson.model;
import hudson.Util;
import static hudson.Util.fixNull;
import hudson.model.label.LabelAtom;
import hudson.model.label.LabelExpression;
import hudson.slaves.NodeProvisioner;
import hudson.slaves.Cloud;
import hudson.util.VariableResolver;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
......@@ -52,8 +56,8 @@ import com.thoughtworks.xstream.io.HierarchicalStreamReader;
* @see Hudson#getLabel(String)
*/
@ExportedBean
public class Label implements Comparable<Label>, ModelObject {
private final String name;
public abstract class Label implements Comparable<Label>, ModelObject {
protected final String name;
private volatile Set<Node> nodes;
private volatile Set<Cloud> clouds;
......@@ -92,6 +96,31 @@ public class Label implements Comparable<Label>, ModelObject {
return name;
}
/**
* Evaluates whether the label expression is true given the specified value assignment.
* IOW, returns true if the assignment provided by the resolver matches this label expression.
*/
public abstract boolean matches(VariableResolver<Boolean> resolver);
/**
* Evaluates whether the label expression is true when an entity owns the given set of
* {@link LabelAtom}s.
*/
public final boolean matches(final Collection<LabelAtom> labels) {
return matches(new VariableResolver<Boolean>() {
public Boolean resolve(String name) {
for (LabelAtom a : labels)
if (a.getName().equals(name))
return true;
return false;
}
});
}
public final boolean matches(Node n) {
return matches(n.getAssignedLabels());
}
/**
* Returns true if this label is a "self label",
* which means the label is the name of a {@link Node}.
......@@ -111,10 +140,10 @@ public class Label implements Comparable<Label>, ModelObject {
Set<Node> r = new HashSet<Node>();
Hudson h = Hudson.getInstance();
if(h.getAssignedLabels().contains(this))
if(this.matches(h))
r.add(h);
for (Node n : h.getNodes()) {
if(n.getAssignedLabels().contains(this))
if(this.matches(n))
r.add(n);
}
return this.nodes = Collections.unmodifiableSet(r);
......@@ -325,7 +354,7 @@ public class Label implements Comparable<Label>, ModelObject {
}
public boolean canConvert(Class type) {
return type==Label.class;
return Label.class.isAssignableFrom(type);
}
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
......@@ -348,12 +377,12 @@ public class Label implements Comparable<Label>, ModelObject {
* so that the caller can add more to the set.
* @since 1.308
*/
public static Set<Label> parse(String labels) {
Set<Label> r = new TreeSet<Label>();
public static Set<LabelAtom> parse(String labels) {
Set<LabelAtom> r = new TreeSet<LabelAtom>();
labels = fixNull(labels);
if(labels.length()>0)
for( String l : labels.split(" +"))
r.add(Hudson.getInstance().getLabel(l));
r.add(Hudson.getInstance().getLabelAtom(l));
return r;
}
......@@ -363,4 +392,20 @@ public class Label implements Comparable<Label>, ModelObject {
public static Label get(String l) {
return Hudson.getInstance().getLabel(l);
}
/**
* Parses the expression into a label expression tree.
*
* TODO: replace this with a real parser later
*/
public static Label parseExpression(String labelExpression) {
Label l = null;
for (String atom : labelExpression.split("&&")) {
atom = atom.trim();
Label r = new LabelAtom(atom);
if (l==null) l = r;
else l = new LabelExpression.And(l,r);
}
return l;
}
}
......@@ -26,6 +26,7 @@ package hudson.model;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.model.label.LabelAtom;
import java.util.Collection;
......@@ -56,5 +57,5 @@ public abstract class LabelFinder implements ExtensionPoint {
* @return
* A set of labels for the node. Can be empty but never null.
*/
public abstract Collection<Label> findLabels(Node node);
public abstract Collection<LabelAtom> findLabels(Node node);
}
package hudson.model;
/**
* Atomic single token label, like "foo" or "bar".
*
* @author Kohsuke Kawaguchi
* @since 1.COMPOSITELABEL
*/
public class LabelToken extends Label {
public LabelToken(String name) {
super(name);
}
}
......@@ -23,11 +23,13 @@
*/
package hudson.model;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import hudson.ExtensionPoint;
import hudson.FilePath;
import hudson.FileSystemProvisioner;
import hudson.Launcher;
import hudson.model.Queue.Task;
import hudson.model.label.LabelAtom;
import hudson.model.queue.CauseOfBlockage;
import hudson.node_monitors.NodeMonitor;
import hudson.remoting.VirtualChannel;
......@@ -165,9 +167,9 @@ public abstract class Node extends AbstractModelObject implements Describable<No
/**
* Return the possibly empty tag cloud for the labels of this node.
*/
public TagCloud<Label> getLabelCloud() {
return new TagCloud<Label>(getAssignedLabels(),new WeightFunction<Label>() {
public float weight(Label item) {
public TagCloud<LabelAtom> getLabelCloud() {
return new TagCloud<LabelAtom>(getAssignedLabels(),new WeightFunction<LabelAtom>() {
public float weight(LabelAtom item) {
return item.getTiedJobs().size();
}
});
......@@ -183,8 +185,8 @@ public abstract class Node extends AbstractModelObject implements Describable<No
* connecting.
*/
@Exported
public Set<Label> getAssignedLabels() {
Set<Label> r = Label.parse(getLabelString());
public Set<LabelAtom> getAssignedLabels() {
Set<LabelAtom> r = Label.parse(getLabelString());
r.add(getSelfLabel());
r.addAll(getDynamicLabels());
return Collections.unmodifiableSet(r);
......@@ -196,12 +198,14 @@ public abstract class Node extends AbstractModelObject implements Describable<No
* the results into Labels.
* @return HashSet<Label>.
*/
private final HashSet<Label> getDynamicLabels() {
HashSet<Label> result = new HashSet<Label>();
for (LabelFinder labeler : LabelFinder.all())
private HashSet<LabelAtom> getDynamicLabels() {
HashSet<LabelAtom> result = new HashSet<LabelAtom>();
for (LabelFinder labeler : LabelFinder.all()) {
// Filter out any bad(null) results from plugins
// for compatibility reasons, findLabels may return LabelExpression and not atom.
for (Label label : labeler.findLabels(this))
if (label != null) result.add(label);
if (label instanceof LabelAtom) result.add((LabelAtom)label);
}
return result;
}
......@@ -219,8 +223,9 @@ public abstract class Node extends AbstractModelObject implements Describable<No
/**
* Gets the special label that represents this node itself.
*/
public Label getSelfLabel() {
return Label.get(getNodeName());
@WithBridgeMethods(Label.class)
public LabelAtom getSelfLabel() {
return LabelAtom.get(getNodeName());
}
/**
......
......@@ -257,7 +257,7 @@ public abstract class View extends AbstractModelObject implements AccessControll
for (Computer c: computers) {
Node n = c.getNode();
if (c != null) {
if (n != null) {
if (roam && n.getMode() == Mode.NORMAL || !Collections.disjoint(n.getAssignedLabels(), labels)) {
result.add(c);
}
......
package hudson.model.label;
import hudson.model.Hudson;
import hudson.model.Label;
import hudson.util.VariableResolver;
/**
* Atomic single token label, like "foo" or "bar".
*
* @author Kohsuke Kawaguchi
* @since 1.COMPOSITELABEL
*/
public class LabelAtom extends Label {
public LabelAtom(String name) {
super(name);
}
@Override
public boolean matches(VariableResolver<Boolean> resolver) {
return resolver.resolve(name);
}
/**
* Obtains an atom by its {@linkplain #getName() name}.
*/
public static LabelAtom get(String l) {
return Hudson.getInstance().getLabelAtom(l);
}
}
package hudson.model;
package hudson.model.label;
import hudson.model.Label;
import hudson.util.VariableResolver;
/**
* Boolean expression of labels.
......@@ -7,9 +10,22 @@ package hudson.model;
* @since 1.COMPOSITELABEL
*/
public abstract class LabelExpression extends Label {
protected LabelExpression(String name) {
super(name);
}
public static final class And extends LabelExpression {
private final Label lhs,rhs;
public And(Label lhs, Label rhs) {
super(lhs.getName()+"&&"+rhs.getName());
this.lhs = lhs;
this.rhs = rhs;
}
@Override
public boolean matches(VariableResolver<Boolean> resolver) {
return lhs.matches(resolver) && rhs.matches(resolver);
}
}
}
......@@ -49,7 +49,7 @@ public abstract class CauseOfBlockage {
}
/**
* Build is blocked because all the nodes in a given label is offline.
* Build is blocked because all the nodes that match a given label is offline.
*/
public static final class BecauseLabelIsOffline extends CauseOfBlockage {
public final Label label;
......@@ -79,7 +79,7 @@ public abstract class CauseOfBlockage {
}
/**
* Build is blocked because a node is fully busy
* Build is blocked because everyone that matches the specified label is fully busy
*/
public static final class BecauseLabelIsBusy extends CauseOfBlockage {
public final Label label;
......
......@@ -49,15 +49,8 @@ THE SOFTWARE.
<j:if test="${app.labels.size() gt 1 || (it.assignedLabel!=null and it.assignedLabel!=app.selfLabel)}">
<f:optionalBlock name="hasSlaveAffinity" title="${%Tie this project to a node}" checked="${it.assignedLabel!=null}"
help="/help/project-config/slave.html">
<f:entry title="${%Node}">
<select class="setting-input" name="slave">
<j:forEach var="s" items="${app.labels}">
<j:if test="${s.isAssignable()}">
<f:option selected="${s==it.assignedLabel}" value="${s.name}"
>${s.name} <j:if test="${!empty(s.description)}">(${s.description})</j:if></f:option>
</j:if>
</j:forEach>
</select>
<f:entry title="${%Node}" field="assignedLabelString">
<f:textbox />
</f:entry>
</f:optionalBlock>
</j:if>
......
......@@ -23,6 +23,7 @@
*/
package hudson.slaves;
import hudson.model.label.LabelAtom;
import junit.framework.TestCase;
import hudson.model.Hudson;
import hudson.model.Node;
......@@ -78,7 +79,7 @@ public class NodeListTest extends TestCase {
throw new UnsupportedOperationException();
}
public Set<Label> getAssignedLabels() {
public Set<LabelAtom> getAssignedLabels() {
throw new UnsupportedOperationException();
}
......@@ -86,10 +87,6 @@ public class NodeListTest extends TestCase {
throw new UnsupportedOperationException();
}
public Set<Label> getDynamicLabels() {
throw new UnsupportedOperationException();
}
public FilePath getWorkspaceFor(TopLevelItem item) {
throw new UnsupportedOperationException();
}
......
......@@ -51,6 +51,15 @@ import org.apache.commons.io.FileUtils;
* @author Kohsuke Kawaguchi
*/
public class AbstractProjectTest extends HudsonTestCase {
public void testConfigRoundtrip() throws Exception {
FreeStyleProject project = createFreeStyleProject();
Label l = hudson.getLabel("foo && bar");
project.setAssignedLabel(l);
configRoundtrip(project);
assertEquals(l,project.getAssignedLabel());
}
/**
* Tests the workspace deletion.
*/
......
package hudson.tasks;
import hudson.EnvVars;
import hudson.model.label.LabelAtom;
import hudson.tools.ToolProperty;
import hudson.maven.MavenModuleSet;
import hudson.maven.MavenModuleSetBuild;
......@@ -48,8 +49,8 @@ public class EnvVarsInConfigTasksTest extends HudsonTestCase {
// create slaves
EnvVars additionalEnv = new EnvVars(DUMMY_LOCATION_VARNAME, "");
slaveEnv = createSlave(new Label("slaveEnv"), additionalEnv);
slaveRegular = createSlave(new Label("slaveRegular"));
slaveEnv = createSlave(new LabelAtom("slaveEnv"), additionalEnv);
slaveRegular = createSlave(new LabelAtom("slaveRegular"));
}
private String withVariable(String s) {
......
......@@ -26,6 +26,7 @@ package hudson.tools;
import hudson.Functions;
import hudson.model.*;
import hudson.model.label.LabelAtom;
import hudson.slaves.DumbSlave;
import hudson.tasks.Maven;
import hudson.tasks.BatchFile;
......@@ -182,7 +183,7 @@ public class ToolLocationNodePropertyTest extends HudsonTestCase {
// so empty path breaks the test.
env.put("PATH", "/bin:/usr/bin");
env.put("M2_HOME", "empty");
slave = createSlave(new Label("slave"), env);
slave = createSlave(new LabelAtom("slave"), env);
project = createFreeStyleProject();
project.setAssignedLabel(slave.getSelfLabel());
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册