WorkspaceActivityChecker { @ScheduleDelay( initialDelayParameterName = "che.workspace.activity_cleanup_scheduler_initial_delay_s", delayParameterName = "che.workspace.activity_cleanup_scheduler_period_s") @VisibleForTesting void cleanup() { checkActivityRecordsValidity(); reconcileActivityStatuses(); } @Inject  WorkspaceActivityChecker(
      WorkspaceActivityDao activityDao,
      WorkspaceManager workspaceManager,
      WorkspaceRuntimes workspaceRuntimes,
      WorkspaceActivityManager workspaceActivityManager); @VisibleForTesting  WorkspaceActivityChecker(
      WorkspaceActivityDao activityDao,
      WorkspaceManager workspaceManager,
      WorkspaceRuntimes workspaceRuntimes,
      WorkspaceActivityManager workspaceActivityManager,
      Clock clock);   }
@Test public void shouldRecreateMissingActivityRecord() throws Exception { String id = "1"; when(workspaceRuntimes.getRunning()).thenReturn(singleton(id)); when(workspaceActivityDao.findActivity(eq(id))).thenReturn(null); when(workspaceManager.getWorkspace(eq(id))) .thenReturn( WorkspaceImpl.builder() .setId(id) .setAttributes(ImmutableMap.of(Constants.CREATED_ATTRIBUTE_NAME, "15")) .build()); clock.forward(Duration.of(1, ChronoUnit.SECONDS)); checker.cleanup(); ArgumentCaptor<WorkspaceActivity> captor = ArgumentCaptor.forClass(WorkspaceActivity.class); verify(workspaceActivityDao).createActivity(captor.capture()); WorkspaceActivity created = captor.getValue(); assertEquals(id, created.getWorkspaceId()); assertEquals(Long.valueOf(15), created.getCreated()); assertEquals(WorkspaceStatus.RUNNING, created.getStatus()); assertNotNull(created.getLastRunning()); assertEquals(clock.millis(), (long) created.getLastRunning()); assertNotNull(created.getExpiration()); assertEquals(clock.millis() + DEFAULT_TIMEOUT, (long) created.getExpiration()); }
@Test public void shouldRestoreCreatedTimeOnInvalidActivityRecord() throws Exception { String id = "1"; WorkspaceActivity invalidActivity = new WorkspaceActivity(); invalidActivity.setWorkspaceId(id); when(workspaceRuntimes.getRunning()).thenReturn(singleton(id)); when(workspaceActivityDao.findActivity(eq(id))).thenReturn(invalidActivity); when(workspaceManager.getWorkspace(eq(id))) .thenReturn( WorkspaceImpl.builder() .setId(id) .setAttributes(ImmutableMap.of(Constants.CREATED_ATTRIBUTE_NAME, "15")) .build()); checker.cleanup(); verify(workspaceActivityDao).setCreatedTime(eq(id), eq(15L)); }
@Test public void shouldContinueCheckActivitiesValidityIfExceptionOccurredOnRestoringOne() throws Exception { String id = "1"; WorkspaceActivity invalidActivity = new WorkspaceActivity(); invalidActivity.setWorkspaceId(id); when(workspaceRuntimes.getRunning()).thenReturn(ImmutableSet.of("problematic", id)); doThrow(new ServerException("error")) .when(workspaceActivityDao) .findActivity(eq("problematic")); doReturn(invalidActivity).when(workspaceActivityDao).findActivity(eq(id)); when(workspaceManager.getWorkspace(eq(id))) .thenReturn( WorkspaceImpl.builder() .setId(id) .setAttributes(ImmutableMap.of(Constants.CREATED_ATTRIBUTE_NAME, "15")) .build()); checker.cleanup(); verify(workspaceActivityDao).setCreatedTime(eq(id), eq(15L)); }
@Test public void shouldRestoreLastRunningTimeOnInvalidActivityRecordUsingCreatedTime() throws Exception { String id = "1"; WorkspaceActivity invalidActivity = new WorkspaceActivity(); invalidActivity.setWorkspaceId(id); invalidActivity.setCreated(15); when(workspaceRuntimes.getRunning()).thenReturn(singleton(id)); when(workspaceActivityDao.findActivity(eq(id))).thenReturn(invalidActivity); clock.forward(Duration.of(1, ChronoUnit.SECONDS)); checker.cleanup(); verify(workspaceActivityDao, never()).setCreatedTime(eq(id), anyLong()); verify(workspaceActivityDao) .setStatusChangeTime(eq(id), eq(WorkspaceStatus.RUNNING), eq(clock.millis())); }
@Test public void shouldRestoreLastRunningTimeOnInvalidActivityRecordUsingLastStartingTime() throws Exception { String id = "1"; WorkspaceActivity invalidActivity = new WorkspaceActivity(); invalidActivity.setWorkspaceId(id); invalidActivity.setLastStarting(10); when(workspaceRuntimes.getRunning()).thenReturn(singleton(id)); when(workspaceActivityDao.findActivity(eq(id))).thenReturn(invalidActivity); when(workspaceManager.getWorkspace(eq(id))) .thenReturn( WorkspaceImpl.builder() .setId(id) .setAttributes(ImmutableMap.of(Constants.CREATED_ATTRIBUTE_NAME, "15")) .build()); clock.forward(Duration.of(1, ChronoUnit.SECONDS)); checker.cleanup(); verify(workspaceActivityDao).setCreatedTime(eq(id), eq(15L)); verify(workspaceActivityDao) .setStatusChangeTime(eq(id), eq(WorkspaceStatus.RUNNING), eq(clock.millis())); }
@Test public void shouldRestoreExpirationTimeMoreThanASecondAfterRunning() throws Exception { long lastRunning = clock.millis(); String id = "1"; WorkspaceActivity invalidActivity = new WorkspaceActivity(); invalidActivity.setWorkspaceId(id); invalidActivity.setCreated(clock.millis()); invalidActivity.setLastRunning(lastRunning); when(workspaceRuntimes.getRunning()).thenReturn(singleton(id)); when(workspaceActivityDao.findActivity(eq(id))).thenReturn(invalidActivity); clock.forward(Duration.of(1500, ChronoUnit.MILLIS)); checker.cleanup(); verify(workspaceActivityDao).setExpirationTime(eq(id), eq(lastRunning + DEFAULT_TIMEOUT)); }
@Test public void shouldNotRestoreExpirationTimeLessThanASecondAfterRunning() throws Exception { String id = "1"; WorkspaceActivity invalidActivity = new WorkspaceActivity(); invalidActivity.setWorkspaceId(id); invalidActivity.setCreated(clock.millis()); invalidActivity.setLastRunning(clock.millis()); when(workspaceRuntimes.getRunning()).thenReturn(singleton(id)); when(workspaceActivityDao.findActivity(eq(id))).thenReturn(invalidActivity); clock.forward(Duration.of(900, ChronoUnit.MILLIS)); checker.cleanup(); verify(workspaceActivityDao, never()).setExpirationTime(anyString(), anyLong()); }
@Test public void shouldRestoreTrueStateOfWorkspaceIfActivityDoesntReflectThat() throws Exception { String wsId = "1"; WorkspaceActivity activity = new WorkspaceActivity(); activity.setCreated(clock.millis()); activity.setWorkspaceId(wsId); activity.setStatus(WorkspaceStatus.STARTING); activity.setLastStarting(clock.millis()); doAnswer( inv -> { int maxItems = inv.getArgument(0); long skipCount = inv.getArgument(1); if (skipCount < 1) { return new Page<>(singleton(activity), skipCount, maxItems, 1); } else { return new Page<>(emptyList(), skipCount, maxItems, 1); } }) .when(workspaceActivityDao) .getAll(anyInt(), anyLong()); when(workspaceRuntimes.getStatus(eq(wsId))).thenReturn(WorkspaceStatus.STOPPED); checker.cleanup(); verify(workspaceActivityDao) .setStatusChangeTime(eq(wsId), eq(WorkspaceStatus.STOPPED), eq(clock.millis())); }
@Test public void shouldContinueReconcileStatusesWhenExceptionOccurredOnOne() throws Exception { String wsId1 = "1"; WorkspaceActivity activity1 = new WorkspaceActivity(); activity1.setCreated(clock.millis()); activity1.setWorkspaceId(wsId1); activity1.setStatus(WorkspaceStatus.STARTING); activity1.setLastStarting(clock.millis()); String wsId2 = "2"; WorkspaceActivity activity2 = new WorkspaceActivity(); activity2.setCreated(clock.millis()); activity2.setWorkspaceId(wsId2); activity2.setStatus(WorkspaceStatus.STARTING); activity2.setLastStarting(clock.millis()); doAnswer( inv -> { int maxItems = inv.getArgument(0); long skipCount = inv.getArgument(1); switch ((int) skipCount) { case 0: return new Page<>(singleton(activity1), 0, 1, 2); case 1: return new Page<>(singleton(activity2), 1, 1, 2); default: return new Page<>(emptyList(), skipCount, maxItems, 2); } }) .when(workspaceActivityDao) .getAll(anyInt(), anyLong()); doReturn(WorkspaceStatus.STOPPED).when(workspaceRuntimes).getStatus(any()); doThrow(new ServerException("Error")) .when(workspaceActivityDao) .setStatusChangeTime(eq(wsId1), any(WorkspaceStatus.class), anyLong()); checker.cleanup(); verify(workspaceActivityDao) .setStatusChangeTime(eq(wsId2), eq(WorkspaceStatus.STOPPED), eq(clock.millis())); }
@Test public void shouldNotThrowExceptionWhenErrorOccurredDuringActivitiesListingOnReconciling() throws Exception { doThrow(new ServerException("error")).when(workspaceActivityDao).getAll(anyInt(), anyLong()); checker.cleanup(); }