/* * The MIT License * * Copyright 2013 Red Hat, 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.model; import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; import com.gargoylesoftware.htmlunit.HttpMethod; import com.gargoylesoftware.htmlunit.Page; import com.gargoylesoftware.htmlunit.WebRequest; import hudson.EnvVars; import hudson.FilePath; import hudson.maven.MavenModuleSet; import hudson.model.Node.Mode; import hudson.model.Queue.WaitingItem; import hudson.model.labels.*; import hudson.model.queue.CauseOfBlockage; import hudson.security.ACL; import hudson.security.GlobalMatrixAuthorizationStrategy; import hudson.security.HudsonPrivateSecurityRealm; import hudson.security.Permission; import hudson.slaves.ComputerListener; import hudson.slaves.DumbSlave; import hudson.slaves.NodeProperty; import hudson.slaves.OfflineCause; import hudson.slaves.OfflineCause.ByCLI; import hudson.slaves.OfflineCause.UserCause; import hudson.util.TagCloud; import java.net.HttpURLConnection; import java.util.*; import java.util.concurrent.Callable; import jenkins.model.Jenkins; import jenkins.security.QueueItemAuthenticatorConfiguration; import org.acegisecurity.context.SecurityContextHolder; import static org.junit.Assert.*; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.RunLoadCounter; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.MockQueueItemAuthenticator; import org.jvnet.hudson.test.TestExtension; /** * * @author Lucie Votypkova */ public class NodeTest { @Rule public JenkinsRule j = new JenkinsRule(); public static boolean addDynamicLabel = false; public static boolean notTake = false; @Before public void before(){ addDynamicLabel = false; notTake = false; j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); } @Test public void testSetTemporaryOfflineCause() throws Exception { Node node = j.createOnlineSlave(); FreeStyleProject project = j.createFreeStyleProject(); project.setAssignedLabel(j.jenkins.getLabel(node.getDisplayName())); OfflineCause cause = new ByCLI("message"); node.setTemporaryOfflineCause(cause); for(ComputerListener l : ComputerListener.all()){ l.onOnline(node.toComputer(), TaskListener.NULL); } assertEquals("Node should have offline cause which was set.", cause, node.toComputer().getOfflineCause()); OfflineCause cause2 = new ByCLI("another message"); node.setTemporaryOfflineCause(cause2); assertEquals("Node should have original offline cause after setting another.", cause, node.toComputer().getOfflineCause()); } @Test public void testOfflineCause() throws Exception { Node node = j.createOnlineSlave(); Computer computer = node.toComputer(); OfflineCause.UserCause cause; final User someone = User.get("someone@somewhere.com"); ACL.impersonate(someone.impersonate()); computer.doToggleOffline("original message"); cause = (UserCause) computer.getOfflineCause(); assertTrue(cause.toString(), cause.toString().matches("^.*?Disconnected by someone@somewhere.com : original message")); assertEquals(someone, cause.getUser()); final User root = User.get("root@localhost"); ACL.impersonate(root.impersonate()); computer.doChangeOfflineCause("new message"); cause = (UserCause) computer.getOfflineCause(); assertTrue(cause.toString(), cause.toString().matches("^.*?Disconnected by root@localhost : new message")); assertEquals(root, cause.getUser()); computer.doToggleOffline(null); assertNull(computer.getOfflineCause()); } @Test public void testGetLabelCloud() throws Exception { Node node = j.createOnlineSlave(); node.setLabelString("label1 label2"); FreeStyleProject project = j.createFreeStyleProject(); final Label label = j.jenkins.getLabel("label1"); project.setAssignedLabel(label); label.reset(); // Make sure cached value is not used TagCloud<LabelAtom> cloud = node.getLabelCloud(); for(int i =0; i< cloud.size(); i ++){ TagCloud.Entry e = cloud.get(i); if(e.item.equals(label)){ assertEquals("Label label1 should have one tied project.", 1, e.weight, 0); } else{ assertEquals("Label " + e.item + " should not have any tied project.", 0, e.weight, 0); } } } @Test public void testGetAssignedLabels() throws Exception { Node node = j.createOnlineSlave(); node.setLabelString("label1 label2"); LabelAtom notContained = j.jenkins.getLabelAtom("notContained"); addDynamicLabel = true; assertTrue("Node should have label1.", node.getAssignedLabels().contains(j.jenkins.getLabelAtom("label1"))); assertTrue("Node should have label2.", node.getAssignedLabels().contains(j.jenkins.getLabelAtom("label2"))); assertTrue("Node should have dynamicly added dynamicLabel.", node.getAssignedLabels().contains(j.jenkins.getLabelAtom("dynamicLabel"))); assertFalse("Node should not have label notContained.", node.getAssignedLabels().contains(notContained)); assertTrue("Node should have self label.", node.getAssignedLabels().contains(node.getSelfLabel())); } @Test public void testCanTake() throws Exception { Node node = j.createOnlineSlave(); node.setLabelString("label1 label2"); FreeStyleProject project = j.createFreeStyleProject(); project.setAssignedLabel(j.jenkins.getLabel("label1")); FreeStyleProject project2 = j.createFreeStyleProject(); FreeStyleProject project3 = j.createFreeStyleProject(); project3.setAssignedLabel(j.jenkins.getLabel("notContained")); Queue.BuildableItem item = new Queue.BuildableItem(new WaitingItem(new GregorianCalendar(), project, new ArrayList<Action>())); Queue.BuildableItem item2 = new Queue.BuildableItem(new WaitingItem(new GregorianCalendar(), project2, new ArrayList<Action>())); Queue.BuildableItem item3 = new Queue.BuildableItem(new WaitingItem(new GregorianCalendar(), project3, new ArrayList<Action>())); assertNull("Node should take project which is assigned to its label.", node.canTake(item)); assertNull("Node should take project which is assigned to its label.", node.canTake(item2)); assertNotNull("Node should not take project which is not assigned to its label.", node.canTake(item3)); String message = Messages._Node_LabelMissing(node.getNodeName(),j.jenkins.getLabel("notContained")).toString(); assertEquals("Cause of blockage should be missing label.", message, node.canTake(item3).getShortDescription()); ((Slave)node).setMode(Node.Mode.EXCLUSIVE); assertNotNull("Node should not take project which has null label bacause it is in exclusive mode.", node.canTake(item2)); message = Messages._Node_BecauseNodeIsReserved(node.getNodeName()).toString(); assertEquals("Cause of blockage should be reserved label.", message, node.canTake(item2).getShortDescription()); node.getNodeProperties().add(new NodePropertyImpl()); notTake = true; assertNotNull("Node should not take project because node property not alow it.", node.canTake(item)); assertTrue("Cause of blockage should be bussy label.", node.canTake(item) instanceof CauseOfBlockage.BecauseLabelIsBusy); User user = User.get("John"); GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy(); j.jenkins.setAuthorizationStrategy(auth); j.jenkins.setCrumbIssuer(null); HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false); j.jenkins.setSecurityRealm(realm); realm.createAccount("John", ""); notTake = false; QueueItemAuthenticatorConfiguration.get().getAuthenticators().add(new MockQueueItemAuthenticator(Collections.singletonMap(project.getFullName(), user.impersonate()))); assertNotNull("Node should not take project because user does not have build permission.", node.canTake(item)); message = Messages._Node_LackingBuildPermission(item.authenticate().getName(),node.getNodeName()).toString(); assertEquals("Cause of blockage should be bussy label.", message, node.canTake(item).getShortDescription()); } @Test public void testCreatePath() throws Exception { Node node = j.createOnlineSlave(); Node node2 = j.createSlave(); String absolutPath = ((Slave)node).remoteFS; FilePath path = node.createPath(absolutPath); assertNotNull("Path should be created.", path); assertNotNull("Channel should be set.", path.getChannel()); assertEquals("Channel should be equals to channel of node.", node.getChannel(), path.getChannel()); path = node2.createPath(absolutPath); assertNull("Path should be null if slave have channel null.", path); } @Test public void testHasPermission() throws Exception { Node node = j.createOnlineSlave(); GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy(); j.jenkins.setAuthorizationStrategy(auth); j.jenkins.setCrumbIssuer(null); HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false); j.jenkins.setSecurityRealm(realm); User user = realm.createAccount("John Smith","abcdef"); SecurityContextHolder.getContext().setAuthentication(user.impersonate()); assertFalse("Current user should not have permission read.", node.hasPermission(Permission.READ)); auth.add(Computer.CONFIGURE, user.getId()); assertTrue("Current user should have permission CONFIGURE.", user.hasPermission(Permission.CONFIGURE)); auth.add(Jenkins.ADMINISTER, user.getId()); assertTrue("Current user should have permission read, because he has permission administer.", user.hasPermission(Permission.READ)); SecurityContextHolder.getContext().setAuthentication(Jenkins.ANONYMOUS); user = User.get("anonymous"); assertFalse("Current user should not have permission read, because does not have global permission read and authentication is anonymous.", user.hasPermission(Permission.READ)); } @Test public void testGetChannel() throws Exception { Slave slave = j.createOnlineSlave(); Node nodeOffline = j.createSlave(); Node node = new DumbSlave("slave2", "description", slave.getRemoteFS(), "1", Mode.NORMAL, "", slave.getLauncher(), slave.getRetentionStrategy(), slave.getNodeProperties()); assertNull("Channel of node should be null because node has not assigned computer.", node.getChannel()); assertNull("Channel of node should be null because assigned computer is offline.", nodeOffline.getChannel()); assertNotNull("Channel of node should not be null.", slave.getChannel()); } @Test public void testToComputer() throws Exception { Slave slave = j.createOnlineSlave(); Node node = new DumbSlave("slave2", "description", slave.getRemoteFS(), "1", Mode.NORMAL, "", slave.getLauncher(), slave.getRetentionStrategy(), slave.getNodeProperties()); assertNull("Slave which is not added into Jenkins list nodes should not have assigned computer.", node.toComputer()); assertNotNull("Slave which is added into Jenkins list nodes should have assigned computer.", slave.toComputer()); } /** * Verify that the Label#getTiedJobCount does not perform a lazy loading operation. */ @Issue("JENKINS-26391") @Test public void testGetAssignedLabelWithJobs() throws Exception { final Node node = j.createOnlineSlave(); node.setLabelString("label1 label2"); MavenModuleSet mavenProject = j.jenkins.createProject(MavenModuleSet.class, "p"); mavenProject.setAssignedLabel(j.jenkins.getLabel("label1")); RunLoadCounter.prepare(mavenProject); j.assertBuildStatus(Result.FAILURE, mavenProject.scheduleBuild2(0).get()); Integer labelCount = RunLoadCounter.assertMaxLoads(mavenProject, 0, new Callable<Integer>() { @Override public Integer call() throws Exception { final Label label = j.jenkins.getLabel("label1"); label.reset(); // Make sure cached value is not used return label.getTiedJobCount(); } }); assertEquals("Should have only one job tied to label.", 1, labelCount.intValue()); } @Issue("JENKINS-27188") @Test public void envPropertiesImmutable() throws Exception { Slave slave = j.createSlave(); String propertyKey = "JENKINS-27188"; EnvVars envVars = slave.getComputer().getEnvironment(); envVars.put(propertyKey, "huuhaa"); assertTrue(envVars.containsKey(propertyKey)); assertFalse(slave.getComputer().getEnvironment().containsKey(propertyKey)); assertNotSame(slave.getComputer().getEnvironment(), slave.getComputer().getEnvironment()); } /** * Create two projects which have the same label and verify that both are accounted for when getting a count * of the jobs tied to the current label. * */ @Issue("JENKINS-26391") @Test public void testGetAssignedLabelMultipleSlaves() throws Exception { final Node node1 = j.createOnlineSlave(); node1.setLabelString("label1"); final Node node2 = j.createOnlineSlave(); node1.setLabelString("label1"); MavenModuleSet project = j.jenkins.createProject(MavenModuleSet.class, "p1"); final Label label = j.jenkins.getLabel("label1"); project.setAssignedLabel(label); j.assertBuildStatus(Result.FAILURE, project.scheduleBuild2(0).get()); MavenModuleSet project2 = j.jenkins.createProject(MavenModuleSet.class, "p2"); project2.setAssignedLabel(label); j.assertBuildStatus(Result.FAILURE, project2.scheduleBuild2(0).get()); label.reset(); // Make sure cached value is not used assertEquals("Two jobs should be tied to this label.", 2, label.getTiedJobCount()); } /** * Verify that when a label is removed from a job that the tied job count does not include the removed job. */ @Issue("JENKINS-26391") @Test public void testGetAssignedLabelWhenLabelRemoveFromProject() throws Exception { final Node node = j.createOnlineSlave(); node.setLabelString("label1"); MavenModuleSet project = j.jenkins.createProject(MavenModuleSet.class, "p"); final Label label = j.jenkins.getLabel("label1"); project.setAssignedLabel(label); j.assertBuildStatus(Result.FAILURE, project.scheduleBuild2(0).get()); project.setAssignedLabel(null); label.reset(); // Make sure cached value is not used assertEquals("Label1 should have no tied jobs after the job label was removed.", 0, label.getTiedJobCount()); } /** * Create a project with the OR label expression. */ @Issue("JENKINS-26391") @Test public void testGetAssignedLabelWithLabelOrExpression() throws Exception { Node node = j.createOnlineSlave(); node.setLabelString("label1 label2"); FreeStyleProject project = j.createFreeStyleProject(); project.setAssignedLabel(new LabelExpression.Or(j.jenkins.getLabel("label1"), j.jenkins.getLabel("label2"))); TagCloud<LabelAtom> cloud = node.getLabelCloud(); assertThatCloudLabelContains(cloud, "label1", 0); assertThatCloudLabelContains(cloud, "label2", 0); } @Issue("JENKINS-26391") @Test public void testGetAssignedLabelWithLabelAndExpression() throws Exception { Node node = j.createOnlineSlave(); node.setLabelString("label1 label2"); FreeStyleProject project = j.createFreeStyleProject(); project.setAssignedLabel(new LabelExpression.And(j.jenkins.getLabel("label1"), j.jenkins.getLabel("label2"))); TagCloud<LabelAtom> cloud = node.getLabelCloud(); assertThatCloudLabelContains(cloud, "label1", 0); assertThatCloudLabelContains(cloud, "label2", 0); } @Issue("JENKINS-26391") @Test public void testGetAssignedLabelWithBothAndOrExpression() throws Exception { Node n1 = j.createOnlineSlave(); Node n2 = j.createOnlineSlave(); Node n3 = j.createOnlineSlave(); Node n4 = j.createOnlineSlave(); n1.setLabelString("label1 label2 label3"); n2.setLabelString("label1"); n3.setLabelString("label1 label2"); n4.setLabelString("label1 label"); FreeStyleProject p = j.createFreeStyleProject(); p.setAssignedLabel(LabelExpression.parseExpression("label1 && (label2 || label3)")); // Node 1 should not be tied to any labels TagCloud<LabelAtom> n1LabelCloud = n1.getLabelCloud(); assertThatCloudLabelContains(n1LabelCloud, "label1", 0); assertThatCloudLabelContains(n1LabelCloud, "label2", 0); assertThatCloudLabelContains(n1LabelCloud, "label3", 0); // Node 2 should not be tied to any labels TagCloud<LabelAtom> n2LabelCloud = n1.getLabelCloud(); assertThatCloudLabelContains(n2LabelCloud, "label1", 0); // Node 3 should not be tied to any labels TagCloud<LabelAtom> n3LabelCloud = n1.getLabelCloud(); assertThatCloudLabelContains(n3LabelCloud, "label1", 0); assertThatCloudLabelContains(n3LabelCloud, "label2", 0); // Node 4 should not be tied to any labels TagCloud<LabelAtom> n4LabelCloud = n1.getLabelCloud(); assertThatCloudLabelContains(n4LabelCloud, "label1", 0); } @Issue("JENKINS-26391") @Test public void testGetAssignedLabelWithSpaceOnly() throws Exception { Node n = j.createOnlineSlave(); n.setLabelString("label1 label2"); FreeStyleProject p = j.createFreeStyleProject(); p.setAssignedLabel(j.jenkins.getLabel("label1 label2")); TagCloud<LabelAtom> cloud = n.getLabelCloud(); assertThatCloudLabelDoesNotContain(cloud, "label1 label2", 0); } @Issue("SECURITY-281") @Test public void masterComputerConfigDotXml() throws Exception { JenkinsRule.WebClient wc = j.createWebClient(); wc.assertFails("computer/(master)/config.xml", HttpURLConnection.HTTP_BAD_REQUEST); WebRequest settings = new WebRequest(wc.createCrumbedUrl("computer/(master)/config.xml")); settings.setHttpMethod(HttpMethod.POST); settings.setRequestBody("<hudson/>"); try { Page page = wc.getPage(settings); fail(page.getWebResponse().getContentAsString()); } catch (FailingHttpStatusCodeException x) { assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, x.getStatusCode()); } } /** * Assert that a tag cloud contains label name and weight. */ public void assertThatCloudLabel(boolean contains, TagCloud<LabelAtom> tagCloud, String expectedLabel, int expectedWeight) { StringBuilder containsFailureMessage = new StringBuilder().append("Unable to find label cloud. Expected: [") .append(expectedLabel).append(", ").append(expectedWeight).append("]").append(" Actual: ["); for (TagCloud.Entry entry : tagCloud) { if (expectedLabel.equals(((LabelAtom) entry.item).getName())) { if (expectedWeight == entry.weight) { if (!contains) { fail("Cloud label should not contain [" + expectedLabel + ", " + expectedWeight + "]"); } else { return; } } } // Gather information for failure message just in case. containsFailureMessage.append("{").append(entry.item.toString()).append(", ").append(entry.weight).append("}"); } // If a label should be part of the cloud label then fail. if (contains) { fail(containsFailureMessage.toString() + "]"); } } /** * Assert that a tag cloud does not contain the label name and weight. */ public void assertThatCloudLabelDoesNotContain(TagCloud<LabelAtom> tagCloud, String expectedLabel, int expectedWeight) { assertThatCloudLabel(false, tagCloud, expectedLabel, expectedWeight); } /** * Assert that a tag cloud contains label name and weight. */ public void assertThatCloudLabelContains(TagCloud<LabelAtom> tagCloud, String expectedLabel, int expectedWeight) { assertThatCloudLabel(true, tagCloud, expectedLabel, expectedWeight); } @TestExtension public static class LabelFinderImpl extends LabelFinder{ @Override public Collection<LabelAtom> findLabels(Node node) { List<LabelAtom> atoms = new ArrayList<LabelAtom>(); if(addDynamicLabel){ atoms.add(Jenkins.getInstance().getLabelAtom("dynamicLabel")); } return atoms; } } @TestExtension public static class NodePropertyImpl extends NodeProperty{ @Override public CauseOfBlockage canTake(Queue.BuildableItem item){ if(notTake) return new CauseOfBlockage.BecauseLabelIsBusy(item.getAssignedLabel()); return null; } } }