|
24 | 24 |
|
25 | 25 | package hudson.model; |
26 | 26 |
|
| 27 | +import static org.awaitility.Awaitility.await; |
27 | 28 | import static org.hamcrest.MatcherAssert.assertThat; |
28 | 29 | import static org.hamcrest.Matchers.contains; |
29 | 30 | import static org.hamcrest.Matchers.containsString; |
|
71 | 72 | import hudson.security.Permission; |
72 | 73 | import hudson.security.ProjectMatrixAuthorizationStrategy; |
73 | 74 | import hudson.security.SparseACL; |
| 75 | +import hudson.slaves.ComputerLauncher; |
74 | 76 | import hudson.slaves.DumbSlave; |
75 | 77 | import hudson.slaves.OfflineCause; |
| 78 | +import hudson.slaves.RetentionStrategy; |
| 79 | +import hudson.slaves.SlaveComputer; |
76 | 80 | import hudson.tasks.BatchFile; |
77 | 81 | import hudson.tasks.BuildTrigger; |
78 | 82 | import hudson.tasks.Shell; |
|
107 | 111 | import jenkins.model.Jenkins; |
108 | 112 | import jenkins.model.queue.QueueIdStrategy; |
109 | 113 | import jenkins.security.QueueItemAuthenticatorConfiguration; |
| 114 | +import jenkins.util.Timer; |
110 | 115 | import org.acegisecurity.acls.sid.PrincipalSid; |
111 | 116 | import org.htmlunit.HttpMethod; |
112 | 117 | import org.htmlunit.Page; |
@@ -1329,4 +1334,64 @@ public void run() { |
1329 | 1334 | execute(new BuildExecution()); |
1330 | 1335 | } |
1331 | 1336 | } |
| 1337 | + |
| 1338 | + private static class SlowSlave extends Slave { |
| 1339 | + SlowSlave(String name, File remoteFS, ComputerLauncher launcher) throws Descriptor.FormException, IOException { |
| 1340 | + super(name, remoteFS.getAbsolutePath(), launcher); |
| 1341 | + } |
| 1342 | + |
| 1343 | + @Override public Computer createComputer() { |
| 1344 | + return new SlowComputer(this); |
| 1345 | + } |
| 1346 | + } |
| 1347 | + |
| 1348 | + private static class SlowComputer extends SlaveComputer { |
| 1349 | + SlowComputer(SlowSlave slave) { |
| 1350 | + super(slave); |
| 1351 | + } |
| 1352 | + |
| 1353 | + @Override |
| 1354 | + public boolean isOffline() { |
| 1355 | + try { |
| 1356 | + // This delay is just big enough to allow the test to simulate a computer failure at the time we expect. |
| 1357 | + Thread.sleep(100); |
| 1358 | + } catch (InterruptedException e) { |
| 1359 | + // ignore |
| 1360 | + } |
| 1361 | + return super.isOffline(); |
| 1362 | + } |
| 1363 | + } |
| 1364 | + |
| 1365 | + @Test |
| 1366 | + void computerFailsJustAfterCreatingExecutor() throws Throwable { |
| 1367 | + r.jenkins.setNumExecutors(0); |
| 1368 | + var p = r.createFreeStyleProject(); |
| 1369 | + p.setAssignedLabel(r.jenkins.getLabel("agent")); |
| 1370 | + Slave onlineSlave = new SlowSlave("special", new File(r.jenkins.getRootDir(), "agent-work-dirs/special"), r.createComputerLauncher(null)); |
| 1371 | + onlineSlave.setLabelString("agent"); |
| 1372 | + onlineSlave.setRetentionStrategy(RetentionStrategy.NOOP); |
| 1373 | + r.jenkins.addNode(onlineSlave); |
| 1374 | + r.waitOnline(onlineSlave); |
| 1375 | + |
| 1376 | + var computer = onlineSlave.toComputer(); |
| 1377 | + Timer.get().execute(() -> { |
| 1378 | + // Simulate a computer failure just after the executor is created |
| 1379 | + while (computer.getExecutors().get(0).getStartTime() == 0) { |
| 1380 | + try { |
| 1381 | + Thread.sleep(10); |
| 1382 | + } catch (InterruptedException e) { |
| 1383 | + // ignore |
| 1384 | + } |
| 1385 | + } |
| 1386 | + computer.disconnect(new OfflineCause.ChannelTermination(new IllegalStateException())); |
| 1387 | + }); |
| 1388 | + var f = p.scheduleBuild2(0); |
| 1389 | + await().until(computer::isOffline); |
| 1390 | + Thread.sleep(1000); |
| 1391 | + assertFalse(r.jenkins.getQueue().isEmpty(), "Queue item should be back as the executor got killed before it could be picked up"); |
| 1392 | + // Put the computer back online |
| 1393 | + r.waitOnline(onlineSlave); |
| 1394 | + r.assertBuildStatusSuccess(f); |
| 1395 | + assertTrue(r.jenkins.getQueue().isEmpty()); |
| 1396 | + } |
1332 | 1397 | } |
0 commit comments