/******************************************************************************* * 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.util.concurrent.ThreadFactoryBuilder; import org.eclipse.che.api.agent.server.AgentRegistry; import org.eclipse.che.api.agent.server.impl.AgentSorter; import org.eclipse.che.api.agent.server.launcher.AgentLauncherFactory; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.model.workspace.Environment; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.api.environment.server.CheEnvironmentEngine; import org.eclipse.che.api.environment.server.ContainerNameGenerator; import org.eclipse.che.api.environment.server.DefaultServicesStartStrategy; import org.eclipse.che.api.environment.server.EnvironmentParser; import org.eclipse.che.api.environment.server.InfrastructureProvisioner; import org.eclipse.che.api.environment.server.MachineInstanceProvider; import org.eclipse.che.api.environment.server.model.CheServiceImpl; import org.eclipse.che.api.environment.server.model.CheServicesEnvironmentImpl; import org.eclipse.che.api.machine.server.MachineInstanceProviders; import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.machine.server.spi.SnapshotDao; import org.eclipse.che.api.machine.server.util.RecipeDownloader; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceRuntimeImpl; import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto; import org.eclipse.che.api.workspace.shared.dto.ExtendedMachineDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.commons.test.mockito.answer.WaitingAnswer; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.slf4j.Logger; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.slf4j.LoggerFactory.getLogger; /** * @author Alexander Garagatyi */ @Listeners(MockitoTestNGListener.class) public class WorkspaceRuntimeIntegrationTest { private static final Logger LOG = getLogger(WorkspaceRuntimeIntegrationTest.class); private static final String WORKSPACE_ID = "workspace123"; private static final String ENV_NAME = "default-env"; @Mock private EventService eventService; @Mock private MachineInstanceProviders machineInstanceProviders; @Mock private EnvironmentParser environmentParser; @Mock private MachineInstanceProvider instanceProvider; @Mock private InfrastructureProvisioner infrastructureProvisioner; @Mock private RecipeDownloader recipeDownloader; @Mock private ContainerNameGenerator containerNameGenerator; @Mock private AgentRegistry agentRegistry; @Mock private AgentSorter agentSorter; @Mock private AgentLauncherFactory launcherFactory; @Mock private WorkspaceSharedPool sharedPool; @Mock private SnapshotDao snapshotDao; @Captor private ArgumentCaptor<Callable<WorkspaceRuntimeImpl>> taskCaptor; private ExecutorService executor; private WorkspaceRuntimes runtimes; @BeforeMethod public void setUp() throws Exception { CheEnvironmentEngine environmentEngine = new CheEnvironmentEngine(snapshotDao, machineInstanceProviders, "/tmp", 2000, eventService, environmentParser, new DefaultServicesStartStrategy(), instanceProvider, infrastructureProvisioner, "http://localhost:8080/api", recipeDownloader, containerNameGenerator, agentRegistry, sharedPool); runtimes = new WorkspaceRuntimes(eventService, environmentEngine, agentSorter, launcherFactory, agentRegistry, snapshotDao, sharedPool); executor = Executors.newFixedThreadPool( 1, new ThreadFactoryBuilder().setNameFormat(this.getClass().toString() + "-%d").build()); EnvironmentContext.getCurrent().setSubject(new SubjectImpl("name", "id", "token", false)); } @AfterMethod public void tearDown() throws Exception { executor.shutdownNow(); EnvironmentContext.reset(); } // Check for https://github.com/codenvy/codenvy/issues/593 @Test(expectedExceptions = NotFoundException.class, expectedExceptionsMessageRegExp = "Workspace with id '" + WORKSPACE_ID + "' is not running") public void environmentEngineShouldDestroyAllMachinesBeforeRemovalOfEnvironmentRecord() throws Exception { // given EnvironmentDto environment = newDto(EnvironmentDto.class); environment.withMachines(singletonMap("service1", newDto(ExtendedMachineDto.class) .withAgents(singletonList("org.eclipse.che.ws-agent")))); WorkspaceConfigDto config = newDto(WorkspaceConfigDto.class) .withDefaultEnv(ENV_NAME) .withName("ws1") .withEnvironments(singletonMap(ENV_NAME, environment)); WorkspaceDto workspace = newDto(WorkspaceDto.class).withId(WORKSPACE_ID) .withNamespace("namespace") .withConfig(config); Instance instance = mock(Instance.class); MachineConfigImpl machineConfig = new MachineConfigImpl(); machineConfig.setDev(true); machineConfig.setName("service1"); when(instance.getWorkspaceId()).thenReturn(WORKSPACE_ID); when(instance.getId()).thenReturn("machineId"); when(instance.getConfig()).thenReturn(machineConfig); CheServicesEnvironmentImpl internalEnv = new CheServicesEnvironmentImpl(); internalEnv.getServices().put("service1", new CheServiceImpl().withId("machineId")); when(environmentParser.parse(any(Environment.class))).thenReturn(internalEnv); when(instanceProvider.startService(anyString(), anyString(), anyString(), anyString(), anyBoolean(), anyString(), any(CheServiceImpl.class), any(LineConsumer.class))) .thenReturn(instance); runtimes.startAsync(workspace, ENV_NAME, false); verify(sharedPool).submit(taskCaptor.capture()); taskCaptor.getValue().call(); WaitingAnswer<Void> waitingAnswer = new WaitingAnswer<>(); doAnswer(waitingAnswer).when(instance).destroy(); // when executor.execute(() -> { try { runtimes.stop(WORKSPACE_ID); } catch (Exception e) { LOG.error(e.getLocalizedMessage(), e); } }); waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS); // then // no exception - environment and workspace are still running runtimes.getRuntime(WORKSPACE_ID); // let instance removal proceed waitingAnswer.completeAnswer(); // verify destroying was called verify(instance, timeout(1000)).destroy(); verify(instanceProvider, timeout(1000)).destroyNetwork(anyString()); // wait to ensure that removal of runtime is finished Thread.sleep(500); // runtime is removed - now getting of it should throw an exception runtimes.getRuntime(WORKSPACE_ID); } }