|
16 | 16 | // under the License. |
17 | 17 | package com.cloud.storage.snapshot; |
18 | 18 |
|
| 19 | +import com.cloud.api.ApiDispatcher; |
19 | 20 | import com.cloud.event.ActionEventUtils; |
20 | 21 | import com.cloud.event.EventTypes; |
21 | 22 | import com.cloud.event.EventVO; |
22 | 23 | import com.cloud.event.dao.EventDao; |
| 24 | +import com.cloud.server.TaggedResourceService; |
23 | 25 | import com.cloud.storage.Snapshot; |
24 | 26 | import com.cloud.storage.SnapshotPolicyVO; |
25 | 27 | import com.cloud.storage.SnapshotScheduleVO; |
26 | 28 | import com.cloud.storage.SnapshotVO; |
27 | 29 | import com.cloud.storage.VolumeVO; |
| 30 | +import com.cloud.storage.dao.SnapshotDao; |
28 | 31 | import com.cloud.storage.dao.SnapshotPolicyDao; |
29 | 32 | import com.cloud.storage.dao.SnapshotScheduleDao; |
30 | 33 | import com.cloud.storage.dao.VolumeDao; |
|
34 | 37 | import com.cloud.vm.VMInstanceVO; |
35 | 38 | import com.cloud.vm.VirtualMachine; |
36 | 39 | import com.cloud.vm.dao.VMInstanceDao; |
| 40 | +import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher; |
| 41 | +import org.apache.cloudstack.framework.jobs.AsyncJobManager; |
37 | 42 | import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao; |
38 | 43 | import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; |
39 | 44 | import org.apache.cloudstack.jobs.JobInfo; |
|
53 | 58 | import java.util.Date; |
54 | 59 | import java.util.List; |
55 | 60 |
|
| 61 | +import static org.mockito.Mockito.doNothing; |
56 | 62 | import static org.mockito.Mockito.doReturn; |
| 63 | +import static org.mockito.Mockito.doThrow; |
57 | 64 | import static org.mockito.Mockito.mock; |
58 | 65 | import static org.mockito.Mockito.never; |
59 | 66 | import static org.mockito.Mockito.verify; |
@@ -107,6 +114,20 @@ public class SnapshotSchedulerImplTest { |
107 | 114 | @Mock |
108 | 115 | private SnapshotVO snapshotVoMock; |
109 | 116 |
|
| 117 | + @Mock |
| 118 | + private ApiDispatcher apiDispatcherMock; |
| 119 | + |
| 120 | + @Mock |
| 121 | + private AsyncJobManager asyncJobManagerMock; |
| 122 | + |
| 123 | + @Mock |
| 124 | + private TaggedResourceService taggedResourceServiceMock; |
| 125 | + |
| 126 | + @Mock |
| 127 | + private AsyncJobDispatcher asyncJobDispatcherMock; |
| 128 | + |
| 129 | + @Mock |
| 130 | + private SnapshotDao snapshotDaoMock; |
110 | 131 |
|
111 | 132 | @Test |
112 | 133 | public void scheduleNextSnapshotJobTestParameterIsNullReturnNull() { |
@@ -343,7 +364,7 @@ public void countConsecutiveFailedAttemptsTestNoEventsReturnsZero() { |
343 | 364 | // --- getScopedConfigValue (#13454) --- |
344 | 365 |
|
345 | 366 | @Test |
346 | | - public void getScopedConfigValueTestFallsBackToGlobalDefaultWhenNoDepotConfigured() { |
| 367 | + public void getScopedConfigValueTestFallsBackToGlobalDefaultWhenNoScopeOverrideConfigured() { |
347 | 368 | doReturn(1L).when(volumeVoMock).getAccountId(); |
348 | 369 | doReturn(1L).when(volumeVoMock).getDataCenterId(); |
349 | 370 | doReturn(1L).when(accountVoMock).getDomainId(); |
@@ -430,6 +451,119 @@ public void recordSnapshotAttemptOutcomeTestAtMaxNotifiesWithDistinctEventType() |
430 | 451 | } |
431 | 452 | } |
432 | 453 |
|
| 454 | + @Test |
| 455 | + public void recordSnapshotAttemptOutcomeTestSucceededLogsInfoEventAndReturns() { |
| 456 | + doReturn(volumeVoMock).when(volumeDaoMock).findByIdIncludingRemoved(Mockito.anyLong()); |
| 457 | + |
| 458 | + try (MockedStatic<ActionEventUtils> actionEventUtilsMocked = Mockito.mockStatic(ActionEventUtils.class)) { |
| 459 | + snapshotSchedulerImplSpy.recordSnapshotAttemptOutcome(snapshotScheduleVoMock, true, null); |
| 460 | + |
| 461 | + actionEventUtilsMocked.verify(() -> ActionEventUtils.onCreatedActionEvent( |
| 462 | + Mockito.anyLong(), Mockito.anyLong(), Mockito.eq(EventVO.LEVEL_INFO), Mockito.eq(EventTypes.EVENT_SNAPSHOT_CREATE), |
| 463 | + Mockito.anyBoolean(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString())); |
| 464 | + } |
| 465 | + |
| 466 | + verify(accountDaoMock, never()).findById(Mockito.anyLong()); |
| 467 | + verify(eventDaoMock, never()).listLatestEventsByResource(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyInt()); |
| 468 | + } |
| 469 | + |
| 470 | + // --- dispatchSnapshotCreateJob (#13454) --- |
| 471 | + |
| 472 | + @Test |
| 473 | + public void dispatchSnapshotCreateJobTestBuildsAsyncJobAndReturnsScheduledEventId() throws Exception { |
| 474 | + doReturn(1L).when(snapshotScheduleVoMock).getId(); |
| 475 | + doReturn(1L).when(snapshotScheduleVoMock).getPolicyId(); |
| 476 | + doReturn(2L).when(snapshotScheduleVoMock).getVolumeId(); |
| 477 | + doReturn(new Date()).when(snapshotScheduleVoMock).getScheduledTimestamp(); |
| 478 | + |
| 479 | + doReturn(1L).when(volumeVoMock).getAccountId(); |
| 480 | + doReturn("volume-uuid").when(volumeVoMock).getUuid(); |
| 481 | + |
| 482 | + doReturn(Collections.emptyList()).when(taggedResourceServiceMock).listByResourceTypeAndId(Mockito.any(), Mockito.anyLong()); |
| 483 | + doReturn(7L).when(asyncJobManagerMock).submitAsyncJob(Mockito.any(AsyncJobVO.class)); |
| 484 | + doReturn("SnapshotDispatcher").when(asyncJobDispatcherMock).getName(); |
| 485 | + snapshotSchedulerImplSpy.setAsyncJobDispatcher(asyncJobDispatcherMock); |
| 486 | + |
| 487 | + try (MockedStatic<ActionEventUtils> actionEventUtilsMocked = Mockito.mockStatic(ActionEventUtils.class); |
| 488 | + MockedStatic<com.cloud.utils.component.ComponentContext> componentContextMocked = Mockito.mockStatic(com.cloud.utils.component.ComponentContext.class)) { |
| 489 | + actionEventUtilsMocked.when(() -> ActionEventUtils.onScheduledActionEvent( |
| 490 | + Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyLong()) |
| 491 | + ).thenReturn(42L); |
| 492 | + |
| 493 | + Long eventId = snapshotSchedulerImplSpy.dispatchSnapshotCreateJob(snapshotScheduleVoMock, volumeVoMock, snapshotScheduleVoMock); |
| 494 | + |
| 495 | + Assert.assertEquals(Long.valueOf(42L), eventId); |
| 496 | + } |
| 497 | + |
| 498 | + verify(snapshotScheduleVoMock).setAsyncJobId(7L); |
| 499 | + verify(snapshotScheduleDaoMock).update(1L, snapshotScheduleVoMock); |
| 500 | + } |
| 501 | + |
| 502 | + // --- shouldSkipSchedule / scheduleSnapshots (#13454, #6827) --- |
| 503 | + |
| 504 | + @Test |
| 505 | + public void shouldSkipScheduleTestCannotBeScheduledReturnsTrueWithoutCheckingUnchangedVolume() { |
| 506 | + doReturn(false).when(snapshotSchedulerImplSpy).canSnapshotBeScheduled(Mockito.any(), Mockito.any()); |
| 507 | + |
| 508 | + boolean result = snapshotSchedulerImplSpy.shouldSkipSchedule(snapshotScheduleVoMock, volumeVoMock); |
| 509 | + |
| 510 | + Assert.assertTrue(result); |
| 511 | + verify(snapshotSchedulerImplSpy, never()).shouldSkipUnchangedVolumeSnapshot(Mockito.any()); |
| 512 | + } |
| 513 | + |
| 514 | + @Test |
| 515 | + public void shouldSkipScheduleTestUnchangedVolumeReschedulesAndReturnsTrue() { |
| 516 | + doReturn(true).when(snapshotSchedulerImplSpy).canSnapshotBeScheduled(Mockito.any(), Mockito.any()); |
| 517 | + doReturn(true).when(snapshotSchedulerImplSpy).shouldSkipUnchangedVolumeSnapshot(Mockito.any()); |
| 518 | + doNothing().when(snapshotSchedulerImplSpy).skipAndRescheduleSnapshot(Mockito.any(), Mockito.any()); |
| 519 | + |
| 520 | + boolean result = snapshotSchedulerImplSpy.shouldSkipSchedule(snapshotScheduleVoMock, volumeVoMock); |
| 521 | + |
| 522 | + Assert.assertTrue(result); |
| 523 | + verify(snapshotSchedulerImplSpy).skipAndRescheduleSnapshot(snapshotScheduleVoMock, volumeVoMock); |
| 524 | + } |
| 525 | + |
| 526 | + @Test |
| 527 | + public void shouldSkipScheduleTestCanBeScheduledAndNotUnchangedReturnsFalse() { |
| 528 | + doReturn(true).when(snapshotSchedulerImplSpy).canSnapshotBeScheduled(Mockito.any(), Mockito.any()); |
| 529 | + doReturn(false).when(snapshotSchedulerImplSpy).shouldSkipUnchangedVolumeSnapshot(Mockito.any()); |
| 530 | + |
| 531 | + boolean result = snapshotSchedulerImplSpy.shouldSkipSchedule(snapshotScheduleVoMock, volumeVoMock); |
| 532 | + |
| 533 | + Assert.assertFalse(result); |
| 534 | + verify(snapshotSchedulerImplSpy, never()).skipAndRescheduleSnapshot(Mockito.any(), Mockito.any()); |
| 535 | + } |
| 536 | + |
| 537 | + @Test |
| 538 | + public void scheduleSnapshotsTestSkippedScheduleIsNeverLockedOrDispatched() throws Exception { |
| 539 | + ReflectionTestUtils.setField(snapshotSchedulerImplSpy, "_currentTimestamp", new Date()); |
| 540 | + doReturn(List.of(snapshotScheduleVoMock)).when(snapshotScheduleDaoMock).getSchedulesToExecute(Mockito.any(Date.class)); |
| 541 | + doReturn(volumeVoMock).when(volumeDaoMock).findByIdIncludingRemoved(Mockito.anyLong()); |
| 542 | + doReturn(true).when(snapshotSchedulerImplSpy).shouldSkipSchedule(Mockito.any(), Mockito.any()); |
| 543 | + |
| 544 | + snapshotSchedulerImplSpy.scheduleSnapshots(); |
| 545 | + |
| 546 | + verify(snapshotScheduleDaoMock, never()).acquireInLockTable(Mockito.anyLong()); |
| 547 | + verify(snapshotSchedulerImplSpy, never()).dispatchSnapshotCreateJob(Mockito.any(), Mockito.any(), Mockito.any()); |
| 548 | + } |
| 549 | + |
| 550 | + @Test |
| 551 | + public void scheduleSnapshotsTestDispatchFailureIsHandledAndLockIsReleased() throws Exception { |
| 552 | + ReflectionTestUtils.setField(snapshotSchedulerImplSpy, "_currentTimestamp", new Date()); |
| 553 | + doReturn(1L).when(snapshotScheduleVoMock).getId(); |
| 554 | + doReturn(List.of(snapshotScheduleVoMock)).when(snapshotScheduleDaoMock).getSchedulesToExecute(Mockito.any(Date.class)); |
| 555 | + doReturn(volumeVoMock).when(volumeDaoMock).findByIdIncludingRemoved(Mockito.anyLong()); |
| 556 | + doReturn(false).when(snapshotSchedulerImplSpy).shouldSkipSchedule(Mockito.any(), Mockito.any()); |
| 557 | + doReturn(snapshotScheduleVoMock).when(snapshotScheduleDaoMock).acquireInLockTable(1L); |
| 558 | + doThrow(new RuntimeException("dispatch boom")).when(snapshotSchedulerImplSpy).dispatchSnapshotCreateJob(Mockito.any(), Mockito.any(), Mockito.any()); |
| 559 | + doNothing().when(snapshotSchedulerImplSpy).handleFailedSnapshotDispatch(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); |
| 560 | + |
| 561 | + snapshotSchedulerImplSpy.scheduleSnapshots(); |
| 562 | + |
| 563 | + verify(snapshotSchedulerImplSpy).handleFailedSnapshotDispatch(Mockito.eq(snapshotScheduleVoMock), Mockito.eq(volumeVoMock), Mockito.eq(snapshotScheduleVoMock), Mockito.isNull(), Mockito.any(Exception.class)); |
| 564 | + verify(snapshotScheduleDaoMock).releaseFromLockTable(1L); |
| 565 | + } |
| 566 | + |
433 | 567 | // --- shouldSkipUnchangedVolumeSnapshot (#6827) --- |
434 | 568 |
|
435 | 569 | @Test |
@@ -538,4 +672,39 @@ public void skipAndRescheduleSnapshotTestUpdatesScheduleAndLogsEvent() { |
538 | 672 | verify(snapshotScheduleDaoMock).update(1L, snapshotScheduleVoMock); |
539 | 673 | verify(snapshotScheduleDaoMock).releaseFromLockTable(1L); |
540 | 674 | } |
| 675 | + |
| 676 | + @Test |
| 677 | + public void skipAndRescheduleSnapshotTestCouldNotAcquireLockDoesNothing() { |
| 678 | + doReturn(1L).when(snapshotScheduleVoMock).getId(); |
| 679 | + doReturn(null).when(snapshotScheduleDaoMock).acquireInLockTable(1L); |
| 680 | + |
| 681 | + try (MockedStatic<ActionEventUtils> actionEventUtilsMocked = Mockito.mockStatic(ActionEventUtils.class)) { |
| 682 | + snapshotSchedulerImplSpy.skipAndRescheduleSnapshot(snapshotScheduleVoMock, volumeVoMock); |
| 683 | + |
| 684 | + actionEventUtilsMocked.verifyNoInteractions(); |
| 685 | + } |
| 686 | + |
| 687 | + verify(snapshotScheduleDaoMock, never()).update(Mockito.anyLong(), Mockito.any()); |
| 688 | + verify(snapshotScheduleDaoMock, never()).releaseFromLockTable(Mockito.anyLong()); |
| 689 | + } |
| 690 | + |
| 691 | + // --- findLastSnapshot --- |
| 692 | + |
| 693 | + @Test |
| 694 | + public void findLastSnapshotTestReturnsMostRecentSnapshot() { |
| 695 | + doReturn(List.of(snapshotVoMock)).when(snapshotDaoMock).listByVolumeId(Mockito.any(), Mockito.eq(1L)); |
| 696 | + |
| 697 | + SnapshotVO result = snapshotSchedulerImplSpy.findLastSnapshot(1L); |
| 698 | + |
| 699 | + Assert.assertSame(snapshotVoMock, result); |
| 700 | + } |
| 701 | + |
| 702 | + @Test |
| 703 | + public void findLastSnapshotTestNoSnapshotsReturnsNull() { |
| 704 | + doReturn(Collections.emptyList()).when(snapshotDaoMock).listByVolumeId(Mockito.any(), Mockito.eq(1L)); |
| 705 | + |
| 706 | + SnapshotVO result = snapshotSchedulerImplSpy.findLastSnapshot(1L); |
| 707 | + |
| 708 | + Assert.assertNull(result); |
| 709 | + } |
541 | 710 | } |
0 commit comments