/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.workspace.server; import com.google.common.collect.ImmutableSet; import org.eclipse.che.account.api.AccountManager; import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.machine.MachineStatus; import org.eclipse.che.api.core.model.workspace.ExtendedMachine; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.machine.server.exception.SnapshotException; import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.machine.server.model.impl.MachineImpl; import org.eclipse.che.api.machine.server.model.impl.MachineLimitsImpl; import org.eclipse.che.api.machine.server.model.impl.MachineRuntimeInfoImpl; import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl; import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; import org.eclipse.che.api.machine.server.spi.SnapshotDao; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentRecipeImpl; import org.eclipse.che.api.workspace.server.model.impl.ExtendedMachineImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceRuntimeImpl; import org.eclipse.che.api.workspace.server.spi.WorkspaceDao; import org.eclipse.che.api.workspace.shared.Constants; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import static com.google.common.base.Strings.isNullOrEmpty; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.RUNNING; import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STARTING; import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STOPPED; import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STOPPING; import static org.eclipse.che.api.workspace.server.WorkspaceManager.CREATED_ATTRIBUTE_NAME; import static org.eclipse.che.api.workspace.server.WorkspaceManager.SNAPSHOTTED_AT_ATTRIBUTE_NAME; import static org.eclipse.che.api.workspace.server.WorkspaceManager.UPDATED_ATTRIBUTE_NAME; import static org.eclipse.che.api.workspace.shared.Constants.AUTO_CREATE_SNAPSHOT; import static org.eclipse.che.api.workspace.shared.Constants.AUTO_RESTORE_FROM_SNAPSHOT; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; /** * Covers main cases of {@link WorkspaceManager}. * * @author Yevhenii Voevodin */ @Listeners(value = {MockitoTestNGListener.class}) public class WorkspaceManagerTest { private static final String USER_ID = "user123"; private static final String NAMESPACE = "userNS"; private static final String NAMESPACE_2 = "userNS2"; @Mock private WorkspaceDao workspaceDao; @Mock private WorkspaceRuntimes runtimes; @Mock private AccountManager accountManager; @Mock private SnapshotDao snapshotDao; @Mock private WorkspaceSharedPool sharedPool; @Mock private EventService eventService; @Captor private ArgumentCaptor<WorkspaceImpl> workspaceCaptor; @Captor private ArgumentCaptor<Runnable> taskCaptor; private WorkspaceManager workspaceManager; @BeforeMethod public void setUp() throws Exception { MockitoAnnotations.initMocks(this); workspaceManager = new WorkspaceManager(workspaceDao, runtimes, eventService, accountManager, false, false, snapshotDao, sharedPool); when(accountManager.getByName(NAMESPACE)).thenReturn(new AccountImpl("accountId", NAMESPACE, "test")); when(accountManager.getByName(NAMESPACE_2)).thenReturn(new AccountImpl("accountId2", NAMESPACE_2, "test")); when(workspaceDao.create(any(WorkspaceImpl.class))).thenAnswer(invocation -> invocation.getArguments()[0]); when(workspaceDao.update(any(WorkspaceImpl.class))).thenAnswer(invocation -> invocation.getArguments()[0]); EnvironmentContext.setCurrent(new EnvironmentContext() { @Override public Subject getSubject() { return new SubjectImpl(NAMESPACE, USER_ID, "token", false); } }); } @Test public void shouldBeAbleToCreateWorkspace() throws Exception { final WorkspaceConfig cfg = createConfig(); final WorkspaceImpl workspace = workspaceManager.createWorkspace(cfg, NAMESPACE); assertNotNull(workspace); assertFalse(isNullOrEmpty(workspace.getId())); assertEquals(workspace.getNamespace(), NAMESPACE); assertEquals(workspace.getConfig(), cfg); assertFalse(workspace.isTemporary()); assertEquals(workspace.getStatus(), STOPPED); assertNotNull(workspace.getAttributes().get(CREATED_ATTRIBUTE_NAME)); verify(workspaceDao).create(workspace); } @Test public void shouldBeAbleToCreateWorkspaceWithAttributes() throws Exception { WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE, singletonMap("testKey", "testValue")); assertEquals(workspace.getAttributes().get("testKey"), "testValue"); assertEquals(workspace.getStatus(), STOPPED); } @Test public void getsWorkspaceByIdWithoutRuntime() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); final WorkspaceImpl result = workspaceManager.getWorkspace(workspace.getId()); assertEquals(result, workspace); } @Test public void getsWorkspaceByIdWithRuntime() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); mockRuntime(workspace, STARTING); WorkspaceImpl result = workspaceManager.getWorkspace(workspace.getId()); assertEquals(result.getStatus(), STARTING, "Workspace status must be taken from the runtime instance"); } @Test public void shouldBeAbleToGetWorkspaceByName() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); WorkspaceImpl result = workspaceManager.getWorkspace(workspace.getConfig().getName(), workspace.getNamespace()); assertEquals(result, workspace); } @Test(expectedExceptions = NotFoundException.class) public void getWorkspaceShouldThrowNotFoundExceptionWhenWorkspaceDoesNotExist() throws Exception { when(workspaceDao.get(any())).thenThrow(new NotFoundException("not found")); workspaceManager.getWorkspace("workspace123"); } @Test public void shouldBeAbleToGetWorkspaceByKey() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); WorkspaceImpl result = workspaceManager.getWorkspace(workspace.getNamespace() + "/" + workspace.getConfig().getName()); assertEquals(result, workspace); } @Test public void shouldBeAbleToGetWorkspaceByKeyPreviousFormat() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); WorkspaceImpl result = workspaceManager.getWorkspace(workspace.getNamespace() + ":" + workspace.getConfig().getName()); assertEquals(result, workspace); } @Test public void shouldBeAbleToGetWorkspaceByKeyNamespaceOptionalPreviousFormat() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); WorkspaceImpl result = workspaceManager.getWorkspace(":" + workspace.getConfig().getName()); assertEquals(result, workspace); } @Test public void shouldBeAbleToGetWorkspaceByKeyWithoutOwner() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); WorkspaceImpl result = workspaceManager.getWorkspace(":" + workspace.getConfig().getName()); assertEquals(result, workspace); } @Test public void shouldBeAbleToGetWorkspacesAvailableForUser() throws Exception { // given final WorkspaceConfig config = createConfig(); final WorkspaceImpl workspace1 = createAndMockWorkspace(config, NAMESPACE); final WorkspaceImpl workspace2 = createAndMockWorkspace(config, NAMESPACE_2); when(workspaceDao.getWorkspaces(NAMESPACE)).thenReturn(asList(workspace1, workspace2)); mockRuntime(workspace1, STOPPED); mockRuntime(workspace2, RUNNING); // when final List<WorkspaceImpl> result = workspaceManager.getWorkspaces(NAMESPACE, true); // then assertEquals(result.size(), 2); final WorkspaceImpl res1 = result.get(0); assertEquals(res1.getStatus(), STOPPED); assertFalse(res1.isTemporary(), "Workspace must be permanent"); final WorkspaceImpl res2 = result.get(1); assertEquals(res2.getStatus(), RUNNING, "Workspace status wasn't changed to the runtime instance status"); assertFalse(res2.isTemporary(), "Workspace must be permanent"); } @Test public void shouldBeAbleToGetWorkspacesByNamespace() throws Exception { // given final WorkspaceImpl workspace = createAndMockWorkspace(); mockRuntime(workspace, RUNNING); // when final List<WorkspaceImpl> result = workspaceManager.getByNamespace(workspace.getNamespace(), true); // then assertEquals(result.size(), 1); final WorkspaceImpl res1 = result.get(0); assertEquals(res1.getStatus(), RUNNING, "Workspace status wasn't changed to the runtime instance status"); assertFalse(res1.isTemporary(), "Workspace must be permanent"); } @Test public void getWorkspaceByNameShouldReturnWorkspaceWithStatusEqualToItsRuntimeStatus() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); mockRuntime(workspace, STARTING); final WorkspaceImpl result = workspaceManager.getWorkspace(workspace.getConfig().getName(), workspace.getNamespace()); assertEquals(result.getStatus(), STARTING, "Workspace status must be taken from the runtime instance"); } @Test public void shouldBeAbleToUpdateWorkspace() throws Exception { WorkspaceImpl workspace = new WorkspaceImpl(createAndMockWorkspace()); workspace.setTemporary(true); workspace.getAttributes().put("new attribute", "attribute"); when(workspaceDao.update(any())).thenAnswer(inv -> inv.getArguments()[0]); workspaceManager.updateWorkspace(workspace.getId(), workspace); verify(workspaceDao).update(workspace); } @Test public void workspaceUpdateShouldReturnWorkspaceWithStatusEqualToItsRuntimeStatus() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); mockRuntime(workspace, STARTING); final WorkspaceImpl updated = workspaceManager.updateWorkspace(workspace.getId(), workspace); assertEquals(updated.getStatus(), STARTING); } @Test public void shouldRemoveWorkspace() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); workspaceManager.removeWorkspace(workspace.getId()); verify(workspaceDao).remove(workspace.getId()); } @Test(expectedExceptions = ConflictException.class) public void shouldNotRemoveWorkspaceIfItIsNotStopped() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); when(runtimes.hasRuntime(workspace.getId())).thenReturn(true); workspaceManager.removeWorkspace(workspace.getId()); } @Test public void shouldBeAbleToStartWorkspaceById() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); mockStart(workspace); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), null); verify(runtimes).startAsync(workspace, workspace.getConfig().getDefaultEnv(), false); assertNotNull(workspace.getAttributes().get(UPDATED_ATTRIBUTE_NAME)); } @Test public void shouldRecoverWorkspaceWhenRecoverParameterIsNullAndAutoRestoreAttributeIsSetAndSnapshotExists() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); workspace.getAttributes().put(AUTO_RESTORE_FROM_SNAPSHOT, "true"); mockStart(workspace); SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder() .generateId() .setEnvName("env") .setDev(true) .setMachineName("machine1") .setWorkspaceId(workspace.getId()) .setType("docker") .setMachineSource(new MachineSourceImpl("image")); SnapshotImpl snapshot1 = snapshotBuilder.build(); SnapshotImpl snapshot2 = snapshotBuilder.generateId() .setDev(false) .setMachineName("machine2") .build(); when(snapshotDao.findSnapshots(workspace.getId())) .thenReturn(asList(snapshot1, snapshot2)); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), null); verify(runtimes).startAsync(workspace, workspace.getConfig().getDefaultEnv(), true); assertNotNull(workspace.getAttributes().get(UPDATED_ATTRIBUTE_NAME)); } @Test public void shouldRecoverWorkspaceWhenRecoverParameterIsTrueAndSnapshotExists() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); mockStart(workspace); SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder() .generateId() .setEnvName("env") .setDev(true) .setMachineName("machine1") .setWorkspaceId(workspace.getId()) .setType("docker") .setMachineSource(new MachineSourceImpl("image")); SnapshotImpl snapshot1 = snapshotBuilder.build(); SnapshotImpl snapshot2 = snapshotBuilder.generateId() .setDev(false) .setMachineName("machine2") .build(); when(snapshotDao.findSnapshots(workspace.getId())) .thenReturn(asList(snapshot1, snapshot2)); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), true); verify(runtimes).startAsync(workspace, workspace.getConfig().getDefaultEnv(), true); assertNotNull(workspace.getAttributes().get(UPDATED_ATTRIBUTE_NAME)); } @Test public void shouldNotRecoverWorkspaceWhenRecoverParameterIsNullAndAutoRestoreAttributesIsSetButSnapshotDoesNotExist() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); workspace.getAttributes().put(AUTO_RESTORE_FROM_SNAPSHOT, "true"); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); mockStart(workspace); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), null); verify(runtimes).startAsync(workspace, workspace.getConfig().getDefaultEnv(), false); assertNotNull(workspace.getAttributes().get(UPDATED_ATTRIBUTE_NAME)); } @Test public void shouldNotRecoverWorkspaceWhenRecoverParameterIsTrueButSnapshotDoesNotExist() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); mockStart(workspace); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), true); verify(runtimes).startAsync(workspace, workspace.getConfig().getDefaultEnv(), false); assertNotNull(workspace.getAttributes().get(UPDATED_ATTRIBUTE_NAME)); } @Test public void shouldNotRecoverWorkspaceWhenRecoverParameterIsFalseAndAutoRestoreAttributeIsSetAndSnapshotExists() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); workspace.getAttributes().put(AUTO_RESTORE_FROM_SNAPSHOT, "true"); mockStart(workspace); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), false); verify(runtimes).startAsync(workspace, workspace.getConfig().getDefaultEnv(), false); assertNotNull(workspace.getAttributes().get(UPDATED_ATTRIBUTE_NAME)); } @Test public void workspaceStartShouldUseDefaultEnvIfNullEnvNameProvided() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); mockStart(workspace); workspaceManager.startWorkspace(workspace.getId(), null, null); verify(runtimes).startAsync(workspace, workspace.getConfig().getDefaultEnv(), false); } @Test public void usesProvidedEnvironmentInsteadOfDefault() throws Exception { WorkspaceConfigImpl config = createConfig(); config.getEnvironments().put("non-default-env", new EnvironmentImpl(null, null)); WorkspaceImpl workspace = createAndMockWorkspace(config, NAMESPACE); mockStart(workspace); workspaceManager.startWorkspace(workspace.getId(), "non-default-env", false); verify(runtimes).startAsync(workspaceCaptor.capture(), eq("non-default-env"), eq(false)); assertEquals(workspaceCaptor.getValue().getConfig(), config); } @Test(expectedExceptions = NotFoundException.class, expectedExceptionsMessageRegExp = "Workspace '.*' doesn't contain environment '.*'") public void startShouldThrowNotFoundExceptionWhenProvidedEnvDoesNotExist() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); workspaceManager.startWorkspace(workspace.getId(), "fake", null); } @Test public void shouldBeAbleToStartTemporaryWorkspace() throws Exception { mockAnyWorkspaceStart(); workspaceManager.startWorkspace(createConfig(), NAMESPACE, true); verify(runtimes).startAsync(workspaceCaptor.capture(), anyString(), anyBoolean()); final WorkspaceImpl captured = workspaceCaptor.getValue(); assertTrue(captured.isTemporary()); } @Test public void shouldBeAbleToStopWorkspace() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(createConfig(), NAMESPACE); mockRuntime(workspace, RUNNING); // when workspaceManager.stopWorkspace(workspace.getId()); // then captureRunAsyncCallsAndRunSynchronously(); verify(runtimes).stop(workspace.getId()); verify(workspaceDao).update(workspaceCaptor.capture()); WorkspaceImpl updated = workspaceCaptor.getValue(); assertNotNull(updated.getAttributes().get(UPDATED_ATTRIBUTE_NAME)); } @Test public void createsSnapshotBeforeStoppingWorkspace() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); mockRuntime(workspace, RUNNING); workspaceManager.stopWorkspace(workspace.getId(), true); captureRunAsyncCallsAndRunSynchronously(); verify(runtimes).snapshot(workspace.getId()); } @Test(expectedExceptions = ConflictException.class, expectedExceptionsMessageRegExp = "Could not stop the workspace 'namespace/test/dev-workspace' because its " + "status is 'STOPPING'. Workspace must be either 'STARTING' or 'RUNNING'") public void failsToStopNotRunningWorkspace() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); mockRuntime(workspace, STOPPING); workspaceManager.stopWorkspace(workspace.getId()); } @Test public void shouldStopWorkspaceEventIfSnapshotCreationFailed() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); mockRuntime(workspace, RUNNING); doThrow(new ServerException("Test")).when(runtimes).snapshot(workspace.getId()); workspaceManager.stopWorkspace(workspace.getId(), true); captureRunAsyncCallsAndRunSynchronously(); verify(runtimes).stop(any()); } @Test public void shouldRemoveTemporaryWorkspaceAfterStop() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); workspace.setTemporary(true); mockRuntime(workspace, RUNNING); workspaceManager.stopWorkspace(workspace.getId()); captureRunAsyncCallsAndRunSynchronously(); verify(workspaceDao).remove(workspace.getId()); } @Test public void shouldRemoveTemporaryWorkspaceAfterStartFailed() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); workspace.setTemporary(true); mockRuntime(workspace, RUNNING); doThrow(new ServerException("")).when(runtimes).stop(workspace.getId()); workspaceManager.stopWorkspace(workspace.getId()); captureRunAsyncCallsAndRunSynchronously(); verify(workspaceDao).remove(workspace.getId()); } @Test public void shouldBeAbleToGetSnapshots() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); final SnapshotImpl wsSnapshot = SnapshotImpl.builder() .setDev(true) .setEnvName("envName") .setId("snap1") .setMachineName("machine1") .setWorkspaceId(workspace.getId()).build(); when(snapshotDao.findSnapshots(workspace.getId())).thenReturn(singletonList(wsSnapshot)); final List<SnapshotImpl> snapshots = workspaceManager.getSnapshot(workspace.getId()); assertEquals(snapshots.size(), 1); assertEquals(snapshots.get(0), wsSnapshot); } @Test public void shouldNotCreateSnapshotIfWorkspaceIsTemporaryAndAutoCreateSnapshotActivated() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); workspace.getAttributes().put(Constants.AUTO_CREATE_SNAPSHOT, "true"); mockRuntime(workspace, RUNNING); SnapshotImpl oldSnapshot = mock(SnapshotImpl.class); when(snapshotDao.getSnapshot(eq(workspace.getId()), eq(workspace.getConfig().getDefaultEnv()), anyString())) .thenReturn(oldSnapshot); workspace.setTemporary(true); workspaceManager.stopWorkspace(workspace.getId()); captureRunAsyncCallsAndRunSynchronously(); verify(runtimes, never()).snapshot(workspace.getId()); verify(runtimes).stop(workspace.getId()); } @Test public void shouldNotCreateSnapshotIfWorkspaceIsTemporaryAndAutoCreateSnapshotDisactivated() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); workspace.getAttributes().put(Constants.AUTO_CREATE_SNAPSHOT, "false"); mockRuntime(workspace, RUNNING); SnapshotImpl oldSnapshot = mock(SnapshotImpl.class); when(snapshotDao.getSnapshot(eq(workspace.getId()), eq(workspace.getConfig().getDefaultEnv()), anyString())) .thenReturn(oldSnapshot); workspace.setTemporary(true); workspaceManager.stopWorkspace(workspace.getId()); captureRunAsyncCallsAndRunSynchronously(); verify(runtimes, never()).snapshot(workspace.getId()); verify(runtimes).stop(workspace.getId()); } @Test public void shouldCreateWorkspaceSnapshotUsingDefaultValueForAutoRestore() throws Exception { // given workspaceManager = new WorkspaceManager(workspaceDao, runtimes, eventService, accountManager, true, false, snapshotDao, sharedPool); final WorkspaceImpl workspace = createAndMockWorkspace(); mockRuntime(workspace, RUNNING); SnapshotImpl oldSnapshot = mock(SnapshotImpl.class); when(snapshotDao.getSnapshot(eq(workspace.getId()), eq(workspace.getConfig().getDefaultEnv()), anyString())) .thenReturn(oldSnapshot); // when workspaceManager.stopWorkspace(workspace.getId()); // then captureRunAsyncCallsAndRunSynchronously(); verify(runtimes).snapshot(workspace.getId()); verify(runtimes).stop(workspace.getId()); } @Test public void shouldStartWorkspaceFromSnapshotUsingDefaultValueForAutoRestore() throws Exception { workspaceManager = new WorkspaceManager(workspaceDao, runtimes, eventService, accountManager, false, true, snapshotDao, sharedPool); WorkspaceImpl workspace = createAndMockWorkspace(); mockStart(workspace); mockSnapshots(workspace, 12345L); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), null); verify(runtimes).startAsync(workspace, workspace.getConfig().getDefaultEnv(), true); } @Test public void shouldBeAbleToRemoveMachinesSnapshots() throws Exception { // given String testWsId = "testWsId"; String testNamespace = "testNamespace"; WorkspaceImpl workspaceMock = mock(WorkspaceImpl.class); when(workspaceDao.get(testWsId)).thenReturn(workspaceMock); when(workspaceMock.getNamespace()).thenReturn(testNamespace); SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder() .generateId() .setEnvName("env") .setDev(true) .setMachineName("machine1") .setWorkspaceId(testWsId) .setType("docker") .setMachineSource(new MachineSourceImpl("image")); SnapshotImpl snapshot1 = snapshotBuilder.build(); SnapshotImpl snapshot2 = snapshotBuilder.generateId() .setDev(false) .setMachineName("machine2") .build(); when(snapshotDao.findSnapshots(testWsId)).thenReturn(asList(snapshot1, snapshot2)); // when workspaceManager.removeSnapshots(testWsId); // then captureExecuteCallsAndRunSynchronously(); verify(runtimes).removeBinaries(asList(snapshot1, snapshot2)); InOrder snapshotDaoInOrder = inOrder(snapshotDao); snapshotDaoInOrder.verify(snapshotDao).removeSnapshot(snapshot1.getId()); snapshotDaoInOrder.verify(snapshotDao).removeSnapshot(snapshot2.getId()); } @Test public void shouldRemoveMachinesSnapshotsEvenSomeRemovalFails() throws Exception { // given String testWsId = "testWsId"; String testNamespace = "testNamespace"; WorkspaceImpl workspaceMock = mock(WorkspaceImpl.class); when(workspaceDao.get(testWsId)).thenReturn(workspaceMock); when(workspaceMock.getNamespace()).thenReturn(testNamespace); SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder() .generateId() .setEnvName("env") .setDev(true) .setMachineName("machine1") .setWorkspaceId(testWsId) .setType("docker") .setMachineSource(new MachineSourceImpl("image")); SnapshotImpl snapshot1 = snapshotBuilder.build(); SnapshotImpl snapshot2 = snapshotBuilder.generateId() .setDev(false) .setMachineName("machine2") .build(); when(snapshotDao.findSnapshots(testWsId)).thenReturn(asList(snapshot1, snapshot2)); doThrow(new SnapshotException("test")).when(snapshotDao).removeSnapshot(snapshot1.getId()); // when workspaceManager.removeSnapshots(testWsId); // then captureExecuteCallsAndRunSynchronously(); verify(runtimes).removeBinaries(singletonList(snapshot2)); verify(snapshotDao).removeSnapshot(snapshot1.getId()); verify(snapshotDao).removeSnapshot(snapshot2.getId()); } @Test public void shouldBeAbleToStartMachineInRunningWs() throws Exception { // given WorkspaceImpl workspace = createAndMockWorkspace(); WorkspaceRuntimeImpl runtime = mockRuntime(workspace, RUNNING); MachineConfigImpl machineConfig = createMachine(workspace.getId(), runtime.getActiveEnv(), false).getConfig(); // when workspaceManager.startMachine(machineConfig, workspace.getId()); // then captureExecuteCallsAndRunSynchronously(); verify(runtimes).startMachine(workspace.getId(), machineConfig); } @Test(expectedExceptions = ConflictException.class, expectedExceptionsMessageRegExp = "Workspace .* is not running, new machine can't be started") public void shouldThrowExceptionOnStartMachineInNonRunningWs() throws Exception { // given WorkspaceImpl workspace = createAndMockWorkspace(); MachineConfigImpl machineConfig = createMachine(workspace.getId(), "env1", false).getConfig(); // when workspaceManager.startMachine(machineConfig, workspace.getId()); } @Test public void shouldBeAbleToCreateSnapshot() throws Exception { // then WorkspaceImpl workspace = createAndMockWorkspace(); mockRuntime(workspace, RUNNING); SnapshotImpl oldSnapshot = mock(SnapshotImpl.class); when(snapshotDao.getSnapshot(eq(workspace.getId()), eq(workspace.getConfig().getDefaultEnv()), anyString())) .thenReturn(oldSnapshot); // when workspaceManager.createSnapshot(workspace.getId()); // then verify(runtimes).snapshotAsync(workspace.getId()); } @Test public void shouldBeAbleToStopMachine() throws Exception { // given final WorkspaceImpl workspace = createAndMockWorkspace(); WorkspaceRuntimeImpl runtime = mockRuntime(workspace, RUNNING); MachineImpl machine = runtime.getMachines().get(0); // when workspaceManager.stopMachine(workspace.getId(), machine.getId()); // then verify(runtimes).stopMachine(workspace.getId(), machine.getId()); } @Test(expectedExceptions = ConflictException.class, expectedExceptionsMessageRegExp = "Could not .* the workspace '.*' because its status is '.*'.") public void shouldNotStopMachineIfWorkspaceIsNotRunning() throws Exception { // given final WorkspaceImpl workspace = createAndMockWorkspace(); // when workspaceManager.stopMachine(workspace.getId(), "someId"); } @Test public void shouldBeAbleToGetMachineInstanceIfWorkspaceIsRunning() throws Exception { // given final WorkspaceImpl workspace = createAndMockWorkspace(); WorkspaceRuntimeImpl runtime = mockRuntime(workspace, RUNNING); MachineImpl machine = runtime.getMachines().get(0); // when workspaceManager.getMachineInstance(workspace.getId(), machine.getId()); // then verify(runtimes).getMachine(workspace.getId(), machine.getId()); } @Test public void shouldBeAbleToGetMachineInstanceIfWorkspaceIsStarting() throws Exception { // given final WorkspaceImpl workspace = createAndMockWorkspace(); WorkspaceRuntimeImpl runtime = mockRuntime(workspace, STARTING); MachineImpl machine = runtime.getMachines().get(0); // when workspaceManager.getMachineInstance(workspace.getId(), machine.getId()); // then verify(runtimes).getMachine(workspace.getId(), machine.getId()); } @Test public void passedCreateSnapshotParameterIsUsedInPreferenceToAttribute() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); workspace.getAttributes().put(AUTO_CREATE_SNAPSHOT, "true"); mockRuntime(workspace, RUNNING); workspaceManager.stopWorkspace(workspace.getId(), false); captureRunAsyncCallsAndRunSynchronously(); verify(runtimes, never()).snapshot(workspace.getId()); } @Test public void passedNullCreateSnapshotParameterIsIgnored() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); workspace.getAttributes().put(AUTO_CREATE_SNAPSHOT, "true"); mockRuntime(workspace, RUNNING); workspaceManager.stopWorkspace(workspace.getId(), null); captureRunAsyncCallsAndRunSynchronously(); verify(runtimes).snapshot(workspace.getId()); } @Test public void passedFalseCreateSnapshotParameterIsUsedInPreferenceToAttribute() throws Exception { final WorkspaceImpl workspace = createAndMockWorkspace(); workspace.getAttributes().put(AUTO_CREATE_SNAPSHOT, "true"); mockRuntime(workspace, RUNNING); workspaceManager.stopWorkspace(workspace.getId(), false); captureRunAsyncCallsAndRunSynchronously(); verify(runtimes, never()).snapshot(workspace.getId()); } @Test public void stopsRunningWorkspacesOnShutdown() throws Exception { when(runtimes.refuseWorkspacesStart()).thenReturn(true); WorkspaceImpl stopped = createAndMockWorkspace(); mockRuntime(stopped, STOPPED); WorkspaceImpl starting = createAndMockWorkspace(); mockRuntime(starting, STARTING); WorkspaceImpl running = createAndMockWorkspace(); mockRuntime(running, RUNNING); when(runtimes.getRuntimesIds()).thenReturn(new HashSet<>(asList(running.getId(), starting.getId()))); // action workspaceManager.shutdown(); captureRunAsyncCallsAndRunSynchronously(); verify(runtimes).stop(running.getId()); verify(runtimes).stop(starting.getId()); verify(runtimes, never()).stop(stopped.getId()); verify(runtimes).shutdown(); verify(sharedPool).shutdown(); } @Test public void getsRunningWorkspacesIds() { ImmutableSet<String> ids = ImmutableSet.of("id1", "id2", "id3"); when(runtimes.getRuntimesIds()).thenReturn(ids); assertEquals(workspaceManager.getRunningWorkspacesIds(), ids); } @Test public void snapshottedAtAttributeIncludedToWorkspaceWhenGettingByKey() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); mockSnapshots(workspace, 12345); WorkspaceImpl result = workspaceManager.getWorkspace(workspace.getId()); assertEquals(result.getAttributes().get(SNAPSHOTTED_AT_ATTRIBUTE_NAME), "12345"); } @Test public void snapshottedAtAttributeIncludedToWorkspaceWhenGettingByNamespaceAndName() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); mockSnapshots(workspace, 12345); WorkspaceImpl result = workspaceManager.getWorkspace(workspace.getConfig().getName(), workspace.getNamespace()); assertEquals(result.getAttributes().get(SNAPSHOTTED_AT_ATTRIBUTE_NAME), "12345"); } @Test public void snapshottedAtAttributeIncludedToWorkspaceWhenGettingByUserId() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); mockSnapshots(workspace, 12345); List<WorkspaceImpl> workspaces = workspaceManager.getWorkspaces(USER_ID, false); assertEquals(workspaces.get(0).getAttributes().get(SNAPSHOTTED_AT_ATTRIBUTE_NAME), "12345"); } @Test public void snapshottedAtAttributeIncludedToWorkspaceWhenGettingByNamespace() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); mockSnapshots(workspace, 12345); List<WorkspaceImpl> workspaces = workspaceManager.getByNamespace(workspace.getNamespace(), false); assertEquals(workspaces.get(0).getAttributes().get(SNAPSHOTTED_AT_ATTRIBUTE_NAME), "12345"); } @Test public void snapshottedAtAttributeIncludedToWorkspaceWhenStartingById() throws Exception { WorkspaceImpl workspace = createAndMockWorkspace(); mockSnapshots(workspace, 12345); mockStart(workspace); Workspace result = workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), false); assertEquals(result.getAttributes().get(SNAPSHOTTED_AT_ATTRIBUTE_NAME), "12345"); } private List<SnapshotImpl> mockSnapshots(Workspace workspace, long creation) throws SnapshotException { SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder() .generateId() .setCreationDate(creation) .setEnvName(workspace.getConfig().getDefaultEnv()) .setWorkspaceId(workspace.getId()) .setType("docker") .setMachineSource(new MachineSourceImpl("image")); SnapshotImpl snapshot1 = snapshotBuilder.build(); SnapshotImpl snapshot2 = snapshotBuilder.generateId() .setDev(false) .setMachineName("machine2") .build(); List<SnapshotImpl> snapshots = asList(snapshot1, snapshot2); when(snapshotDao.findSnapshots(workspace.getId())).thenReturn(snapshots); return snapshots; } private void captureRunAsyncCallsAndRunSynchronously() { verify(sharedPool, atLeastOnce()).runAsync(taskCaptor.capture()); for (Runnable runnable : taskCaptor.getAllValues()) { runnable.run(); } } private void captureExecuteCallsAndRunSynchronously() { verify(sharedPool, atLeastOnce()).execute(taskCaptor.capture()); for (Runnable runnable : taskCaptor.getAllValues()) { runnable.run(); } } private WorkspaceRuntimeImpl mockRuntime(WorkspaceImpl workspace, WorkspaceStatus status) { when(runtimes.getStatus(workspace.getId())).thenReturn(status); MachineImpl machine1 = spy(createMachine(workspace.getId(), workspace.getConfig().getDefaultEnv(), true)); MachineImpl machine2 = spy(createMachine(workspace.getId(), workspace.getConfig().getDefaultEnv(), false)); Map<String, MachineImpl> machines = new HashMap<>(); machines.put(machine1.getId(), machine1); machines.put(machine2.getId(), machine2); WorkspaceRuntimeImpl runtime = new WorkspaceRuntimeImpl(workspace.getConfig().getDefaultEnv(), machines.values()); doAnswer(inv -> { workspace.setStatus(status); workspace.setRuntime(runtime); return null; }).when(runtimes).injectRuntime(workspace); when(runtimes.isAnyRunning()).thenReturn(true); return runtime; } private WorkspaceImpl createAndMockWorkspace() throws NotFoundException, ServerException { return createAndMockWorkspace(createConfig(), "namespace/test"); } private WorkspaceImpl createAndMockWorkspace(WorkspaceConfig cfg, String namespace) throws NotFoundException, ServerException { WorkspaceImpl workspace = WorkspaceImpl.builder() .generateId() .setConfig(cfg) .setAccount(new AccountImpl("id", namespace, "type")) .setStatus(WorkspaceStatus.STOPPED) .build(); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(workspaceDao.get(workspace.getConfig().getName(), workspace.getNamespace())).thenReturn(workspace); when(workspaceDao.get(workspace.getConfig().getName(), NAMESPACE)).thenReturn(workspace); when(workspaceDao.getByNamespace(workspace.getNamespace())).thenReturn(singletonList(workspace)); when(workspaceDao.getByNamespace(NAMESPACE)).thenReturn(singletonList(workspace)); when(workspaceDao.getWorkspaces(USER_ID)).thenReturn(singletonList(workspace)); return workspace; } private void mockStart(Workspace workspace) throws Exception { CompletableFuture<WorkspaceRuntimeImpl> cmpFuture = CompletableFuture.completedFuture(mock(WorkspaceRuntimeImpl.class)); when(runtimes.startAsync(eq(workspace), anyString(), anyBoolean())).thenReturn(cmpFuture); } private void mockAnyWorkspaceStart() throws Exception { CompletableFuture<WorkspaceRuntimeImpl> cmpFuture = CompletableFuture.completedFuture(mock(WorkspaceRuntimeImpl.class)); when(runtimes.startAsync(anyObject(), anyString(), anyBoolean())).thenReturn(cmpFuture); } private static WorkspaceConfigImpl createConfig() { EnvironmentImpl environment = new EnvironmentImpl(new EnvironmentRecipeImpl("type", "contentType", "content", null), singletonMap("dev-machine", new ExtendedMachineImpl(singletonList("org.eclipse.che.ws-agent"), null, singletonMap("memoryLimitBytes", "10000")))); return WorkspaceConfigImpl.builder() .setName("dev-workspace") .setDefaultEnv("dev-env") .setEnvironments(singletonMap("dev-env", environment)) .build(); } private MachineImpl createMachine(String workspaceId, String envName, boolean isDev) { return MachineImpl.builder() .setConfig(MachineConfigImpl.builder() .setDev(isDev) .setName("machineName" + UUID.randomUUID()) .setSource(new MachineSourceImpl("type").setContent("content")) .setLimits(new MachineLimitsImpl(1024)) .setType("docker") .build()) .setId("id" + UUID.randomUUID()) .setOwner("userName") .setStatus(MachineStatus.RUNNING) .setWorkspaceId(workspaceId) .setEnvName(envName) .setRuntime(new MachineRuntimeInfoImpl(new HashMap<>(), new HashMap<>(), new HashMap<>())) .build(); } }