/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi * * 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.html.DomElement; import com.gargoylesoftware.htmlunit.html.DomNode; import com.gargoylesoftware.htmlunit.html.HtmlFileInput; import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlFormUtil; import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.xml.XmlPage; import hudson.Launcher; import hudson.XmlFile; import hudson.matrix.Axis; import hudson.matrix.AxisList; import hudson.matrix.LabelAxis; import hudson.matrix.MatrixBuild; import hudson.matrix.MatrixProject; import hudson.matrix.MatrixRun; import hudson.matrix.TextAxis; import hudson.model.Cause.RemoteCause; import hudson.model.Cause.UserIdCause; import hudson.model.Queue.BlockedItem; import hudson.model.Queue.Executable; import hudson.model.Queue.WaitingItem; import hudson.model.labels.LabelExpression; import hudson.model.queue.AbstractQueueTask; import hudson.model.queue.CauseOfBlockage; import hudson.model.queue.QueueTaskDispatcher; import hudson.model.queue.QueueTaskFuture; import hudson.model.queue.ScheduleResult; import hudson.model.queue.SubTask; import hudson.security.ACL; import hudson.security.AuthorizationMatrixProperty; import hudson.security.GlobalMatrixAuthorizationStrategy; import hudson.security.Permission; import hudson.security.ProjectMatrixAuthorizationStrategy; import hudson.security.SparseACL; import hudson.slaves.DumbSlave; import hudson.slaves.DummyCloudImpl; import hudson.slaves.NodeProperty; import hudson.slaves.NodePropertyDescriptor; import hudson.slaves.NodeProvisionerRule; import hudson.tasks.BuildTrigger; import hudson.tasks.Shell; import hudson.triggers.SCMTrigger.SCMTriggerCause; import hudson.triggers.TimerTrigger.TimerTriggerCause; import hudson.util.OneShotEvent; import hudson.util.XStream2; import jenkins.model.Jenkins; import jenkins.security.QueueItemAuthenticatorConfiguration; import jenkins.triggers.ReverseBuildTrigger; import org.acegisecurity.Authentication; import org.acegisecurity.GrantedAuthority; import org.acegisecurity.acls.sid.PrincipalSid; import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.FileUtils; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.MockQueueItemAuthenticator; import org.jvnet.hudson.test.SequenceLock; import org.jvnet.hudson.test.SleepBuilder; import org.jvnet.hudson.test.TestBuilder; import org.jvnet.hudson.test.TestExtension; import org.jvnet.hudson.test.recipes.LocalData; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.*; /** * @author Kohsuke Kawaguchi */ public class QueueTest { @Rule public JenkinsRule r = new NodeProvisionerRule(-1, 0, 10); /** * Checks the persistence of queue. */ @Test public void persistence() throws Exception { Queue q = r.jenkins.getQueue(); // prevent execution to push stuff into the queue r.jenkins.setNumExecutors(0); FreeStyleProject testProject = r.createFreeStyleProject("test"); testProject.scheduleBuild(new UserIdCause()); q.save(); System.out.println(FileUtils.readFileToString(new File(r.jenkins.getRootDir(), "queue.xml"))); assertEquals(1, q.getItems().length); q.clear(); assertEquals(0,q.getItems().length); // load the contents back q.load(); assertEquals(1, q.getItems().length); // did it bind back to the same object? assertSame(q.getItems()[0].task,testProject); } /** * Make sure the queue can be reconstructed from a List queue.xml. * Prior to the Queue.State class, the Queue items were just persisted as a List. */ @LocalData @Test public void recover_from_legacy_list() throws Exception { Queue q = r.jenkins.getQueue(); // loaded the legacy queue.xml from test LocalData located in // resources/hudson/model/QueueTest/recover_from_legacy_list.zip assertEquals(1, q.getItems().length); // The current counter should be the id from the item brought back // from the persisted queue.xml. assertEquals(3, Queue.WaitingItem.getCurrentCounterValue()); } /** * Can {@link Queue} successfully recover removal? */ @Test public void persistence2() throws Exception { Queue q = r.jenkins.getQueue(); resetQueueState(); assertEquals(0, Queue.WaitingItem.getCurrentCounterValue()); // prevent execution to push stuff into the queue r.jenkins.setNumExecutors(0); FreeStyleProject testProject = r.createFreeStyleProject("test"); testProject.scheduleBuild(new UserIdCause()); q.save(); System.out.println(FileUtils.readFileToString(new File(r.jenkins.getRootDir(), "queue.xml"))); assertEquals(1, q.getItems().length); q.clear(); assertEquals(0,q.getItems().length); // delete the project before loading the queue back testProject.delete(); q.load(); assertEquals(0,q.getItems().length); // The counter state should be maintained. assertEquals(1, Queue.WaitingItem.getCurrentCounterValue()); } /** * Forces a reset of the private queue COUNTER. * Could make changes to Queue to make that easier, but decided against that. */ private void resetQueueState() throws IOException { File queueFile = r.jenkins.getQueue().getXMLQueueFile(); XmlFile xmlFile = new XmlFile(Queue.XSTREAM, queueFile); xmlFile.write(new Queue.State()); r.jenkins.getQueue().load(); } @Test public void queue_id_to_run_mapping() throws Exception { FreeStyleProject testProject = r.createFreeStyleProject("test"); FreeStyleBuild build = r.assertBuildStatusSuccess(testProject.scheduleBuild2(0)); Assert.assertNotEquals(Run.QUEUE_ID_UNKNOWN, build.getQueueId()); } /** * {@link hudson.model.Queue.BlockedItem} is not static. Make sure its persistence doesn't end up re-persisting the whole Queue instance. */ @Test public void persistenceBlockedItem() throws Exception { Queue q = r.jenkins.getQueue(); final SequenceLock seq = new SequenceLock(); FreeStyleProject p = r.createFreeStyleProject(); p.getBuildersList().add(new TestBuilder() { @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { seq.phase(0); // first, we let one build going seq.phase(2); return true; } }); Future<FreeStyleBuild> b1 = p.scheduleBuild2(0); seq.phase(1); // and make sure we have one build under way // get another going Future<FreeStyleBuild> b2 = p.scheduleBuild2(0); q.scheduleMaintenance().get(); Queue.Item[] items = q.getItems(); assertEquals(1,items.length); assertTrue("Got "+items[0], items[0] instanceof BlockedItem); q.save(); } public static final class FileItemPersistenceTestServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); resp.getWriter().println( "<html><body><form action='/' method=post name=main enctype='multipart/form-data'>" + "<input type=file name=test><input type=submit>"+ "</form></body></html>" ); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { ServletFileUpload f = new ServletFileUpload(new DiskFileItemFactory()); List<?> v = f.parseRequest(req); assertEquals(1,v.size()); XStream2 xs = new XStream2(); System.out.println(xs.toXML(v.get(0))); } catch (FileUploadException e) { throw new ServletException(e); } } } @Test public void fileItemPersistence() throws Exception { // TODO: write a synchronous connector? byte[] testData = new byte[1024]; for( int i=0; i<testData.length; i++ ) testData[i] = (byte)i; Server server = new Server(); ServerConnector connector = new ServerConnector(server); server.addConnector(connector); ServletHandler handler = new ServletHandler(); handler.addServletWithMapping(new ServletHolder(new FileItemPersistenceTestServlet()),"/"); server.setHandler(handler); server.start(); try { JenkinsRule.WebClient wc = r.createWebClient(); @SuppressWarnings("deprecation") HtmlPage p = (HtmlPage) wc.getPage("http://localhost:" + connector.getLocalPort() + '/'); HtmlForm f = p.getFormByName("main"); HtmlFileInput input = (HtmlFileInput) f.getInputByName("test"); input.setData(testData); HtmlFormUtil.submit(f); } finally { server.stop(); } } @Issue("JENKINS-33467") @Test public void foldableCauseAction() throws Exception { final OneShotEvent buildStarted = new OneShotEvent(); final OneShotEvent buildShouldComplete = new OneShotEvent(); r.setQuietPeriod(0); FreeStyleProject project = r.createFreeStyleProject(); // Make build sleep a while so it blocks new builds project.getBuildersList().add(new TestBuilder() { public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { buildStarted.signal(); buildShouldComplete.block(); return true; } }); // Start one build to block others assertTrue(project.scheduleBuild(new UserIdCause())); buildStarted.block(); // wait for the build to really start // Schedule a new build, and trigger it many ways while it sits in queue Future<FreeStyleBuild> fb = project.scheduleBuild2(0, new UserIdCause()); assertNotNull(fb); assertTrue(project.scheduleBuild(new SCMTriggerCause(""))); assertTrue(project.scheduleBuild(new UserIdCause())); assertTrue(project.scheduleBuild(new TimerTriggerCause())); assertTrue(project.scheduleBuild(new RemoteCause("1.2.3.4", "test"))); assertTrue(project.scheduleBuild(new RemoteCause("4.3.2.1", "test"))); assertTrue(project.scheduleBuild(new SCMTriggerCause(""))); assertTrue(project.scheduleBuild(new RemoteCause("1.2.3.4", "test"))); assertTrue(project.scheduleBuild(new RemoteCause("1.2.3.4", "foo"))); assertTrue(project.scheduleBuild(new SCMTriggerCause(""))); assertTrue(project.scheduleBuild(new TimerTriggerCause())); // Wait for 2nd build to finish buildShouldComplete.signal(); FreeStyleBuild build = fb.get(); // Make sure proper folding happened. CauseAction ca = build.getAction(CauseAction.class); assertNotNull(ca); StringBuilder causes = new StringBuilder(); for (Cause c : ca.getCauses()) causes.append(c.getShortDescription() + "\n"); assertEquals("Build causes should have all items, even duplicates", "Started by user SYSTEM\nStarted by user SYSTEM\n" + "Started by an SCM change\nStarted by an SCM change\nStarted by an SCM change\n" + "Started by timer\nStarted by timer\n" + "Started by remote host 1.2.3.4 with note: test\n" + "Started by remote host 1.2.3.4 with note: test\n" + "Started by remote host 4.3.2.1 with note: test\n" + "Started by remote host 1.2.3.4 with note: foo\n", causes.toString()); // View for build should group duplicates JenkinsRule.WebClient wc = r.createWebClient(); String nl = System.getProperty("line.separator"); String buildPage = wc.getPage(build, "").asText().replace(nl," "); assertTrue("Build page should combine duplicates and show counts: " + buildPage, buildPage.contains("Started by user SYSTEM (2 times) " + "Started by an SCM change (3 times) " + "Started by timer (2 times) " + "Started by remote host 1.2.3.4 with note: test (2 times) " + "Started by remote host 4.3.2.1 with note: test " + "Started by remote host 1.2.3.4 with note: foo")); System.out.println(new XmlFile(new File(build.getRootDir(), "build.xml")).asString()); } @Issue("JENKINS-8790") @Test public void flyweightTasks() throws Exception { MatrixProject m = r.jenkins.createProject(MatrixProject.class, "p"); m.addProperty(new ParametersDefinitionProperty( new StringParameterDefinition("FOO","value") )); m.getBuildersList().add(new Shell("sleep 3")); m.setAxes(new AxisList(new TextAxis("DoesntMatter", "aaa","bbb"))); List<Future<MatrixBuild>> futures = new ArrayList<Future<MatrixBuild>>(); for (int i = 0; i < 3; i++) { futures.add(m.scheduleBuild2(0, new UserIdCause(), new ParametersAction(new StringParameterValue("FOO", "value" + i)))); } for (Future<MatrixBuild> f : futures) { r.assertBuildStatusSuccess(f); } } @Issue("JENKINS-7291") @Test public void flyweightTasksWithoutMasterExecutors() throws Exception { DummyCloudImpl cloud = new DummyCloudImpl(r, 0); cloud.label = r.jenkins.getLabel("remote"); r.jenkins.clouds.add(cloud); r.jenkins.setNumExecutors(0); r.jenkins.setNodes(Collections.<Node>emptyList()); MatrixProject m = r.jenkins.createProject(MatrixProject.class, "p"); m.setAxes(new AxisList(new LabelAxis("label", Arrays.asList("remote")))); MatrixBuild build; try { build = m.scheduleBuild2(0).get(60, TimeUnit.SECONDS); } catch (TimeoutException x) { throw (AssertionError) new AssertionError(r.jenkins.getQueue().getItems().toString()).initCause(x); } r.assertBuildStatusSuccess(build); assertEquals("", build.getBuiltOnStr()); List<MatrixRun> runs = build.getRuns(); assertEquals(1, runs.size()); assertEquals("slave0", runs.get(0).getBuiltOnStr()); } @Issue("JENKINS-10944") @Test public void flyweightTasksBlockedByShutdown() throws Exception { r.jenkins.doQuietDown(true, 0); AtomicInteger cnt = new AtomicInteger(); TestFlyweightTask task = new TestFlyweightTask(cnt, null); assertTrue(Queue.isBlockedByShutdown(task)); r.jenkins.getQueue().schedule2(task, 0); r.jenkins.getQueue().maintain(); r.jenkins.doCancelQuietDown(); assertFalse(Queue.isBlockedByShutdown(task)); r.waitUntilNoActivity(); assertEquals(1, cnt.get()); assert task.exec instanceof OneOffExecutor : task.exec; } @Issue("JENKINS-24519") @Test public void flyweightTasksBlockedBySlave() throws Exception { Label label = Label.get("myslave"); AtomicInteger cnt = new AtomicInteger(); TestFlyweightTask task = new TestFlyweightTask(cnt, label); r.jenkins.getQueue().schedule2(task, 0); r.jenkins.getQueue().maintain(); r.createSlave(label); r.waitUntilNoActivity(); assertEquals(1, cnt.get()); assert task.exec instanceof OneOffExecutor : task.exec; } @Issue("JENKINS-27256") @Test public void inQueueTaskLookupByAPI() throws Exception { FreeStyleProject p = r.createFreeStyleProject(); Label label = Label.get("unknown-slave"); // Give the project an "unknown-slave" label, forcing it to // stay in the queue after we schedule it, allowing us to query it. p.setAssignedLabel(label); p.scheduleBuild2(0); JenkinsRule.WebClient webclient = r.createWebClient(); XmlPage queueItems = webclient.goToXml("queue/api/xml"); String queueTaskId = queueItems.getXmlDocument().getElementsByTagName("id").item(0).getTextContent(); assertNotNull(queueTaskId); XmlPage queueItem = webclient.goToXml("queue/item/" + queueTaskId + "/api/xml"); assertNotNull(queueItem); String tagName = queueItem.getDocumentElement().getTagName(); assertTrue(tagName.equals("blockedItem") || tagName.equals("buildableItem")); } @Issue("JENKINS-28926") @Test public void upstreamDownstreamCycle() throws Exception { FreeStyleProject trigger = r.createFreeStyleProject(); FreeStyleProject chain1 = r.createFreeStyleProject(); FreeStyleProject chain2a = r.createFreeStyleProject(); FreeStyleProject chain2b = r.createFreeStyleProject(); FreeStyleProject chain3 = r.createFreeStyleProject(); trigger.getPublishersList().add(new BuildTrigger(String.format("%s, %s, %s, %s", chain1.getName(), chain2a.getName(), chain2b.getName(), chain3.getName()), true)); trigger.setQuietPeriod(0); chain1.setQuietPeriod(1); chain2a.setQuietPeriod(1); chain2b.setQuietPeriod(1); chain3.setQuietPeriod(1); chain1.getPublishersList().add(new BuildTrigger(String.format("%s, %s", chain2a.getName(), chain2b.getName()), true)); chain2a.getPublishersList().add(new BuildTrigger(chain3.getName(), true)); chain2b.getPublishersList().add(new BuildTrigger(chain3.getName(), true)); chain1.setBlockBuildWhenDownstreamBuilding(true); chain2a.setBlockBuildWhenDownstreamBuilding(true); chain2b.setBlockBuildWhenDownstreamBuilding(true); chain3.setBlockBuildWhenUpstreamBuilding(true); r.jenkins.rebuildDependencyGraph(); r.buildAndAssertSuccess(trigger); // the trigger should build immediately and schedule the cycle r.waitUntilNoActivity(); final Queue queue = r.getInstance().getQueue(); assertThat("The cycle should have been defanged and chain1 executed", queue.getItem(chain1), nullValue()); assertThat("The cycle should have been defanged and chain2a executed", queue.getItem(chain2a), nullValue()); assertThat("The cycle should have been defanged and chain2b executed", queue.getItem(chain2b), nullValue()); assertThat("The cycle should have been defanged and chain3 executed", queue.getItem(chain3), nullValue()); } public static class TestFlyweightTask extends TestTask implements Queue.FlyweightTask { Executor exec; private final Label assignedLabel; public TestFlyweightTask(AtomicInteger cnt, Label assignedLabel) { super(cnt); this.assignedLabel = assignedLabel; } @Override protected void doRun() { exec = Executor.currentExecutor(); } @Override public Label getAssignedLabel() { return assignedLabel; } } @Test public void taskEquality() throws Exception { AtomicInteger cnt = new AtomicInteger(); ScheduleResult result = r.jenkins.getQueue().schedule2(new TestTask(cnt), 0); assertTrue(result.isCreated()); WaitingItem item = result.getCreateItem(); assertFalse(r.jenkins.getQueue().schedule2(new TestTask(cnt), 0).isCreated()); item.getFuture().get(); r.waitUntilNoActivity(); assertEquals(1, cnt.get()); } static class TestTask extends AbstractQueueTask { private final AtomicInteger cnt; TestTask(AtomicInteger cnt) { this.cnt = cnt; } @Override public boolean equals(Object o) { return o instanceof TestTask && cnt == ((TestTask) o).cnt; } @Override public int hashCode() { return cnt.hashCode(); } @Override public boolean isBuildBlocked() {return false;} @Override public String getWhyBlocked() {return null;} @Override public String getName() {return "test";} @Override public String getFullDisplayName() {return "Test";} @Override public void checkAbortPermission() {} @Override public boolean hasAbortPermission() {return true;} @Override public String getUrl() {return "test/";} @Override public String getDisplayName() {return "Test";} @Override public Label getAssignedLabel() {return null;} @Override public Node getLastBuiltOn() {return null;} @Override public long getEstimatedDuration() {return -1;} @Override public ResourceList getResourceList() {return new ResourceList();} protected void doRun() {} @Override public Executable createExecutable() throws IOException { return new Executable() { @Override public SubTask getParent() {return TestTask.this;} @Override public long getEstimatedDuration() {return -1;} @Override public void run() { doRun(); cnt.incrementAndGet(); } }; } } @Test public void waitForStart() throws Exception { final OneShotEvent ev = new OneShotEvent(); FreeStyleProject p = r.createFreeStyleProject(); p.getBuildersList().add(new TestBuilder() { @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { ev.block(); return true; } }); QueueTaskFuture<FreeStyleBuild> v = p.scheduleBuild2(0); FreeStyleBuild b = v.waitForStart(); assertEquals(1,b.getNumber()); assertTrue(b.isBuilding()); assertSame(p, b.getProject()); ev.signal(); // let the build complete FreeStyleBuild b2 = r.assertBuildStatusSuccess(v); assertSame(b, b2); } /** * Make sure that the running build actually carries an credential. */ @Test public void accessControl() throws Exception { FreeStyleProject p = r.createFreeStyleProject(); QueueItemAuthenticatorConfiguration.get().getAuthenticators().add(new MockQueueItemAuthenticator(Collections.singletonMap(p.getFullName(), alice))); p.getBuildersList().add(new TestBuilder() { @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { assertEquals(alice,Jenkins.getAuthentication()); return true; } }); r.assertBuildStatusSuccess(p.scheduleBuild2(0)); } private static Authentication alice = new UsernamePasswordAuthenticationToken("alice","alice",new GrantedAuthority[0]); /** * Make sure that the slave assignment honors the permissions. * * We do this test by letting a build run twice to determine its natural home, * and then introduce a security restriction to prohibit that. */ @Test public void permissionSensitiveSlaveAllocations() throws Exception { r.jenkins.setNumExecutors(0); // restrict builds to those slaves DumbSlave s1 = r.createSlave(); DumbSlave s2 = r.createSlave(); FreeStyleProject p = r.createFreeStyleProject(); QueueItemAuthenticatorConfiguration.get().getAuthenticators().add(new MockQueueItemAuthenticator(Collections.singletonMap(p.getFullName(), alice))); p.getBuildersList().add(new TestBuilder() { @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { assertEquals(alice,Jenkins.getAuthentication()); return true; } }); final FreeStyleBuild b1 = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); final FreeStyleBuild b2 = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); // scheduling algorithm would prefer running the same job on the same node // kutzi: 'prefer' != 'enforce', therefore disabled this assertion: assertSame(b1.getBuiltOn(),b2.getBuiltOn()); // ACL that allow anyone to do anything except Alice can't build. final SparseACL aliceCantBuild = new SparseACL(null); aliceCantBuild.add(new PrincipalSid(alice), Computer.BUILD, false); aliceCantBuild.add(new PrincipalSid("anonymous"), Jenkins.ADMINISTER, true); GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy() { @Override public ACL getACL(Node node) { if (node==b1.getBuiltOn()) return aliceCantBuild; return super.getACL(node); } }; auth.add(Jenkins.ADMINISTER,"anonymous"); r.jenkins.setAuthorizationStrategy(auth); // now that we prohibit alice to do a build on the same node, the build should run elsewhere for (int i=0; i<3; i++) { FreeStyleBuild b3 = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); assertNotSame(b3.getBuiltOnStr(), b1.getBuiltOnStr()); } } @Test public void pendingsConsistenceAfterErrorDuringMaintain() throws IOException, ExecutionException, InterruptedException{ FreeStyleProject project1 = r.createFreeStyleProject(); FreeStyleProject project2 = r.createFreeStyleProject(); TopLevelItemDescriptor descriptor = new TopLevelItemDescriptor(FreeStyleProject.class){ @Override public FreeStyleProject newInstance(ItemGroup parent, String name) { return (FreeStyleProject) new FreeStyleProject(parent,name){ @Override public Label getAssignedLabel(){ throw new IllegalArgumentException("Test exception"); //cause dead of executor } @Override public void save(){ //do not need save } }; } }; FreeStyleProject projectError = (FreeStyleProject) r.jenkins.createProject(descriptor, "throw-error"); project1.setAssignedLabel(r.jenkins.getSelfLabel()); project2.setAssignedLabel(r.jenkins.getSelfLabel()); project1.getBuildersList().add(new Shell("sleep 2")); project1.scheduleBuild2(0); QueueTaskFuture<FreeStyleBuild> v = project2.scheduleBuild2(0); projectError.scheduleBuild2(0); Executor e = r.jenkins.toComputer().getExecutors().get(0); Thread.sleep(2000); while(project2.getLastBuild()==null){ if(!e.isAlive()){ break; // executor is dead due to exception } if(e.isIdle()){ assertTrue("Node went to idle before project had" + project2.getDisplayName() + " been started", v.isDone()); } Thread.sleep(1000); } if(project2.getLastBuild()!=null) return; Queue.getInstance().cancel(projectError); // cancel job which cause dead of executor while(!e.isIdle()){ //executor should take project2 from queue Thread.sleep(1000); } //project2 should not be in pendings List<Queue.BuildableItem> items = Queue.getInstance().getPendingItems(); for(Queue.BuildableItem item : items){ assertFalse("Project " + project2.getDisplayName() + " stuck in pendings",item.task.getName().equals(project2.getName())); } } @Test public void cancelInQueue() throws Exception { // parepare an offline slave. DumbSlave slave = r.createOnlineSlave(); assertFalse(slave.toComputer().isOffline()); slave.toComputer().disconnect(null).get(); assertTrue(slave.toComputer().isOffline()); FreeStyleProject p = r.createFreeStyleProject(); p.setAssignedNode(slave); QueueTaskFuture<FreeStyleBuild> f = p.scheduleBuild2(0); try { f.get(3, TimeUnit.SECONDS); fail("Should time out (as the slave is offline)."); } catch (TimeoutException e) { } Queue.Item item = Queue.getInstance().getItem(p); assertNotNull(item); Queue.getInstance().doCancelItem(item.getId()); assertNull(Queue.getInstance().getItem(p)); try { f.get(10, TimeUnit.SECONDS); fail("Should not get (as it is cancelled)."); } catch (CancellationException e) { } } @Test public void waitForStartAndCancelBeforeStart() throws Exception { final OneShotEvent ev = new OneShotEvent(); FreeStyleProject p = r.createFreeStyleProject(); QueueTaskFuture<FreeStyleBuild> f = p.scheduleBuild2(10); final Queue.Item item = Queue.getInstance().getItem(p); assertNotNull(item); final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); executor.schedule(new Runnable() { @Override public void run() { try { Queue.getInstance().doCancelItem(item.getId()); } catch (IOException | ServletException e) { e.printStackTrace(); } } }, 2, TimeUnit.SECONDS); try { f.waitForStart(); fail("Expected an CancellationException to be thrown"); } catch (CancellationException e) {} } @Issue("JENKINS-27871") @Test public void testBlockBuildWhenUpstreamBuildingLock() throws Exception { final String prefix = "JENKINS-27871"; r.getInstance().setNumExecutors(4); r.getInstance().save(); final FreeStyleProject projectA = r.createFreeStyleProject(prefix+"A"); projectA.getBuildersList().add(new SleepBuilder(5000)); final FreeStyleProject projectB = r.createFreeStyleProject(prefix+"B"); projectB.getBuildersList().add(new SleepBuilder(10000)); projectB.setBlockBuildWhenUpstreamBuilding(true); final FreeStyleProject projectC = r.createFreeStyleProject(prefix+"C"); projectC.getBuildersList().add(new SleepBuilder(10000)); projectC.setBlockBuildWhenUpstreamBuilding(true); projectA.getPublishersList().add(new BuildTrigger(Arrays.asList(projectB), Result.SUCCESS)); projectB.getPublishersList().add(new BuildTrigger(Arrays.asList(projectC), Result.SUCCESS)); final QueueTaskFuture<FreeStyleBuild> taskA = projectA.scheduleBuild2(0, new TimerTriggerCause()); Thread.sleep(1000); final QueueTaskFuture<FreeStyleBuild> taskB = projectB.scheduleBuild2(0, new TimerTriggerCause()); final QueueTaskFuture<FreeStyleBuild> taskC = projectC.scheduleBuild2(0, new TimerTriggerCause()); final FreeStyleBuild buildA = taskA.get(60, TimeUnit.SECONDS); final FreeStyleBuild buildB = taskB.get(60, TimeUnit.SECONDS); final FreeStyleBuild buildC = taskC.get(60, TimeUnit.SECONDS); long buildBEndTime = buildB.getStartTimeInMillis() + buildB.getDuration(); assertTrue("Project B build should be finished before the build of project C starts. " + "B finished at " + buildBEndTime + ", C started at " + buildC.getStartTimeInMillis(), buildC.getStartTimeInMillis() >= buildBEndTime); } @Issue("JENKINS-30084") @Test /* * When a flyweight task is restricted to run on a specific node, the node will be provisioned * and the flyweight task will be executed. */ public void shouldRunFlyweightTaskOnProvisionedNodeWhenNodeRestricted() throws Exception { MatrixProject matrixProject = r.jenkins.createProject(MatrixProject.class, "p"); matrixProject.setAxes(new AxisList( new Axis("axis", "a", "b") )); Label label = LabelExpression.get("aws-linux-dummy"); DummyCloudImpl dummyCloud = new DummyCloudImpl(r, 0); dummyCloud.label = label; r.jenkins.clouds.add(dummyCloud); matrixProject.setAssignedLabel(label); r.assertBuildStatusSuccess(matrixProject.scheduleBuild2(0)); assertEquals("aws-linux-dummy", matrixProject.getBuilds().getLastBuild().getBuiltOn().getLabelString()); } @Test public void shouldBeAbleToBlockFlyweightTaskAtTheLastMinute() throws Exception { MatrixProject matrixProject = r.jenkins.createProject(MatrixProject.class, "downstream"); matrixProject.setDisplayName("downstream"); matrixProject.setAxes(new AxisList( new Axis("axis", "a", "b") )); Label label = LabelExpression.get("aws-linux-dummy"); DummyCloudImpl dummyCloud = new DummyCloudImpl(r, 0); dummyCloud.label = label; BlockDownstreamProjectExecution property = new BlockDownstreamProjectExecution(); dummyCloud.getNodeProperties().add(property); r.jenkins.clouds.add(dummyCloud); matrixProject.setAssignedLabel(label); FreeStyleProject upstreamProject = r.createFreeStyleProject("upstream"); upstreamProject.getBuildersList().add(new SleepBuilder(10000)); upstreamProject.setDisplayName("upstream"); //let's assume the flyweighttask has an upstream project and that must be blocked // when the upstream project is running matrixProject.addTrigger(new ReverseBuildTrigger("upstream", Result.SUCCESS)); matrixProject.setBlockBuildWhenUpstreamBuilding(true); //we schedule the project but we pretend no executors are available thus //the flyweight task is in the buildable queue without being executed QueueTaskFuture downstream = matrixProject.scheduleBuild2(0); if (downstream == null) { throw new Exception("the flyweight task could not be scheduled, thus the test will be interrupted"); } //let s wait for the Queue instance to be updated while (Queue.getInstance().getBuildableItems().size() != 1) { Thread.sleep(10); } //in this state the build is not blocked, it's just waiting for an available executor assertFalse(Queue.getInstance().getItems()[0].isBlocked()); //we start the upstream project that should block the downstream one QueueTaskFuture upstream = upstreamProject.scheduleBuild2(0); if (upstream == null) { throw new Exception("the upstream task could not be scheduled, thus the test will be interrupted"); } //let s wait for the Upstream to enter the buildable Queue boolean enteredTheQueue = false; while (!enteredTheQueue) { for (Queue.BuildableItem item : Queue.getInstance().getBuildableItems()) { if (item.task.getDisplayName() != null && item.task.getDisplayName().equals(upstreamProject.getDisplayName())) { enteredTheQueue = true; } } } //let's wait for the upstream project to actually start so that we're sure the Queue has been updated //when the upstream starts the downstream has already left the buildable queue and the queue is empty while (!Queue.getInstance().getBuildableItems().isEmpty()) { Thread.sleep(10); } assertTrue(Queue.getInstance().getItems()[0].isBlocked()); assertTrue(Queue.getInstance().getBlockedItems().get(0).task.getDisplayName().equals(matrixProject.displayName)); //once the upstream is completed, the downstream can join the buildable queue again. r.assertBuildStatusSuccess(upstream); while (Queue.getInstance().getBuildableItems().isEmpty()) { Thread.sleep(10); } assertFalse(Queue.getInstance().getItems()[0].isBlocked()); assertTrue(Queue.getInstance().getBlockedItems().isEmpty()); assertTrue(Queue.getInstance().getBuildableItems().get(0).task.getDisplayName().equals(matrixProject.displayName)); } //let's make sure that the downstram project is not started before the upstream --> we want to simulate // the case: buildable-->blocked-->buildable public static class BlockDownstreamProjectExecution extends NodeProperty<Slave> { @Override public CauseOfBlockage canTake(Queue.BuildableItem item) { if (item.task.getName().equals("downstream")) { return new CauseOfBlockage() { @Override public String getShortDescription() { return "slave not provisioned"; } }; } return null; } @TestExtension("shouldBeAbleToBlockFlyWeightTaskOnLastMinute") public static class DescriptorImpl extends NodePropertyDescriptor {} } @Test public void queueApiOutputShouldBeFilteredByUserPermission() throws Exception { r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); ProjectMatrixAuthorizationStrategy str = new ProjectMatrixAuthorizationStrategy(); str.add(Jenkins.READ, "bob"); str.add(Jenkins.READ, "alice"); str.add(Jenkins.READ, "james"); r.jenkins.setAuthorizationStrategy(str); FreeStyleProject project = r.createFreeStyleProject("project"); Map<Permission, Set<String>> permissions = new HashMap<Permission, Set<String>>(); permissions.put(Item.READ, Collections.singleton("bob")); permissions.put(Item.DISCOVER, Collections.singleton("james")); AuthorizationMatrixProperty prop1 = new AuthorizationMatrixProperty(permissions); project.addProperty(prop1); project.getBuildersList().add(new SleepBuilder(10)); project.scheduleBuild2(0); JenkinsRule.WebClient webClient = r.createWebClient(); webClient.login("bob", "bob"); XmlPage p = webClient.goToXml("queue/api/xml"); //bob has permission on the project and will be able to see it in the queue together with information such as the URL and the name. for (DomNode element: p.getFirstChild().getFirstChild().getChildNodes()){ if (element.getNodeName().equals("task")) { for (DomNode child: ((DomElement) element).getChildNodes()) { if (child.getNodeName().equals("name")) { assertEquals(child.asText(), "project"); } else if (child.getNodeName().equals("url")) { assertNotNull(child.asText()); } } } } webClient = r.createWebClient(); webClient.login("alice"); XmlPage p2 = webClient.goToXml("queue/api/xml"); //alice does not have permission on the project and will not see it in the queue. assertTrue(p2.getByXPath("/queue/node()").isEmpty()); webClient = r.createWebClient(); webClient.login("james"); XmlPage p3 = webClient.goToXml("queue/api/xml"); //james has DISCOVER permission on the project and will only be able to see the task name. List projects = p3.getByXPath("/queue/discoverableItem/task/name/text()"); assertEquals(1, projects.size()); assertEquals("project", projects.get(0).toString()); } //we force the project not to be executed so that it stays in the queue @TestExtension("queueApiOutputShouldBeFilteredByUserPermission") public static class MyQueueTaskDispatcher extends QueueTaskDispatcher { @Override public CauseOfBlockage canTake(Node node, Queue.BuildableItem item) { return new CauseOfBlockage() { @Override public String getShortDescription() { return "blocked by canTake"; } }; } } }