|
37 | 37 | import com.imageworks.spcue.VirtualProc; |
38 | 38 | import com.imageworks.spcue.dao.FrameDao; |
39 | 39 | import com.imageworks.spcue.dao.LayerDao; |
| 40 | +import com.imageworks.spcue.dao.ShowDao; |
40 | 41 | import com.imageworks.spcue.dispatcher.Dispatcher; |
41 | 42 | import com.imageworks.spcue.dispatcher.DispatchSupport; |
42 | 43 | import com.imageworks.spcue.dispatcher.FrameCompleteHandler; |
|
56 | 57 | import static org.junit.Assert.assertEquals; |
57 | 58 | import static org.junit.Assert.assertFalse; |
58 | 59 | import static org.junit.Assert.assertTrue; |
| 60 | +import static org.mockito.AdditionalAnswers.delegatesTo; |
59 | 61 | import static org.mockito.ArgumentMatchers.any; |
| 62 | +import static org.mockito.ArgumentMatchers.eq; |
60 | 63 | import static org.mockito.Mockito.doAnswer; |
| 64 | +import static org.mockito.Mockito.doReturn; |
61 | 65 | import static org.mockito.Mockito.doThrow; |
| 66 | +import static org.mockito.Mockito.mock; |
| 67 | +import static org.mockito.Mockito.never; |
62 | 68 | import static org.mockito.Mockito.spy; |
| 69 | +import static org.mockito.Mockito.times; |
| 70 | +import static org.mockito.Mockito.verify; |
63 | 71 |
|
64 | 72 | import org.springframework.transaction.CannotCreateTransactionException; |
65 | 73 |
|
@@ -534,4 +542,104 @@ public void testDependRetryExhausted() { |
534 | 542 | // All three calls fail — depend remains unsatisfied, frame stays in DEPEND |
535 | 543 | executeDependWithRetry(3, 1, FrameState.DEPEND); |
536 | 544 | } |
| 545 | + |
| 546 | + /** |
| 547 | + * Drives a frame completion through the proc-reuse branch (the job still has a WAITING frame, |
| 548 | + * so it stays dispatchable) and asserts whether the scheduler-managed release path fired. When |
| 549 | + * the show is scheduler-managed, the standalone scheduler owns dispatch, so Cuebot must release |
| 550 | + * (unbook) the proc here instead of rebooking it on the same proc — otherwise the proc lingers |
| 551 | + * with pk_frame=NULL holding reserved cores. The release is identified by the unique reason |
| 552 | + * string passed to unbookProc, which makes this independent of the nondeterministic downstream |
| 553 | + * dispatch. |
| 554 | + * |
| 555 | + * <p> |
| 556 | + * isSchedulerManaged is stubbed on a spy rather than written to the DB on purpose: the real |
| 557 | + * flag lives in an in-memory cache on ShowDaoJdbc that is NOT rolled back with the transaction, |
| 558 | + * so a real write would leak scheduler-managed behavior into other tests sharing the Spring |
| 559 | + * context. |
| 560 | + */ |
| 561 | + private void executeProcReuseGate(boolean schedulerManaged, FrameState terminalState) { |
| 562 | + JobDetail job = jobManager.findJobDetail("pipe-default-testuser_test_depend"); |
| 563 | + jobManager.setJobPaused(job, false); |
| 564 | + |
| 565 | + DispatchHost host = getHost(HOSTNAME); |
| 566 | + List<VirtualProc> procs = dispatcher.dispatchHost(host); |
| 567 | + assertEquals(1, procs.size()); |
| 568 | + VirtualProc proc = procs.get(0); |
| 569 | + |
| 570 | + RunningFrameInfo info = RunningFrameInfo.newBuilder().setJobId(proc.getJobId()) |
| 571 | + .setLayerId(proc.getLayerId()).setFrameId(proc.getFrameId()) |
| 572 | + .setResourceId(proc.getProcId()).build(); |
| 573 | + // The report must carry a healthy host (UP, plenty of free memory), otherwise the handler |
| 574 | + // unbooks the proc early (e.g. the < 512MB low-memory check) and returns before reaching |
| 575 | + // the |
| 576 | + // WAITING/SUCCEEDED proc-reuse branch that the scheduler-managed gate lives in. |
| 577 | + RenderHost reportHost = |
| 578 | + RenderHost.newBuilder().setName(HOSTNAME).setFreeMem((int) CueUtil.GB8) |
| 579 | + .setNimbyEnabled(false).setState(HardwareState.UP).build(); |
| 580 | + FrameCompleteReport report = FrameCompleteReport.newBuilder().setFrame(info) |
| 581 | + .setHost(reportHost).setExitStatus(0).build(); |
| 582 | + |
| 583 | + DispatchJob dispatchJob = jobManager.getDispatchJob(proc.getJobId()); |
| 584 | + DispatchFrame dispatchFrame = jobManager.getDispatchFrame(report.getFrame().getFrameId()); |
| 585 | + FrameDetail frameDetail = jobManager.getFrameDetail(report.getFrame().getFrameId()); |
| 586 | + dispatchSupport.stopFrame(dispatchFrame, terminalState, report.getExitStatus(), |
| 587 | + report.getFrame().getMaxRss()); |
| 588 | + |
| 589 | + // The DAO beans are Spring JDK dynamic proxies (final), so they can't be spied. Wrap them |
| 590 | + // in interface mocks that delegate to the real bean for everything except the one method we |
| 591 | + // stub, while still recording invocations for verification. |
| 592 | + ShowDao originalShowDao = frameCompleteHandler.getShowDao(); |
| 593 | + DispatchSupport originalDispatchSupport = frameCompleteHandler.getDispatchSupport(); |
| 594 | + ShowDao mockShowDao = mock(ShowDao.class, delegatesTo(originalShowDao)); |
| 595 | + DispatchSupport mockDispatchSupport = |
| 596 | + mock(DispatchSupport.class, delegatesTo(originalDispatchSupport)); |
| 597 | + doReturn(schedulerManaged).when(mockShowDao).isSchedulerManaged(proc.getShowId()); |
| 598 | + |
| 599 | + frameCompleteHandler.setShowDao(mockShowDao); |
| 600 | + frameCompleteHandler.setDispatchSupport(mockDispatchSupport); |
| 601 | + try { |
| 602 | + frameCompleteHandler.handlePostFrameCompleteOperations(proc, report, dispatchJob, |
| 603 | + dispatchFrame, terminalState, frameDetail); |
| 604 | + } finally { |
| 605 | + frameCompleteHandler.setShowDao(originalShowDao); |
| 606 | + frameCompleteHandler.setDispatchSupport(originalDispatchSupport); |
| 607 | + } |
| 608 | + |
| 609 | + verify(mockDispatchSupport, schedulerManaged ? times(1) : never()) |
| 610 | + .unbookProc(any(VirtualProc.class), eq("scheduler-managed show, releasing proc")); |
| 611 | + } |
| 612 | + |
| 613 | + @Test |
| 614 | + @Transactional |
| 615 | + @Rollback(true) |
| 616 | + public void testSchedulerManagedShowReleasesProc() { |
| 617 | + // Scheduler-managed, SUCCEEDED: the proc must be unbooked (released), not reused. |
| 618 | + executeProcReuseGate(true, FrameState.SUCCEEDED); |
| 619 | + } |
| 620 | + |
| 621 | + @Test |
| 622 | + @Transactional |
| 623 | + @Rollback(true) |
| 624 | + public void testNonSchedulerManagedShowReusesProc() { |
| 625 | + // Control, SUCCEEDED: the scheduler-managed release branch must not fire. |
| 626 | + executeProcReuseGate(false, FrameState.SUCCEEDED); |
| 627 | + } |
| 628 | + |
| 629 | + @Test |
| 630 | + @Transactional |
| 631 | + @Rollback(true) |
| 632 | + public void testSchedulerManagedShowReleasesProcWaiting() { |
| 633 | + // Scheduler-managed, WAITING: the other side of the WAITING/SUCCEEDED gate must also |
| 634 | + // release the proc. |
| 635 | + executeProcReuseGate(true, FrameState.WAITING); |
| 636 | + } |
| 637 | + |
| 638 | + @Test |
| 639 | + @Transactional |
| 640 | + @Rollback(true) |
| 641 | + public void testNonSchedulerManagedShowReusesProcWaiting() { |
| 642 | + // Control, WAITING: the scheduler-managed release branch must not fire. |
| 643 | + executeProcReuseGate(false, FrameState.WAITING); |
| 644 | + } |
537 | 645 | } |
0 commit comments