diff --git a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java index bbc99a6093..e457acd717 100644 --- a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java +++ b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java @@ -15,7 +15,6 @@ import com.google.inject.multibindings.Multibinder; import com.google.inject.name.Names; import org.eclipse.che.api.machine.shared.Constants; -import org.eclipse.che.api.user.server.ProfileService; import org.eclipse.che.inject.DynaModule; /** @author andrew00x */ @@ -62,7 +61,7 @@ public class WsMasterModule extends AbstractModule { .toInstance("predefined-recipes.json"); - bindConstant().annotatedWith(Names.named(org.eclipse.che.api.machine.server.wsagent.WsAgentLauncherImpl.WS_AGENT_PROCESS_START_COMMAND)) + bindConstant().annotatedWith(Names.named(org.eclipse.che.api.agent.server.wsagent.WsAgentLauncherImpl.WS_AGENT_PROCESS_START_COMMAND)) .to("rm -rf ~/che && mkdir -p ~/che && unzip -qq /mnt/che/ws-agent.zip -d ~/che/ws-agent && " + "sudo sh -c \"chown -R $(id -u -n) /projects || true\" && " + "export JPDA_ADDRESS=\"4403\" && ~/che/ws-agent/bin/catalina.sh jpda run"); @@ -75,10 +74,10 @@ public class WsMasterModule extends AbstractModule { bind(org.eclipse.che.api.workspace.server.event.MachineStateListener.class).asEagerSingleton(); - bind(org.eclipse.che.api.machine.server.wsagent.WsAgentLauncher.class) - .to(org.eclipse.che.api.machine.server.wsagent.WsAgentLauncherImpl.class); + bind(org.eclipse.che.api.agent.server.wsagent.WsAgentLauncher.class) + .to(org.eclipse.che.api.agent.server.wsagent.WsAgentLauncherImpl.class); - bind(org.eclipse.che.api.machine.server.terminal.MachineTerminalLauncher.class); + bind(org.eclipse.che.api.agent.server.terminal.MachineTerminalLauncher.class); bind(org.eclipse.che.api.deploy.WsMasterAnalyticsAddresser.class); Multibinder machineImageProviderMultibinder = diff --git a/core/che-core-api-core/src/test/java/org/eclipse/che/api/core/rest/RemoteServiceDescriptorTest.java b/core/che-core-api-core/src/test/java/org/eclipse/che/api/core/rest/RemoteServiceDescriptorTest.java index 684f219848..0b89dbc6c7 100644 --- a/core/che-core-api-core/src/test/java/org/eclipse/che/api/core/rest/RemoteServiceDescriptorTest.java +++ b/core/che-core-api-core/src/test/java/org/eclipse/che/api/core/rest/RemoteServiceDescriptorTest.java @@ -18,8 +18,7 @@ import org.eclipse.che.api.core.rest.annotations.Required; import org.eclipse.che.api.core.rest.annotations.Valid; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.ServiceDescriptor; -import org.eclipse.che.commons.test.SelfReturningAnswer; -import org.eclipse.che.dto.server.DtoFactory; +import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer; import org.everrest.assured.EverrestJetty; import org.mockito.Mock; import org.mockito.Spy; @@ -144,4 +143,4 @@ public class RemoteServiceDescriptorTest { private String getServerUrl(ITestContext ctx) { return "http://localhost:" + ctx.getAttribute(EverrestJetty.JETTY_PORT) + "/rest"; } -} \ No newline at end of file +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/Recipe.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/Recipe.java index d3b095ca10..106d4fe2b5 100644 --- a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/Recipe.java +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/Recipe.java @@ -26,6 +26,4 @@ public interface Recipe { * Returns recipe script, which is used to instantiate new machine */ String getScript(); - - -} \ No newline at end of file +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/WorkspaceStatus.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/WorkspaceStatus.java index 8b9be182f8..8d71e81c61 100644 --- a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/WorkspaceStatus.java +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/WorkspaceStatus.java @@ -10,16 +10,8 @@ *******************************************************************************/ package org.eclipse.che.api.core.model.workspace; -import org.eclipse.che.api.core.model.machine.MachineStatus; - /** - * Defines the contract between workspace and its dev-machine. - * - *

Workspace status itself shows the state of the workspace dev-machine. - * If workspace environment which is running contains not only the dev-machine, those machines - * states won't affect workspace status at all. - * - *

{@link MachineStatus} is responsible for states of machines different from the dev-machine. + * Defines the contract between workspace and its active environment. * *

Workspace is rather part of the {@link Workspace} than {@link WorkspaceRuntime} or {@link WorkspaceConfig}, * as it shows the state of certain user's workspace and exists earlier than runtime workspace instance @@ -31,7 +23,7 @@ import org.eclipse.che.api.core.model.machine.MachineStatus; public enum WorkspaceStatus { /** - * Workspace considered as starting if and only if its dev-machine is booting(creating). + * Workspace considered as starting if and only if its active environment is booting. * *

Workspace becomes starting only if it was {@link #STOPPED}. * The status map: @@ -39,45 +31,39 @@ public enum WorkspaceStatus { * STOPPED -> STARTING -> RUNNING (normal behaviour) * STOPPED -> STARTING -> STOPPED (failed to start) * - * - * @see MachineStatus#CREATING */ STARTING, /** - * Workspace considered as running if and only if its dev-machine was successfully started and it is running. + * Workspace considered as running if and only if its environment is running. * *

Workspace becomes running after it was {@link #STARTING}. * The status map: *

      *  STARTING -> RUNNING -> STOPPING (normal behaviour)
-     *  STARTING -> RUNNING -> STOPPED (dev-machine was interrupted)
+     *  STARTING -> RUNNING -> STOPPED (environment start was interrupted)
      * 
- * - * @see MachineStatus#RUNNING */ RUNNING, /** - * Workspace considered as stopping if and only if its dev-machine is shutting down(destroying). + * Workspace considered as stopping if and only if its active environment is shutting down. * *

Workspace is in stopping status only if it was in {@link #RUNNING} status before. * The status map: *

      *  RUNNING -> STOPPING -> STOPPED (normal behaviour)/(error while stopping)
      * 
- * - * @see MachineStatus#DESTROYING */ STOPPING, /** * Workspace considered as stopped when: * * @@ -85,7 +71,7 @@ public enum WorkspaceStatus { *
      *  STOPPING -> STOPPED (normal behaviour)/(error while stopping)
      *  STARTING -> STOPPED (failed to start)
-     *  RUNNING  -> STOPPED (dev-machine was interrupted)
+     *  RUNNING  -> STOPPED (environment machine was interrupted)
      * 
*/ STOPPED diff --git a/core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/SelfReturningAnswer.java b/core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/mockito/answer/SelfReturningAnswer.java similarity index 96% rename from core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/SelfReturningAnswer.java rename to core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/mockito/answer/SelfReturningAnswer.java index dce50acb0e..395fe6df8d 100644 --- a/core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/SelfReturningAnswer.java +++ b/core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/mockito/answer/SelfReturningAnswer.java @@ -8,7 +8,7 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.commons.test; +package org.eclipse.che.commons.test.mockito.answer; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; diff --git a/core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/mockito/answer/WaitingAnswer.java b/core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/mockito/answer/WaitingAnswer.java new file mode 100644 index 0000000000..d9a460ec67 --- /dev/null +++ b/core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/mockito/answer/WaitingAnswer.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.commons.test.mockito.answer; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Answer class that helps to lock execution of test in separate threads for testing purposes. + *

+ * It can hold waiting thread until this answer is called.
+ * It can hold waiting thread that uses mock until answering to mock call is allowed. + * + * Here is an example of complex test that ensures that (for example) locked area is not called + * when other thread try to access it. + *


+ *     // given
+ *     WaitingAnswer waitingAnswer = new WaitingAnswer<>();
+ *     doAnswer(waitingAnswer).when(someClassUsedInTestedClass).someMethod(eq(param1), eq(param2));
+ *
+ *     // start doing something in a separate thread
+ *     executor.execute(() -> testedClass.doSomething());
+ *     // wait until separate thread call answer to find the moment when critical area is occupied
+ *     // to make test fast wait not more that provided timeout
+ *     waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS);
+ *
+ *     // when
+ *     try {
+ *         // start doing something in current thread
+ *         testedClass.doSomething()
+ *         // this area should not be reachable until answer is completed!
+ *         fail("Error message");
+ *     } finally {
+ *         // In this case exception can be suppressed
+ *         // Test is simplified to provide clean example
+ *
+ *         // then
+ *         // complete waiting answer
+ *         waitingAnswer.completeAnswer();
+ *         // ensure that someMethod is called only once to confirm that testedClass.doSomething()
+ *         // doesn't call it this particular test
+ *         verify(someClassUsedInTestedClass, timeout(100).times(1)).someMethod(any(), any());
+ *     }
+ * 
+ * + * @author Alexander Garagatyi + */ +public class WaitingAnswer implements Answer { + + private final CountDownLatch answerIsCalledLatch; + private final CountDownLatch answerResultIsUnlockedLatch; + + private long maxWaitingTime; + private TimeUnit maxWaitingUnit; + private T result; + + private volatile String error; + + public WaitingAnswer() { + this.maxWaitingTime = 1; + this.maxWaitingUnit = TimeUnit.SECONDS; + this.result = null; + this.answerIsCalledLatch = new CountDownLatch(1); + this.answerResultIsUnlockedLatch = new CountDownLatch(1); + this.error = null; + } + + public WaitingAnswer(long maxWaitingTime, TimeUnit maxWaitingUnit) { + this(); + this.maxWaitingTime = maxWaitingTime; + this.maxWaitingUnit = maxWaitingUnit; + } + + public WaitingAnswer(T result) { + this(); + this.result = result; + } + + public WaitingAnswer(T result, + long maxWaitingTime, + TimeUnit maxWaitingUnit) { + this(); + this.result = result; + this.maxWaitingTime = maxWaitingTime; + this.maxWaitingUnit = maxWaitingUnit; + } + + /** + * Waits until answer is called in method {@link #answer(InvocationOnMock)}. + * + * @param maxWaitingTime + * max time to wait + * @param maxWaitingUnit + * time unit of the max waiting time argument + * @throws Exception + * if the waiting time elapsed before this answer is called + * @see #answer(InvocationOnMock) + */ + public void waitAnswerCall(long maxWaitingTime, TimeUnit maxWaitingUnit) throws Exception { + if (!answerIsCalledLatch.await(maxWaitingTime, maxWaitingUnit)) { + error = "Waiting time elapsed but answer is not called"; + throw new Exception(error); + } + } + + /** + * Stops process of waiting returning result of answer in method {@link #answer(InvocationOnMock)}. + * + * @throws Exception + * if this answer waiting time elapsed before this method is called + * @see #answer(InvocationOnMock) + */ + public void completeAnswer() throws Exception { + answerResultIsUnlockedLatch.countDown(); + if (error != null) { + throw new Exception(error); + } + } + + /** + * Stops waiting until answer is called in method {@link #waitAnswerCall(long, TimeUnit)} and + * then waits until method {@link #completeAnswer()} is called. + * + * @param invocationOnMock + * see {@link Answer#answer(InvocationOnMock)} + * @return returns answer result if provided in constructor or null otherwise + * @throws Exception + * if answer call or answer result waiting time is elapsed + * @throws Throwable + * in the same cases as in {@link Answer#answer(InvocationOnMock)} + * @see #waitAnswerCall(long, TimeUnit) + * @see #completeAnswer() + * @see Answer#answer(InvocationOnMock) + */ + @Override + public T answer(InvocationOnMock invocationOnMock) throws Throwable { + // report start of answer call + answerIsCalledLatch.countDown(); + if (error != null) { + throw new Exception(error); + } + // wait until another thread unlocks returning of answer + if (!answerResultIsUnlockedLatch.await(maxWaitingTime, maxWaitingUnit)) { + error = "Waiting time elapsed but completeAnswer is not called"; + throw new Exception(error); + } + return result; + } +} diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/MachineServiceClient.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/MachineServiceClient.java index f815119900..dde6755636 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/MachineServiceClient.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/MachineServiceClient.java @@ -30,11 +30,13 @@ public interface MachineServiceClient { /** * Get machine information by it's id. * + * @param workspaceId + * ID of workspace * @param machineId * ID of the machine * @return a promise that resolves to the {@link MachineDto}, or rejects with an error */ - Promise getMachine(@NotNull String machineId); + Promise getMachine(@NotNull String workspaceId, @NotNull String machineId); /** * Returns list of machines which are bounded to the specified workspace. @@ -48,15 +50,19 @@ public interface MachineServiceClient { /** * Destroy machine with the specified ID. * + * @param workspaceId + * ID of workspace * @param machineId * ID of machine that should be destroyed * @return a promise that will resolve when the machine has been destroyed, or rejects with an error */ - Promise destroyMachine(@NotNull String machineId); + Promise destroyMachine(@NotNull String workspaceId, @NotNull String machineId); /** * Execute a command in machine. * + * @param workspaceId + * ID of workspace * @param machineId * ID of the machine where command should be executed * @param command @@ -65,42 +71,34 @@ public interface MachineServiceClient { * websocket chanel for execution logs * @return a promise that resolves to the {@link MachineProcessDto}, or rejects with an error */ - Promise executeCommand(@NotNull String machineId, + Promise executeCommand(@NotNull String workspaceId, + @NotNull String machineId, @NotNull Command command, @Nullable String outputChannel); /** * Get processes from the specified machine. * + * @param workspaceId + * ID of workspace * @param machineId * ID of machine to get processes information from * @return a promise that will provide a list of {@link MachineProcessDto}s for the given machine ID */ - Promise> getProcesses(@NotNull String machineId); + Promise> getProcesses(@NotNull String workspaceId, @NotNull String machineId); /** * Stop process in machine. * + * @param workspaceId + * ID of workspace * @param machineId * ID of the machine where process should be stopped * @param processId * ID of the process to stop * @return a promise that will resolve when the process has been stopped, or rejects with an error */ - Promise stopProcess(@NotNull String machineId, int processId); - - /** - * Get file content. - * - * @param machineId - * ID of the machine - * @param path - * path to file on machine instance - * @param startFrom - * line number to start reading from - * @param limit - * limitation on line - * @return a promise that will provide the file content, or rejects with an error - */ - Promise getFileContent(@NotNull String machineId, @NotNull String path, int startFrom, int limit); + Promise stopProcess(@NotNull String workspaceId, + @NotNull String machineId, + int processId); } diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/MachineServiceClientImpl.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/MachineServiceClientImpl.java index 32c0cf56ed..89fc9c2066 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/MachineServiceClientImpl.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/MachineServiceClientImpl.java @@ -20,7 +20,6 @@ import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.rest.AsyncRequestFactory; import org.eclipse.che.ide.rest.DtoUnmarshallerFactory; import org.eclipse.che.ide.rest.RestContext; -import org.eclipse.che.ide.rest.StringUnmarshaller; import org.eclipse.che.ide.ui.loaders.request.LoaderFactory; import javax.validation.constraints.NotNull; @@ -50,12 +49,14 @@ public class MachineServiceClientImpl implements MachineServiceClient { this.dtoUnmarshallerFactory = dtoUnmarshallerFactory; this.asyncRequestFactory = asyncRequestFactory; this.loaderFactory = loaderFactory; - this.baseHttpUrl = restContext + "/machine"; + this.baseHttpUrl = restContext + "/workspace/"; } @Override - public Promise getMachine(@NotNull final String machineId) { - return asyncRequestFactory.createGetRequest(baseHttpUrl + '/' + machineId) + public Promise getMachine(@NotNull final String workspaceId, + @NotNull final String machineId) { + return asyncRequestFactory.createGetRequest(baseHttpUrl + workspaceId + + "/machine/" + machineId) .header(ACCEPT, APPLICATION_JSON) .loader(loaderFactory.newLoader("Getting info about machine...")) .send(dtoUnmarshallerFactory.newUnmarshaller(MachineDto.class)); @@ -63,50 +64,57 @@ public class MachineServiceClientImpl implements MachineServiceClient { @Override public Promise> getMachines(@NotNull String workspaceId) { - return asyncRequestFactory.createGetRequest(baseHttpUrl + "?workspace=" + workspaceId) + return asyncRequestFactory.createGetRequest(baseHttpUrl + workspaceId + "/machine") .header(ACCEPT, APPLICATION_JSON) .loader(loaderFactory.newLoader("Getting info about bound machines...")) .send(dtoUnmarshallerFactory.newListUnmarshaller(MachineDto.class)); } @Override - public Promise destroyMachine(@NotNull final String machineId) { - return asyncRequestFactory.createRequest(DELETE, baseHttpUrl + '/' + machineId, null, false) + public Promise destroyMachine(@NotNull final String workspaceId, + @NotNull final String machineId) { + return asyncRequestFactory.createRequest(DELETE, + baseHttpUrl + workspaceId + + "/machine/" + machineId, + null, + false) .loader(loaderFactory.newLoader("Destroying machine...")) .send(); } @Override - public Promise executeCommand(@NotNull final String machineId, + public Promise executeCommand(@NotNull final String workspaceId, + @NotNull final String machineId, @NotNull final Command command, @Nullable final String outputChannel) { - return asyncRequestFactory.createPostRequest(baseHttpUrl + '/' + machineId + "/command?outputChannel=" + outputChannel, command) + return asyncRequestFactory.createPostRequest(baseHttpUrl + workspaceId + + "/machine/" + machineId + + "/command?outputChannel=" + outputChannel, + command) .header(ACCEPT, APPLICATION_JSON) .loader(loaderFactory.newLoader("Executing command...")) .send(dtoUnmarshallerFactory.newUnmarshaller(MachineProcessDto.class)); } @Override - public Promise> getProcesses(@NotNull final String machineId) { - return asyncRequestFactory.createGetRequest(baseHttpUrl + "/" + machineId + "/process") + public Promise> getProcesses(@NotNull final String workspaceId, + @NotNull final String machineId) { + return asyncRequestFactory.createGetRequest(baseHttpUrl + workspaceId + + "/machine/" + machineId + + "/process") .header(ACCEPT, APPLICATION_JSON) .loader(loaderFactory.newLoader("Getting machine processes...")) .send(dtoUnmarshallerFactory.newListUnmarshaller(MachineProcessDto.class)); } @Override - public Promise stopProcess(@NotNull final String machineId, final int processId) { - return asyncRequestFactory.createDeleteRequest(baseHttpUrl + '/' + machineId + "/process/" + processId) + public Promise stopProcess(@NotNull final String workspaceId, + @NotNull final String machineId, + final int processId) { + return asyncRequestFactory.createDeleteRequest(baseHttpUrl + workspaceId + + "/machine/" + machineId + + "/process/" + processId) .loader(loaderFactory.newLoader("Stopping process...")) .send(); } - - @Override - public Promise getFileContent(@NotNull final String machineId, final @NotNull String path, final int startFrom, - final int limit) { - String url = baseHttpUrl + "/" + machineId + "/filepath/" + path + "?startFrom=" + startFrom + "&limit=" + limit; - return asyncRequestFactory.createGetRequest(url) - .loader(loaderFactory.newLoader("Loading file content...")) - .send(new StringUnmarshaller()); - } } diff --git a/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/workspace/create/CreateWorkspacePresenterTest.java b/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/workspace/create/CreateWorkspacePresenterTest.java index 4219515164..6a3d7b1173 100644 --- a/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/workspace/create/CreateWorkspacePresenterTest.java +++ b/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/workspace/create/CreateWorkspacePresenterTest.java @@ -27,7 +27,7 @@ import org.eclipse.che.ide.api.workspace.WorkspaceServiceClient; import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; -import org.eclipse.che.commons.test.SelfReturningAnswer; +import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer; import org.eclipse.che.ide.CoreLocalizationConstant; import org.eclipse.che.ide.workspace.DefaultWorkspaceComponent; import org.eclipse.che.ide.workspace.WorkspaceComponent; diff --git a/plugins/plugin-docker/che-plugin-docker-client/src/test/java/org/eclipse/che/plugin/docker/client/DockerConnectorTest.java b/plugins/plugin-docker/che-plugin-docker-client/src/test/java/org/eclipse/che/plugin/docker/client/DockerConnectorTest.java index fdeeaf938b..74bc6b89e5 100644 --- a/plugins/plugin-docker/che-plugin-docker-client/src/test/java/org/eclipse/che/plugin/docker/client/DockerConnectorTest.java +++ b/plugins/plugin-docker/che-plugin-docker-client/src/test/java/org/eclipse/che/plugin/docker/client/DockerConnectorTest.java @@ -18,7 +18,7 @@ import com.google.gson.GsonBuilder; import org.eclipse.che.commons.json.JsonParseException; import org.eclipse.che.commons.lang.ws.rs.ExtMediaType; -import org.eclipse.che.commons.test.SelfReturningAnswer; +import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.plugin.docker.client.connection.CloseConnectionInputStream; import org.eclipse.che.plugin.docker.client.connection.DockerConnection; diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstance.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstance.java index 2b2417b619..cf14f055a2 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstance.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstance.java @@ -211,11 +211,11 @@ public class DockerInstance extends AbstractInstance { } @Override - public MachineSource saveToSnapshot(String owner) throws MachineException { + public MachineSource saveToSnapshot() throws MachineException { try { String image = generateRepository(); if(!snapshotUseRegistry) { - commitContainer(owner, image, LATEST_TAG); + commitContainer(image, LATEST_TAG); return new DockerMachineSource(image).withTag(LATEST_TAG); } @@ -224,7 +224,7 @@ public class DockerInstance extends AbstractInstance { .withTag(LATEST_TAG); final String fullRepo = pushParams.getFullRepo(); - commitContainer(owner, fullRepo, LATEST_TAG); + commitContainer(fullRepo, LATEST_TAG); //TODO fix this workaround. Docker image is not visible after commit when using swarm Thread.sleep(2000); final ProgressLineFormatterImpl lineFormatter = new ProgressLineFormatterImpl(); @@ -246,10 +246,9 @@ public class DockerInstance extends AbstractInstance { } @VisibleForTesting - void commitContainer(String owner, String repository, String tag) throws IOException { + void commitContainer(String repository, String tag) throws IOException { String comment = format("Suspended at %1$ta %1$tb %1$td %1$tT %1$tZ %1$tY", System.currentTimeMillis()); - comment = owner == null ? comment : comment + " by " + owner; // !! We SHOULD NOT pause container before commit because all execs will fail // to push image to private registry it should be tagged with registry in repo name // https://docs.docker.com/reference/api/docker_remote_api_v1.16/#push-an-image-on-the-registry diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProvider.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProvider.java index 5dbb7466e9..b35fa3a900 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProvider.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProvider.java @@ -79,7 +79,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -506,7 +505,7 @@ public class DockerInstanceProvider implements InstanceProvider { final String imageName, final LineConsumer outputConsumer) throws MachineException { - Optional containerIdOptional = null; + String containerIdCopy = null; try { final Map> portsToExpose; final String[] volumes; @@ -558,7 +557,7 @@ public class DockerInstanceProvider implements InstanceProvider { final String containerId = docker.createContainer(CreateContainerParams.create(config) .withContainerName(containerName)) .getId(); - containerIdOptional = Optional.ofNullable(containerId); + containerIdCopy = containerId; docker.startContainer(StartContainerParams.create(containerId)); @@ -593,7 +592,9 @@ public class DockerInstanceProvider implements InstanceProvider { machine.getId(), containerId, node.getHost()); } - dockerInstanceStopDetector.startDetection(containerId, machine.getId()); + dockerInstanceStopDetector.startDetection(containerId, + machine.getId(), + machine.getWorkspaceId()); return dockerMachineFactory.createInstance(machine, containerId, @@ -601,18 +602,17 @@ public class DockerInstanceProvider implements InstanceProvider { node, outputConsumer); } catch (IOException e) { - cleanUpContainer(containerIdOptional); + cleanUpContainer(containerIdCopy); throw new MachineException(e.getLocalizedMessage(), e); } catch (MachineException e) { - cleanUpContainer(containerIdOptional); + cleanUpContainer(containerIdCopy); throw e; } } - private void cleanUpContainer(Optional containerIdOptional) { + private void cleanUpContainer(@Nullable String containerId) { try { - if (containerIdOptional.isPresent()) { - String containerId = containerIdOptional.get(); + if (containerId != null) { docker.removeContainer(RemoveContainerParams.create(containerId).withRemoveVolumes(true).withForce(true)); } } catch (Exception ex) { diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceStopDetector.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceStopDetector.java index 4e16349cde..999f933e4a 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceStopDetector.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceStopDetector.java @@ -16,6 +16,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.machine.server.event.InstanceStateEvent; +import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.plugin.docker.client.DockerConnector; import org.eclipse.che.plugin.docker.client.MessageProcessor; import org.eclipse.che.plugin.docker.client.json.Event; @@ -43,10 +44,10 @@ import java.util.concurrent.TimeUnit; public class DockerInstanceStopDetector { private static final Logger LOG = LoggerFactory.getLogger(DockerInstanceStopDetector.class); - private final EventService eventService; - private final DockerConnector dockerConnector; - private final ExecutorService executorService; - private final Map instances; + private final EventService eventService; + private final DockerConnector dockerConnector; + private final ExecutorService executorService; + private final Map> instances; /* Helps differentiate container main process OOM from other processes OOM Algorithm: @@ -59,7 +60,7 @@ public class DockerInstanceStopDetector { That's why cache expires in X seconds. X was set as 10 empirically. */ - private final Cache containersOomTimestamps; + private final Cache containersOomTimestamps; private long lastProcessedEventDate = 0; @@ -84,9 +85,13 @@ public class DockerInstanceStopDetector { * id of a container to start detection for * @param machineId * id of a machine which container implements + * @param workspaceId + * id of a workspace that owns machine */ - public void startDetection(String containerId, String machineId) { - instances.put(containerId, machineId); + public void startDetection(String containerId, + String machineId, + String workspaceId) { + instances.put(containerId, Pair.of(machineId, workspaceId)); } /** @@ -140,9 +145,11 @@ public class DockerInstanceStopDetector { } else { instanceStateChangeType = InstanceStateEvent.Type.DIE; } - final String instanceId = instances.get(message.getId()); - if (instanceId != null) { - eventService.publish(new InstanceStateEvent(instanceId, instanceStateChangeType)); + Pair instanceIds = instances.get(message.getId()); + if (instanceIds != null) { + eventService.publish(new InstanceStateEvent(instanceIds.first, + instanceIds.second, + instanceStateChangeType)); lastProcessedEventDate = message.getTime(); } break; diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerMachineImplTerminalLauncher.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerMachineImplTerminalLauncher.java index 72b5fcaf72..5e8c17db63 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerMachineImplTerminalLauncher.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerMachineImplTerminalLauncher.java @@ -12,7 +12,7 @@ package org.eclipse.che.plugin.docker.machine; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.spi.Instance; -import org.eclipse.che.api.machine.server.terminal.MachineImplSpecificTerminalLauncher; +import org.eclipse.che.api.agent.server.terminal.MachineImplSpecificTerminalLauncher; import org.eclipse.che.plugin.docker.client.DockerConnector; import org.eclipse.che.plugin.docker.client.Exec; import org.eclipse.che.plugin.docker.client.LogMessage; diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/cleaner/DockerContainerCleaner.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/cleaner/DockerContainerCleaner.java index 9d7ca53360..e9f7dbc59d 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/cleaner/DockerContainerCleaner.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/cleaner/DockerContainerCleaner.java @@ -13,7 +13,8 @@ package org.eclipse.che.plugin.docker.machine.cleaner; import com.google.inject.Inject; import com.google.inject.Singleton; -import org.eclipse.che.api.machine.server.MachineRegistry; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.environment.server.CheEnvironmentEngine; import org.eclipse.che.commons.schedule.ScheduleRate; import org.eclipse.che.plugin.docker.client.DockerConnector; import org.eclipse.che.plugin.docker.client.json.ContainerListEntry; @@ -39,15 +40,16 @@ public class DockerContainerCleaner implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(DockerContainerCleaner.class); - private final MachineRegistry machineRegistry; + // TODO replace with WorkspaceManager + private final CheEnvironmentEngine environmentEngine; private final DockerConnector dockerConnector; private final DockerContainerNameGenerator nameGenerator; @Inject - public DockerContainerCleaner(MachineRegistry machineRegistry, + public DockerContainerCleaner(CheEnvironmentEngine environmentEngine, DockerConnector dockerConnector, DockerContainerNameGenerator nameGenerator) { - this.machineRegistry = machineRegistry; + this.environmentEngine = environmentEngine; this.dockerConnector = dockerConnector; this.nameGenerator = nameGenerator; } @@ -60,8 +62,17 @@ public class DockerContainerCleaner implements Runnable { try { for (ContainerListEntry container : dockerConnector.listContainers()) { Optional optional = nameGenerator.parse(container.getNames()[0]); - if (optional.isPresent() && !machineRegistry.isExist(optional.get().getMachineId())) { - cleanUp(container); + if (optional.isPresent()) { + try { + // container is orphaned if not found exception is thrown + environmentEngine.getMachine(optional.get().getWorkspaceId(), + optional.get().getMachineId()); + + } catch (NotFoundException e) { + cleanUp(container); + } catch (Exception e) { + LOG.error("Failed to clean up inactive container. " + e.getLocalizedMessage(), e); + } } } } catch (IOException e) { diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ext/DockerTerminalModule.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ext/DockerTerminalModule.java index a60e9c1431..acd9620021 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ext/DockerTerminalModule.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ext/DockerTerminalModule.java @@ -15,7 +15,7 @@ import com.google.inject.multibindings.Multibinder; import com.google.inject.name.Names; import org.eclipse.che.api.core.model.machine.ServerConf; -import org.eclipse.che.api.machine.server.terminal.MachineImplSpecificTerminalLauncher; +import org.eclipse.che.api.agent.server.terminal.MachineImplSpecificTerminalLauncher; import org.eclipse.che.plugin.docker.machine.DockerMachineImplTerminalLauncher; import org.eclipse.che.plugin.docker.machine.ext.provider.TerminalServerConfProvider; diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/local/LocalDockerModule.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/local/LocalDockerModule.java index b8d4d909a6..58d7c53f14 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/local/LocalDockerModule.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/local/LocalDockerModule.java @@ -15,7 +15,7 @@ import com.google.inject.assistedinject.FactoryModuleBuilder; import com.google.inject.multibindings.Multibinder; import com.google.inject.name.Names; -import org.eclipse.che.api.machine.server.MachineService; +import org.eclipse.che.api.environment.server.MachineService; import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.machine.server.spi.InstanceProcess; import org.eclipse.che.plugin.docker.machine.DockerInstance; diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceTest.java b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceTest.java index f2cb2df4d4..7a318abfe3 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceTest.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceTest.java @@ -139,9 +139,9 @@ public class DockerInstanceTest { @Test public void shouldCreateDockerImageLocally() throws Exception { final String comment = format("Suspended at %1$ta %1$tb %1$td %1$tT %1$tZ %1$tY", - System.currentTimeMillis()) + " by " + OWNER; + System.currentTimeMillis()); - dockerInstance.commitContainer(OWNER, REPOSITORY, TAG); + dockerInstance.commitContainer(REPOSITORY, TAG); verify(dockerConnectorMock, times(1)).commit(CommitParams.create(CONTAINER) .withRepository(REPOSITORY) @@ -151,7 +151,7 @@ public class DockerInstanceTest { @Test public void shouldSaveDockerInstanceStateIntoLocalImage() throws Exception { - final MachineSource result = dockerInstance.saveToSnapshot(OWNER); + final MachineSource result = dockerInstance.saveToSnapshot(); assertTrue(result instanceof DockerMachineSource); DockerMachineSource dockerMachineSource = (DockerMachineSource) result; @@ -173,7 +173,7 @@ public class DockerInstanceTest { dockerInstance = getDockerInstance(getMachine(), REGISTRY, CONTAINER, IMAGE, true); when(dockerConnectorMock.push(any(PushParams.class), any(ProgressMonitor.class))).thenReturn(digest); - final MachineSource result = dockerInstance.saveToSnapshot(OWNER); + final MachineSource result = dockerInstance.saveToSnapshot(); assertTrue(result instanceof DockerMachineSource); DockerMachineSource dockerMachineSource = (DockerMachineSource) result; @@ -186,7 +186,7 @@ public class DockerInstanceTest { public void shouldThrowMachineExceptionWhenDockerCommitFailed() throws Exception{ when(dockerConnectorMock.commit(any(CommitParams.class))).thenThrow(new IOException("err")); - dockerInstance.saveToSnapshot(OWNER); + dockerInstance.saveToSnapshot(); } @Test(expectedExceptions = MachineException.class) @@ -195,7 +195,7 @@ public class DockerInstanceTest { when(dockerConnectorMock.push(any(PushParams.class), any(ProgressMonitor.class))).thenThrow(new IOException("err")); - dockerInstance.saveToSnapshot(OWNER); + dockerInstance.saveToSnapshot(); } private DockerInstance getDockerInstance() { diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/cleaner/DockerContainerCleanerTest.java b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/cleaner/DockerContainerCleanerTest.java index 85799a5620..3b9c65dad1 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/cleaner/DockerContainerCleanerTest.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/cleaner/DockerContainerCleanerTest.java @@ -10,9 +10,10 @@ *******************************************************************************/ package org.eclipse.che.plugin.docker.machine.cleaner; -import org.eclipse.che.api.machine.server.MachineRegistry; -import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.environment.server.CheEnvironmentEngine; import org.eclipse.che.api.machine.server.model.impl.MachineImpl; +import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.plugin.docker.client.DockerConnector; import org.eclipse.che.plugin.docker.client.json.ContainerListEntry; import org.eclipse.che.plugin.docker.client.params.RemoveContainerParams; @@ -28,8 +29,8 @@ import org.testng.annotations.Test; import java.io.IOException; import java.util.Optional; -import static java.util.Optional.of; import static java.util.Arrays.asList; +import static java.util.Optional.of; import static org.eclipse.che.plugin.docker.machine.DockerContainerNameGenerator.ContainerNameInfo; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.never; @@ -64,12 +65,14 @@ public class DockerContainerCleanerTest { private static final String RUNNING_STATUS = "Up 6 hour ago"; @Mock - private MachineRegistry machineRegistry; + private CheEnvironmentEngine environmentEngine; @Mock private DockerConnector dockerConnector; @Mock private DockerContainerNameGenerator nameGenerator; + @Mock + private Instance instance; @Mock private MachineImpl machineImpl1; @Mock @@ -93,8 +96,9 @@ public class DockerContainerCleanerTest { private DockerContainerCleaner cleaner; @BeforeMethod - public void setUp() throws MachineException, IOException { - when(machineRegistry.isExist(machineId1)).thenReturn(true); + public void setUp() throws Exception { + when(environmentEngine.getMachine(workspaceId1, machineId1)).thenReturn(instance); + when(environmentEngine.getMachine(workspaceId2, machineId2)).thenThrow(new NotFoundException("test")); when(machineImpl1.getId()).thenReturn(machineId1); when(machineImpl1.getWorkspaceId()).thenReturn(workspaceId1); @@ -121,27 +125,30 @@ public class DockerContainerCleanerTest { when(containerNameInfo2.getMachineId()).thenReturn(machineId2); when(containerNameInfo2.getWorkspaceId()).thenReturn(workspaceId2); + + when(containerNameInfo3.getMachineId()).thenReturn(machineId2); + when(containerNameInfo3.getWorkspaceId()).thenReturn(workspaceId2); } @Test public void cleanerShouldKillAndRemoveContainerIfThisContainerIsRunningAndContainerNameInfoIsNotEmptyAndContainerIsNotExistInTheAPI() - throws MachineException, IOException { + throws Exception { cleaner.run(); verify(dockerConnector).listContainers(); verify(nameGenerator, times(3)).parse(anyString()); - verify(machineRegistry, times(3)).isExist(anyString()); + verify(environmentEngine, times(3)).getMachine(anyString(), anyString()); verify(dockerConnector, times(2)).killContainer(anyString()); - verify(dockerConnector, times(2)).removeContainer(Matchers.anyObject()); + verify(dockerConnector, times(2)).removeContainer(Matchers.anyObject()); verify(dockerConnector, never()).killContainer(containerId1); verify(dockerConnector, never()).removeContainer(RemoveContainerParams.create(containerId1).withForce(true).withRemoveVolumes(true)); } @Test - public void cleanerShouldRemoveButShouldNotKillContainerWithStatusNotRunning() throws IOException, MachineException { + public void cleanerShouldRemoveButShouldNotKillContainerWithStatusNotRunning() throws Exception { when(container2.getStatus()).thenReturn(EXITED_STATUS); cleaner.run(); @@ -150,14 +157,14 @@ public class DockerContainerCleanerTest { } @Test - public void cleanerShouldNotKillAndRemoveContainerIfMachineManagerDetectedExistingThisContainerInTheAPI() throws IOException { - when(machineRegistry.isExist(anyString())).thenReturn(true); + public void cleanerShouldNotKillAndRemoveContainerIfMachineManagerDetectedExistingThisContainerInTheAPI() throws Exception { + when(environmentEngine.getMachine(anyString(), anyString())).thenReturn(instance); cleaner.run(); verify(dockerConnector, never()).killContainer(anyString()); - verify(dockerConnector, never()).removeContainer(Matchers.anyObject()); + verify(dockerConnector, never()).removeContainer(Matchers.anyObject()); } @Test @@ -168,6 +175,6 @@ public class DockerContainerCleanerTest { verify(dockerConnector, never()).killContainer(anyString()); - verify(dockerConnector, never()).removeContainer(Matchers.anyObject()); + verify(dockerConnector, never()).removeContainer(Matchers.anyObject()); } } diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/integration/ServiceTest.java b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/integration/ServiceTest.java deleted file mode 100644 index 8a4ac374a2..0000000000 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/integration/ServiceTest.java +++ /dev/null @@ -1,462 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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.plugin.docker.machine.integration; - -/*import org.eclipse.che.api.core.NotFoundException; -import org.eclipse.che.api.core.model.machine.MachineStatus; -import org.eclipse.che.api.core.notification.EventService; -import org.eclipse.che.api.core.util.LineConsumer; -import org.eclipse.che.api.core.util.ValueHolder; -import org.eclipse.che.api.machine.server.MachineInstanceProviders; -import org.eclipse.che.api.machine.server.MachineManager; -import org.eclipse.che.api.machine.server.MachineRegistry; -import org.eclipse.che.api.machine.server.MachineService; -import org.eclipse.che.api.machine.server.spi.SnapshotDao; -import org.eclipse.che.api.machine.server.exception.MachineException; -import org.eclipse.che.api.machine.server.spi.impl.SnapshotImpl; -import org.eclipse.che.api.machine.server.model.impl.MachineImpl; -import org.eclipse.che.api.machine.server.model.impl.MachineStateImpl; -import org.eclipse.che.api.machine.server.spi.InstanceProvider; -import org.eclipse.che.api.machine.shared.dto.CommandDto; -import org.eclipse.che.api.machine.shared.dto.MachineProcessDto; -import org.eclipse.che.api.machine.shared.dto.recipe.MachineRecipe; -import org.eclipse.che.commons.env.EnvironmentContext; -import org.eclipse.che.commons.user.User; -import org.eclipse.che.commons.user.UserImpl; -import org.eclipse.che.inject.ConfigurationProperties; -import org.eclipse.che.plugin.docker.client.DockerConnector; -import org.eclipse.che.plugin.docker.client.InitialAuthConfig; -import org.eclipse.che.plugin.docker.client.ProgressLineFormatterImpl; -import org.eclipse.che.plugin.docker.client.json.ContainerConfig; -import org.eclipse.che.plugin.docker.client.json.HostConfig; -import org.eclipse.che.plugin.docker.client.json.PortBinding; -import org.eclipse.che.plugin.docker.machine.DockerInstanceKey; -import org.eclipse.che.plugin.docker.machine.DockerInstanceProvider; -import org.eclipse.che.plugin.docker.machine.DockerInstanceStopDetector; -import org.eclipse.che.plugin.docker.machine.DockerMachineFactory; -import org.eclipse.che.plugin.docker.machine.node.DockerNode; -import org.eclipse.che.plugin.docker.machine.TestDockerMachineFactory; -import org.eclipse.che.plugin.docker.machine.WorkspaceFolderNodePathProvider; -import org.mockito.Mock; -import org.mockito.testng.MockitoTestNGListener; -import org.testng.annotations.AfterClass; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -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.anyString; -import static org.mockito.Mockito.mock; -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; -import static org.testng.Assert.fail;*/ - -// TODO rework authentication -// TODO check removeSnapshotTest with https and password -// TODO bind, unbind -// TODO should we check result of tests with native calls? - -/** - * @author Alexander Garagatyi - */ -//@Listeners(value = {MockitoTestNGListener.class}) -public class ServiceTest { -/* private static final String USER = "userId"; - private static final String SNAPSHOT_ID = "someSnapshotId"; - private static LineConsumer lineConsumer = new StdErrLineConsumer(); - - // set in method {@link saveSnapshotTest} - // used in methods {@link createMachineFromSnapshotTest} and {@link removeSnapshotTest} - private DockerInstanceKey pushedImage; - - private SnapshotDao snapshotDao; - private MachineRegistry machineRegistry; - private DockerConnector docker; - private MachineManager machineManager; - private MachineService machineService; - private String registryContainerId; - @Mock - private WorkspaceFolderNodePathProvider workspaceFolderNodePathProvider; - @Mock - private ConfigurationProperties configurationProperties; - @Mock - private DockerInstanceStopDetector dockerInstanceStopDetector; - - private DockerMachineFactory dockerMachineFactory; - - @BeforeClass - public void setUpClass() throws Exception { - when(configurationProperties.getProperties(anyString())).thenReturn(Collections.emptyMap()); - InitialAuthConfig authConfigs = new InitialAuthConfig(configurationProperties); - - docker = new DockerConnector(authConfigs); - - machineRegistry = new MachineRegistry(); - - assertTrue(pull("registry", "latest", null)); - - dockerMachineFactory = new TestDockerMachineFactory(docker); - - final ContainerConfig containerConfig = new ContainerConfig() - .withImage("registry") - .withExposedPorts(singletonMap("5000/tcp", Collections.emptyMap())) - .withHostConfig(new HostConfig().withPortBindings( - singletonMap("5000/tcp", new PortBinding[]{new PortBinding().withHostPort("5000")}))); - - registryContainerId = docker.createContainer(containerConfig, null).getUserId(); - - docker.startContainer(registryContainerId, null); - } - - @AfterClass - public void tearDownClass() throws IOException { - docker.killContainer(registryContainerId); - docker.removeContainer(registryContainerId, true, false); - } - - @BeforeMethod - public void setUp() throws Exception { - snapshotDao = mock(SnapshotDao.class); - - DockerNode dockerNode = mock(DockerNode.class); - - EventService eventService = mock(EventService.class); - RuntimeWorkspaceRegistry runtimeWorkspaceRegistry = mock(RuntimeWorkspaceRegistry.class); - EnvironmentContext envCont = new EnvironmentContext(); - envCont.setUser(new UserImpl("user", null, null, null, false)); - EnvironmentContext.setCurrent(envCont); - RuntimeWorkspaceImpl runtimeWorkspaceImpl = mock(RuntimeWorkspaceImpl.class); - when(runtimeWorkspaceRegistry.get(any())).thenReturn(runtimeWorkspaceImpl); - when(runtimeWorkspaceImpl.getName()).thenReturn("workspace"); - - - InstanceProvider dockerInstanceProvider = new DockerInstanceProvider(docker, - dockerMachineFactory, - dockerInstanceStopDetector, - Collections.emptySet(), - Collections.emptySet(), - Collections.emptySet(), - Collections.emptySet(), - null, - "fake", - workspaceFolderNodePathProvider); - - machineManager = new MachineManager(snapshotDao, - machineRegistry, - new MachineInstanceProviders(Collections.singleton(dockerInstanceProvider)), - "/tmp", - eventService, - 100); - - machineService = spy(new MachineService(machineManager)); - - EnvironmentContext.getCurrent().setUser(new User() { - @Override - public String getName() { - return null; - } - - @Override - public boolean isMemberOf(String s) { - return false; - } - - @Override - public String getToken() { - return null; - } - - @Override - public String getUserId() { - return USER; - } - - @Override - public boolean isTemporary() { - return false; - } - }); - - when(dockerNode.getProjectsFolder()).thenReturn(System.getProperty("user.dir")); - } - - @AfterMethod - public void tearDown() throws Exception { - for (MachineStateImpl machine : new ArrayList<>(machineManager.getMachinesStates())) { - machineManager.destroy(machine.getUserId(), false); - } - EnvironmentContext.reset(); - } - - @Test - public void createFromRecipeTest() throws Exception { - final MachineStateDescriptor machine = machineService.createMachineFromRecipe( - newDto(RecipeMachineCreationMetadata.class) - .withType("docker") - .withDisplayName("MachineDisplayName") - .withWorkspaceId("wsId") - .withRecipe(newDto(MachineRecipe.class) - .withType("Dockerfile") - .withScript("FROM ubuntu\nCMD tail -f /dev/null\n"))); - - waitMachineIsRunning(machine.getUserId()); - } - - @Test(dependsOnMethods = "saveSnapshotTest", enabled = false) - public void createMachineFromSnapshotTest() throws Exception { - // remove local copy of image to check pulling - docker.removeImage(pushedImage.getImageId(), true); - - SnapshotImpl snapshot = mock(SnapshotImpl.class); - when(snapshotDao.getSnapshot(SNAPSHOT_ID)).thenReturn(snapshot); - when(snapshot.getType()).thenReturn("docker"); - when(snapshot.getWorkspaceId()).thenReturn("wsId"); - when(snapshot.getInstanceKey()).thenReturn(pushedImage); - when(snapshot.getNamespace()).thenReturn(USER); - - final MachineStateDescriptor machine = machineService - .createMachineFromSnapshot(newDto(SnapshotMachineCreationMetadata.class).withSnapshotId(SNAPSHOT_ID)); - - waitMachineIsRunning(machine.getUserId()); - } - - @Test - public void getMachineTest() throws Exception { - final MachineImpl machine = createMachineAndWaitRunningState(); - - final MachineDescriptor machineById = machineService.getMachineById(machine.getUserId()); - - assertEquals(machineById.getUserId(), machine.getUserId()); - } - - @Test - public void getMachinesTest() throws Exception { - Set expected = new HashSet<>(); - expected.add(createMachineAndWaitRunningState().getUserId()); - expected.add(createMachineAndWaitRunningState().getUserId()); - - Set actual = machineManager.getMachinesStates() - .stream() - .map(MachineImpl::getUserId) - .collect(Collectors.toSet()); - assertEquals(actual, expected); - } - - @Test - public void destroyMachineTest() throws Exception { - final MachineImpl machine = createMachineAndWaitRunningState(); - - machineService.destroyMachine(machine.getUserId()); - - assertEquals(machineService.getMachineStateById(machine.getUserId()).getStatus(), MachineStatus.DESTROYING); - - int counter = 0; - while (++counter < 1000) { - try { - machineManager.getMachine(machine.getUserId()); - } catch (NotFoundException e) { - return; - } - Thread.sleep(500); - } - fail(); - } - - @Test(enabled = false)// TODO Add ability to check when snapshot creation is finishes or fails - public void saveSnapshotTest() throws Exception { - final MachineImpl machine = createMachineAndWaitRunningState(); - - // use machine manager instead of machine service because it returns future with snapshot - // that allows check operation result - final SnapshotImpl snapshot = machineManager.save(machine.getUserId(), USER, "test description"); - - for (int i = 0; snapshot.getInstanceKey() == null && i < 10; ++i) { - Thread.sleep(500); - } - assertNotNull(snapshot.getInstanceKey()); - - final DockerInstanceKey instanceKey = (DockerInstanceKey)snapshot.getInstanceKey(); - - final boolean pullIsSuccessful = pull(instanceKey.getRepository(), instanceKey.getTag(), instanceKey.getRegistry()); - - assertTrue(pullIsSuccessful); - - pushedImage = instanceKey; - } - - // depends on saveSnapshotTest to be able to remove image from registry - // actually doesn't depend on createMachineFromSnapshotTest, - // but this test will fail createMachineFromSnapshotTest if called before - @Test(dependsOnMethods = {"saveSnapshotTest", "createMachineFromSnapshotTest"}, enabled = false)// TODO - public void removeSnapshotTest() throws Exception { - SnapshotImpl snapshot = mock(SnapshotImpl.class); - when(snapshotDao.getSnapshot(SNAPSHOT_ID)).thenReturn(snapshot); - when(snapshot.getType()).thenReturn("docker"); - when(snapshot.getNamespace()).thenReturn(USER); - when(snapshot.getInstanceKey()).thenReturn(pushedImage); - - machineService.removeSnapshot(SNAPSHOT_ID); - - verify(snapshotDao).removeSnapshot(SNAPSHOT_ID); - - try { - final boolean isPullSuccessful = pull(pushedImage.getRepository(), pushedImage.getTag(), pushedImage.getRegistry()); - assertFalse(isPullSuccessful); - } catch (Exception e) { - fail(e.getLocalizedMessage(), e); - } - } - - @Test - public void executeTest() throws Exception { - final MachineImpl machine = createMachineAndWaitRunningState(); - - String commandInMachine = "echo \"command in machine\" && tail -f /dev/null"; - machineService.executeCommandInMachine(machine.getUserId(), - DtoFactory.newDto(CommandDto.class).withCommandLine(commandInMachine), - null); - - Thread.sleep(500); - - final List processes = machineService.getProcesses(machine.getUserId()); - assertEquals(processes.size(), 1); - assertEquals(processes.get(0).getCommandLine(), commandInMachine); - } - - @Test - public void getProcessesTest() throws Exception { - final MachineImpl machine = createMachineAndWaitRunningState(); - - Set commands = new HashSet<>(2); - commands.add("tail -f /dev/null"); - commands.add("sleep 10000"); - - for (String command : commands) { - machineService.executeCommandInMachine(machine.getUserId(), DtoFactory.newDto(CommandDto.class).withCommandLine(command), null); - } - - Thread.sleep(500); - - final List processes = machineService.getProcesses(machine.getUserId()); - assertEquals(processes.size(), 2); - Set actualCommandLines = new HashSet<>(2); - for (MachineProcessDto process : processes) { - assertTrue(process.getPid() > 0); - actualCommandLines.add(process.getCommandLine()); - } - assertEquals(actualCommandLines, commands); - } - - @Test - public void stopProcessTest() throws Exception { - final MachineImpl machine = createMachineAndWaitRunningState(); - - String commandInMachine = "echo \"command in machine\" && tail -f /dev/null"; - machineService.executeCommandInMachine(machine.getUserId(), - DtoFactory.newDto(CommandDto.class).withCommandLine(commandInMachine), - null); - - Thread.sleep(500); - - final List processes = machineService.getProcesses(machine.getUserId()); - assertEquals(processes.size(), 1); - assertEquals(processes.get(0).getCommandLine(), commandInMachine); - - machineService.stopProcess(machine.getUserId(), processes.get(0).getPid()); - - assertTrue(machineService.getProcesses(machine.getUserId()).isEmpty()); - } - - @Test(expectedExceptions = NotFoundException.class, expectedExceptionsMessageRegExp = "Process with pid .* not found") - public void shouldThrowNotFoundExceptionOnProcessKillIfProcessPidMissing() throws Exception { - final MachineImpl machine = createMachineAndWaitRunningState(); - - String commandInMachine = "echo \"command in machine\" && tail -f /dev/null"; - machineService.executeCommandInMachine(machine.getUserId(), - DtoFactory.newDto(CommandDto.class).withCommandLine(commandInMachine), - null); - - Thread.sleep(500); - - final List processes = machineService.getProcesses(machine.getUserId()); - assertEquals(processes.size(), 1); - assertEquals(processes.get(0).getCommandLine(), commandInMachine); - - machineService.stopProcess(machine.getUserId(), processes.get(0).getPid() + 100); - } - - private MachineImpl createMachineAndWaitRunningState() throws Exception { - final MachineImpl machine = machineManager.create(newDto(RecipeMachineCreationMetadata.class) - .withWorkspaceId("wsId") - .withType("docker") - .withDisplayName("MachineDisplayName") - .withRecipe(newDto(MachineRecipe.class) - .withType("Dockerfile") - .withScript( - "FROM ubuntu\nCMD tail -f " + - "/dev/null\n")) - .withDev(false) - .withDisplayName("displayName" + System.currentTimeMillis()) - , false); - waitMachineIsRunning(machine.getUserId()); - return machine; - } - - private void waitMachineIsRunning(String machineId) throws NotFoundException, InterruptedException, MachineException { - while (MachineStatus.RUNNING != machineManager.getMachineState(machineId).getStatus()) { - Thread.sleep(500); - } - } - - private boolean pull(String image, String tag, String registry) throws Exception { - final ValueHolder isSuccessfulValueHolder = new ValueHolder<>(true); - final ProgressLineFormatterImpl progressLineFormatter = new ProgressLineFormatterImpl(); - docker.pull(image, tag, registry, currentProgressStatus -> { - try { - if (currentProgressStatus.getError() != null) { - isSuccessfulValueHolder.set(false); - } - lineConsumer.writeLine(progressLineFormatter.format(currentProgressStatus)); - } catch (IOException ignored) { - } - }); - - return isSuccessfulValueHolder.get(); - } - - private static class StdErrLineConsumer implements LineConsumer { - @Override - public void writeLine(String line) throws IOException { - System.err.println(line); - } - - @Override - public void close() throws IOException { - } - }*/ -} diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/local/interceptor/EnableOfflineDockerMachineBuildInterceptorTest.java b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/local/interceptor/EnableOfflineDockerMachineBuildInterceptorTest.java index d44a836e3e..184dfb5bc6 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/local/interceptor/EnableOfflineDockerMachineBuildInterceptorTest.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/local/interceptor/EnableOfflineDockerMachineBuildInterceptorTest.java @@ -13,12 +13,15 @@ package org.eclipse.che.plugin.docker.machine.local.interceptor; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; +import com.google.inject.multibindings.Multibinder; import com.google.inject.name.Names; import com.google.inject.spi.ConstructorBinding; import org.aopalliance.intercept.MethodInvocation; import org.eclipse.che.api.core.model.machine.MachineConfig; import org.eclipse.che.api.core.model.machine.Recipe; +import org.eclipse.che.api.machine.server.dao.SnapshotDao; +import org.eclipse.che.api.machine.server.spi.InstanceProvider; import org.eclipse.che.api.machine.server.util.RecipeDownloader; import org.eclipse.che.api.machine.server.util.RecipeRetriever; import org.eclipse.che.api.workspace.server.WorkspaceManager; @@ -187,6 +190,12 @@ public class EnableOfflineDockerMachineBuildInterceptorTest { bind(WorkspaceManager.class).toInstance(workspaceManager); bind(RecipeRetriever.class).toInstance(recipeRetriever); bind(RecipeDownloader.class).toInstance(mock(RecipeDownloader.class)); + bind(SnapshotDao.class).toInstance(mock(SnapshotDao.class)); + Multibinder machineImageProviderMultibinder = + Multibinder.newSetBinder(binder(), + org.eclipse.che.api.machine.server.spi.InstanceProvider.class); + machineImageProviderMultibinder.addBinding() + .to(org.eclipse.che.plugin.docker.machine.DockerInstanceProvider.class); bindConstant().annotatedWith(Names.named("machine.docker.privilege_mode")).to(false); bindConstant().annotatedWith(Names.named("machine.docker.pull_image")).to(true); @@ -195,6 +204,8 @@ public class EnableOfflineDockerMachineBuildInterceptorTest { bindConstant().annotatedWith(Names.named("che.machine.projects.internal.storage")).to("/tmp"); bindConstant().annotatedWith(Names.named("machine.docker.machine_extra_hosts")).to(""); bindConstant().annotatedWith(Names.named("che.workspace.storage")).to("/tmp"); + bindConstant().annotatedWith(Names.named("machine.default_mem_size_mb")).to("1024"); + bindConstant().annotatedWith(Names.named("machine.logs.location")).to("/tmp"); install(new DockerMachineModule()); install(new AllowOfflineMachineCreationModule()); diff --git a/plugins/plugin-java-debugger/che-plugin-java-debugger-ide/src/main/java/org/eclipse/che/plugin/jdb/ide/configuration/JavaDebugConfigurationPagePresenter.java b/plugins/plugin-java-debugger/che-plugin-java-debugger-ide/src/main/java/org/eclipse/che/plugin/jdb/ide/configuration/JavaDebugConfigurationPagePresenter.java index f3922225e8..d22f62bc84 100644 --- a/plugins/plugin-java-debugger/che-plugin-java-debugger-ide/src/main/java/org/eclipse/che/plugin/jdb/ide/configuration/JavaDebugConfigurationPagePresenter.java +++ b/plugins/plugin-java-debugger/che-plugin-java-debugger-ide/src/main/java/org/eclipse/che/plugin/jdb/ide/configuration/JavaDebugConfigurationPagePresenter.java @@ -84,7 +84,8 @@ public class JavaDebugConfigurationPagePresenter implements JavaDebugConfigurati } private void setPortsList() { - machineServiceClient.getMachine(appContext.getDevMachine().getId()).then(new Operation() { + machineServiceClient.getMachine(appContext.getWorkspaceId(), + appContext.getDevMachine().getId()).then(new Operation() { @Override public void apply(MachineDto machineDto) throws OperationException { Machine machine = entityFactory.createMachine(machineDto); diff --git a/plugins/plugin-java-debugger/che-plugin-java-debugger-ide/src/test/java/org/eclipse/che/plugin/jdb/ide/configuration/JavaDebugConfigurationPagePresenterTest.java b/plugins/plugin-java-debugger/che-plugin-java-debugger-ide/src/test/java/org/eclipse/che/plugin/jdb/ide/configuration/JavaDebugConfigurationPagePresenterTest.java index e3e69ab713..c695bdf2e9 100644 --- a/plugins/plugin-java-debugger/che-plugin-java-debugger-ide/src/test/java/org/eclipse/che/plugin/jdb/ide/configuration/JavaDebugConfigurationPagePresenterTest.java +++ b/plugins/plugin-java-debugger/che-plugin-java-debugger-ide/src/test/java/org/eclipse/che/plugin/jdb/ide/configuration/JavaDebugConfigurationPagePresenterTest.java @@ -78,7 +78,7 @@ public class JavaDebugConfigurationPagePresenterTest { @Test public void testGo() throws Exception { - when(machineServiceClient.getMachine(anyString())).thenReturn(mock(Promise.class)); + when(machineServiceClient.getMachine(anyString(), anyString())).thenReturn(mock(Promise.class)); AcceptsOneWidget container = Mockito.mock(AcceptsOneWidget.class); diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/RecipeScriptDownloadServiceClientImpl.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/RecipeScriptDownloadServiceClientImpl.java index 3223ce9967..2f0cf08f56 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/RecipeScriptDownloadServiceClientImpl.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/RecipeScriptDownloadServiceClientImpl.java @@ -35,7 +35,7 @@ public class RecipeScriptDownloadServiceClientImpl implements RecipeScriptDownlo @Override public Promise getRecipeScript(Machine machine) { return asyncRequestFactory - .createGetRequest(restContext + "/recipe/script/" + machine.getId()) + .createGetRequest(restContext + "/recipe/script/" + machine.getWorkspaceId() + "/" + machine.getId()) .send(new StringUnmarshaller()); } } diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/command/CommandManager.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/command/CommandManager.java index ea8eee6a33..137613449d 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/command/CommandManager.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/command/CommandManager.java @@ -119,7 +119,11 @@ public class CommandManager { .withCommandLine(arg) .withType(configuration.getType().getId()); - final Promise processPromise = machineServiceClient.executeCommand(machine.getId(), command, outputChannel); + final Promise processPromise = + machineServiceClient.executeCommand(machine.getWorkspaceId(), + machine.getId(), + command, + outputChannel); processPromise.then(new Operation() { @Override public void apply(MachineProcessDto process) throws OperationException { diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/command/valueproviders/ServerPortProvider.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/command/valueproviders/ServerPortProvider.java index 2c966c9e3d..bf5fc22627 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/command/valueproviders/ServerPortProvider.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/command/valueproviders/ServerPortProvider.java @@ -15,9 +15,6 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; -import org.eclipse.che.ide.api.machine.MachineServiceClient; -import org.eclipse.che.ide.api.machine.events.WsAgentStateEvent; -import org.eclipse.che.ide.api.machine.events.WsAgentStateHandler; import org.eclipse.che.api.machine.shared.dto.MachineDto; import org.eclipse.che.api.machine.shared.dto.ServerDto; import org.eclipse.che.api.promises.client.Operation; @@ -25,6 +22,9 @@ import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.promises.client.js.Promises; import org.eclipse.che.ide.api.app.AppContext; +import org.eclipse.che.ide.api.machine.MachineServiceClient; +import org.eclipse.che.ide.api.machine.events.WsAgentStateEvent; +import org.eclipse.che.ide.api.machine.events.WsAgentStateHandler; import java.util.Map; import java.util.Set; @@ -70,7 +70,7 @@ public class ServerPortProvider implements WsAgentStateHandler { private void registerProviders() { String devMachineId = appContext.getDevMachine().getId(); if (devMachineId != null) { - machineServiceClient.getMachine(devMachineId).then(registerProviders); + machineServiceClient.getMachine(appContext.getWorkspaceId(), devMachineId).then(registerProviders); } } diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImpl.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImpl.java index 09f0d3f4d9..86964909ee 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImpl.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImpl.java @@ -146,7 +146,8 @@ public class MachineManagerImpl implements MachineManager { @Override public Promise destroyMachine(final Machine machineState) { - return machineServiceClient.destroyMachine(machineState.getId()).then(new Operation() { + return machineServiceClient.destroyMachine(machineState.getWorkspaceId(), + machineState.getId()).then(new Operation() { @Override public void apply(Void arg) throws OperationException { eventBus.fireEvent(new MachineStateEvent(machineState, DESTROYED)); diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/MachineStatusNotifier.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/MachineStatusNotifier.java index 48dee2b78a..57ef1cc2a3 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/MachineStatusNotifier.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/MachineStatusNotifier.java @@ -66,13 +66,14 @@ public class MachineStatusNotifier implements MachineStatusChangedEvent.Handler public void onMachineStatusChanged(final MachineStatusChangedEvent event) { final String machineName = event.getMachineName(); final String machineId = event.getMachineId(); + final String workspaceId = event.getWorkspaceId(); switch (event.getEventType()) { case CREATING: - getMachine(machineId).then(notifyMachineCreating()); + getMachine(workspaceId, machineId).then(notifyMachineCreating()); break; case RUNNING: - getMachine(machineId).then(notifyMachineRunning()); + getMachine(workspaceId, machineId).then(notifyMachineRunning()); break; case DESTROYED: notificationManager.notify(locale.notificationMachineDestroyed(machineName), SUCCESS, EMERGE_MODE); @@ -83,8 +84,8 @@ public class MachineStatusNotifier implements MachineStatusChangedEvent.Handler } } - private Promise getMachine(final String machineId) { - return machineServiceClient.getMachine(machineId).catchError(new Operation() { + private Promise getMachine(final String workspaceId, final String machineId) { + return machineServiceClient.getMachine(workspaceId, machineId).catchError(new Operation() { @Override public void apply(PromiseError arg) throws OperationException { notificationManager.notify(locale.failedToFindMachine(machineId)); diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/create/CreateMachinePresenter.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/create/CreateMachinePresenter.java index 1276703359..b6e87a409e 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/create/CreateMachinePresenter.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/create/CreateMachinePresenter.java @@ -136,7 +136,8 @@ public class CreateMachinePresenter implements CreateMachineView.ActionDelegate final String recipeURL = view.getRecipeURL(); if (appContext.getDevMachine() != null) { - machineServiceClient.getMachine(appContext.getDevMachine().getId()).then(new Operation() { + machineServiceClient.getMachine(appContext.getWorkspaceId(), + appContext.getDevMachine().getId()).then(new Operation() { @Override public void apply(MachineDto machine) throws OperationException { machineManager.destroyMachine(machine); diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/outputspanel/console/CommandOutputConsolePresenter.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/outputspanel/console/CommandOutputConsolePresenter.java index 92f8be1e46..95e5294fb7 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/outputspanel/console/CommandOutputConsolePresenter.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/outputspanel/console/CommandOutputConsolePresenter.java @@ -20,8 +20,8 @@ import org.eclipse.che.api.machine.shared.dto.MachineProcessDto; import org.eclipse.che.api.machine.shared.dto.event.MachineProcessEvent; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; -import org.eclipse.che.ide.api.machine.MachineServiceClient; import org.eclipse.che.ide.api.machine.CommandOutputMessageUnmarshaller; +import org.eclipse.che.ide.api.machine.MachineServiceClient; import org.eclipse.che.ide.extension.machine.client.MachineResources; import org.eclipse.che.ide.extension.machine.client.command.CommandConfiguration; import org.eclipse.che.ide.extension.machine.client.command.CommandManager; @@ -231,7 +231,9 @@ public class CommandOutputConsolePresenter implements CommandOutputConsole, Outp @Override public void stop() { - machineServiceClient.stopProcess(machine.getId(), pid); + machineServiceClient.stopProcess(machine.getWorkspaceId(), + machine.getId(), + pid); } @Override @@ -249,7 +251,9 @@ public class CommandOutputConsolePresenter implements CommandOutputConsole, Outp if (isFinished()) { commandManager.executeCommand(commandConfiguration, machine); } else { - machineServiceClient.stopProcess(machine.getId(), pid).then(new Operation() { + machineServiceClient.stopProcess(machine.getWorkspaceId(), + machine.getId(), + pid).then(new Operation() { @Override public void apply(Void arg) throws OperationException { commandManager.executeCommand(commandConfiguration, machine); diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/appliance/processes/ProcessesPresenter.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/appliance/processes/ProcessesPresenter.java index c4f1934c05..311d7e5625 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/appliance/processes/ProcessesPresenter.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/appliance/processes/ProcessesPresenter.java @@ -50,8 +50,8 @@ public class ProcessesPresenter implements TabPresenter, ProcessesView.ActionDel * @param machineId * machine identifier for which need get processes */ - public void showProcesses(@NotNull String machineId) { - Promise> processesPromise = service.getProcesses(machineId); + public void showProcesses(@NotNull String workspaceId, @NotNull String machineId) { + Promise> processesPromise = service.getProcesses(workspaceId, machineId); processesPromise.then(new Operation>() { @Override diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/panel/MachinePanelPresenter.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/panel/MachinePanelPresenter.java index 6d3fa5c7f2..42152b0612 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/panel/MachinePanelPresenter.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/panel/MachinePanelPresenter.java @@ -17,19 +17,19 @@ import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; import org.eclipse.che.api.core.model.machine.MachineStatus; -import org.eclipse.che.ide.api.machine.MachineServiceClient; import org.eclipse.che.api.machine.shared.dto.MachineDto; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.promises.client.PromiseError; -import org.eclipse.che.ide.api.workspace.event.WorkspaceStartedEvent; -import org.eclipse.che.ide.api.workspace.event.WorkspaceStoppedEvent; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.event.ActivePartChangedEvent; import org.eclipse.che.ide.api.event.ActivePartChangedHandler; +import org.eclipse.che.ide.api.machine.MachineServiceClient; import org.eclipse.che.ide.api.parts.base.BasePresenter; +import org.eclipse.che.ide.api.workspace.event.WorkspaceStartedEvent; +import org.eclipse.che.ide.api.workspace.event.WorkspaceStoppedEvent; import org.eclipse.che.ide.extension.machine.client.MachineLocalizationConstant; import org.eclipse.che.ide.extension.machine.client.MachineResources; import org.eclipse.che.ide.extension.machine.client.inject.factories.EntityFactory; @@ -163,7 +163,8 @@ public class MachinePanelPresenter extends BasePresenter implements MachinePanel return; } - service.getMachine(selectedMachine.getId()).then(new Operation() { + service.getMachine(selectedMachine.getWorkspaceId(), + selectedMachine.getId()).then(new Operation() { @Override public void apply(MachineDto machineDto) throws OperationException { if (machineDto.getStatus() == MachineStatus.RUNNING) { diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/AddTerminalClickHandler.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/AddTerminalClickHandler.java index af3773e3a9..d9c40114f6 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/AddTerminalClickHandler.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/AddTerminalClickHandler.java @@ -25,5 +25,5 @@ public interface AddTerminalClickHandler { * @param machineId * id of machine in which the terminal will be added */ - void onAddTerminalClick(@NotNull String machineId); + void onAddTerminalClick(@NotNull String workspaceId, @NotNull String machineId); } diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/ConsolesPanelPresenter.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/ConsolesPanelPresenter.java index d7c7b65cf3..5718e50051 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/ConsolesPanelPresenter.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/ConsolesPanelPresenter.java @@ -322,7 +322,8 @@ public class ConsolesPanelPresenter implements ConsolesPanelView.ActionDelegate, } private void restoreState(final org.eclipse.che.api.core.model.machine.Machine machine) { - machineService.getProcesses(machine.getId()).then(new Operation>() { + machineService.getProcesses(machine.getWorkspaceId(), + machine.getId()).then(new Operation>() { @Override public void apply(List arg) throws OperationException { for (MachineProcessDto machineProcessDto : arg) { @@ -418,17 +419,17 @@ public class ConsolesPanelPresenter implements ConsolesPanelView.ActionDelegate, if (selectedTreeNode == null) { if (appContext.getDevMachine() != null) { - onAddTerminal(appContext.getDevMachine().getId()); + onAddTerminal(appContext.getWorkspaceId(), appContext.getDevMachine().getId()); } return; } if (selectedTreeNode.getType() == MACHINE_NODE) { - onAddTerminal(selectedTreeNode.getId()); + onAddTerminal(appContext.getWorkspaceId(), selectedTreeNode.getId()); } else { if (selectedTreeNode.getParent() != null && selectedTreeNode.getParent().getType() == MACHINE_NODE) { - onAddTerminal(appContext.getDevMachine().getId()); + onAddTerminal(appContext.getWorkspaceId(), appContext.getDevMachine().getId()); } } } @@ -440,8 +441,8 @@ public class ConsolesPanelPresenter implements ConsolesPanelView.ActionDelegate, * id of machine in which the terminal will be added */ @Override - public void onAddTerminal(@NotNull final String machineId) { - machineService.getMachine(machineId).then(new Operation() { + public void onAddTerminal(@NotNull final String workspaceId, @NotNull final String machineId) { + machineService.getMachine(workspaceId, machineId).then(new Operation() { @Override public void apply(MachineDto arg) throws OperationException { org.eclipse.che.ide.extension.machine.client.machine.Machine machine = entityFactory.createMachine(arg); diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/ConsolesPanelView.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/ConsolesPanelView.java index ecda1c6972..eb7abd837b 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/ConsolesPanelView.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/ConsolesPanelView.java @@ -89,7 +89,7 @@ public interface ConsolesPanelView extends View { event.preventDefault(); if (addTerminalClickHandler != null) { - addTerminalClickHandler.onAddTerminalClick(machine.getId()); + addTerminalClickHandler.onAddTerminalClick(machine.getWorkspaceId(), machine.getId()); } } }, true); diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerCategoryPresenter.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerCategoryPresenter.java index 4223977907..6b5ecc1c72 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerCategoryPresenter.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerCategoryPresenter.java @@ -143,7 +143,8 @@ public class DockerCategoryPresenter implements CategoryPage, TargetManager, Doc return; } - machineService.destroyMachine(machine.getId()).then(new Operation() { + machineService.destroyMachine(machine.getWorkspaceId(), + machine.getId()).then(new Operation() { @Override public void apply(Void arg) throws OperationException { eventBus.fireEvent(new MachineStateEvent(machine, MachineStateEvent.MachineAction.DESTROYED)); diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/ssh/SshCategoryPresenter.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/ssh/SshCategoryPresenter.java index 37236e8a24..e145239438 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/ssh/SshCategoryPresenter.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/ssh/SshCategoryPresenter.java @@ -462,7 +462,8 @@ public class SshCategoryPresenter implements CategoryPage, TargetManager, SshVie } sshView.setConnectButtonText(null); - machineService.destroyMachine(machine.getId()).then(new Operation() { + machineService.destroyMachine(machine.getWorkspaceId(), + machine.getId()).then(new Operation() { @Override public void apply(Void arg) throws OperationException { eventBus.fireEvent(new MachineStateEvent(machine, MachineStateEvent.MachineAction.DESTROYED)); @@ -509,7 +510,8 @@ public class SshCategoryPresenter implements CategoryPage, TargetManager, SshVie return; } - machineService.destroyMachine(machine.getId()).then(new Operation() { + machineService.destroyMachine(machine.getWorkspaceId(), + machine.getId()).then(new Operation() { @Override public void apply(Void arg) throws OperationException { eventBus.fireEvent(new MachineStateEvent(machine, MachineStateEvent.MachineAction.DESTROYED)); @@ -557,14 +559,14 @@ public class SshCategoryPresenter implements CategoryPage, TargetManager, SshVie /** * Ensures machine is started. */ - private void onConnected(final String machineId) { + private void onConnected(final String workspaceId, final String machineId) { // There is a little bug in machine service on the server side. // The machine info is updated with a little delay after running a machine. // Using timer must fix the problem. new Timer() { @Override public void run() { - machineService.getMachine(machineId).then(new Operation() { + machineService.getMachine(workspaceId, machineId).then(new Operation() { @Override public void apply(MachineDto machineDto) throws OperationException { if (machineDto.getStatus() == RUNNING) { @@ -644,7 +646,7 @@ public class SshCategoryPresenter implements CategoryPage, TargetManager, SshVie if (MachineStatusEvent.EventType.RUNNING == event.getEventType() && connectNotification != null && connectTargetName != null && connectTargetName.equals(event.getMachineName())) { - onConnected(event.getMachineId()); + onConnected(event.getWorkspaceId(), event.getMachineId()); return; } diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImplTest.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImplTest.java index e6cd54a526..fc7873105f 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImplTest.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImplTest.java @@ -108,6 +108,7 @@ public class MachineManagerImplTest { @Test public void checkUseValidSource() throws OperationException { final String ID = "id"; + final String WORKSPACE_ID = "testWorkspaceId"; final String DISPLAY_NAME = "my-display-name"; final boolean IS_DEV = true; @@ -117,9 +118,10 @@ public class MachineManagerImplTest { org.eclipse.che.api.core.model.machine.Machine machineState = mock(org.eclipse.che.api.core.model.machine.Machine.class); when(machineState.getId()).thenReturn(ID); + when(machineState.getWorkspaceId()).thenReturn(WORKSPACE_ID); Promise promise = mock(Promise.class); Promise promiseThen = mock(Promise.class); - when(machineServiceClient.destroyMachine(eq(ID))).thenReturn(promise); + when(machineServiceClient.destroyMachine(eq(WORKSPACE_ID), eq(ID))).thenReturn(promise); when(promise.then(Matchers.>anyObject())).thenReturn(promiseThen); MachineSource machineSource = mock(MachineSource.class); diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/MachineStateNotifierTest.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/MachineStateNotifierTest.java index 23a6d84a08..62e39aa287 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/MachineStateNotifierTest.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/MachineStateNotifierTest.java @@ -54,6 +54,7 @@ import static org.mockito.Mockito.when; public class MachineStateNotifierTest { private static final String MACHINE_NAME = "machineName"; private static final String MACHINE_ID = "machineId"; + private static final String WORKSPACE_ID = "workspaceId"; //constructor mocks @Mock @@ -90,8 +91,9 @@ public class MachineStateNotifierTest { when(machine.getConfig()).thenReturn(machineConfig); when(machineConfig.getName()).thenReturn(MACHINE_NAME); when(machineStatusChangedEvent.getMachineId()).thenReturn(MACHINE_ID); + when(machineStatusChangedEvent.getWorkspaceId()).thenReturn(WORKSPACE_ID); when(machineStatusChangedEvent.getMachineName()).thenReturn(MACHINE_NAME); - when(machineServiceClient.getMachine(MACHINE_ID)).thenReturn(machinePromise); + when(machineServiceClient.getMachine(WORKSPACE_ID, MACHINE_ID)).thenReturn(machinePromise); when(machinePromise.then(Matchers.>anyObject())).thenReturn(machinePromise); when(machinePromise.catchError(Matchers.>anyObject())).thenReturn(machinePromise); } diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/create/CreateMachinePresenterTest.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/create/CreateMachinePresenterTest.java index 6c72efea4f..ec9722cb7b 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/create/CreateMachinePresenterTest.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/create/CreateMachinePresenterTest.java @@ -10,14 +10,14 @@ *******************************************************************************/ package org.eclipse.che.ide.extension.machine.client.machine.create; -import org.eclipse.che.ide.api.machine.DevMachine; -import org.eclipse.che.ide.api.machine.MachineManager; -import org.eclipse.che.ide.api.machine.MachineServiceClient; import org.eclipse.che.api.machine.shared.dto.MachineDto; -import org.eclipse.che.ide.api.project.ProjectTypeServiceClient; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.ide.api.app.AppContext; +import org.eclipse.che.ide.api.machine.DevMachine; +import org.eclipse.che.ide.api.machine.MachineManager; +import org.eclipse.che.ide.api.machine.MachineServiceClient; +import org.eclipse.che.ide.api.project.ProjectTypeServiceClient; import org.eclipse.che.ide.extension.machine.client.inject.factories.EntityFactory; import org.junit.Before; import org.junit.Test; @@ -42,6 +42,7 @@ public class CreateMachinePresenterTest { private final static String RECIPE_URL = "http://www.host.com/recipe"; private final static String MACHINE_NAME = "machine"; + private final static String WORKSPACE_ID = "testWorkspace123"; private final static String SOME_TEXT = "someText"; @@ -122,15 +123,17 @@ public class CreateMachinePresenterTest { public void shouldReplaceDevMachine() throws Exception { DevMachine devMachine = mock(DevMachine.class); when(appContext.getDevMachine()).thenReturn(devMachine); + when(appContext.getWorkspaceId()).thenReturn(WORKSPACE_ID); when(devMachine.getId()).thenReturn(SOME_TEXT); - when(machineServiceClient.getMachine(SOME_TEXT)).thenReturn(machineDescriptorPromise); + when(devMachine.getWorkspace()).thenReturn(WORKSPACE_ID); + when(machineServiceClient.getMachine(WORKSPACE_ID, SOME_TEXT)).thenReturn(machineDescriptorPromise); presenter.onReplaceDevMachineClicked(); verify(view).getMachineName(); verify(view).getRecipeURL(); verify(appContext, times(2)).getDevMachine(); - verify(machineServiceClient).getMachine(SOME_TEXT); + verify(machineServiceClient).getMachine(WORKSPACE_ID, SOME_TEXT); verify(machineDescriptorPromise).then(machineCaptor.capture()); machineCaptor.getValue().apply(mock(MachineDto.class)); verify(machineManager).destroyMachine(any(MachineDto.class)); @@ -141,7 +144,7 @@ public class CreateMachinePresenterTest { @Test public void shouldStartNewDevMachine() throws Exception { when(appContext.getDevMachine()).thenReturn(null); - when(machineServiceClient.getMachine(SOME_TEXT)).thenReturn(machineDescriptorPromise); + when(machineServiceClient.getMachine(WORKSPACE_ID, SOME_TEXT)).thenReturn(machineDescriptorPromise); presenter.onReplaceDevMachineClicked(); @@ -150,7 +153,7 @@ public class CreateMachinePresenterTest { verify(appContext).getDevMachine(); verify(machineManager).startDevMachine(eq(RECIPE_URL), eq(MACHINE_NAME)); verify(view).close(); - verify(machineServiceClient, never()).getMachine(SOME_TEXT); + verify(machineServiceClient, never()).getMachine(WORKSPACE_ID, SOME_TEXT); verify(machineManager, never()).destroyMachine(any(MachineDto.class)); verify(machineManager).startDevMachine(eq(RECIPE_URL), eq(MACHINE_NAME)); } diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/appliance/processes/ProcessesPresenterTest.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/appliance/processes/ProcessesPresenterTest.java index edc186d969..22401e96bb 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/appliance/processes/ProcessesPresenterTest.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/appliance/processes/ProcessesPresenterTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.when; public class ProcessesPresenterTest { private static final String MACHINE_ID = "someId"; + private static final String WORKSPACE_ID = "wsId"; //constructor mocks @Mock @@ -66,11 +67,11 @@ public class ProcessesPresenterTest { @Test public void processesShouldBeGot() throws Exception { - when(service.getProcesses(MACHINE_ID)).thenReturn(processPromise); + when(service.getProcesses(WORKSPACE_ID, MACHINE_ID)).thenReturn(processPromise); - presenter.showProcesses(MACHINE_ID); + presenter.showProcesses(WORKSPACE_ID, MACHINE_ID); - verify(service).getProcesses(MACHINE_ID); + verify(service).getProcesses(WORKSPACE_ID, MACHINE_ID); verify(processPromise).then(operationCaptor.capture()); operationCaptor.getValue().apply(descriptors); diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/panel/MachinePanelPresenterTest.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/panel/MachinePanelPresenterTest.java index ad4c4809c7..2354cb12d0 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/panel/MachinePanelPresenterTest.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/perspective/widgets/machine/panel/MachinePanelPresenterTest.java @@ -162,7 +162,7 @@ public class MachinePanelPresenterTest { when(service.getMachines(anyString())).thenReturn(machinesPromise); when(machinesPromise.then(Matchers.>>anyObject())).thenReturn(machinesPromise); - when(service.getMachine(anyString())).thenReturn(machinePromise); + when(service.getMachine(anyString(), anyString())).thenReturn(machinePromise); when(machinePromise.then(Matchers.>anyObject())).thenReturn(machinePromise); when(appContext.getWorkspace()).thenReturn(usersWorkspaceDto); @@ -239,7 +239,7 @@ public class MachinePanelPresenterTest { verify(appliance).showAppliance(machine1); - verify(service, never()).getMachine(anyString()); + verify(service, never()).getMachine(anyString(), anyString()); assertThat(selectedMachine1, is(equalTo(presenter.getSelectedMachineState()))); assertThat(presenter.isMachineRunning(), is(true)); diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/processes/ConsolesPanelPresenterTest.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/processes/ConsolesPanelPresenterTest.java index 01fe80e91e..c11702093d 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/processes/ConsolesPanelPresenterTest.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/processes/ConsolesPanelPresenterTest.java @@ -155,10 +155,10 @@ public class ConsolesPanelPresenterTest { when(appContext.getDevMachine()).thenReturn(devMachine); when(machineService.getMachines(anyString())).thenReturn(machinesPromise); - when(machineService.getMachine(anyString())).thenReturn(machinePromise); + when(machineService.getMachine(anyString(), anyString())).thenReturn(machinePromise); when(machinePromise.then(Matchers.>anyObject())).thenReturn(machinePromise); - when(machineService.getProcesses(anyString())).thenReturn(processesPromise); + when(machineService.getProcesses(anyString(), anyString())).thenReturn(processesPromise); when(processesPromise.then(Matchers.>>anyObject())).thenReturn(processesPromise); when(commandConsoleFactory.create(anyString())).thenReturn(mock(OutputConsole.class)); @@ -334,7 +334,7 @@ public class ConsolesPanelPresenterTest { when(terminal.getView()).thenReturn(terminalWidget); presenter.addCommandOutput(MACHINE_ID, outputConsole); - presenter.onAddTerminal(MACHINE_ID); + presenter.onAddTerminal(WORKSPACE_ID, MACHINE_ID); verify(machinePromise).then(machineCaptor.capture()); machineCaptor.getValue().apply(machineDto); @@ -409,7 +409,7 @@ public class ConsolesPanelPresenterTest { IsWidget terminalWidget = mock(IsWidget.class); when(terminal.getView()).thenReturn(terminalWidget); - presenter.onAddTerminal(MACHINE_ID); + presenter.onAddTerminal(WORKSPACE_ID, MACHINE_ID); verify(machinePromise).then(machineCaptor.capture()); machineCaptor.getValue().apply(machineDto); diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerCategoryPresenterTest.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerCategoryPresenterTest.java index d1ae54d667..2e1e6f27d6 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerCategoryPresenterTest.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerCategoryPresenterTest.java @@ -111,9 +111,10 @@ public class DockerCategoryPresenterTest { when(target.getName()).thenReturn(deletingTargetName); when(targetsTreeManager.getMachineByName(deletingTargetName)).thenReturn(machine); when(machine.getId()).thenReturn(deletingMachineId); + when(machine.getWorkspaceId()).thenReturn("WS_ID"); when(machine.getStatus()).thenReturn(RUNNING); when(machineLocale.targetsViewDeleteConfirm(deletingTargetName)).thenReturn(deleteProposal); - when(machineService.destroyMachine(deletingMachineId)).thenReturn(promise); + when(machineService.destroyMachine("WS_ID", deletingMachineId)).thenReturn(promise); arbitraryCategoryPresenter.onDeleteClicked(target); @@ -124,7 +125,7 @@ public class DockerCategoryPresenterTest { confirmCaptor.getValue().accepted(); verify(targetsTreeManager).getMachineByName(deletingTargetName); - verify(machineService).destroyMachine(deletingMachineId); + verify(machineService).destroyMachine("WS_ID", deletingMachineId); operationSuccessCapture.getValue().apply(null); diff --git a/plugins/plugin-machine/che-plugin-machine-ext-server/pom.xml b/plugins/plugin-machine/che-plugin-machine-ext-server/pom.xml index d2145de417..bb6b985720 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-server/pom.xml +++ b/plugins/plugin-machine/che-plugin-machine-ext-server/pom.xml @@ -32,10 +32,6 @@ javax.inject javax.inject - - javax.ws.rs - javax.ws.rs-api - org.eclipse.che.core che-core-api-core @@ -56,6 +52,10 @@ org.eclipse.che.core che-core-api-ssh + + org.eclipse.che.core + che-core-api-workspace + org.eclipse.che.core che-core-commons-inject diff --git a/plugins/plugin-machine/che-plugin-machine-ext-server/src/main/java/org/eclipse/che/ide/ext/machine/server/MachineModule.java b/plugins/plugin-machine/che-plugin-machine-ext-server/src/main/java/org/eclipse/che/ide/ext/machine/server/MachineModule.java index b574d83711..6204b166dc 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-server/src/main/java/org/eclipse/che/ide/ext/machine/server/MachineModule.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-server/src/main/java/org/eclipse/che/ide/ext/machine/server/MachineModule.java @@ -12,7 +12,7 @@ package org.eclipse.che.ide.ext.machine.server; import com.google.inject.AbstractModule; -import org.eclipse.che.api.machine.server.MachineService; +import org.eclipse.che.api.workspace.server.RecipeScriptDownloadService; import org.eclipse.che.ide.ext.machine.server.ssh.KeysInjector; import org.eclipse.che.inject.DynaModule; diff --git a/plugins/plugin-machine/che-plugin-machine-ext-server/src/main/java/org/eclipse/che/ide/ext/machine/server/ssh/KeysInjector.java b/plugins/plugin-machine/che-plugin-machine-ext-server/src/main/java/org/eclipse/che/ide/ext/machine/server/ssh/KeysInjector.java index bd95b98fc2..d21445fde6 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-server/src/main/java/org/eclipse/che/ide/ext/machine/server/ssh/KeysInjector.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-server/src/main/java/org/eclipse/che/ide/ext/machine/server/ssh/KeysInjector.java @@ -14,7 +14,7 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; -import org.eclipse.che.api.machine.server.MachineManager; +import org.eclipse.che.api.environment.server.CheEnvironmentEngine; import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; import org.eclipse.che.api.ssh.server.SshManager; @@ -43,20 +43,21 @@ import java.util.stream.Collectors; public class KeysInjector { private static final Logger LOG = LoggerFactory.getLogger(KeysInjector.class); - private final EventService eventService; - private final DockerConnector docker; - private final MachineManager machineManager; - private final SshManager sshManager; + private final EventService eventService; + private final DockerConnector docker; + private final SshManager sshManager; + // TODO replace with WorkspaceManager + private final CheEnvironmentEngine environmentEngine; @Inject public KeysInjector(EventService eventService, DockerConnector docker, - MachineManager machineManager, - SshManager sshManager) { + SshManager sshManager, + CheEnvironmentEngine environmentEngine) { this.eventService = eventService; this.docker = docker; - this.machineManager = machineManager; this.sshManager = sshManager; + this.environmentEngine = environmentEngine; } @PostConstruct @@ -66,7 +67,8 @@ public class KeysInjector { public void onEvent(MachineStatusEvent event) { if (event.getEventType() == MachineStatusEvent.EventType.RUNNING) { try { - final Instance machine = machineManager.getInstance(event.getMachineId()); + final Instance machine = environmentEngine.getMachine(event.getWorkspaceId(), + event.getMachineId()); List sshPairs = sshManager.getPairs(machine.getOwner(), "machine"); final List publicKeys = sshPairs.stream() .filter(sshPair -> sshPair.getPublicKey() != null) diff --git a/plugins/plugin-machine/che-plugin-machine-ext-server/src/test/java/org/eclipse/che/ide/ext/machine/server/ssh/KeysInjectorTest.java b/plugins/plugin-machine/che-plugin-machine-ext-server/src/test/java/org/eclipse/che/ide/ext/machine/server/ssh/KeysInjectorTest.java index 0174d55b62..e2c818c9a8 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-server/src/test/java/org/eclipse/che/ide/ext/machine/server/ssh/KeysInjectorTest.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-server/src/test/java/org/eclipse/che/ide/ext/machine/server/ssh/KeysInjectorTest.java @@ -14,7 +14,7 @@ import org.eclipse.che.api.core.model.machine.MachineRuntimeInfo; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.core.util.LineConsumer; -import org.eclipse.che.api.machine.server.MachineManager; +import org.eclipse.che.api.environment.server.CheEnvironmentEngine; import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; import org.eclipse.che.api.ssh.server.SshManager; @@ -56,6 +56,7 @@ import static org.testng.Assert.assertEquals; */ @Listeners(MockitoTestNGListener.class) public class KeysInjectorTest { + private static final String WORKSPACE_ID = "workspace123"; private static final String MACHINE_ID = "machine123"; private static final String OWNER_ID = "user123"; private static final String CONTAINER_ID = "container123"; @@ -82,7 +83,7 @@ public class KeysInjectorTest { @Mock DockerConnector docker; @Mock - MachineManager machineManager; + CheEnvironmentEngine environmentEngine; @Mock SshManager sshManager; @@ -97,7 +98,7 @@ public class KeysInjectorTest { metadataProperties.put("id", CONTAINER_ID); when(machineRuntime.getProperties()).thenReturn(metadataProperties); - when(machineManager.getInstance(MACHINE_ID)).thenReturn(instance); + when(environmentEngine.getMachine(WORKSPACE_ID, MACHINE_ID)).thenReturn(instance); when(instance.getOwner()).thenReturn(OWNER_ID); when(instance.getRuntime()).thenReturn(machineRuntime); when(instance.getLogger()).thenReturn(lineConsumer); @@ -114,7 +115,7 @@ public class KeysInjectorTest { public void shouldNotDoAnythingIfEventTypeDoesNotEqualToRunning() { subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.DESTROYING)); - verifyZeroInteractions(docker, machineManager, sshManager); + verifyZeroInteractions(docker, environmentEngine, sshManager); } @Test @@ -122,11 +123,12 @@ public class KeysInjectorTest { when(sshManager.getPairs(anyString(), anyString())).thenReturn(Collections.emptyList()); subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING) - .withMachineId(MACHINE_ID)); + .withMachineId(MACHINE_ID) + .withWorkspaceId(WORKSPACE_ID)); - verify(machineManager).getInstance(eq(MACHINE_ID)); + verify(environmentEngine).getMachine(eq(WORKSPACE_ID), eq(MACHINE_ID)); verify(sshManager).getPairs(eq(OWNER_ID), eq("machine")); - verifyZeroInteractions(docker, machineManager, sshManager); + verifyZeroInteractions(docker, environmentEngine, sshManager); } @Test @@ -135,11 +137,12 @@ public class KeysInjectorTest { .thenReturn(Collections.singletonList(new SshPairImpl("machine", "myPair", null, null))); subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING) - .withMachineId(MACHINE_ID)); + .withMachineId(MACHINE_ID) + .withWorkspaceId(WORKSPACE_ID)); - verify(machineManager).getInstance(eq(MACHINE_ID)); + verify(environmentEngine).getMachine(eq(WORKSPACE_ID), eq(MACHINE_ID)); verify(sshManager).getPairs(eq(OWNER_ID), eq("machine")); - verifyZeroInteractions(docker, machineManager, sshManager); + verifyZeroInteractions(docker, environmentEngine, sshManager); } @Test @@ -149,9 +152,10 @@ public class KeysInjectorTest { new SshPairImpl("machine", "myPair", "publicKey2", null))); subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING) - .withMachineId(MACHINE_ID)); + .withMachineId(MACHINE_ID) + .withWorkspaceId(WORKSPACE_ID)); - verify(machineManager).getInstance(eq(MACHINE_ID)); + verify(environmentEngine).getMachine(eq(WORKSPACE_ID), eq(MACHINE_ID)); verify(sshManager).getPairs(eq(OWNER_ID), eq("machine")); ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(CreateExecParams.class); @@ -160,7 +164,7 @@ public class KeysInjectorTest { "&& echo 'publicKey1' >> ~/.ssh/authorized_keys" + "&& echo 'publicKey2' >> ~/.ssh/authorized_keys"}); verify(docker).startExec(eq(StartExecParams.create(EXEC_ID)), anyObject()); - verifyZeroInteractions(docker, machineManager, sshManager); + verifyZeroInteractions(docker, environmentEngine, sshManager); } @Test @@ -171,7 +175,8 @@ public class KeysInjectorTest { when(logMessage.getContent()).thenReturn("FAILED"); subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING) - .withMachineId(MACHINE_ID)); + .withMachineId(MACHINE_ID) + .withWorkspaceId(WORKSPACE_ID)); verify(docker).startExec(eq(StartExecParams.create(EXEC_ID)), messageProcessorCaptor.capture()); final MessageProcessor value = messageProcessorCaptor.getValue(); diff --git a/plugins/plugin-maven/che-plugin-maven-server/src/test/java/org/eclipse/che/plugin/maven/server/projecttype/MavenProjectTypeTest.java b/plugins/plugin-maven/che-plugin-maven-server/src/test/java/org/eclipse/che/plugin/maven/server/projecttype/MavenProjectTypeTest.java index 2cc5e7dc76..c522206bb5 100644 --- a/plugins/plugin-maven/che-plugin-maven-server/src/test/java/org/eclipse/che/plugin/maven/server/projecttype/MavenProjectTypeTest.java +++ b/plugins/plugin-maven/che-plugin-maven-server/src/test/java/org/eclipse/che/plugin/maven/server/projecttype/MavenProjectTypeTest.java @@ -23,7 +23,7 @@ import org.eclipse.che.api.project.server.type.ProjectTypeRegistry; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; -import org.eclipse.che.commons.test.SelfReturningAnswer; +import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.plugin.java.server.projecttype.JavaProjectType; import org.eclipse.che.plugin.java.server.projecttype.JavaValueProviderFactory; diff --git a/plugins/plugin-ssh-machine/pom.xml b/plugins/plugin-ssh-machine/pom.xml index 2c5f0a06cc..048afdbad1 100644 --- a/plugins/plugin-ssh-machine/pom.xml +++ b/plugins/plugin-ssh-machine/pom.xml @@ -66,6 +66,10 @@ org.eclipse.che.core che-core-api-model + + org.eclipse.che.core + che-core-api-workspace + org.eclipse.che.core che-core-commons-annotations diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineImplTerminalLauncher.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineImplTerminalLauncher.java index ce16d62956..722d95215d 100644 --- a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineImplTerminalLauncher.java +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineImplTerminalLauncher.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.eclipse.che.plugin.machine.ssh; +import org.eclipse.che.api.agent.server.terminal.MachineImplSpecificTerminalLauncher; +import org.eclipse.che.api.agent.server.terminal.WebsocketTerminalFilesPathProvider; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.util.AbstractLineConsumer; import org.eclipse.che.api.core.util.ListLineConsumer; @@ -17,8 +19,6 @@ import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.model.impl.CommandImpl; import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.machine.server.spi.InstanceProcess; -import org.eclipse.che.api.machine.server.terminal.MachineImplSpecificTerminalLauncher; -import org.eclipse.che.api.machine.server.terminal.WebsocketTerminalFilesPathProvider; import org.slf4j.Logger; import javax.inject.Inject; diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstance.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstance.java index bdb14fd6f8..170a167e49 100644 --- a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstance.java +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstance.java @@ -141,7 +141,7 @@ public class SshMachineInstance extends AbstractInstance { * {@inheritDoc} */ @Override - public MachineSource saveToSnapshot(String owner) throws MachineException { + public MachineSource saveToSnapshot() throws MachineException { throw new MachineException("Snapshot feature is unsupported for ssh machine implementation"); } diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineModule.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineModule.java index 6910d04e8e..35de256d63 100644 --- a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineModule.java +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineModule.java @@ -15,7 +15,7 @@ import com.google.inject.assistedinject.FactoryModuleBuilder; import com.google.inject.multibindings.Multibinder; import com.google.inject.name.Names; -import org.eclipse.che.api.machine.server.terminal.MachineImplSpecificTerminalLauncher; +import org.eclipse.che.api.agent.server.terminal.MachineImplSpecificTerminalLauncher; /** * Provides bindings needed for ssh machine implementation usage. diff --git a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectServiceTest.java b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectServiceTest.java index 61730509d1..8c32cf59b7 100644 --- a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectServiceTest.java +++ b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectServiceTest.java @@ -54,7 +54,7 @@ import org.eclipse.che.commons.json.JsonHelper; import org.eclipse.che.commons.lang.IoUtil; import org.eclipse.che.commons.lang.ws.rs.ExtMediaType; import org.eclipse.che.commons.subject.SubjectImpl; -import org.eclipse.che.commons.test.SelfReturningAnswer; +import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer; import org.eclipse.che.dto.server.DtoFactory; import org.everrest.core.ApplicationContext; import org.everrest.core.ResourceBinder; diff --git a/wsmaster/che-core-api-auth/src/test/java/org/eclipse/che/security/oauth/RemoteOAuthTokenProviderTest.java b/wsmaster/che-core-api-auth/src/test/java/org/eclipse/che/security/oauth/RemoteOAuthTokenProviderTest.java index 719963da9d..b9e05e471c 100644 --- a/wsmaster/che-core-api-auth/src/test/java/org/eclipse/che/security/oauth/RemoteOAuthTokenProviderTest.java +++ b/wsmaster/che-core-api-auth/src/test/java/org/eclipse/che/security/oauth/RemoteOAuthTokenProviderTest.java @@ -17,7 +17,7 @@ import org.eclipse.che.api.core.rest.HttpJsonRequest; import org.eclipse.che.api.core.rest.HttpJsonRequestFactory; import org.eclipse.che.api.core.rest.HttpJsonResponse; import org.eclipse.che.api.core.rest.shared.dto.Link; -import org.eclipse.che.commons.test.SelfReturningAnswer; +import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer; import org.eclipse.che.dto.server.DtoFactory; import org.mockito.ArgumentCaptor; import org.mockito.Mock; diff --git a/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/Constants.java b/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/Constants.java index ffbdb49a36..be09e01c14 100644 --- a/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/Constants.java +++ b/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/Constants.java @@ -22,19 +22,13 @@ public class Constants { public static final String LINK_REL_UPDATE_RECIPE = "update recipe"; public static final String LINK_REL_SELF = "self link"; - public static final String LINK_REL_GET_MACHINE = "get machine"; public static final String LINK_REL_GET_MACHINES = "get machines"; public static final String LINK_REL_DESTROY_MACHINE = "destroy machine"; - public static final String LINK_REL_GET_SNAPSHOTS = "get snapshots"; - public static final String LINK_REL_SAVE_SNAPSHOT = "save snapshot"; - public static final String LINK_REL_REMOVE_SNAPSHOT = "remove snapshot"; public static final String LINK_REL_EXECUTE_COMMAND = "execute command"; public static final String LINK_REL_GET_PROCESSES = "get processes"; public static final String LINK_REL_STOP_PROCESS = "stop process"; public static final String LINK_REL_GET_MACHINE_LOGS = "get machine logs"; public static final String LINK_REL_GET_PROCESS_LOGS = "get process logs"; - public static final String LINK_REL_GET_MACHINE_LOGS_CHANNEL = "get machine logs channel"; - public static final String LINK_REL_GET_MACHINE_STATUS_CHANNEL = "get machine status channel"; public static final String WSAGENT_REFERENCE = "wsagent"; public static final String WSAGENT_WEBSOCKET_REFERENCE = "wsagent.websocket"; diff --git a/wsmaster/che-core-api-machine/pom.xml b/wsmaster/che-core-api-machine/pom.xml index 2fecdcfce0..bbbd019f8f 100644 --- a/wsmaster/che-core-api-machine/pom.xml +++ b/wsmaster/che-core-api-machine/pom.xml @@ -37,10 +37,6 @@ com.google.inject guice - - io.swagger - swagger-annotations - javax.annotation javax.annotation-api @@ -77,10 +73,6 @@ org.eclipse.che.core che-core-commons-annotations - - org.eclipse.che.core - che-core-commons-inject - org.eclipse.che.core che-core-commons-lang diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/ChannelsImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/ChannelsImpl.java deleted file mode 100644 index 9bce4c52e3..0000000000 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/ChannelsImpl.java +++ /dev/null @@ -1,52 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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.machine.server; - -/** - * @author Alexander Garagatyi - */ -public class ChannelsImpl { - private final String outputChannel; - private final String statusChannel; - - public ChannelsImpl(String outputChannel, String statusChannel) { - this.outputChannel = outputChannel; - this.statusChannel = statusChannel; - } - - public String getOutput() { - return outputChannel; - } - - public String getStatus() { - return statusChannel; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ChannelsImpl)) return false; - - ChannelsImpl channels = (ChannelsImpl)o; - - if (outputChannel != null ? !outputChannel.equals(channels.outputChannel) : channels.outputChannel != null) return false; - return !(statusChannel != null ? !statusChannel.equals(channels.statusChannel) : channels.statusChannel != null); - - } - - @Override - public int hashCode() { - int result = outputChannel != null ? outputChannel.hashCode() : 0; - result = 31 * result + (statusChannel != null ? statusChannel.hashCode() : 0); - return result; - } -} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineManager.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineManager.java deleted file mode 100644 index 425af98605..0000000000 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineManager.java +++ /dev/null @@ -1,1110 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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.machine.server; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Strings; -import com.google.common.util.concurrent.ThreadFactoryBuilder; - -import org.eclipse.che.api.core.ApiException; -import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.core.ConflictException; -import org.eclipse.che.api.core.ForbiddenException; -import org.eclipse.che.api.core.NotFoundException; -import org.eclipse.che.api.core.model.machine.Command; -import org.eclipse.che.api.core.model.machine.Machine; -import org.eclipse.che.api.core.model.machine.MachineConfig; -import org.eclipse.che.api.core.model.machine.MachineStatus; -import org.eclipse.che.api.core.notification.EventService; -import org.eclipse.che.api.core.notification.EventSubscriber; -import org.eclipse.che.api.core.util.CompositeLineConsumer; -import org.eclipse.che.api.core.util.FileLineConsumer; -import org.eclipse.che.api.core.util.LineConsumer; -import org.eclipse.che.api.core.util.WebsocketLineConsumer; -import org.eclipse.che.api.machine.server.dao.SnapshotDao; -import org.eclipse.che.api.machine.server.event.InstanceStateEvent; -import org.eclipse.che.api.machine.server.exception.InvalidRecipeException; -import org.eclipse.che.api.machine.server.exception.MachineException; -import org.eclipse.che.api.machine.server.exception.SnapshotException; -import org.eclipse.che.api.machine.server.exception.SourceNotFoundException; -import org.eclipse.che.api.machine.server.exception.UnsupportedRecipeException; -import org.eclipse.che.api.machine.server.model.impl.LimitsImpl; -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.MachineSourceImpl; -import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; -import org.eclipse.che.api.machine.server.spi.Instance; -import org.eclipse.che.api.machine.server.spi.InstanceProcess; -import org.eclipse.che.api.machine.server.spi.InstanceProvider; -import org.eclipse.che.api.machine.server.wsagent.WsAgentLauncher; -import org.eclipse.che.api.machine.shared.dto.event.MachineProcessEvent; -import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; -import org.eclipse.che.commons.annotation.Nullable; -import org.eclipse.che.commons.env.EnvironmentContext; -import org.eclipse.che.commons.lang.IoUtil; -import org.eclipse.che.commons.lang.NameGenerator; -import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext; -import org.eclipse.che.dto.server.DtoFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; -import java.io.File; -import java.io.IOException; -import java.io.Reader; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import static java.lang.String.format; -import static org.eclipse.che.api.machine.server.event.InstanceStateEvent.Type.DIE; -import static org.eclipse.che.api.machine.server.event.InstanceStateEvent.Type.OOM; -import static org.eclipse.che.dto.server.DtoFactory.newDto; - -/** - * Facade for Machine level operations. - * - * @author gazarenkov - * @author Alexander Garagatyi - * @author Yevhenii Voevodin - */ -@Singleton -public class MachineManager { - private static final Logger LOG = LoggerFactory.getLogger(MachineManager.class); - /* machine name must contain only {a-zA-Z0-9_-} characters and it's needed for validation machine names */ - private static final Pattern MACHINE_DISPLAY_NAME_PATTERN = Pattern.compile("^/?[a-zA-Z0-9_-]+$"); - - private final SnapshotDao snapshotDao; - private final File machineLogsDir; - private final MachineInstanceProviders machineInstanceProviders; - private final MachineRegistry machineRegistry; - private final EventService eventService; - private final int defaultMachineMemorySizeMB; - private final MachineCleaner machineCleaner; - private final WsAgentLauncher wsAgentLauncher; - - @VisibleForTesting - final ExecutorService executor; - - @Inject - public MachineManager(SnapshotDao snapshotDao, - MachineRegistry machineRegistry, - MachineInstanceProviders machineInstanceProviders, - @Named("machine.logs.location") String machineLogsDir, - EventService eventService, - @Named("machine.default_mem_size_mb") int defaultMachineMemorySizeMB, - WsAgentLauncher wsAgentLauncher) { - this.snapshotDao = snapshotDao; - this.machineInstanceProviders = machineInstanceProviders; - this.eventService = eventService; - this.wsAgentLauncher = wsAgentLauncher; - this.machineLogsDir = new File(machineLogsDir); - this.machineRegistry = machineRegistry; - this.defaultMachineMemorySizeMB = defaultMachineMemorySizeMB; - - executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("MachineManager-%d") - .setDaemon(false) - .build()); - this.machineCleaner = new MachineCleaner(); - } - - /** - * Synchronously creates and starts machine from scratch. - * - * @param machineConfig - * configuration that contains all information needed for machine creation - * @param workspaceId - * id of the workspace the created machine will belong to - * @param environmentName - * environment name the created machine will belongs to - * @param outputConsumer - * output consumer of machine - * @return new machine - * @throws NotFoundException - * if machine type from recipe is unsupported - * @throws NotFoundException - * if snapshot not found - * @throws NotFoundException - * if no instance provider implementation found for provided machine type - * @throws SnapshotException - * if error occurs on retrieving snapshot information - * @throws ConflictException - * if machine with given name already exists - * @throws BadRequestException - * if machine display name is invalid - * @throws MachineException - * if any other exception occurs during starting - */ - public MachineImpl createMachineSync(MachineConfig machineConfig, - final String workspaceId, - final String environmentName, - LineConsumer outputConsumer) - throws NotFoundException, - SnapshotException, - ConflictException, - MachineException, - BadRequestException { - LOG.info("Creating machine [ws = {}: env = {}: machine = {}]", - workspaceId, - environmentName, - machineConfig.getName()); - final MachineImpl machine = createMachine(normalizeMachineConfig(machineConfig), - workspaceId, - environmentName, - this::createInstance, - null, - outputConsumer); - LOG.info("Machine [ws = {}: env = {}: machine = {}] was successfully created, its id is '{}'", - workspaceId, - environmentName, - machine.getConfig().getName(), - machine.getId()); - - return machineRegistry.getMachine(machine.getId()); - } - - /** - * Synchronously recovers machine from snapshot. - * - * @param machineConfig - * machine meta information which is needed for {@link Machine machine} instance creation - * @param workspaceId - * workspace id - * @param envName - * name of environment - * @param outputConsumer - * output consumer of machine - * @return machine instance - * @throws NotFoundException - * when snapshot doesn't exist - * @throws SnapshotException - * when any error occurs during snapshot fetching - * @throws MachineException - * when any error occurs during machine start - * @throws ConflictException - * when any conflict occurs during machine creation (e.g Machine with given name already registered for certain workspace) - * @throws BadRequestException - * when either machineConfig or workspace id, or environment name is not valid - */ - public MachineImpl recoverMachine(MachineConfig machineConfig, - String workspaceId, - String envName, - LineConsumer outputConsumer) throws NotFoundException, - SnapshotException, - MachineException, - ConflictException, - BadRequestException { - - final SnapshotImpl snapshot = snapshotDao.getSnapshot(workspaceId, envName, machineConfig.getName()); - - LOG.info("Recovering machine [ws = {}: env = {}: machine = {}] from snapshot", workspaceId, envName, machineConfig.getName()); - final MachineImpl machine = createMachine(normalizeMachineConfig(machineConfig), - workspaceId, - envName, - this::createInstance, - snapshot, - outputConsumer); - LOG.info("Machine [ws = {}: env = {}: machine = {}] was successfully recovered, its id '{}'", - workspaceId, - envName, - machine.getConfig().getName(), - machine.getId()); - - return machineRegistry.getMachine(machine.getId()); - } - - /** - * Asynchronously creates and starts machine from scratch. - * - * @param machineConfig - * configuration that contains all information needed for machine creation - * @param workspaceId - * id of the workspace the created machine will belong to - * @param environmentName - * environment name the created machine will belongs to - * @param outputConsumer - * output consumer of machine - * @return new machine - * @throws NotFoundException - * if machine type from recipe is unsupported - * @throws NotFoundException - * if snapshot not found - * @throws NotFoundException - * if no instance provider implementation found for provided machine type - * @throws SnapshotException - * if error occurs on retrieving snapshot information - * @throws ConflictException - * if machine with given name already exists - * @throws BadRequestException - * if machine display name is invalid - * @throws MachineException - * if any other exception occurs during starting - */ - public MachineImpl createMachineAsync(MachineConfig machineConfig, - final String workspaceId, - final String environmentName, - LineConsumer outputConsumer) - throws NotFoundException, - SnapshotException, - ConflictException, - MachineException, - BadRequestException { - return createMachine(normalizeMachineConfig(machineConfig), - workspaceId, - environmentName, - (instanceProvider, machine, machineLogger) -> - executor.execute(ThreadLocalPropagateContext.wrap(() -> { - try { - createInstance(instanceProvider, - machine, - machineLogger); - } catch (MachineException e) { - if (!(e.getCause() instanceof InvalidRecipeException)) { - LOG.error(e.getLocalizedMessage(), e); - } - // todo what should we do in that case? - } - })), - null, - outputConsumer); - } - - private MachineImpl createMachine(MachineConfigImpl machineConfig, - String workspaceId, - String environmentName, - MachineInstanceCreator instanceCreator, - SnapshotImpl snapshot, - LineConsumer outputConsumer) - throws NotFoundException, - SnapshotException, - ConflictException, - BadRequestException, - MachineException { - final MachineSourceImpl sourceCopy = machineConfig.getSource(); - final InstanceProvider instanceProvider = machineInstanceProviders.getProvider(machineConfig.getType()); - - // Backward compatibility for source type 'Recipe'. - // Only 'dockerfile' impl of source type existed when 'Recipe' was valid source type. - // Changed in 4.2.0-RC1 - // todo remove that several versions later - if ("recipe".equalsIgnoreCase(machineConfig.getSource().getType())) { - machineConfig.getSource().setType("dockerfile"); - } - if (!instanceProvider.getRecipeTypes().contains(machineConfig.getSource().getType().toLowerCase())) { - throw new UnsupportedRecipeException(format("Recipe type %s of %s machine is unsupported", - machineConfig.getSource().getType(), - machineConfig.getName())); - } - - if (!MACHINE_DISPLAY_NAME_PATTERN.matcher(machineConfig.getName()).matches()) { - throw new BadRequestException("Invalid machine name " + machineConfig.getName()); - } - - for (MachineImpl machine : machineRegistry.getMachines()) { - if (machine.getWorkspaceId().equals(workspaceId) && machine.getConfig().getName().equals(machineConfig.getName())) { - throw new ConflictException("Machine with name " + machineConfig.getName() + " already exists"); - } - } - - // recover key from snapshot if there is one - if (snapshot != null) { - machineConfig.setSource(snapshot.getMachineSource()); - } - - final String machineId = generateMachineId(); - final String creator = EnvironmentContext.getCurrent().getSubject().getUserId(); - - if (machineConfig.getLimits().getRam() == 0) { - machineConfig.setLimits(new LimitsImpl(defaultMachineMemorySizeMB)); - } - - final MachineImpl machine = new MachineImpl(machineConfig, - machineId, - workspaceId, - environmentName, - creator, - MachineStatus.CREATING, - null); - - createMachineLogsDir(machineId); - final LineConsumer machineLogger = getMachineLogger(machineId, outputConsumer); - - try { - machineRegistry.addMachine(machine); - try { - instanceCreator.createInstance(instanceProvider, machine, machineLogger); - } catch (MachineException ex) { - if (snapshot == null) { - throw ex; - } - if (ex.getCause() instanceof SourceNotFoundException) { - LOG.error("Image of snapshot for machine " + machineConfig.getName() + " not found. " + - "Machine will be created from origin source"); - machine.getConfig().setSource(sourceCopy); - try { - machineRegistry.remove(machineId); - } catch (NotFoundException ignored) { - // todo looks like not needed anymore, test and remove - // machine is already removed, should never happen - } - machineRegistry.addMachine(machine); - instanceCreator.createInstance(instanceProvider, machine, outputConsumer); - } - } - return machine; - } catch (ApiException apiEx) { - try { - machineLogger.close(); - } catch (IOException ioEx) { - LOG.error(ioEx.getLocalizedMessage(), ioEx); - } - try { - machineRegistry.remove(machineId); - } catch (NotFoundException ignored) { - // todo looks like not needed anymore, test and remove - // machine is already removed - } - - throw new MachineException(apiEx.getLocalizedMessage(), apiEx); - } - } - - private void createInstance(InstanceProvider instanceProvider, - Machine machine, - LineConsumer machineLogger) throws MachineException { - Instance instance = null; - try { - eventService.publish(DtoFactory.newDto(MachineStatusEvent.class) - .withEventType(MachineStatusEvent.EventType.CREATING) - .withMachineId(machine.getId()) - .withDev(machine.getConfig().isDev()) - .withWorkspaceId(machine.getWorkspaceId()) - .withMachineName(machine.getConfig().getName())); - - instance = instanceProvider.createInstance(machine, machineLogger); - - instance.setStatus(MachineStatus.RUNNING); - - machineRegistry.update(instance); - - if (machine.getConfig().isDev()) { - wsAgentLauncher.startWsAgent(machine.getWorkspaceId()); - } - - eventService.publish(DtoFactory.newDto(MachineStatusEvent.class) - .withEventType(MachineStatusEvent.EventType.RUNNING) - .withDev(machine.getConfig().isDev()) - .withMachineId(machine.getId()) - .withWorkspaceId(machine.getWorkspaceId()) - .withMachineName(machine.getConfig().getName())); - - } catch (Exception creationEx) { - try { - machineRegistry.remove(machine.getId()); - } catch (NotFoundException ignore) { - } - eventService.publish(DtoFactory.newDto(MachineStatusEvent.class) - .withEventType(MachineStatusEvent.EventType.ERROR) - .withMachineId(machine.getId()) - .withDev(machine.getConfig().isDev()) - .withWorkspaceId(machine.getWorkspaceId()) - .withMachineName(machine.getConfig().getName()) - .withError(creationEx.getLocalizedMessage())); - try { - machineLogger.writeLine(String.format("[ERROR] %s", creationEx.getLocalizedMessage())); - } catch (IOException ioEx) { - LOG.error(ioEx.getLocalizedMessage()); - } - - try { - if (instance != null) { - instance.destroy(); - } - } catch (MachineException destroyingEx) { - LOG.error(destroyingEx.getLocalizedMessage(), destroyingEx); - } - throw new MachineException(creationEx.getLocalizedMessage(), creationEx); - } - } - - private interface MachineInstanceCreator { - void createInstance(InstanceProvider instanceProvider, - Machine machineState, - LineConsumer machineLogger) throws MachineException; - } - - /** - * Get machine by id - * - * @param machineId - * id of required machine - * @return machine with specified id - * @throws NotFoundException - * if machine with specified if not found - */ - public MachineImpl getMachine(String machineId) throws NotFoundException, MachineException { - return machineRegistry.getMachine(machineId); - } - - /** - * Get machine instance by id - * - * @param machineId - * id of required machine - * @return machine with specified id - * @throws NotFoundException - * if machine with specified if not found - */ - public Instance getInstance(String machineId) throws NotFoundException, MachineException { - return machineRegistry.getInstance(machineId); - } - - /** - * Find machines connected with specific workspace - * - * @param workspaceId - * workspace binding - * @return list of machines or empty list - */ - public List getMachines(String workspaceId) throws MachineException, BadRequestException { - return machineRegistry.getMachines() - .stream() - .filter(machine -> machine.getWorkspaceId().equals(workspaceId)) - .collect(Collectors.toList()); - } - - /** - * Returns all active machines - */ - public List getMachines() throws MachineException { - return new ArrayList<>(machineRegistry.getMachines()); - } - - /** - * Returns {@link MachineImpl} of dev machine of workspace - */ - public MachineImpl getDevMachine(String workspaceId) throws NotFoundException, MachineException { - return machineRegistry.getDevMachine(workspaceId); - } - - /** - * Asynchronously saves machine to snapshot. - * - * @param machineId - * id of machine for saving - * @param namespace - * owner for new snapshot - * @param description - * optional description that should help to understand purpose of new snapshot in future - * @return {@link SnapshotImpl} that will be stored in background - * @throws NotFoundException - * if machine with specified id doesn't exist - * @throws MachineException - * if other error occur - */ - public SnapshotImpl save(String machineId, String namespace, String description) - throws NotFoundException, MachineException { - final Instance machine = getInstance(machineId); - final SnapshotImpl snapshot = SnapshotImpl.builder() - .generateId() - .setType(machine.getConfig().getType()) - .setNamespace(namespace) - .setWorkspaceId(machine.getWorkspaceId()) - .setDescription(description) - .setDev(machine.getConfig().isDev()) - .setEnvName(machine.getEnvName()) - .setMachineName(machine.getConfig().getName()) - .useCurrentCreationDate() - .build(); - executor.execute(ThreadLocalPropagateContext.wrap(() -> { - try { - doSaveMachine(snapshot, machine); - } catch (Exception ignored) { - // exception is already logged in #doSaveMachine - } - })); - return snapshot; - } - - /** - * Synchronously saves machine to snapshot. - * - * @param machineId - * id of machine for saving - * @param namespace - * snapshot namespace (e.g. owner) - * @param description - * optional description that should help to understand purpose of new snapshot in future - * @return {@link SnapshotImpl} that will be stored in background - * @throws NotFoundException - * if machine with specified id doesn't exist - * @throws SnapshotException - * when any error occurs during snapshot storing - * @throws MachineException - * if other error occur - */ - public SnapshotImpl saveSync(String machineId, String namespace, String description) throws MachineException, - SnapshotException, - NotFoundException { - final Instance machine = getInstance(machineId); - final SnapshotImpl snapshot = SnapshotImpl.builder() - .generateId() - .setType(machine.getConfig().getType()) - .setNamespace(namespace) - .setWorkspaceId(machine.getWorkspaceId()) - .setDescription(description) - .setDev(machine.getConfig().isDev()) - .setEnvName(machine.getEnvName()) - .setMachineName(machine.getConfig().getName()) - .useCurrentCreationDate() - .build(); - return doSaveMachine(snapshot, machine); - } - - /** - * Get snapshot by id - * - * @param snapshotId - * id of required snapshot - * @return snapshot with specified id - * @throws NotFoundException - * if snapshot with provided id not found - * @throws SnapshotException - * if other error occur - */ - public SnapshotImpl getSnapshot(String snapshotId) throws NotFoundException, SnapshotException { - return snapshotDao.getSnapshot(snapshotId); - } - - /** - * Gets list of Snapshots by project. - * - * @param owner - * id of owner of machine - * @param workspaceId - * workspace binding - * @return list of Snapshots - * @throws SnapshotException - * if error occur - */ - public List getSnapshots(String owner, String workspaceId) throws SnapshotException { - return snapshotDao.findSnapshots(owner, workspaceId); - } - - /** - * Remove snapshot by id - * - * @param snapshotId - * id of snapshot to remove - * @throws NotFoundException - * if snapshot with specified id not found - * @throws SnapshotException - * if other error occurs - */ - public void removeSnapshot(String snapshotId) throws NotFoundException, SnapshotException { - final SnapshotImpl snapshot = getSnapshot(snapshotId); - final String instanceType = snapshot.getType(); - final InstanceProvider instanceProvider = machineInstanceProviders.getProvider(instanceType); - instanceProvider.removeInstanceSnapshot(snapshot.getMachineSource()); - - snapshotDao.removeSnapshot(snapshotId); - } - - /** - * Removes Snapshots by owner, workspace and project. - * - * @param owner - * owner of required snapshots - * @param workspaceId - * workspace binding - * @throws SnapshotException - * error occur - */ - public void removeSnapshots(String owner, String workspaceId) throws SnapshotException { - for (SnapshotImpl snapshot : snapshotDao.findSnapshots(owner, workspaceId)) { - try { - removeSnapshot(snapshot.getId()); - } catch (NotFoundException ignored) { - // This is not expected since we just get list of snapshots from DAO. - } catch (SnapshotException e) { - LOG.error(e.getLocalizedMessage(), e); - } - } - } - - /** - * Execute a command in machine - * - * @param machineId - * id of the machine where command should be executed - * @param command - * command that should be executed in the machine - * @return {@link org.eclipse.che.api.machine.server.spi.InstanceProcess} that represents started process in machine - * @throws NotFoundException - * if machine with specified id not found - * @throws BadRequestException - * if value of required parameter is invalid - * @throws MachineException - * if other error occur - */ - public InstanceProcess exec(final String machineId, final Command command, @Nullable String outputChannel) - throws NotFoundException, MachineException, BadRequestException { - requiredNotNull(machineId, "Machine ID is required"); - requiredNotNull(command, "Command is required"); - requiredNotNull(command.getCommandLine(), "Command line is required"); - requiredNotNull(command.getName(), "Command name is required"); - requiredNotNull(command.getType(), "Command type is required"); - - final Instance machine = getInstance(machineId); - final InstanceProcess instanceProcess = machine.createProcess(command, outputChannel); - final int pid = instanceProcess.getPid(); - - final LineConsumer processLogger = getProcessLogger(machineId, pid, outputChannel); - - executor.execute(ThreadLocalPropagateContext.wrap(() -> { - try { - eventService.publish(newDto(MachineProcessEvent.class) - .withEventType(MachineProcessEvent.EventType.STARTED) - .withMachineId(machineId) - .withProcessId(pid)); - - instanceProcess.start(processLogger); - - eventService.publish(newDto(MachineProcessEvent.class) - .withEventType(MachineProcessEvent.EventType.STOPPED) - .withMachineId(machineId) - .withProcessId(pid)); - } catch (ConflictException | MachineException error) { - eventService.publish(newDto(MachineProcessEvent.class) - .withEventType(MachineProcessEvent.EventType.ERROR) - .withMachineId(machineId) - .withProcessId(pid) - .withError(error.getLocalizedMessage())); - - try { - processLogger.writeLine(String.format("[ERROR] %s", error.getMessage())); - } catch (IOException ignored) { - } - } finally { - try { - processLogger.close(); - } catch (IOException ignored) { - } - } - })); - return instanceProcess; - } - - /** - * Get list of active processes from specific machine - * - * @param machineId - * id of machine to get processes information from - * @return list of {@link org.eclipse.che.api.machine.server.spi.InstanceProcess} - * @throws NotFoundException - * if machine with specified id not found - * @throws MachineException - * if other error occur - */ - public List getProcesses(String machineId) throws NotFoundException, MachineException { - return getInstance(machineId).getProcesses(); - } - - /** - * Stop process in machine - * - * @param machineId - * if of the machine where process should be stopped - * @param pid - * id of the process that should be stopped in machine - * @throws NotFoundException - * if machine or process with specified id not found - * @throws ForbiddenException - * if process is finished already - * @throws MachineException - * if other error occur - */ - public void stopProcess(String machineId, int pid) throws NotFoundException, MachineException, ForbiddenException { - final InstanceProcess process = getInstance(machineId).getProcess(pid); - if (!process.isAlive()) { - throw new ForbiddenException("Process finished already"); - } - - process.kill(); - - eventService.publish(newDto(MachineProcessEvent.class) - .withEventType(MachineProcessEvent.EventType.STOPPED) - .withMachineId(machineId) - .withProcessId(pid)); - } - - /** - * Destroy machine with specified id - * - * @param machineId - * id of machine that should be destroyed - * @param async - * should destroying be asynchronous or not - * @throws NotFoundException - * if machine with specified id not found - * @throws MachineException - * if other error occur - */ - public void destroy(final String machineId, boolean async) throws NotFoundException, MachineException { - final Instance machine = getInstance(machineId); - - machine.setStatus(MachineStatus.DESTROYING); - - eventService.publish(newDto(MachineStatusEvent.class) - .withEventType(MachineStatusEvent.EventType.DESTROYING) - .withMachineId(machineId) - .withDev(machine.getConfig().isDev()) - .withWorkspaceId(machine.getWorkspaceId()) - .withMachineName(machine.getConfig().getName())); - - if (async) { - executor.execute(ThreadLocalPropagateContext.wrap(() -> { - try { - doDestroy(machine); - } catch (NotFoundException | MachineException e) { - LOG.error(e.getLocalizedMessage(), e); - } - })); - } else { - doDestroy(machine); - } - } - - /** - * Gets logs reader from machine by specified id - * - * @param machineId - * machine id whose process reader will be returned - * @return reader for logs on specified machine - * @throws NotFoundException - * if machine with specified id not found - * @throws MachineException - * if other error occur - */ - public Reader getMachineLogReader(String machineId) throws NotFoundException, MachineException { - final File machineLogsFile = getMachineLogsFile(machineId); - if (machineLogsFile.isFile()) { - try { - return Files.newBufferedReader(machineLogsFile.toPath(), Charset.defaultCharset()); - } catch (IOException e) { - throw new MachineException(String.format("Unable read log file for machine '%s'. %s", machineId, e.getMessage())); - } - } - throw new NotFoundException(String.format("Logs for machine '%s' are not available", machineId)); - } - - /** - * Gets process reader from machine by specified id. - * - * @param machineId - * machine id whose process reader will be returned - * @param pid - * process id - * @return reader for specified process on machine - * @throws NotFoundException - * if machine with specified id not found - * @throws MachineException - * if other error occur - */ - public Reader getProcessLogReader(String machineId, int pid) throws NotFoundException, MachineException { - final File processLogsFile = getProcessLogsFile(machineId, pid); - if (processLogsFile.isFile()) { - try { - return Files.newBufferedReader(processLogsFile.toPath(), Charset.defaultCharset()); - } catch (IOException e) { - throw new MachineException( - String.format("Unable read log file for process '%s' of machine '%s'. %s", pid, machineId, e.getMessage())); - } - } - throw new NotFoundException(String.format("Logs for process '%s' of machine '%s' are not available", pid, machineId)); - } - - private SnapshotImpl doSaveMachine(SnapshotImpl snapshot, Instance machine) throws SnapshotException, MachineException { - final SnapshotImpl snapshotWithKey; - try { - LOG.info("Creating snapshot of machine [ws = {}: env = {}: machine name = {}: machine id = {}]", - snapshot.getWorkspaceId(), - snapshot.getEnvName(), - snapshot.getMachineName(), - machine.getId()); - - snapshotWithKey = new SnapshotImpl(snapshot); - snapshotWithKey.setMachineSourceImpl(machine.saveToSnapshot(machine.getOwner())); - - try { - SnapshotImpl oldSnapshot = snapshotDao.getSnapshot(snapshot.getWorkspaceId(), - snapshot.getEnvName(), - snapshot.getMachineName()); - snapshotDao.removeSnapshot(oldSnapshot.getId()); - machineInstanceProviders.getProvider(oldSnapshot.getType()).removeInstanceSnapshot(oldSnapshot.getMachineSource()); - } catch (NotFoundException ignored) { - //DO nothing if we has no snapshots or when provider not found - } catch (SnapshotException se) { - LOG.error("Failed to delete snapshot: {}, because {}", - snapshot, - se.getLocalizedMessage()); - } - snapshotDao.saveSnapshot(snapshotWithKey); - - LOG.info("Snapshot of machine [ws = {}: env = {}: machine name = {}: machine id = {}] was successfully created, its id is '{}'", - snapshot.getWorkspaceId(), - snapshot.getEnvName(), - snapshot.getMachineName(), - machine.getId(), - snapshot.getId()); - } catch (MachineException | SnapshotException ex) { - try { - machine.getLogger().writeLine("Snapshot storing failed. " + ex.getLocalizedMessage()); - } catch (IOException ignore) { - } - LOG.error("Failed to create snapshot of machine [ws = {}: env = {}: machine = {}], because {}", - snapshot.getWorkspaceId(), - snapshot.getEnvName(), - snapshot.getMachineName(), - ex.getLocalizedMessage()); - throw ex; - } - return snapshotWithKey; - } - - private void doDestroy(Instance machine) throws MachineException, NotFoundException { - LOG.info("Destroying machine [ws = {}: env = {}: machine name = {}: machine id = {}]", - machine.getWorkspaceId(), - machine.getEnvName(), - machine.getConfig().getName(), - machine.getId()); - - try { - machineRegistry.remove(machine.getId()); - } finally { - try { - machine.destroy(); - - LOG.info("Machine [ws = {}: env = {}: machine name = {}: machine id = {}] was successfully destroyed", - machine.getWorkspaceId(), - machine.getEnvName(), - machine.getConfig().getName(), - machine.getId()); - } catch (MachineException e) { - LOG.error(e.getLocalizedMessage(), e); - } - } - - eventService.publish(newDto(MachineStatusEvent.class) - .withEventType(MachineStatusEvent.EventType.DESTROYED) - .withDev(machine.getConfig().isDev()) - .withMachineId(machine.getId()) - .withWorkspaceId(machine.getWorkspaceId()) - .withMachineName(machine.getConfig().getName())); - } - - private void createMachineLogsDir(String machineId) throws MachineException { - File dir = new File(machineLogsDir, machineId); - if (!dir.exists() && !dir.mkdirs()) { - throw new MachineException("Can't create folder for the logs of machine"); - } - } - - private FileLineConsumer getMachineFileLogger(String machineId) throws MachineException { - try { - return new FileLineConsumer(getMachineLogsFile(machineId)); - } catch (IOException e) { - throw new MachineException(String.format("Unable create log file for machine '%s'. %s", machineId, e.getMessage())); - } - } - - private File getMachineLogsFile(String machineId) { - return new File(new File(machineLogsDir, machineId), "machineId.logs"); - } - - private File getProcessLogsFile(String machineId, int pid) { - return new File(new File(machineLogsDir, machineId), Integer.toString(pid)); - } - - private FileLineConsumer getProcessFileLogger(String machineId, int pid) throws MachineException { - try { - return new FileLineConsumer(getProcessLogsFile(machineId, pid)); - } catch (IOException e) { - throw new MachineException( - String.format("Unable create log file for process '%s' of machine '%s'. %s", pid, machineId, e.getMessage())); - } - } - - String generateMachineId() { - return NameGenerator.generate("machine", 16); - } - - @VisibleForTesting - LineConsumer getMachineLogger(String machineId, LineConsumer outputConsumer) throws MachineException { - return new CompositeLineConsumer(getMachineFileLogger(machineId), outputConsumer); - } - - @VisibleForTesting - LineConsumer getProcessLogger(String machineId, int pid, String outputChannel) throws MachineException { - return getLogger(getProcessFileLogger(machineId, pid), outputChannel); - } - - private LineConsumer getLogger(LineConsumer fileLogger, String outputChannel) throws MachineException { - if (outputChannel != null) { - return new CompositeLineConsumer(fileLogger, new WebsocketLineConsumer(outputChannel)); - } - return fileLogger; - } - - // cleanup machine if event about instance failure comes - private class MachineCleaner implements EventSubscriber { - @Override - public void onEvent(InstanceStateEvent event) { - if ((event.getType() == OOM) || (event.getType() == DIE)) { - try { - final Instance machine = getInstance(event.getMachineId()); - - machineRegistry.remove(machine.getId()); - - String message = "Machine is destroyed. "; - if (event.getType() == OOM) { - message = message + - "The processes in this machine need more RAM. This machine started with " + - machine.getConfig().getLimits().getRam() + - "MB. Create a new machine configuration that allocates additional RAM or increase " + - "the workspace RAM limit in the user dashboard."; - } - - try { - if (!Strings.isNullOrEmpty(message)) { - machine.getLogger().writeLine(message); - } - machine.getLogger().close(); - } catch (IOException ignore) { - } - - eventService.publish(newDto(MachineStatusEvent.class) - .withEventType(MachineStatusEvent.EventType.DESTROYED) - .withDev(machine.getConfig().isDev()) - .withMachineId(machine.getId()) - .withWorkspaceId(machine.getWorkspaceId()) - .withMachineName(machine.getConfig().getName())); - } catch (NotFoundException | MachineException e) { - LOG.warn(e.getLocalizedMessage(), e); - } - } - } - } - - /** - * Checks object reference is not {@code null} - * - * @param object - * object reference to check - * @param message - * used as subject of exception message "{subject} required" - * @throws org.eclipse.che.api.core.BadRequestException - * when object reference is {@code null} - */ - private void requiredNotNull(Object object, String message) throws BadRequestException { - if (object == null) { - throw new BadRequestException(message + " required"); - } - } - - @SuppressWarnings("unused") - @PostConstruct - private void createLogsDir() { - eventService.subscribe(machineCleaner); - - if (!(machineLogsDir.exists() || machineLogsDir.mkdirs())) { - throw new IllegalStateException(String.format("Unable create directory %s", machineLogsDir.getAbsolutePath())); - } - } - - @PreDestroy - private void cleanup() { - eventService.unsubscribe(machineCleaner); - - boolean interrupted = false; - - executor.shutdown(); - - final ExecutorService destroyMachinesExecutor = - Executors.newFixedThreadPool(2 * Runtime.getRuntime().availableProcessors(), - new ThreadFactoryBuilder().setNameFormat("DestroyMachine-%d") - .setDaemon(false) - .build()); - try { - for (MachineImpl machine : machineRegistry.getMachines()) { - destroyMachinesExecutor.execute(() -> { - try { - destroy(machine.getId(), false); - } catch (NotFoundException ignore) { - // it is ok, machine has been already destroyed - } catch (Exception e) { - LOG.warn(e.getMessage()); - } - }); - } - destroyMachinesExecutor.shutdown(); - if (!destroyMachinesExecutor.awaitTermination(50, TimeUnit.SECONDS)) { - destroyMachinesExecutor.shutdownNow(); - if (!destroyMachinesExecutor.awaitTermination(10, TimeUnit.SECONDS)) { - LOG.warn("Unable terminate destroy machines pool"); - } - } - } catch (InterruptedException e) { - interrupted = true; - destroyMachinesExecutor.shutdownNow(); - } catch (MachineException e) { - LOG.error(e.getLocalizedMessage(), e); - } - - try { - if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { - executor.shutdownNow(); - if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { - LOG.warn("Unable terminate main pool"); - } - } - } catch (InterruptedException e) { - interrupted = true; - executor.shutdownNow(); - } - - final java.io.File[] files = machineLogsDir.listFiles(); - if (files != null && files.length > 0) { - for (java.io.File f : files) { - if (!IoUtil.deleteRecursive(f)) { - LOG.warn("Failed delete {}", f); - } - } - } - if (interrupted) { - Thread.currentThread().interrupt(); - } - } - - private MachineConfigImpl normalizeMachineConfig(MachineConfig machineConfig) { - return new MachineConfigImpl(machineConfig); - } -} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineRegistry.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineRegistry.java deleted file mode 100644 index f3cd97b5c4..0000000000 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineRegistry.java +++ /dev/null @@ -1,191 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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.machine.server; - -import org.eclipse.che.api.core.ConflictException; -import org.eclipse.che.api.core.NotFoundException; -import org.eclipse.che.api.machine.server.exception.MachineException; -import org.eclipse.che.api.machine.server.model.impl.MachineImpl; -import org.eclipse.che.api.machine.server.spi.Instance; - -import javax.inject.Singleton; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Holds active machines - * - * @author Alexander Garagatyi - */ -@Singleton -public class MachineRegistry { - // TODO add locks per workspace or machine - private final HashMap instances; - private final HashMap machines; - - public MachineRegistry() { - instances = new HashMap<>(); - machines = new HashMap<>(); - } - - //TODO return unmodifiable lists - - /** - * Get all active machines - * - * @throws MachineException - * if any error occurs - */ - public synchronized List getMachines() throws MachineException { - final List list = new ArrayList<>(machines.size() + instances.size()); - list.addAll(machines.values()); - list.addAll(instances.values().stream().map(this::toMachine).collect(Collectors.toList())); - return Collections.unmodifiableList(list); - } - - /** - * Get machine by ID, machine can be in running or not - * - * @param machineId - * id of machine - * @throws NotFoundException - * if machine was not found - * @throws MachineException - * if other error occurs - */ - public synchronized MachineImpl getMachine(String machineId) throws NotFoundException, MachineException { - MachineImpl machine = machines.get(machineId); - if (machine == null) { - final Instance instance = instances.get(machineId); - if (instance == null) { - throw new NotFoundException("Machine " + machineId + " is not found"); - } - machine = toMachine(instance); - } - - return machine; - } - - /** - * Return true if machine with unique {@code machineId} is exist, or false otherwise. - * - * @param machineId - * unique machine identifier - */ - public synchronized boolean isExist(String machineId) { - return machines.containsKey(machineId) || instances.containsKey(machineId); - } - - /** - * Get dev machine of specific workspace. Dev machine should be in RUNNING state - * - * @param workspaceId - * id of workspace - * @throws NotFoundException - * if dev machine was not found or it is not in RUNNING - * @throws MachineException - * if other error occurs - */ - public synchronized MachineImpl getDevMachine(String workspaceId) throws NotFoundException, MachineException { - for (Instance instance : instances.values()) { - if (instance.getWorkspaceId().equals(workspaceId) && instance.getConfig().isDev()) { - return toMachine(instance); - } - } - - throw new NotFoundException("Dev machine of workspace " + workspaceId + " is not running."); - } - - /** - * Get machine by id. It should be in RUNNING state - * - * @param machineId - * id of machine - * @return machine with specified id - * @throws NotFoundException - * if machine with specified id not found - * @throws MachineException - * if other error occurs - */ - public synchronized Instance getInstance(String machineId) throws NotFoundException, MachineException { - final Instance instance = instances.get(machineId); - if (instance == null) { - throw new NotFoundException("Machine " + machineId + " is not found"); - } else { - return instance; - } - } - - /** - * Add not yet running machine - * - * @param machine - * machine - * @throws ConflictException - * if machine with the same ID already exists - * @throws MachineException - * if any other error occurs - */ - public synchronized void addMachine(MachineImpl machine) throws MachineException, ConflictException { - if (machines.containsKey(machine.getId())) { - throw new ConflictException("Machine with id " + machine.getId() + " is already exist"); - } - machines.put(machine.getId(), machine); - } - - /** - * Replace not running machine with instance of running machine - * - * @param instance - * running machine - * @throws NotFoundException - * if not running machine is not found - * @throws MachineException - * if any other error occurs - */ - public synchronized void update(Instance instance) throws NotFoundException, MachineException { - if (!instances.containsKey(instance.getId()) && !machines.containsKey(instance.getId())) { - throw new NotFoundException("Machine " + instance.getId() + " not found"); - } else { - instances.put(instance.getId(), instance); - machines.remove(instance.getId()); - } - } - - /** - * Remove machine by id - * - * @param machineId - * id of machine that should be removed - * @throws NotFoundException - * if machine with specified id not found - */ - public synchronized void remove(String machineId) throws NotFoundException { - final Instance instance = instances.remove(machineId); - final MachineImpl machine = machines.remove(machineId); - if (null == instance && null == machine) { - throw new NotFoundException("Machine " + machineId + " is not found"); - } - } - - private MachineImpl toMachine(Instance instance) { - return new MachineImpl(instance.getConfig(), - instance.getId(), - instance.getWorkspaceId(), - instance.getEnvName(), - instance.getOwner(), - instance.getStatus(), - instance.getRuntime()); - } -} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineService.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineService.java deleted file mode 100644 index f6a35745f4..0000000000 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineService.java +++ /dev/null @@ -1,438 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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.machine.server; - -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; - -import com.google.common.io.CharStreams; - -import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.core.ForbiddenException; -import org.eclipse.che.api.core.NotFoundException; -import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.core.model.machine.Machine; -import org.eclipse.che.api.core.rest.Service; -import org.eclipse.che.api.machine.server.exception.MachineException; -import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; -import org.eclipse.che.api.machine.server.spi.Instance; -import org.eclipse.che.api.machine.shared.dto.CommandDto; -import org.eclipse.che.api.machine.shared.dto.MachineDto; -import org.eclipse.che.api.machine.shared.dto.MachineProcessDto; -import org.eclipse.che.api.machine.shared.dto.NewSnapshotDescriptor; -import org.eclipse.che.api.machine.shared.dto.SnapshotDto; -import org.eclipse.che.commons.env.EnvironmentContext; - -import javax.inject.Inject; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import java.io.IOException; -import java.io.Reader; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Machine API - * - * @author Alexander Garagatyi - * @author Anton Korneta - */ -@Api(value = "/machine", description = "Machine REST API") -@Path("/machine") -public class MachineService extends Service { - private MachineManager machineManager; - - private final MachineServiceLinksInjector linksInjector; - - @Inject - public MachineService(MachineManager machineManager, MachineServiceLinksInjector linksInjector) { - this.machineManager = machineManager; - this.linksInjector = linksInjector; - } - - @GET - @Path("/{machineId}") - @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get machine by ID") - @ApiResponses({@ApiResponse(code = 200, message = "The response contains requested machine entity"), - @ApiResponse(code = 404, message = "Machine with specified id does not exist"), - @ApiResponse(code = 500, message = "Internal server error occurred")}) - public MachineDto getMachineById(@ApiParam(value = "Machine ID") - @PathParam("machineId") - String machineId) - throws ServerException, - ForbiddenException, - NotFoundException { - - final Machine machine = machineManager.getMachine(machineId); - return linksInjector.injectLinks(DtoConverter.asDto(machine), getServiceContext()); - } - - @GET - @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get all machines of workspace with specified ID", - response = MachineDto.class, - responseContainer = "List") - @ApiResponses({@ApiResponse(code = 200, message = "The response contains requested list of machine entities"), - @ApiResponse(code = 400, message = "Workspace ID is not specified"), - @ApiResponse(code = 500, message = "Internal server error occurred")}) - public List getMachines(@ApiParam(value = "Workspace ID", required = true) - @QueryParam("workspace") - String workspaceId) - throws ServerException, - BadRequestException { - - requiredNotNull(workspaceId, "Parameter workspace"); - - return machineManager.getMachines(workspaceId) - .stream() - .map(DtoConverter::asDto) - .map(machineDto -> linksInjector.injectLinks(machineDto, getServiceContext())) - .collect(Collectors.toList()); - } - - @DELETE - @Path("/{machineId}") - @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Destroy machine") - @ApiResponses({@ApiResponse(code = 204, message = "Machine was successfully destroyed"), - @ApiResponse(code = 404, message = "Machine with specified id does not exist"), - @ApiResponse(code = 500, message = "Internal server error occurred")}) - public void destroyMachine(@ApiParam(value = "Machine ID") - @PathParam("machineId") - String machineId) - throws NotFoundException, - ServerException, - ForbiddenException { - - machineManager.destroy(machineId, true); - } - - @GET - @Path("/snapshot") - @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get all snapshots of machines in workspace", - response = SnapshotDto.class, - responseContainer = "List") - @ApiResponses({@ApiResponse(code = 200, message = "The response contains requested list of snapshot entities"), - @ApiResponse(code = 400, message = "Workspace ID is not specified"), - @ApiResponse(code = 500, message = "Internal server error occurred")}) - public List getSnapshots(@ApiParam(value = "Workspace ID", required = true) - @QueryParam("workspace") - String workspaceId) - throws ServerException, - BadRequestException { - - requiredNotNull(workspaceId, "Parameter workspace"); - - final List snapshots = machineManager.getSnapshots(EnvironmentContext.getCurrent().getSubject().getUserId(), workspaceId); - - return snapshots.stream() - .map(DtoConverter::asDto) - .map(snapshotDto -> linksInjector.injectLinks(snapshotDto, getServiceContext())) - .collect(Collectors.toList()); - } - - @POST - @Path("/{machineId}/snapshot") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Save snapshot of machine") - @ApiResponses({@ApiResponse(code = 200, message = "The response contains requested snapshot entity"), - @ApiResponse(code = 400, message = "Snapshot description is not specified"), - @ApiResponse(code = 404, message = "Snapshot with specified ID does not exist"), - @ApiResponse(code = 500, message = "Internal server error occurred")}) - public SnapshotDto saveSnapshot(@ApiParam(value = "Machine ID") - @PathParam("machineId") - String machineId, - @ApiParam(value = "Snapshot description", required = true) - NewSnapshotDescriptor newSnapshotDescriptor) - throws NotFoundException, - ServerException, - ForbiddenException, - BadRequestException { - - requiredNotNull(newSnapshotDescriptor, "Snapshot description"); - return linksInjector.injectLinks(DtoConverter.asDto(machineManager.save(machineId, - EnvironmentContext.getCurrent() - .getSubject() - .getUserId(), - newSnapshotDescriptor.getDescription())), - getServiceContext()); - } - - @DELETE - @Path("/snapshot/{snapshotId}") - @ApiOperation(value = "Remove snapshot of machine") - @ApiResponses({@ApiResponse(code = 204, message = "Snapshot was successfully removed"), - @ApiResponse(code = 404, message = "Snapshot with specified ID does not exist"), - @ApiResponse(code = 500, message = "Internal server error occurred")}) - public void removeSnapshot(@ApiParam(value = "Snapshot ID") - @PathParam("snapshotId") - String snapshotId) - throws ForbiddenException, - NotFoundException, - ServerException { - - machineManager.removeSnapshot(snapshotId); - } - - @POST - @Path("/{machineId}/command") - @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Start specified command in machine") - @ApiResponses({@ApiResponse(code = 200, message = "The response contains entity of created machine process"), - @ApiResponse(code = 400, message = "Command entity is invalid"), - @ApiResponse(code = 404, message = "Machine with specified ID does not exist"), - @ApiResponse(code = 500, message = "Internal server error occurred")}) - public MachineProcessDto executeCommandInMachine(@ApiParam(value = "Machine ID") - @PathParam("machineId") - String machineId, - @ApiParam(value = "Command to execute", required = true) - final CommandDto command, - @ApiParam(value = "Channel for command output", required = false) - @QueryParam("outputChannel") - String outputChannel) - throws NotFoundException, - ServerException, - ForbiddenException, - BadRequestException { - - requiredNotNull(command, "Command description"); - requiredNotNull(command.getCommandLine(), "Commandline"); - return linksInjector.injectLinks(DtoConverter.asDto(machineManager.exec(machineId, command, outputChannel)), - machineId, - getServiceContext()); - } - - @GET - @Path("/{machineId}/process") - @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get processes of machine", - response = MachineProcessDto.class, - responseContainer = "List") - @ApiResponses({@ApiResponse(code = 200, message = "The response contains machine process entities"), - @ApiResponse(code = 404, message = "Machine with specified ID does not exist"), - @ApiResponse(code = 500, message = "Internal server error occurred")}) - public List getProcesses(@ApiParam(value = "Machine ID") - @PathParam("machineId") - String machineId) - throws NotFoundException, - ServerException, - ForbiddenException { - - return machineManager.getProcesses(machineId) - .stream() - .map(DtoConverter::asDto) - .map(machineProcess -> linksInjector.injectLinks(machineProcess, - machineId, - getServiceContext())) - .collect(Collectors.toList()); - } - - @DELETE - @Path("/{machineId}/process/{processId}") - @ApiOperation(value = "Stop process in machine") - @ApiResponses({@ApiResponse(code = 204, message = "Process was successfully stopped"), - @ApiResponse(code = 404, message = "Machine with specified ID does not exist"), - @ApiResponse(code = 500, message = "Internal server error occurred")}) - public void stopProcess(@ApiParam(value = "Machine ID") - @PathParam("machineId") - String machineId, - @ApiParam(value = "Process ID") - @PathParam("processId") - int processId) - throws NotFoundException, - ForbiddenException, - ServerException { - - machineManager.stopProcess(machineId, processId); - } - - @GET - @Path("/{machineId}/logs") - @Produces(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Get logs of machine") - @ApiResponses({@ApiResponse(code = 200, message = "The response contains logs"), - @ApiResponse(code = 404, message = "Machine with specified ID does not exist"), - @ApiResponse(code = 500, message = "Internal server error occurred")}) - public void getMachineLogs(@ApiParam(value = "Machine ID") - @PathParam("machineId") - String machineId, - @Context - HttpServletResponse httpServletResponse) - throws NotFoundException, - ForbiddenException, - ServerException, - IOException { - - addLogsToResponse(machineManager.getMachineLogReader(machineId), httpServletResponse); - } - - @GET - @Path("/{machineId}/process/{pid}/logs") - @Produces(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Get logs of machine process") - @ApiResponses({@ApiResponse(code = 200, message = "The response contains logs"), - @ApiResponse(code = 404, message = "Machine or process with specified ID does not exist"), - @ApiResponse(code = 500, message = "Internal server error occurred")}) - public void getProcessLogs(@ApiParam(value = "Machine ID") - @PathParam("machineId") - String machineId, - @ApiParam(value = "Process ID") - @PathParam("pid") - int pid, - @Context - HttpServletResponse httpServletResponse) - throws NotFoundException, - ForbiddenException, - ServerException, - IOException { - - addLogsToResponse(machineManager.getProcessLogReader(machineId, pid), httpServletResponse); - } - - /** - * Reads file content by specified file path. - * - * @param path - * path to file on machine instance - * @param startFrom - * line number to start reading from - * @param limit - * limitation on line if not specified will used 2000 lines - * @return file content. - * @throws MachineException - * if any error occurs with file reading - */ - @GET - @Path("/{machineId}/filepath/{path:.*}") - @Produces(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Get content of file in machine") - @ApiResponses({@ApiResponse(code = 200, message = "The response contains file content"), - @ApiResponse(code = 404, message = "Machine with specified ID does not exist"), - @ApiResponse(code = 500, message = "Internal server error occurred")}) - public String getFileContent(@ApiParam(value = "Machine ID") - @PathParam("machineId") - String machineId, - @ApiParam(value = "Path of file") - @PathParam("path") - String path, - @ApiParam(value = "From line") - @QueryParam("startFrom") - @DefaultValue("1") - Integer startFrom, - @ApiParam(value = "Number of lines") - @QueryParam("limit") - @DefaultValue("2000") - Integer limit) - throws NotFoundException, - ForbiddenException, - ServerException { - - final Instance machine = machineManager.getInstance(machineId); - return machine.readFileContent(path, startFrom, limit); - } - - /** - * Copies files from specified machine into current machine. - * - * @param sourceMachineId - * source machine id - * @param targetMachineId - * target machine id - * @param sourcePath - * path to file or directory inside specified machine - * @param targetPath - * path to destination file or directory inside machine - * @param overwrite - * If "false" then it will be an error if unpacking the given content would cause - * an existing directory to be replaced with a non-directory and vice versa. - * @throws MachineException - * if any error occurs when files are being copied - * @throws NotFoundException - * if any error occurs with getting source machine - */ - @POST - @Path("/copy") - @ApiOperation(value = "Copy files from one machine to another") - @ApiResponses({@ApiResponse(code = 200, message = "Files were copied successfully"), - @ApiResponse(code = 400, message = "Machine ID or path is not specified"), - @ApiResponse(code = 404, message = "Source machine does not exist"), - @ApiResponse(code = 404, message = "Target machine does not exist"), - @ApiResponse(code = 500, message = "Internal server error occurred")}) - public void copyFilesBetweenMachines(@ApiParam(value = "Source machine ID", required = true) - @QueryParam("sourceMachineId") - String sourceMachineId, - @ApiParam(value = "Target machine ID", required = true) - @QueryParam("targetMachineId") - String targetMachineId, - @ApiParam(value = "Source path", required = true) - @QueryParam("sourcePath") - String sourcePath, - @ApiParam(value = "Target path", required = true) - @QueryParam("targetPath") - String targetPath, - @ApiParam(value = "Is files overwriting allowed") - @QueryParam("overwrite") - @DefaultValue("false") - Boolean overwrite) - throws NotFoundException, - ServerException, - ForbiddenException, - BadRequestException { - - requiredNotNull(sourceMachineId, "Source machine id"); - requiredNotNull(targetMachineId, "Target machine id"); - requiredNotNull(sourcePath, "Source path"); - requiredNotNull(targetPath, "Target path"); - - final Instance sourceMachine = machineManager.getInstance(sourceMachineId); - final Instance targetMachine = machineManager.getInstance(targetMachineId); - targetMachine.copy(sourceMachine, sourcePath, targetPath, overwrite); - } - - private void addLogsToResponse(Reader logsReader, HttpServletResponse httpServletResponse) throws IOException { - // Response is written directly to the servlet request stream - httpServletResponse.setContentType("text/plain"); - CharStreams.copy(logsReader, httpServletResponse.getWriter()); - httpServletResponse.getWriter().flush(); - } - - /** - * Checks object reference is not {@code null} - * - * @param object - * object reference to check - * @param subject - * used as subject of exception message "{subject} required" - * @throws BadRequestException - * when object reference is {@code null} - */ - private void requiredNotNull(Object object, String subject) throws BadRequestException { - if (object == null) { - throw new BadRequestException(subject + " required"); - } - } -} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/event/InstanceStateEvent.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/event/InstanceStateEvent.java index 6220e299fb..5dd02a32ca 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/event/InstanceStateEvent.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/event/InstanceStateEvent.java @@ -26,10 +26,14 @@ public class InstanceStateEvent { } private String machineId; + private String workspaceId; private Type type; - public InstanceStateEvent(String machineId, Type type) { + public InstanceStateEvent(String machineId, + String workspaceId, + Type type) { this.machineId = machineId; + this.workspaceId = workspaceId; this.type = type; } @@ -40,4 +44,8 @@ public class InstanceStateEvent { public Type getType() { return type; } + + public String getWorkspaceId() { + return workspaceId; + } } diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java index 1dd1603a2c..0e27c07487 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java @@ -126,7 +126,7 @@ public class SnapshotImpl implements Snapshot { return this.isDev; } - public void setMachineSourceImpl(MachineSource machineSource) { + public void setMachineSource(MachineSource machineSource) { this.machineSource = machineSource != null ? new MachineSourceImpl(machineSource) : null; } @@ -192,16 +192,16 @@ public class SnapshotImpl implements Snapshot { */ public static class SnapshotBuilder { - private String workspaceId; - private String machineName; - private String envName; - private String id; - private String type; - private String namespace; - private String description; + private String workspaceId; + private String machineName; + private String envName; + private String id; + private String type; + private String namespace; + private String description; private MachineSource machineSource; - private boolean isDev; - private long creationDate; + private boolean isDev; + private long creationDate; public SnapshotBuilder fromConfig(MachineConfig machineConfig) { machineName = machineConfig.getName(); diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/Instance.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/Instance.java index 7a83055244..b38c303edb 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/Instance.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/Instance.java @@ -71,13 +71,11 @@ public interface Instance extends Machine { /** * Save state of the instance * - * @param owner - * id of the user that is owner of the snapshot * @return {@code InstanceSnapshotKey} that describe implementation specific keys of snapshot * @throws MachineException * if error occurs on storing state of the instance */ - MachineSource saveToSnapshot(String owner) throws MachineException; + MachineSource saveToSnapshot() throws MachineException; /** * Destroy instance diff --git a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/MachineManagerTest.java b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/MachineManagerTest.java deleted file mode 100644 index 99b18c2e93..0000000000 --- a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/MachineManagerTest.java +++ /dev/null @@ -1,345 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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.machine.server; - -import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.core.ConflictException; -import org.eclipse.che.api.core.model.machine.Command; -import org.eclipse.che.api.core.model.machine.Limits; -import org.eclipse.che.api.core.model.machine.Machine; -import org.eclipse.che.api.core.model.machine.MachineConfig; -import org.eclipse.che.api.core.model.machine.MachineSource; -import org.eclipse.che.api.core.model.machine.MachineStatus; -import org.eclipse.che.api.core.notification.EventService; -import org.eclipse.che.api.core.util.LineConsumer; -import org.eclipse.che.api.machine.server.dao.SnapshotDao; -import org.eclipse.che.api.machine.server.exception.MachineException; -import org.eclipse.che.api.machine.server.exception.SourceNotFoundException; -import org.eclipse.che.api.machine.server.model.impl.LimitsImpl; -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.MachineSourceImpl; -import org.eclipse.che.api.machine.server.model.impl.ServerConfImpl; -import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; -import org.eclipse.che.api.machine.server.recipe.RecipeImpl; -import org.eclipse.che.api.machine.server.spi.Instance; -import org.eclipse.che.api.machine.server.spi.InstanceProcess; -import org.eclipse.che.api.machine.server.spi.InstanceProvider; -import org.eclipse.che.api.machine.server.wsagent.WsAgentLauncher; -import org.eclipse.che.commons.env.EnvironmentContext; -import org.eclipse.che.commons.lang.IoUtil; -import org.eclipse.che.commons.subject.SubjectImpl; -import org.mockito.Mock; -import org.mockito.testng.MockitoTestNGListener; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.io.File; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.concurrent.ThreadPoolExecutor; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; -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.assertNotNull; - -/** - * Unit tests for {@link MachineManager} - * - * @author Anton Korneta - * @author Alexander Garagatyi - */ -@Listeners(MockitoTestNGListener.class) -public class MachineManagerTest { - - private static final int DEFAULT_MACHINE_MEMORY_SIZE_MB = 1000; - private static final String WS_ID = "testWsId"; - private static final String ENVIRONMENT_NAME = "testEnvName"; - private static final String USER_ID = "userId"; - private static final String MACHINE_ID = "machineId"; - private static final String NAMESPACE = "namespace"; - - private static final SubjectImpl CREATOR = new SubjectImpl("name", USER_ID, "token", false); - - @Mock - private MachineInstanceProviders machineInstanceProviders; - @Mock - private InstanceProvider instanceProvider; - @Mock - private MachineRegistry machineRegistry; - @Mock - private WsAgentLauncher wsAgentLauncher; - @Mock - private Instance instance; - @Mock - private Limits limits; - @Mock - private Command command; - @Mock - private InstanceProcess instanceProcess; - @Mock - private LineConsumer logConsumer; - @Mock - private SnapshotDao snapshotDao; - - private MachineManager manager; - - @BeforeMethod - public void setUp() throws Exception { - final EventService eventService = mock(EventService.class); - final String machineLogsDir = targetDir().resolve("logs-dir").toString(); - IoUtil.deleteRecursive(new File(machineLogsDir)); - manager = spy(new MachineManager(snapshotDao, - machineRegistry, - machineInstanceProviders, - machineLogsDir, - eventService, - DEFAULT_MACHINE_MEMORY_SIZE_MB, - wsAgentLauncher)); - - EnvironmentContext envCont = new EnvironmentContext(); - envCont.setSubject(CREATOR); - EnvironmentContext.setCurrent(envCont); - - RecipeImpl recipe = new RecipeImpl().withScript("script").withType("Dockerfile"); -// doNothing().when(manager).createMachineLogsDir(anyString()); - doReturn(MACHINE_ID).when(manager).generateMachineId(); - doReturn(logConsumer).when(manager).getProcessLogger(MACHINE_ID, 111, "outputChannel"); - when(machineInstanceProviders.getProvider(anyString())).thenReturn(instanceProvider); - HashSet recipeTypes = new HashSet<>(); - recipeTypes.add("test type 1"); - recipeTypes.add("snapshot"); - recipeTypes.add("dockerfile"); - when(instanceProvider.getRecipeTypes()).thenReturn(recipeTypes); - when(instanceProvider.createInstance(any(Machine.class), any(LineConsumer.class))).thenReturn(instance); - when(machineRegistry.getInstance(anyString())).thenReturn(instance); - when(command.getCommandLine()).thenReturn("CommandLine"); - when(command.getName()).thenReturn("CommandName"); - when(command.getType()).thenReturn("CommandType"); - when(machineRegistry.getInstance(MACHINE_ID)).thenReturn(instance); - when(instance.createProcess(command, "outputChannel")).thenReturn(instanceProcess); - when(instanceProcess.getPid()).thenReturn(111); - } - - @AfterMethod - public void tearDown() throws Exception { - EnvironmentContext.reset(); - } - - @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Invalid machine name @name!") - public void shouldThrowExceptionOnMachineCreationIfMachineNameIsInvalid() throws Exception { - MachineConfig machineConfig = new MachineConfigImpl(false, - "@name!", - "machineType", - new MachineSourceImpl("Dockerfile").setLocation("location"), - new LimitsImpl(1024), - Arrays.asList(new ServerConfImpl("ref1", - "8080", - "https", - "some/path"), - new ServerConfImpl("ref2", - "9090/udp", - "someprotocol", - "/some/path")), - Collections.singletonMap("key1", "value1")); - String workspaceId = "wsId"; - String environmentName = "env1"; - - manager.createMachineSync(machineConfig, workspaceId, environmentName, logConsumer); - } - - @Test - public void shouldBeAbleToCreateMachineWithValidName() throws Exception { - String expectedName = "validMachineName"; - final MachineConfigImpl machineConfig = MachineConfigImpl.builder() - .fromConfig(createMachineConfig()) - .setName(expectedName) - .build(); - MachineImpl expectedMachine = new MachineImpl(machineConfig, - MACHINE_ID, - WS_ID, - ENVIRONMENT_NAME, - USER_ID, - MachineStatus.CREATING, - null); - - manager.createMachineSync(machineConfig, WS_ID, ENVIRONMENT_NAME, logConsumer); - - verify(machineRegistry).addMachine(eq(expectedMachine)); - } - - @Test - public void shouldCallWsAgentLauncherAfterDevMachineStart() throws Exception { - final MachineConfigImpl machineConfig = MachineConfigImpl.builder() - .fromConfig(createMachineConfig()) - .setDev(true) - .build(); - - manager.createMachineSync(machineConfig, WS_ID, ENVIRONMENT_NAME, logConsumer); - - verify(wsAgentLauncher).startWsAgent(WS_ID); - } - - @Test - public void shouldNotCallWsAgentLauncherAfterNonDevMachineStart() throws Exception { - final MachineConfigImpl machineConfig = createMachineConfig(); - - manager.createMachineSync(machineConfig, WS_ID, ENVIRONMENT_NAME, logConsumer); - - verify(wsAgentLauncher, never()).startWsAgent(WS_ID); - } - - @Test - public void shouldRemoveMachineFromRegistryIfInstanceDestroyingFailsOnDestroy() throws Exception { - final MachineConfigImpl machineConfig = createMachineConfig(); - when(instance.getConfig()).thenReturn(machineConfig); - when(instance.getWorkspaceId()).thenReturn(WS_ID); - doThrow(new MachineException("test")).when(instance).destroy(); - - try { - manager.destroy(MACHINE_ID, false); - } catch (Exception e) { - verify(machineRegistry).remove(MACHINE_ID); - } - } - - @Test - public void shouldCloseProcessLoggerIfExecIsSuccess() throws Exception { - //when - manager.exec(MACHINE_ID, command, "outputChannel"); - waitForExecutorIsCompletedTask(); - - //then - verify(logConsumer).close(); - } - - @Test - public void shouldCloseProcessLoggerIfExecFails() throws Exception { - //given - doThrow(Exception.class).when(instanceProcess).start(); - - //when - manager.exec(MACHINE_ID, command, "outputChannel"); - waitForExecutorIsCompletedTask(); - - //then - verify(logConsumer).close(); - } - - @Test(expectedExceptions = MachineException.class) - public void shouldCloseMachineLoggerIfMachineCreationFails() throws Exception { - //given - MachineConfig machineConfig = mock(MachineConfig.class); - MachineSource machineSource = mock(MachineSource.class); - LineConsumer machineLogger = mock(LineConsumer.class); - doReturn(machineLogger).when(manager).getMachineLogger(eq(MACHINE_ID), any(LineConsumer.class)); - when(machineConfig.getSource()).thenReturn(machineSource); - when(machineConfig.getName()).thenReturn("Name"); - when(machineSource.getType()).thenReturn("dockerfile"); - doThrow(ConflictException.class).when(machineRegistry).addMachine(any()); - - //when - manager.createMachineSync(machineConfig, "workspaceId", "environmentName", logConsumer); - - //then - verify(machineLogger).close(); - } - - @Test - public void shouldCreateMachineFromOriginalSourceWhenMachineRecoverFails() throws Exception { - final SnapshotImpl snapshot = createSnapshot(); - final MachineConfigImpl config = createMachineConfig(); - when(manager.generateMachineId()).thenReturn(MACHINE_ID); - final MachineImpl machine = new MachineImpl(config, - MACHINE_ID, - WS_ID, - ENVIRONMENT_NAME, - CREATOR.getUserId(), - MachineStatus.CREATING, - null); - machine.getConfig().setSource(snapshot.getMachineSource()); - when(snapshotDao.getSnapshot(WS_ID, ENVIRONMENT_NAME, config.getName())).thenReturn(snapshot); - when(instanceProvider.createInstance(eq(machine), any(LineConsumer.class))).thenThrow(new SourceNotFoundException("")); - when(machineRegistry.getMachine(MACHINE_ID)).thenReturn(machine); - - final MachineImpl result = manager.recoverMachine(config, WS_ID, ENVIRONMENT_NAME, logConsumer); - - machine.getConfig().setSource(config.getSource()); - assertEquals(result, machine); - } - - @Test(expectedExceptions = MachineException.class) - public void shouldThrowExceptionWhenMachineCreationFromOriginSourceFailed() throws Exception { - final MachineConfigImpl config = createMachineConfig(); - when(manager.generateMachineId()).thenReturn(MACHINE_ID); - - when(instanceProvider.createInstance(any(MachineImpl.class), - any(LineConsumer.class))).thenThrow(new MachineException("")); - - manager.recoverMachine(config, WS_ID, ENVIRONMENT_NAME, logConsumer); - } - - private void waitForExecutorIsCompletedTask() throws Exception { - for (int i = 0; ((ThreadPoolExecutor)manager.executor).getCompletedTaskCount() == 0 && i < 10; i++) { - Thread.sleep(300); - } - } - - private static Path targetDir() throws Exception { - final URL url = Thread.currentThread().getContextClassLoader().getResource("."); - assertNotNull(url); - return Paths.get(url.toURI()).getParent(); - } - - private MachineConfigImpl createMachineConfig() { - return new MachineConfigImpl(false, - "MachineName", - "docker", - new MachineSourceImpl("Dockerfile").setLocation("location"), - new LimitsImpl(1024), - Arrays.asList(new ServerConfImpl("ref1", - "8080", - "https", - "some/path"), - new ServerConfImpl("ref2", - "9090/udp", - "someprotocol", - "/some/path")), - Collections.singletonMap("key1", "value1")); - } - - private SnapshotImpl createSnapshot() { - return SnapshotImpl.builder() - .setId("snapshot12") - .fromConfig(createMachineConfig()) - .setWorkspaceId(WS_ID) - .setEnvName(ENVIRONMENT_NAME) - .setNamespace(NAMESPACE) - .setMachineSource(new MachineSourceImpl("snapshot").setLocation("location")) - .setDev(true) - .build(); - } -} diff --git a/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/EnvironmentDto.java b/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/EnvironmentDto.java index 79877ff420..ec8683a2b0 100644 --- a/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/EnvironmentDto.java +++ b/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/EnvironmentDto.java @@ -11,7 +11,6 @@ package org.eclipse.che.api.workspace.shared.dto; import org.eclipse.che.api.core.factory.FactoryParameter; -import org.eclipse.che.api.core.model.machine.Recipe; import org.eclipse.che.api.core.model.workspace.Environment; import org.eclipse.che.api.machine.shared.dto.MachineConfigDto; import org.eclipse.che.dto.shared.DTO; diff --git a/wsmaster/che-core-api-workspace/pom.xml b/wsmaster/che-core-api-workspace/pom.xml index b51fcb01f9..e4bdbe54d8 100644 --- a/wsmaster/che-core-api-workspace/pom.xml +++ b/wsmaster/che-core-api-workspace/pom.xml @@ -53,6 +53,10 @@ javax.inject javax.inject + + javax.servlet + javax.servlet-api + org.eclipse.che.core che-core-api-core @@ -81,6 +85,10 @@ org.eclipse.che.core che-core-commons-annotations + + org.eclipse.che.core + che-core-commons-inject + org.eclipse.che.core che-core-commons-lang @@ -113,6 +121,11 @@ rest-assured test + + org.eclipse.che.core + che-core-commons-test + test + org.everrest everrest-assured diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/MachineImplSpecificTerminalLauncher.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/terminal/MachineImplSpecificTerminalLauncher.java similarity index 95% rename from wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/MachineImplSpecificTerminalLauncher.java rename to wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/terminal/MachineImplSpecificTerminalLauncher.java index f96c1c8f43..7c9d3817f8 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/MachineImplSpecificTerminalLauncher.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/terminal/MachineImplSpecificTerminalLauncher.java @@ -8,7 +8,7 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.machine.server.terminal; +package org.eclipse.che.api.agent.server.terminal; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.spi.Instance; diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/MachineTerminalLauncher.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/terminal/MachineTerminalLauncher.java similarity index 84% rename from wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/MachineTerminalLauncher.java rename to wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/terminal/MachineTerminalLauncher.java index ef41eb0da6..256c923ed5 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/MachineTerminalLauncher.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/terminal/MachineTerminalLauncher.java @@ -8,14 +8,14 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.machine.server.terminal; +package org.eclipse.che.api.agent.server.terminal; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; -import org.eclipse.che.api.machine.server.MachineManager; +import org.eclipse.che.api.environment.server.CheEnvironmentEngine; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; @@ -43,19 +43,20 @@ public class MachineTerminalLauncher { private static final Logger LOG = LoggerFactory.getLogger(MachineTerminalLauncher.class); private final EventService eventService; - private final MachineManager machineManager; + // TODO replace with WorkspaceManager + private final CheEnvironmentEngine cheEnvironmentEngine; private final Map terminalLaunchers; private final ExecutorService executor; @Inject public MachineTerminalLauncher(EventService eventService, - MachineManager machineManager, - Set machineImplLaunchers) { + Set machineImplLaunchers, + CheEnvironmentEngine cheEnvironmentEngine) { this.eventService = eventService; - this.machineManager = machineManager; this.terminalLaunchers = machineImplLaunchers.stream() .collect(toMap(MachineImplSpecificTerminalLauncher::getMachineType, Function.identity())); + this.cheEnvironmentEngine = cheEnvironmentEngine; this.executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("MachineTerminalLauncher-%d") .setDaemon(true) .build()); @@ -70,10 +71,11 @@ public class MachineTerminalLauncher { if (event.getEventType() == MachineStatusEvent.EventType.RUNNING) { executor.execute(() -> { try { - final Instance machine = machineManager.getInstance(event.getMachineId()); + final Instance machine = cheEnvironmentEngine.getMachine(event.getWorkspaceId(), + event.getMachineId()); - MachineImplSpecificTerminalLauncher terminalLauncher = terminalLaunchers.get(machine.getConfig() - .getType()); + MachineImplSpecificTerminalLauncher terminalLauncher = + terminalLaunchers.get(machine.getConfig().getType()); if (terminalLauncher == null) { LOG.warn("Terminal launcher implementation was not found for machine {} with type {}.", machine.getId(), diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/WebsocketTerminalFilesPathProvider.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/terminal/WebsocketTerminalFilesPathProvider.java similarity index 97% rename from wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/WebsocketTerminalFilesPathProvider.java rename to wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/terminal/WebsocketTerminalFilesPathProvider.java index 10675f7397..92a15828fb 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/WebsocketTerminalFilesPathProvider.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/terminal/WebsocketTerminalFilesPathProvider.java @@ -8,7 +8,7 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.machine.server.terminal; +package org.eclipse.che.api.agent.server.terminal; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.inject.ConfigurationProperties; diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/wsagent/WsAgentLauncher.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/wsagent/WsAgentLauncher.java similarity index 72% rename from wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/wsagent/WsAgentLauncher.java rename to wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/wsagent/WsAgentLauncher.java index a7ddaab8d2..26af48eb35 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/wsagent/WsAgentLauncher.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/wsagent/WsAgentLauncher.java @@ -8,10 +8,11 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.machine.server.wsagent; +package org.eclipse.che.api.agent.server.wsagent; import org.eclipse.che.api.core.NotFoundException; -import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.machine.Machine; /** * Starts ws agent in the machine and wait until ws agent sends notification about its start @@ -19,5 +20,6 @@ import org.eclipse.che.api.machine.server.exception.MachineException; * @author Alexander Garagatyi */ public interface WsAgentLauncher { - void startWsAgent(String workspaceId) throws NotFoundException, MachineException, InterruptedException; + void startWsAgent(Machine devMachine) throws NotFoundException, + ServerException; } diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/wsagent/WsAgentLauncherImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/wsagent/WsAgentLauncherImpl.java similarity index 72% rename from wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/wsagent/WsAgentLauncherImpl.java rename to wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/wsagent/WsAgentLauncherImpl.java index c6d3acd7cf..9f46cbc966 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/wsagent/WsAgentLauncherImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/wsagent/WsAgentLauncherImpl.java @@ -8,18 +8,18 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.machine.server.wsagent; +package org.eclipse.che.api.agent.server.wsagent; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.machine.Machine; import org.eclipse.che.api.core.model.machine.Server; import org.eclipse.che.api.core.rest.HttpJsonRequest; import org.eclipse.che.api.core.rest.HttpJsonRequestFactory; import org.eclipse.che.api.core.rest.HttpJsonResponse; -import org.eclipse.che.api.machine.server.MachineManager; -import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.environment.server.MachineProcessManager; import org.eclipse.che.api.machine.server.model.impl.CommandImpl; import org.eclipse.che.api.machine.shared.Constants; import org.slf4j.Logger; @@ -49,23 +49,23 @@ public class WsAgentLauncherImpl implements WsAgentLauncher { private static final String WS_AGENT_PROCESS_OUTPUT_CHANNEL = "workspace:%s:ext-server:output"; private static final String WS_AGENT_SERVER_NOT_FOUND_ERROR = "Workspace agent server not found in dev machine."; - private final Provider machineManagerProvider; - private final HttpJsonRequestFactory httpJsonRequestFactory; - private final String wsAgentStartCommandLine; - private final long wsAgentMaxStartTimeMs; - private final long wsAgentPingDelayMs; - private final int wsAgentPingConnectionTimeoutMs; - private final String pingTimedOutErrorMessage; + private final Provider machineProcessManagerProvider; + private final HttpJsonRequestFactory httpJsonRequestFactory; + private final String wsAgentStartCommandLine; + private final long wsAgentMaxStartTimeMs; + private final long wsAgentPingDelayMs; + private final int wsAgentPingConnectionTimeoutMs; + private final String pingTimedOutErrorMessage; @Inject - public WsAgentLauncherImpl(Provider machineManagerProvider, + public WsAgentLauncherImpl(Provider machineProcessManagerProvider, HttpJsonRequestFactory httpJsonRequestFactory, @Named(WS_AGENT_PROCESS_START_COMMAND) String wsAgentStartCommandLine, @Named("machine.ws_agent.max_start_time_ms") long wsAgentMaxStartTimeMs, @Named("machine.ws_agent.ping_delay_ms") long wsAgentPingDelayMs, @Named("machine.ws_agent.ping_conn_timeout_ms") int wsAgentPingConnectionTimeoutMs, @Named("machine.ws_agent.ping_timed_out_error_msg") String pingTimedOutErrorMessage) { - this.machineManagerProvider = machineManagerProvider; + this.machineProcessManagerProvider = machineProcessManagerProvider; this.httpJsonRequestFactory = httpJsonRequestFactory; this.wsAgentStartCommandLine = wsAgentStartCommandLine; this.wsAgentMaxStartTimeMs = wsAgentMaxStartTimeMs; @@ -79,17 +79,20 @@ public class WsAgentLauncherImpl implements WsAgentLauncher { } @Override - public void startWsAgent(String workspaceId) throws NotFoundException, MachineException, InterruptedException { - final Machine devMachine = getMachineManager().getDevMachine(workspaceId); + public void startWsAgent(Machine devMachine) throws NotFoundException, + ServerException { final HttpJsonRequest wsAgentPingRequest = createPingRequest(devMachine); final String wsAgentPingUrl = wsAgentPingRequest.getUrl(); try { - getMachineManager().exec(devMachine.getId(), - new CommandImpl(WS_AGENT_PROCESS_NAME, wsAgentStartCommandLine, "Arbitrary"), - getWsAgentProcessOutputChannel(workspaceId)); + machineProcessManagerProvider.get().exec(devMachine.getWorkspaceId(), + devMachine.getId(), + new CommandImpl(WS_AGENT_PROCESS_NAME, + wsAgentStartCommandLine, + "Arbitrary"), + getWsAgentProcessOutputChannel(devMachine.getWorkspaceId())); final long pingStartTimestamp = System.currentTimeMillis(); LOG.debug("Starts pinging ws agent. Workspace ID:{}. Url:{}. Timestamp:{}", - workspaceId, + devMachine.getWorkspaceId(), wsAgentPingUrl, pingStartTimestamp); @@ -101,20 +104,23 @@ public class WsAgentLauncherImpl implements WsAgentLauncher { } } } catch (BadRequestException wsAgentLaunchingExc) { - throw new MachineException(wsAgentLaunchingExc.getLocalizedMessage(), wsAgentLaunchingExc); + throw new ServerException(wsAgentLaunchingExc.getLocalizedMessage(), wsAgentLaunchingExc); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ServerException("Ws agent pinging is interrupted"); } - LOG.error("Fail pinging ws agent. Workspace ID:{}. Url:{}. Timestamp:{}", workspaceId, wsAgentPingUrl); - throw new MachineException(pingTimedOutErrorMessage); + LOG.error("Fail pinging ws agent. Workspace ID:{}. Url:{}. Timestamp:{}", devMachine.getWorkspaceId(), wsAgentPingUrl); + throw new ServerException(pingTimedOutErrorMessage); } // forms the ping request based on information about the machine. - protected HttpJsonRequest createPingRequest(Machine machine) throws MachineException { + protected HttpJsonRequest createPingRequest(Machine machine) throws ServerException { Map servers = machine.getRuntime().getServers(); Server wsAgentServer = servers.get(Constants.WS_AGENT_PORT); if (wsAgentServer == null) { LOG.error("{} WorkspaceId: {}, DevMachine Id: {}, found servers: {}", WS_AGENT_SERVER_NOT_FOUND_ERROR, machine.getWorkspaceId(), machine.getId(), servers); - throw new MachineException(WS_AGENT_SERVER_NOT_FOUND_ERROR); + throw new ServerException(WS_AGENT_SERVER_NOT_FOUND_ERROR); } String wsAgentPingUrl = wsAgentServer.getUrl(); // since everrest mapped on the slash in case of it absence @@ -127,7 +133,7 @@ public class WsAgentLauncherImpl implements WsAgentLauncher { .setTimeout(wsAgentPingConnectionTimeoutMs); } - private boolean pingWsAgent(HttpJsonRequest wsAgentPingRequest) throws MachineException { + private boolean pingWsAgent(HttpJsonRequest wsAgentPingRequest) throws ServerException { try { final HttpJsonResponse pingResponse = wsAgentPingRequest.request(); if (pingResponse.getResponseCode() == HttpURLConnection.HTTP_OK) { @@ -137,8 +143,4 @@ public class WsAgentLauncherImpl implements WsAgentLauncher { } return false; } - - private MachineManager getMachineManager() { - return machineManagerProvider.get(); - } } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/CheEnvironmentEngine.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/CheEnvironmentEngine.java new file mode 100644 index 0000000000..6e32c8fe20 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/CheEnvironmentEngine.java @@ -0,0 +1,955 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.environment.server; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; + +import org.eclipse.che.api.core.ApiException; +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.MachineConfig; +import org.eclipse.che.api.core.model.machine.MachineLogMessage; +import org.eclipse.che.api.core.model.machine.MachineSource; +import org.eclipse.che.api.core.model.machine.MachineStatus; +import org.eclipse.che.api.core.model.workspace.Environment; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.notification.EventSubscriber; +import org.eclipse.che.api.core.util.AbstractLineConsumer; +import org.eclipse.che.api.core.util.CompositeLineConsumer; +import org.eclipse.che.api.core.util.FileLineConsumer; +import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.core.util.MessageConsumer; +import org.eclipse.che.api.environment.server.exception.EnvironmentNotRunningException; +import org.eclipse.che.api.machine.server.MachineInstanceProviders; +import org.eclipse.che.api.machine.server.dao.SnapshotDao; +import org.eclipse.che.api.machine.server.event.InstanceStateEvent; +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.machine.server.exception.SourceNotFoundException; +import org.eclipse.che.api.machine.server.model.impl.LimitsImpl; +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.MachineLogMessageImpl; +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.Instance; +import org.eclipse.che.api.machine.server.spi.InstanceProvider; +import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; +import org.eclipse.che.api.workspace.server.StripedLocks; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.lang.IoUtil; +import org.eclipse.che.commons.lang.NameGenerator; +import org.slf4j.Logger; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.io.File; +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static java.lang.String.format; +import static org.eclipse.che.api.machine.server.event.InstanceStateEvent.Type.DIE; +import static org.eclipse.che.api.machine.server.event.InstanceStateEvent.Type.OOM; +import static org.eclipse.che.dto.server.DtoFactory.newDto; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Facade for implementation specific operations with environment runtimes. + * + * @author Alexander Garagatyi + * @author Yevhenii Voevodin + */ +@Singleton +public class CheEnvironmentEngine { + + private static final Logger LOG = getLogger(CheEnvironmentEngine.class); + + private final Map environments; + private final StripedLocks stripedLocks; + private final SnapshotDao snapshotDao; + private final File machineLogsDir; + private final MachineInstanceProviders machineInstanceProviders; + private final int defaultMachineMemorySizeMB; + private final EventService eventService; + + private volatile boolean isPreDestroyInvoked; + + @Inject + public CheEnvironmentEngine(SnapshotDao snapshotDao, + MachineInstanceProviders machineInstanceProviders, + @Named("machine.logs.location") String machineLogsDir, + @Named("machine.default_mem_size_mb") int defaultMachineMemorySizeMB, + EventService eventService) { + this.eventService = eventService; + this.environments = new ConcurrentHashMap<>(); + this.snapshotDao = snapshotDao; + this.machineInstanceProviders = machineInstanceProviders; + this.machineLogsDir = new File(machineLogsDir); + this.defaultMachineMemorySizeMB = defaultMachineMemorySizeMB; + // 16 - experimental value for stripes count, it comes from default hash map size + this.stripedLocks = new StripedLocks(16); + eventService.subscribe(new MachineCleaner()); + } + + /** + * Returns all machines from environment of specific workspace. + * + * @param workspaceId + * ID of workspace that owns environment machines + * @return list of machines + * @throws EnvironmentNotRunningException + * if environment is not running + */ + public List getMachines(String workspaceId) throws EnvironmentNotRunningException { + EnvironmentHolder environment; + try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) { + environment = environments.get(workspaceId); + if (environment == null) { + throw new EnvironmentNotRunningException("Environment with ID '" + workspaceId + "' is not found"); + } + return new ArrayList<>(environment.machines); + } + } + + /** + * Returns specific machine from environment of specific workspace. + * + * @param workspaceId + * ID of workspace that owns environment machines + * @param machineId + * ID of requested machine + * @return requested machine + * @throws EnvironmentNotRunningException + * if environment is not running + * @throws NotFoundException + * if machine is not found in the environment + */ + public Instance getMachine(String workspaceId, String machineId) throws NotFoundException { + EnvironmentHolder environment; + try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) { + environment = environments.get(workspaceId); + } + if (environment == null) { + throw new EnvironmentNotRunningException("Environment with ID '" + workspaceId + "' is not found"); + } + return environment.machines.stream() + .filter(instance -> instance.getId().equals(machineId)) + .findAny() + .orElseThrow(() -> new NotFoundException( + format("Machine with ID '%s' is not found in the environment of workspace '%s'", + machineId, workspaceId))); + } + + /** + * Starts provided environment. + *

+ * Environment starts if and only all machines in environment definition start successfully.
+ * Otherwise exception is thrown by this method.
+ * It is not defined whether environment start fails right after first failure or in the end of the process.
+ * Starting order of machines is not guarantied. Machines can start sequentially or in parallel. + * + * @param workspaceId + * ID of workspace that owns provided environment + * @param env + * environment to start + * @param recover + * whether machines from environment should be recovered or not + * @param messageConsumer + * consumer of log messages from machines in the environment + * @return list of running machines of this environment + * @throws ServerException + * if other error occurs + */ + public List start(String workspaceId, + Environment env, + boolean recover, + MessageConsumer messageConsumer) throws ServerException, + ConflictException { + + // Create a new start queue with a dev machine in the queue head + List startConfigs = env.getMachineConfigs() + .stream() + .map(MachineConfigImpl::new) + .collect(Collectors.toList()); + final MachineConfigImpl devCfg = removeFirstMatching(startConfigs, MachineConfig::isDev); + startConfigs.add(0, devCfg); + + EnvironmentHolder environmentHolder = new EnvironmentHolder(new ArrayDeque<>(startConfigs), + new CopyOnWriteArrayList<>(), + messageConsumer, + EnvStatus.STARTING, + env.getName()); + + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) { + if (environments.putIfAbsent(workspaceId, environmentHolder) != null) { + throw new ConflictException(format("Environment of workspace '%s' already exists", workspaceId)); + } + } + + startQueue(workspaceId, env.getName(), recover, messageConsumer); + + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) { + environmentHolder = environments.get(workspaceId); + // possible only if environment was stopped during its start + if (environmentHolder == null) { + throw new ServerException("Environment start was interrupted by environment stopping"); + } + environmentHolder.status = EnvStatus.RUNNING; + return new ArrayList<>(environmentHolder.machines); + } + } + + /** + * Stops running environment of specified workspace. + * + * @param workspaceId + * ID of workspace that owns environment + * @throws EnvironmentNotRunningException + * when environment is not running + * @throws ServerException + * if other error occurs + */ + public void stop(String workspaceId) throws EnvironmentNotRunningException, + ServerException { + List machinesCopy = null; + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) { + EnvironmentHolder environmentHolder = environments.get(workspaceId); + if (environmentHolder == null || environmentHolder.status != EnvStatus.RUNNING) { + throw new EnvironmentNotRunningException("Environment with ID '" + workspaceId + "' is not found"); + } + environments.remove(workspaceId); + List machines = environmentHolder.machines; + if (machines != null && !machines.isEmpty()) { + machinesCopy = new ArrayList<>(machines); + } + } + + // long operation - perform out of lock + if (machinesCopy != null) { + stopMachines(machinesCopy); + } + } + + /** + * Starts machine in running environment. + * + * @param workspaceId + * ID of workspace that owns environment in which machine should be started + * @param machineConfig + * configuration of machine that should be started + * @return running machine + * @throws EnvironmentNotRunningException + * if environment is not running + * @throws ConflictException + * if machine with the same name already exists in the environment + * @throws ServerException + * if any other error occurs + */ + public Instance startMachine(String workspaceId, + MachineConfig machineConfig) throws ServerException, + EnvironmentNotRunningException, + ConflictException { + MachineConfig machineConfigCopy = new MachineConfigImpl(machineConfig); + EnvironmentHolder environmentHolder; + try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) { + environmentHolder = environments.get(workspaceId); + if (environmentHolder == null || environmentHolder.status != EnvStatus.RUNNING) { + throw new EnvironmentNotRunningException(format("Environment '%s' is not running", workspaceId)); + } + for (Instance machine : environmentHolder.machines) { + if (machine.getConfig().getName().equals(machineConfigCopy.getName())) { + throw new ConflictException( + format("Machine with name '%s' already exists in environment of workspace '%s'", + machineConfigCopy.getName(), workspaceId)); + } + } + } + String machineId = generateMachineId(); + final String creator = EnvironmentContext.getCurrent().getSubject().getUserId(); + + Instance instance = null; + try { + addMachine(workspaceId, + machineId, + environmentHolder.name, + creator, + machineConfigCopy); + + eventService.publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.CREATING) + .withDev(machineConfigCopy.isDev()) + .withMachineName(machineConfigCopy.getName()) + .withMachineId(machineId) + .withWorkspaceId(workspaceId)); + + instance = startMachineInstance(machineConfigCopy, + workspaceId, + machineId, + environmentHolder.name, + creator, + false, + environmentHolder.logger); + + replaceMachine(instance); + + eventService.publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.RUNNING) + .withDev(machineConfigCopy.isDev()) + .withMachineName(machineConfigCopy.getName()) + .withMachineId(instance.getId()) + .withWorkspaceId(workspaceId)); + + return instance; + } catch (Exception e) { + removeMachine(workspaceId, machineId); + + if (instance != null) { + try { + instance.destroy(); + } catch (Exception destroyingExc) { + LOG.error(destroyingExc.getLocalizedMessage(), destroyingExc); + } + } + + eventService.publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.ERROR) + .withDev(machineConfigCopy.isDev()) + .withMachineName(machineConfigCopy.getName()) + .withMachineId(machineId) + .withWorkspaceId(workspaceId)); + + throw e; + } + } + + /** + * Stops machine in running environment. + * + * @param workspaceId + * ID of workspace of environment that owns machine + * @param machineId + * ID of machine that should be stopped + * @throws NotFoundException + * if machine in not found in environment + * @throws EnvironmentNotRunningException + * if environment is not running + * @throws ConflictException + * if stop of dev machine is requested + * @throws ServerException + * if other error occurs + */ + public void stopMachine(String workspaceId, String machineId) throws NotFoundException, + ServerException, + ConflictException { + Instance targetMachine = null; + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) { + EnvironmentHolder environmentHolder = environments.get(workspaceId); + if (environmentHolder == null || environmentHolder.status != EnvStatus.RUNNING) { + throw new EnvironmentNotRunningException(format("Environment '%s' is not running", workspaceId)); + } + for (Instance machine : environmentHolder.machines) { + if (machine.getId().equals(machineId)) { + if (machine.getConfig().isDev()) { + throw new ConflictException( + "Stop of dev machine is not allowed. Please, stop whole environment"); + } + targetMachine = machine; + break; + } + } + environmentHolder.machines.remove(targetMachine); + } + if (targetMachine == null) { + throw new NotFoundException(format("Machine with ID '%s' is not found in environment of workspace '%s'", + machineId, workspaceId)); + } + + // out of lock to prevent blocking on event processing by subscribers + eventService.publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.DESTROYING) + .withDev(targetMachine.getConfig().isDev()) + .withMachineName(targetMachine.getConfig().getName()) + .withMachineId(machineId) + .withWorkspaceId(workspaceId)); + + targetMachine.destroy(); + + eventService.publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.DESTROYED) + .withDev(targetMachine.getConfig().isDev()) + .withMachineName(targetMachine.getConfig().getName()) + .withMachineId(machineId) + .withWorkspaceId(workspaceId)); + } + + /** + * Saves machine into snapshot. + * + * @param namespace namespace of the workspace + * @param workspaceId ID of workspace that owns environment + * @param machineId ID of machine to save + * @return snapshot + * @throws EnvironmentNotRunningException + * if environment of machine is not running + * @throws NotFoundException + * if machine is not running + * @throws ServerException + * if another error occurs + */ + public SnapshotImpl saveSnapshot(String namespace, + String workspaceId, + String machineId) throws ServerException, + NotFoundException { + EnvironmentHolder environmentHolder; + SnapshotImpl snapshot = null; + Instance instance = null; + try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) { + environmentHolder = environments.get(workspaceId); + if (environmentHolder == null || environmentHolder.status != EnvStatus.RUNNING) { + throw new EnvironmentNotRunningException(format("Environment '%s' is not running", workspaceId)); + } + for (Instance machine : environmentHolder.machines) { + if (machine.getId().equals(machineId)) { + instance = machine; + snapshot = SnapshotImpl.builder() + .generateId() + .setType(machine.getConfig().getType()) + .setNamespace(namespace) + .setWorkspaceId(machine.getWorkspaceId()) + .setDescription(machine.getEnvName()) + .setDev(machine.getConfig().isDev()) + .setEnvName(machine.getEnvName()) + .setMachineName(machine.getConfig().getName()) + .useCurrentCreationDate() + .build(); + } + } + } + if (instance == null) { + throw new NotFoundException(format("Machine with id '%s' is not found in environment of workspace '%s'", + machineId, workspaceId)); + } + try { + MachineSource machineSource = instance.saveToSnapshot(); + snapshot.setMachineSource(machineSource); + return snapshot; + } catch (ServerException e) { + try { + instance.getLogger().writeLine("Snapshot storing failed. " + e.getLocalizedMessage()); + } catch (IOException ignore) { + } + throw e; + } + } + + /** + * Removes snapshot of machine. + * + * @param snapshot + * description of snapshot that should be removed + * @throws ServerException + * if error occurs on snapshot removal + */ + public void removeSnapshot(SnapshotImpl snapshot) throws ServerException { + final String instanceType = snapshot.getType(); + try { + final InstanceProvider instanceProvider = machineInstanceProviders.getProvider(instanceType); + instanceProvider.removeInstanceSnapshot(snapshot.getMachineSource()); + } catch (NotFoundException e) { + throw new ServerException(e.getLocalizedMessage(), e); + } + } + + /** + * Starts all machine from machine queue of environment. + */ + private void startQueue(String workspaceId, + String envName, + boolean recover, + MessageConsumer messageConsumer) throws ServerException { + // Starting all machines in environment one by one by getting configs + // from the corresponding starting queue. + // Config will be null only if there are no machines left in the queue + MachineConfigImpl config = queuePeekOrFail(workspaceId); + while (config != null) { + // Environment start is failed when any machine start is failed, so if any error + // occurs during machine creation then environment start fail is reported and + // start resources such as queue and descriptor must be cleaned up + try { + String machineId = generateMachineId(); + final String creator = EnvironmentContext.getCurrent().getSubject().getUserId(); + + addMachine(workspaceId, + machineId, + envName, + creator, + config); + + eventService.publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.CREATING) + .withDev(config.isDev()) + .withMachineName(config.getName()) + .withWorkspaceId(workspaceId) + .withMachineId(machineId)); + + Instance machine = startMachineInstance(config, + workspaceId, + machineId, + envName, + creator, + recover, + messageConsumer); + + // Machine destroying is an expensive operation which must be + // performed outside of the lock, this section checks if + // the environment wasn't stopped while it is starting and sets + // polled flag to true if the environment wasn't stopped. + // Also polls the proceeded machine configuration from the queue + boolean queuePolled = false; + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) { + ensurePreDestroyIsNotExecuted(); + EnvironmentHolder environmentHolder = environments.get(workspaceId); + if (environmentHolder != null) { + final Queue queue = environmentHolder.startQueue; + if (queue != null) { + queue.poll(); + queuePolled = true; + } + } + } + + // If machine config is not polled from the queue + // then environment was stopped and newly created machine + // must be destroyed + if (!queuePolled) { + try { + eventService.publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.DESTROYING) + .withDev(config.isDev()) + .withMachineName(config.getName()) + .withMachineId(machine.getId()) + .withWorkspaceId(workspaceId)); + + machine.destroy(); + + eventService.publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.DESTROYED) + .withDev(config.isDev()) + .withMachineName(config.getName()) + .withMachineId(machine.getId()) + .withWorkspaceId(workspaceId)); + } catch (MachineException e) { + LOG.error(e.getLocalizedMessage(), e); + } + throw new ServerException("Workspace '" + workspaceId + + "' start interrupted. Workspace stopped before all its machines started"); + } + + replaceMachine(machine); + + eventService.publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.RUNNING) + .withDev(config.isDev()) + .withMachineName(config.getName()) + .withMachineId(machine.getId()) + .withWorkspaceId(workspaceId)); + + config = queuePeekOrFail(workspaceId); + } catch (RuntimeException | ServerException e) { + EnvironmentHolder env; + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) { + env = environments.remove(workspaceId); + } + try { + stopMachines(env.machines); + } catch (Exception remEx) { + LOG.error(remEx.getLocalizedMessage(), remEx); + } + throw new ServerException(e.getLocalizedMessage(), e); + } + } + } + + private void addMachine(String workspaceId, + String machineId, + String envName, + String creator, + MachineConfig machineConfig) throws ServerException { + Instance machine = new NoOpMachineInstance(MachineImpl.builder() + .setConfig(machineConfig) + .setId(machineId) + .setWorkspaceId(workspaceId) + .setStatus(MachineStatus.CREATING) + .setEnvName(envName) + .setOwner(creator) + .build()); + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) { + ensurePreDestroyIsNotExecuted(); + EnvironmentHolder environmentHolder = environments.get(workspaceId); + if (environmentHolder != null && environmentHolder.status != EnvStatus.STOPPING) { + environmentHolder.machines.add(machine); + } else { + throw new ServerException( + format("Can't add machine into environment. Environment of workspace '%s' is missing", + workspaceId)); + } + } + } + + private void removeMachine(String workspaceId, + String machineId) { + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) { + EnvironmentHolder environmentHolder = environments.get(workspaceId); + if (environmentHolder != null) { + removeFirstMatching(environmentHolder.machines, m -> m.getId().equals(machineId)); + } + } + } + + private void replaceMachine(Instance machine) throws ServerException { + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(machine.getWorkspaceId())) { + ensurePreDestroyIsNotExecuted(); + EnvironmentHolder environmentHolder = environments.get(machine.getWorkspaceId()); + if (environmentHolder != null) { + for (int i = 0; i < environmentHolder.machines.size(); i++) { + if (environmentHolder.machines.get(i).getId().equals(machine.getId())) { + environmentHolder.machines.set(i, machine); + return; + } + } + } + } + // if this area is reachable then environment/machine is not found and machine should be stopped + try { + machine.destroy(); + } catch (MachineException e) { + LOG.error(e.getLocalizedMessage(), e); + } + // should not happen + throw new ServerException(format( + "Machine with ID '%s' and name '%s' has been stopped because its configuration is not found in the environment of workspace '%s'", + machine.getId(), machine.getConfig().getName(), machine.getWorkspaceId())); + } + + /** + * Gets head config from the queue associated with the given {@code workspaceId}. + * + *

Note that this method won't actually poll the queue. + * + *

Fails if environment start was interrupted by stop(queue doesn't exist). + * + * @return machine config which is in the queue head, or null + * if there are no machine configs left + * @throws ServerException + * if queue doesn't exist which means that {@link #stop(String)} executed + * before all the machines started + * @throws ServerException + * if pre destroy has been invoked before peek config retrieved + */ + private MachineConfigImpl queuePeekOrFail(String workspaceId) throws ServerException { + try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) { + ensurePreDestroyIsNotExecuted(); + EnvironmentHolder environmentHolder = environments.get(workspaceId); + if (environmentHolder == null || environmentHolder.startQueue == null) { + throw new ServerException("Workspace " + workspaceId + + " start interrupted. Workspace was stopped before all its machines were started"); + } + return environmentHolder.startQueue.peek(); + } + } + + private Instance startMachineInstance(MachineConfig originMachineConfig, + String workspaceId, + String machineId, + String environmentName, + String creator, + boolean recover, + MessageConsumer environmentLogger) + throws ServerException { + + final MachineImpl machine = new MachineImpl(originMachineConfig, + machineId, + workspaceId, + environmentName, + creator, + MachineStatus.CREATING, + null); + if ("recipe".equalsIgnoreCase(machine.getConfig().getSource().getType())) { + machine.getConfig().getSource().setType("dockerfile"); + } + if (originMachineConfig.getLimits().getRam() == 0) { + machine.getConfig().setLimits(new LimitsImpl(defaultMachineMemorySizeMB)); + } + final MachineSourceImpl sourceCopy = machine.getConfig().getSource(); + + LineConsumer machineLogger = null; + try { + machineLogger = getMachineLogger(environmentLogger, machineId, originMachineConfig.getName()); + if (recover) { + final SnapshotImpl snapshot = snapshotDao.getSnapshot(workspaceId, + environmentName, + machine.getConfig().getName()); + machine.getConfig().setSource(snapshot.getMachineSource()); + } + + final InstanceProvider instanceProvider = + machineInstanceProviders.getProvider(machine.getConfig().getType()); + + if (!instanceProvider.getRecipeTypes().contains(machine.getConfig() + .getSource() + .getType() + .toLowerCase())) { + throw new ServerException(format("Recipe type %s of %s machine is unsupported", + machine.getConfig().getSource().getType(), + machine.getConfig().getName())); + } + + try { + Instance instance; + try { + instance = instanceProvider.createInstance(machine, machineLogger); + } catch (SourceNotFoundException e) { + if (recover) { + LOG.error("Image of snapshot for machine " + machine.getConfig().getName() + " not found. " + + "Machine will be created from origin source"); + machine.getConfig().setSource(sourceCopy); + instance = instanceProvider.createInstance(machine, machineLogger); + } else { + throw e; + } + } + instance.setStatus(MachineStatus.RUNNING); + return instance; + } catch (ApiException creationEx) { + try { + machineLogger.writeLine("[ERROR] " + creationEx.getLocalizedMessage()); + } catch (IOException ioEx) { + LOG.error(ioEx.getLocalizedMessage()); + } + + throw new MachineException(creationEx.getLocalizedMessage(), creationEx); + } + } catch (ApiException apiEx) { + if (machineLogger != null) { + try { + machineLogger.close(); + } catch (IOException ioEx) { + LOG.error(ioEx.getLocalizedMessage(), ioEx); + } + } + + throw new MachineException(apiEx.getLocalizedMessage(), apiEx); + } + } + + /** + * Stops workspace by destroying all its machines and removing it from in memory storage. + */ + private void stopMachines(List machines) { + for (Instance machine : machines) { + try { + machine.destroy(); + } catch (RuntimeException | MachineException ex) { + LOG.error(format("Could not destroy machine '%s' of workspace '%s'", + machine.getId(), + machine.getWorkspaceId()), + ex); + } + } + } + + @SuppressWarnings("unused") + @PostConstruct + private void createLogsDir() { + if (!(machineLogsDir.exists() || machineLogsDir.mkdirs())) { + throw new IllegalStateException("Unable create directory " + machineLogsDir.getAbsolutePath()); + } + } + + /** + * Removes all descriptors from the in-memory storage, while + * {@link MachineProcessManager#cleanup()} is responsible for machines destroying. + */ + @PreDestroy + @VisibleForTesting + void cleanup() { + isPreDestroyInvoked = true; + final java.io.File[] files = machineLogsDir.listFiles(); + if (files != null && files.length > 0) { + for (java.io.File f : files) { + if (!IoUtil.deleteRecursive(f)) { + LOG.warn("Failed delete {}", f); + } + } + } + } + + private LineConsumer getMachineLogger(MessageConsumer environmentLogger, + String machineId, + String machineName) throws ServerException { + createMachineLogsDir(machineId); + + LineConsumer lineConsumer = new AbstractLineConsumer() { + @Override + public void writeLine(String line) throws IOException { + environmentLogger.consume(new MachineLogMessageImpl(machineName, line)); + } + }; + try { + return new CompositeLineConsumer(new FileLineConsumer(getMachineLogsFile(machineId)), + lineConsumer); + } catch (IOException e) { + throw new MachineException(format("Unable create log file '%s' for machine '%s'.", + e.getLocalizedMessage(), + machineId)); + } + } + + private void createMachineLogsDir(String machineId) throws MachineException { + File dir = new File(machineLogsDir, machineId); + if (!dir.exists() && !dir.mkdirs()) { + throw new MachineException("Can't create folder for the logs of machine"); + } + } + + private File getMachineLogsFile(String machineId) { + return new File(new File(machineLogsDir, machineId), "machineId.logs"); + } + + @VisibleForTesting + String generateMachineId() { + return NameGenerator.generate("machine", 16); + } + + private static T removeFirstMatching(List elements, Predicate predicate) { + T element = null; + for (final Iterator it = elements.iterator(); it.hasNext() && element == null; ) { + final T next = it.next(); + if (predicate.test(next)) { + element = next; + it.remove(); + } + } + return element; + } + + private void ensurePreDestroyIsNotExecuted() throws ServerException { + if (isPreDestroyInvoked) { + throw new ServerException("Could not perform operation because application server is stopping"); + } + } + + private enum EnvStatus { + STARTING, + RUNNING, + STOPPING + } + + private static class EnvironmentHolder { + Queue startQueue; + List machines; + EnvStatus status; + MessageConsumer logger; + String name; + + EnvironmentHolder(Queue startQueue, + List machines, + MessageConsumer envLogger, + EnvStatus envStatus, + String name) { + this.startQueue = startQueue; + this.machines = machines; + this.logger = envLogger; + this.status = envStatus; + this.name = name; + } + + public EnvironmentHolder(EnvironmentHolder environmentHolder) { + this.startQueue = environmentHolder.startQueue; + this.machines = environmentHolder.machines; + this.logger = environmentHolder.logger; + this.status = environmentHolder.status; + this.name = environmentHolder.name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof EnvironmentHolder)) return false; + EnvironmentHolder that = (EnvironmentHolder)o; + return Objects.equals(startQueue, that.startQueue) && + Objects.equals(machines, that.machines) && + status == that.status && + Objects.equals(logger, that.logger) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(startQueue, machines, status, logger, name); + } + } + + // cleanup machine if event about instance failure comes + private class MachineCleaner implements EventSubscriber { + @Override + public void onEvent(InstanceStateEvent event) { + if ((event.getType() == OOM) || (event.getType() == DIE)) { + EnvironmentHolder environmentHolder; + try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock("workspaceId")) { + environmentHolder = environments.get(event.getWorkspaceId()); + } + if (environmentHolder != null) { + for (Instance instance : environmentHolder.machines) { + if (instance.getId().equals(event.getMachineId())) { + String message = "Machine is destroyed. "; + if (event.getType() == OOM) { + message = message + + "The processes in this machine need more RAM. This machine started with " + + instance.getConfig().getLimits().getRam() + + "MB. Create a new machine configuration that allocates additional RAM or increase " + + "the workspace RAM limit in the user dashboard."; + } + + try { + if (!Strings.isNullOrEmpty(message)) { + instance.getLogger().writeLine(message); + } + } catch (IOException ignore) { + } + + eventService.publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.DESTROYED) + .withDev(instance.getConfig().isDev()) + .withMachineId(instance.getId()) + .withWorkspaceId(instance.getWorkspaceId()) + .withMachineName(instance.getConfig().getName())); + } + } + } + } + } + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/CheEnvironmentValidator.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/CheEnvironmentValidator.java new file mode 100644 index 0000000000..467732d9b2 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/CheEnvironmentValidator.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.environment.server; + +import com.google.common.base.Joiner; + +import org.eclipse.che.api.core.model.machine.MachineConfig; +import org.eclipse.che.api.core.model.machine.ServerConf; +import org.eclipse.che.api.core.model.workspace.Environment; +import org.eclipse.che.api.machine.server.MachineInstanceProviders; + +import javax.inject.Inject; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.regex.Pattern; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.lang.String.format; + +/** + * Validates description of environment of workspace. + * + * @author Alexander Garagatyi + */ +public class CheEnvironmentValidator { + /* machine name must contain only {a-zA-Z0-9_-} characters and it's needed for validation machine names */ + private static final Pattern MACHINE_NAME_PATTERN = Pattern.compile("^/?[a-zA-Z0-9_-]+$"); + private static final Pattern SERVER_PORT = Pattern.compile("[1-9]+[0-9]*/(?:tcp|udp)"); + private static final Pattern SERVER_PROTOCOL = Pattern.compile("[a-z][a-z0-9-+.]*"); + + private final MachineInstanceProviders machineInstanceProviders; + + @Inject + public CheEnvironmentValidator(MachineInstanceProviders machineInstanceProviders) { + this.machineInstanceProviders = machineInstanceProviders; + } + + public void validate(Environment env) throws IllegalArgumentException { + String envName = env.getName(); + checkArgument(envName != null && !envName.isEmpty(), + "Environment name should not be neither null nor empty"); + checkArgument(env.getMachineConfigs() != null && !env.getMachineConfigs().isEmpty(), + "Environment '%s' should contain at least 1 machine", + envName); + + final long devCount = env.getMachineConfigs() + .stream() + .filter(MachineConfig::isDev) + .count(); + checkArgument(devCount == 1, + "Environment '%s' should contain exactly 1 dev machine, but contains '%d'", + envName, + devCount); + for (MachineConfig machineCfg : env.getMachineConfigs()) { + validateMachine(machineCfg, envName); + } + } + + private void validateMachine(MachineConfig machineCfg, String envName) throws IllegalArgumentException { + String machineName = machineCfg.getName(); + checkArgument(!isNullOrEmpty(machineName), "Environment '%s' contains machine with null or empty name", envName); + checkArgument(MACHINE_NAME_PATTERN.matcher(machineName).matches(), + "Environment '%s' contains machine with invalid name '%s'", envName, machineName); + checkNotNull(machineCfg.getSource(), "Machine '%s' in environment '%s' doesn't have source", machineName, envName); + checkArgument(machineCfg.getSource().getContent() != null || machineCfg.getSource().getLocation() != null, + "Source of machine '%s' in environment '%s' must contain location or content", machineName, envName); + checkArgument(machineCfg.getSource().getContent() == null || machineCfg.getSource().getLocation() == null, + "Source of machine '%s' in environment '%s' contains mutually exclusive fields location and content", + machineName, envName); + checkArgument(machineInstanceProviders.hasProvider(machineCfg.getType()), + "Type '%s' of machine '%s' in environment '%s' is not supported. Supported values are: %s.", + machineCfg.getType(), + machineName, + envName, + Joiner.on(", ").join(machineInstanceProviders.getProviderTypes())); + + if (machineCfg.getSource().getType().equals("dockerfile") && machineCfg.getSource().getLocation() != null) { + try { + final String protocol = new URL(machineCfg.getSource().getLocation()).getProtocol(); + checkArgument(protocol.equals("http") || protocol.equals("https"), + "Environment '%s' contains machine '%s' with invalid source location protocol: %s", + envName, + machineName, + machineCfg.getSource().getLocation()); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(format("Environment '%s' contains machine '%s' with invalid source location: '%s'", + envName, + machineName, + machineCfg.getSource().getLocation())); + } + } + for (ServerConf serverConf : machineCfg.getServers()) { + checkArgument(serverConf.getPort() != null && SERVER_PORT.matcher(serverConf.getPort()).matches(), + "Machine '%s' in environment '%s' contains server conf with invalid port '%s'", + machineName, + envName, + serverConf.getPort()); + checkArgument(serverConf.getProtocol() == null || SERVER_PROTOCOL.matcher(serverConf.getProtocol()).matches(), + "Machine '%s' in environment '%s' contains server conf with invalid protocol '%s'", + machineName, + envName, + serverConf.getProtocol()); + } + for (Map.Entry envVariable : machineCfg.getEnvVariables().entrySet()) { + checkArgument(!isNullOrEmpty(envVariable.getKey()), + "Machine '%s' in environment '%s' contains environment variable with null or empty name", + machineName, + envName); + checkNotNull(envVariable.getValue(), + "Machine '%s' in environment '%s' contains environment variable '%s' with null value", + machineName, + envName, + envVariable.getKey()); + } + } + + /** + * Checks that object reference is not null, throws {@link IllegalArgumentException} otherwise. + * + *

Exception uses error message built from error message template and error message parameters. + */ + private static void checkNotNull(Object object, String errorMessageTemplate, Object... errorMessageParams) { + if (object == null) { + throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageParams)); + } + } + + /** + * Checks that expression is true, throws {@link IllegalArgumentException} otherwise. + * + *

Exception uses error message built from error message template and error message parameters. + */ + private static void checkArgument(boolean expression, String errorMessage) { + if (!expression) { + throw new IllegalArgumentException(errorMessage); + } + } + + /** + * Checks that expression is true, throws {@link IllegalArgumentException} otherwise. + * + *

Exception uses error message built from error message template and error message parameters. + */ + private static void checkArgument(boolean expression, String errorMessageTemplate, Object... errorMessageParams) + throws IllegalArgumentException { + if (!expression) { + throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageParams)); + } + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/MachineProcessManager.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/MachineProcessManager.java new file mode 100644 index 0000000000..96e04025bf --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/MachineProcessManager.java @@ -0,0 +1,282 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.environment.server; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import org.eclipse.che.api.core.BadRequestException; +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.ForbiddenException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.model.machine.Command; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.util.CompositeLineConsumer; +import org.eclipse.che.api.core.util.FileLineConsumer; +import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.core.util.WebsocketLineConsumer; +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.machine.server.spi.Instance; +import org.eclipse.che.api.machine.server.spi.InstanceProcess; +import org.eclipse.che.api.machine.shared.dto.event.MachineProcessEvent; +import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.eclipse.che.dto.server.DtoFactory.newDto; + +/** + * Facade for Machine process operations. + * + * @author gazarenkov + * @author Alexander Garagatyi + * @author Yevhenii Voevodin + */ +@Singleton +public class MachineProcessManager { + private static final Logger LOG = LoggerFactory.getLogger(MachineProcessManager.class); + + private final File machineLogsDir; + private final CheEnvironmentEngine environmentEngine; + private final EventService eventService; + + @VisibleForTesting + final ExecutorService executor; + + @Inject + public MachineProcessManager(@Named("machine.logs.location") String machineLogsDir, + EventService eventService, + CheEnvironmentEngine environmentEngine) { + this.eventService = eventService; + this.machineLogsDir = new File(machineLogsDir); + this.environmentEngine = environmentEngine; + + executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("MachineProcessManager-%d") + .setDaemon(false) + .build()); + } + + /** + * Execute a command in machine + * + * @param machineId + * id of the machine where command should be executed + * @param command + * command that should be executed in the machine + * @return {@link org.eclipse.che.api.machine.server.spi.InstanceProcess} that represents started process in machine + * @throws NotFoundException + * if machine with specified id not found + * @throws BadRequestException + * if value of required parameter is invalid + * @throws MachineException + * if other error occur + */ + public InstanceProcess exec(String workspaceId, + String machineId, + Command command, + @Nullable String outputChannel) + throws NotFoundException, MachineException, BadRequestException { + requiredNotNull(machineId, "Machine ID is required"); + requiredNotNull(command, "Command is required"); + requiredNotNull(command.getCommandLine(), "Command line is required"); + requiredNotNull(command.getName(), "Command name is required"); + requiredNotNull(command.getType(), "Command type is required"); + + final Instance machine = environmentEngine.getMachine(workspaceId, machineId); + final InstanceProcess instanceProcess = machine.createProcess(command, outputChannel); + final int pid = instanceProcess.getPid(); + + final LineConsumer processLogger = getProcessLogger(machineId, pid, outputChannel); + + executor.execute(ThreadLocalPropagateContext.wrap(() -> { + try { + eventService.publish(newDto(MachineProcessEvent.class) + .withEventType(MachineProcessEvent.EventType.STARTED) + .withMachineId(machineId) + .withProcessId(pid)); + + instanceProcess.start(processLogger); + + eventService.publish(newDto(MachineProcessEvent.class) + .withEventType(MachineProcessEvent.EventType.STOPPED) + .withMachineId(machineId) + .withProcessId(pid)); + } catch (ConflictException | MachineException error) { + eventService.publish(newDto(MachineProcessEvent.class) + .withEventType(MachineProcessEvent.EventType.ERROR) + .withMachineId(machineId) + .withProcessId(pid) + .withError(error.getLocalizedMessage())); + + try { + processLogger.writeLine(String.format("[ERROR] %s", error.getMessage())); + } catch (IOException ignored) { + } + } finally { + try { + processLogger.close(); + } catch (IOException ignored) { + } + } + })); + return instanceProcess; + } + + /** + * Get list of active processes from specific machine + * + * @param machineId + * id of machine to get processes information from + * @return list of {@link org.eclipse.che.api.machine.server.spi.InstanceProcess} + * @throws NotFoundException + * if machine with specified id not found + * @throws MachineException + * if other error occur + */ + public List getProcesses(String workspaceId, String machineId) throws NotFoundException, MachineException { + return environmentEngine.getMachine(workspaceId, machineId).getProcesses(); + } + + /** + * Stop process in machine + * + * @param machineId + * if of the machine where process should be stopped + * @param pid + * id of the process that should be stopped in machine + * @throws NotFoundException + * if machine or process with specified id not found + * @throws ForbiddenException + * if process is finished already + * @throws MachineException + * if other error occur + */ + public void stopProcess(String workspaceId, + String machineId, + int pid) throws NotFoundException, MachineException, ForbiddenException { + final InstanceProcess process = environmentEngine.getMachine(workspaceId, machineId).getProcess(pid); + if (!process.isAlive()) { + throw new ForbiddenException("Process finished already"); + } + + process.kill(); + + eventService.publish(newDto(MachineProcessEvent.class) + .withEventType(MachineProcessEvent.EventType.STOPPED) + .withMachineId(machineId) + .withProcessId(pid)); + } + + /** + * Gets process reader from machine by specified id. + * + * @param machineId + * machine id whose process reader will be returned + * @param pid + * process id + * @return reader for specified process on machine + * @throws NotFoundException + * if machine with specified id not found + * @throws MachineException + * if other error occur + */ + public Reader getProcessLogReader(String machineId, int pid) throws NotFoundException, MachineException { + final File processLogsFile = getProcessLogsFile(machineId, pid); + if (processLogsFile.isFile()) { + try { + return Files.newBufferedReader(processLogsFile.toPath(), Charset.defaultCharset()); + } catch (IOException e) { + throw new MachineException( + String.format("Unable read log file for process '%s' of machine '%s'. %s", pid, machineId, e.getMessage())); + } + } + throw new NotFoundException(String.format("Logs for process '%s' of machine '%s' are not available", pid, machineId)); + } + + private File getProcessLogsFile(String machineId, int pid) { + return new File(new File(machineLogsDir, machineId), Integer.toString(pid)); + } + + private FileLineConsumer getProcessFileLogger(String machineId, int pid) throws MachineException { + try { + return new FileLineConsumer(getProcessLogsFile(machineId, pid)); + } catch (IOException e) { + throw new MachineException( + String.format("Unable create log file for process '%s' of machine '%s'. %s", pid, machineId, e.getMessage())); + } + } + + @VisibleForTesting + LineConsumer getProcessLogger(String machineId, int pid, String outputChannel) throws MachineException { + return getLogger(getProcessFileLogger(machineId, pid), outputChannel); + } + + private LineConsumer getLogger(LineConsumer fileLogger, String outputChannel) throws MachineException { + if (outputChannel != null) { + return new CompositeLineConsumer(fileLogger, new WebsocketLineConsumer(outputChannel)); + } + return fileLogger; + } + + /** + * Checks object reference is not {@code null} + * + * @param object + * object reference to check + * @param message + * used as subject of exception message "{subject} required" + * @throws org.eclipse.che.api.core.BadRequestException + * when object reference is {@code null} + */ + private void requiredNotNull(Object object, String message) throws BadRequestException { + if (object == null) { + throw new BadRequestException(message + " required"); + } + } + + @PreDestroy + private void cleanup() { + boolean interrupted = false; + + executor.shutdown(); + + try { + if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { + executor.shutdownNow(); + if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { + LOG.warn("Unable terminate main pool"); + } + } + } catch (InterruptedException e) { + interrupted = true; + executor.shutdownNow(); + } + + if (interrupted) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/MachineService.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/MachineService.java new file mode 100644 index 0000000000..6e7ac639a5 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/MachineService.java @@ -0,0 +1,333 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.environment.server; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; + +import com.google.common.io.CharStreams; + +import org.eclipse.che.api.core.BadRequestException; +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.ForbiddenException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.core.rest.Service; +import org.eclipse.che.api.machine.server.DtoConverter; +import org.eclipse.che.api.machine.shared.dto.CommandDto; +import org.eclipse.che.api.machine.shared.dto.MachineConfigDto; +import org.eclipse.che.api.machine.shared.dto.MachineDto; +import org.eclipse.che.api.machine.shared.dto.MachineProcessDto; +import org.eclipse.che.api.workspace.server.WorkspaceManager; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; + +import javax.inject.Inject; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import java.io.IOException; +import java.io.Reader; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Machine API + * + * @author Alexander Garagatyi + * @author Anton Korneta + */ +@Api(value = "/machine", description = "Machine REST API") +@Path("/workspace/{workspaceId}/machine") +public class MachineService extends Service { + private final MachineProcessManager machineProcessManager; + private final MachineServiceLinksInjector linksInjector; + private final WorkspaceManager workspaceManager; + + @Inject + public MachineService(MachineProcessManager machineProcessManager, + MachineServiceLinksInjector linksInjector, + WorkspaceManager workspaceManager) { + this.machineProcessManager = machineProcessManager; + this.linksInjector = linksInjector; + this.workspaceManager = workspaceManager; + } + + @GET + @Path("/{machineId}") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Get machine by ID") + @ApiResponses({@ApiResponse(code = 200, message = "The response contains requested machine entity"), + @ApiResponse(code = 404, message = "Machine with specified id does not exist"), + @ApiResponse(code = 500, message = "Internal server error occurred")}) + @Deprecated + public MachineDto getMachineById(@ApiParam(value = "Workspace ID") + @PathParam("workspaceId") + String workspaceId, + @ApiParam(value = "Machine ID") + @PathParam("machineId") + String machineId) + throws ServerException, + ForbiddenException, + NotFoundException { + + final Machine machine = workspaceManager.getMachineInstance(workspaceId, machineId); + return linksInjector.injectLinks(DtoConverter.asDto(machine), getServiceContext()); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Get all machines of workspace with specified ID", + response = MachineDto.class, + responseContainer = "List") + @ApiResponses({@ApiResponse(code = 200, message = "The response contains requested list of machine entities"), + @ApiResponse(code = 400, message = "Workspace ID is not specified"), + @ApiResponse(code = 500, message = "Internal server error occurred")}) + @Deprecated + public List getMachines(@ApiParam(value = "Workspace ID") + @PathParam("workspaceId") + String workspaceId) + throws ServerException, + BadRequestException, + NotFoundException { + + requiredNotNull(workspaceId, "Parameter workspace"); + + WorkspaceImpl workspace = workspaceManager.getWorkspace(workspaceId); + if (workspace.getRuntime() == null) { + return Collections.emptyList(); + } else { + return workspace.getRuntime() + .getMachines() + .stream() + .map(DtoConverter::asDto) + .map(machineDto -> linksInjector.injectLinks(machineDto, getServiceContext())) + .collect(Collectors.toList()); + } + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Create a new machine based on the configuration", + notes = "This operation can be performed only by authorized user") + @ApiResponses({@ApiResponse(code = 204, message = "The machine successfully created"), + @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"), + @ApiResponse(code = 403, message = "The user does not have access to create the new machine"), + @ApiResponse(code = 409, message = "Conflict error occurred during the machine creation" + + "(e.g. The machine with such name already exists)." + + "Workspace is not in RUNNING state"), + @ApiResponse(code = 500, message = "Internal server error occurred")}) + public void startMachine(@ApiParam("The workspace id") + @PathParam("workspaceId") + String workspaceId, + @ApiParam(value = "The new machine configuration", required = true) + MachineConfigDto machineConfig) throws ForbiddenException, + NotFoundException, + ServerException, + ConflictException, + BadRequestException { + requiredNotNull(machineConfig, "Machine configuration"); + requiredNotNull(machineConfig.getType(), "Machine type"); + requiredNotNull(machineConfig.getSource(), "Machine source"); + requiredNotNull(machineConfig.getSource().getType(), "Machine source type"); + // definition of source should come either with a content or with location + requiredOnlyOneNotNull(machineConfig.getSource().getLocation(), machineConfig.getSource().getContent(), + "Machine source should provide either location or content"); + + workspaceManager.startMachine(machineConfig, workspaceId); + } + + @DELETE + @Path("/{machineId}") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Stop machine") + @ApiResponses({@ApiResponse(code = 204, message = "Machine was successfully stopped"), + @ApiResponse(code = 404, message = "Machine with specified id does not exist"), + @ApiResponse(code = 500, message = "Internal server error occurred")}) + public void stopMachine(@ApiParam(value = "Workspace ID") + @PathParam("workspaceId") String workspaceId, + @ApiParam(value = "Machine ID") + @PathParam("machineId") String machineId) throws NotFoundException, + ServerException, + ForbiddenException, + ConflictException { + workspaceManager.stopMachine(workspaceId, machineId); + } + + @POST + @Path("/{machineId}/command") + @Consumes(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Start specified command in machine") + @ApiResponses({@ApiResponse(code = 200, message = "The response contains entity of created machine process"), + @ApiResponse(code = 400, message = "Command entity is invalid"), + @ApiResponse(code = 404, message = "Machine with specified ID does not exist"), + @ApiResponse(code = 500, message = "Internal server error occurred")}) + public MachineProcessDto executeCommandInMachine(@ApiParam(value = "Workspace ID") + @PathParam("workspaceId") + String workspaceId, + @ApiParam(value = "Machine ID") + @PathParam("machineId") + String machineId, + @ApiParam(value = "Command to execute", required = true) + final CommandDto command, + @ApiParam(value = "Channel for command output") + @QueryParam("outputChannel") + String outputChannel) + throws NotFoundException, + ServerException, + ForbiddenException, + BadRequestException { + + requiredNotNull(command, "Command description"); + requiredNotNull(command.getCommandLine(), "Commandline"); + return linksInjector.injectLinks(DtoConverter.asDto(machineProcessManager.exec(workspaceId, + machineId, + command, + outputChannel)), + workspaceId, + machineId, + getServiceContext()); + } + + @GET + @Path("/{machineId}/process") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Get processes of machine", + response = MachineProcessDto.class, + responseContainer = "List") + @ApiResponses({@ApiResponse(code = 200, message = "The response contains machine process entities"), + @ApiResponse(code = 404, message = "Machine with specified ID does not exist"), + @ApiResponse(code = 500, message = "Internal server error occurred")}) + public List getProcesses(@ApiParam(value = "Workspace ID") + @PathParam("workspaceId") + String workspaceId, + @ApiParam(value = "Machine ID") + @PathParam("machineId") + String machineId) + throws NotFoundException, + ServerException, + ForbiddenException { + + return machineProcessManager.getProcesses(workspaceId, machineId) + .stream() + .map(DtoConverter::asDto) + .map(machineProcess -> linksInjector.injectLinks(machineProcess, + workspaceId, + machineId, + getServiceContext())) + .collect(Collectors.toList()); + } + + @DELETE + @Path("/{machineId}/process/{processId}") + @ApiOperation(value = "Stop process in machine") + @ApiResponses({@ApiResponse(code = 204, message = "Process was successfully stopped"), + @ApiResponse(code = 404, message = "Machine with specified ID does not exist"), + @ApiResponse(code = 500, message = "Internal server error occurred")}) + public void stopProcess(@ApiParam(value = "Workspace ID") + @PathParam("workspaceId") + String workspaceId, + @ApiParam(value = "Machine ID") + @PathParam("machineId") + String machineId, + @ApiParam(value = "Process ID") + @PathParam("processId") + int processId) + throws NotFoundException, + ForbiddenException, + ServerException { + + machineProcessManager.stopProcess(workspaceId, machineId, processId); + } + + @GET + @Path("/{machineId}/process/{pid}/logs") + @Produces(MediaType.TEXT_PLAIN) + @ApiOperation(value = "Get logs of machine process") + @ApiResponses({@ApiResponse(code = 200, message = "The response contains logs"), + @ApiResponse(code = 404, message = "Machine or process with specified ID does not exist"), + @ApiResponse(code = 500, message = "Internal server error occurred")}) + public void getProcessLogs(@ApiParam(value = "Workspace ID") + @PathParam("workspaceId") + String workspaceId, + @ApiParam(value = "Machine ID") + @PathParam("machineId") + String machineId, + @ApiParam(value = "Process ID") + @PathParam("pid") + int pid, + @Context + HttpServletResponse httpServletResponse) + throws NotFoundException, + ForbiddenException, + ServerException, + IOException { + + addLogsToResponse(machineProcessManager.getProcessLogReader(machineId, pid), httpServletResponse); + } + + /** + * Checks only one of the given object reference is {@code null} + * + * @param object1 + * object reference to check + * @param object2 + * object reference to check + * @param subject + * used as subject of exception message "{subject} required" + * @throws BadRequestException + * when objects are both null or have both a value reference is {@code null} + */ + private void requiredOnlyOneNotNull(Object object1, Object object2, String subject) throws BadRequestException { + if (object1 == null && object2 == null) { + throw new BadRequestException(subject + " required"); + } + if (object1 != null && object2 != null) { + throw new BadRequestException(subject + " required"); + } + } + + private void addLogsToResponse(Reader logsReader, HttpServletResponse httpServletResponse) throws IOException { + // Response is written directly to the servlet request stream + httpServletResponse.setContentType("text/plain"); + CharStreams.copy(logsReader, httpServletResponse.getWriter()); + httpServletResponse.getWriter().flush(); + } + + /** + * Checks object reference is not {@code null} + * + * @param object + * object reference to check + * @param subject + * used as subject of exception message "{subject} required" + * @throws BadRequestException + * when object reference is {@code null} + */ + private void requiredNotNull(Object object, String subject) throws BadRequestException { + if (object == null) { + throw new BadRequestException(subject + " required"); + } + } +} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineServiceLinksInjector.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/MachineServiceLinksInjector.java similarity index 70% rename from wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineServiceLinksInjector.java rename to wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/MachineServiceLinksInjector.java index a0a498c67d..0a1a844a1c 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineServiceLinksInjector.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/MachineServiceLinksInjector.java @@ -8,7 +8,7 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.machine.server; +package org.eclipse.che.api.environment.server; import com.google.common.collect.Lists; @@ -19,7 +19,6 @@ import org.eclipse.che.api.machine.shared.Constants; import org.eclipse.che.api.machine.shared.dto.MachineDto; import org.eclipse.che.api.machine.shared.dto.MachineProcessDto; import org.eclipse.che.api.machine.shared.dto.ServerDto; -import org.eclipse.che.api.machine.shared.dto.SnapshotDto; import javax.inject.Singleton; import javax.ws.rs.HttpMethod; @@ -35,8 +34,8 @@ import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.MediaType.TEXT_PLAIN; import static org.eclipse.che.api.core.util.LinksHelper.createLink; import static org.eclipse.che.api.machine.shared.Constants.ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE; -import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL; import static org.eclipse.che.api.machine.shared.Constants.ENVIRONMENT_STATUS_CHANNEL_TEMPLATE; +import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL; import static org.eclipse.che.api.machine.shared.Constants.TERMINAL_REFERENCE; import static org.eclipse.che.dto.server.DtoFactory.cloneDto; import static org.eclipse.che.dto.server.DtoFactory.newDto; @@ -56,67 +55,41 @@ public class MachineServiceLinksInjector { links.add(createLink(HttpMethod.GET, uriBuilder.clone() .path(MachineService.class, "getMachineById") - .build(machine.getId()) + .build(machine.getWorkspaceId(), machine.getId()) .toString(), APPLICATION_JSON, "self link")); links.add(createLink(HttpMethod.GET, uriBuilder.clone() .path(MachineService.class, "getMachines") - .build() + .build(machine.getWorkspaceId()) .toString(), null, APPLICATION_JSON, - Constants.LINK_REL_GET_MACHINES, - newDto(LinkParameter.class).withName("workspace") - .withRequired(true) - .withDefaultValue(machine.getWorkspaceId()))); + Constants.LINK_REL_GET_MACHINES)); links.add(createLink(HttpMethod.DELETE, uriBuilder.clone() - .path(MachineService.class, "destroyMachine") - .build(machine.getId()) + .path(MachineService.class, "stopMachine") + .build(machine.getWorkspaceId(), machine.getId()) .toString(), Constants.LINK_REL_DESTROY_MACHINE)); - links.add(createLink(HttpMethod.GET, - uriBuilder.clone() - .path(MachineService.class, "getSnapshots") - .build() - .toString(), - null, - APPLICATION_JSON, - Constants.LINK_REL_GET_SNAPSHOTS, - newDto(LinkParameter.class).withName("workspace") - .withRequired(true) - .withDefaultValue(machine.getWorkspaceId()))); - links.add(createLink(HttpMethod.POST, - uriBuilder.clone() - .path(MachineService.class, "saveSnapshot") - .build(machine.getId()) - .toString(), - APPLICATION_JSON, - APPLICATION_JSON, - Constants.LINK_REL_SAVE_SNAPSHOT)); links.add(createLink(HttpMethod.POST, uriBuilder.clone() .path(MachineService.class, "executeCommandInMachine") - .build(machine.getId()) + .build(machine.getWorkspaceId(), machine.getId()) .toString(), APPLICATION_JSON, APPLICATION_JSON, Constants.LINK_REL_EXECUTE_COMMAND, newDto(LinkParameter.class).withName("outputChannel") .withRequired(false))); + URI getProcessesUri = uriBuilder.clone() + .path(MachineService.class, "getProcesses") + .build(machine.getWorkspaceId(), machine.getId()); links.add(createLink(HttpMethod.GET, - uriBuilder.clone() - .path(MachineService.class, "getProcesses") - .build(machine.getId()) - .toString(), + getProcessesUri.toString(), APPLICATION_JSON, Constants.LINK_REL_GET_PROCESSES)); - final URI getLogsUri = uriBuilder.clone() - .path(MachineService.class, "getMachineLogs") - .build(machine.getId()); - links.add(createLink(HttpMethod.GET, getLogsUri.toString(), TEXT_PLAIN, Constants.LINK_REL_GET_MACHINE_LOGS)); injectTerminalLink(machine, serviceContext, links); @@ -125,7 +98,7 @@ public class MachineServiceLinksInjector { serviceContext.getBaseUriBuilder() .path("ws") .path(machine.getWorkspaceId()) - .scheme("https".equals(getLogsUri.getScheme()) ? "wss" : "ws") + .scheme("https".equals(getProcessesUri.getScheme()) ? "wss" : "ws") .build() .toString(), null); @@ -163,41 +136,39 @@ public class MachineServiceLinksInjector { } } - public MachineProcessDto injectLinks(MachineProcessDto process, String machineId, ServiceContext serviceContext) { + public MachineProcessDto injectLinks(MachineProcessDto process, + String workspaceId, + String machineId, + ServiceContext serviceContext) { final UriBuilder uriBuilder = serviceContext.getServiceUriBuilder(); final List links = Lists.newArrayListWithExpectedSize(3); links.add(createLink(HttpMethod.DELETE, uriBuilder.clone() .path(MachineService.class, "stopProcess") - .build(machineId, process.getPid()) + .build(workspaceId, + machineId, + process.getPid()) .toString(), Constants.LINK_REL_STOP_PROCESS)); links.add(createLink(HttpMethod.GET, uriBuilder.clone() .path(MachineService.class, "getProcessLogs") - .build(machineId, process.getPid()) + .build(workspaceId, + machineId, + process.getPid()) .toString(), TEXT_PLAIN, Constants.LINK_REL_GET_PROCESS_LOGS)); links.add(createLink(HttpMethod.GET, uriBuilder.clone() .path(MachineService.class, "getProcesses") - .build(machineId) + .build(workspaceId, + machineId) .toString(), APPLICATION_JSON, Constants.LINK_REL_GET_PROCESSES)); return process.withLinks(links); } - - public SnapshotDto injectLinks(SnapshotDto snapshot, ServiceContext serviceContext) { - final UriBuilder uriBuilder = serviceContext.getServiceUriBuilder(); - return snapshot.withLinks(singletonList(createLink(HttpMethod.DELETE, - uriBuilder.clone() - .path(MachineService.class, "removeSnapshot") - .build(snapshot.getId()) - .toString(), - Constants.LINK_REL_REMOVE_SNAPSHOT))); - } } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/NoOpMachineInstance.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/NoOpMachineInstance.java new file mode 100644 index 0000000000..d7ab669975 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/NoOpMachineInstance.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.environment.server; + +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.model.machine.Command; +import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.core.model.machine.MachineSource; +import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.machine.server.model.impl.MachineRuntimeInfoImpl; +import org.eclipse.che.api.machine.server.spi.Instance; +import org.eclipse.che.api.machine.server.spi.InstanceNode; +import org.eclipse.che.api.machine.server.spi.InstanceProcess; +import org.eclipse.che.api.machine.server.spi.impl.AbstractInstance; + +import java.util.Collections; +import java.util.List; + +/** + * @author Alexander Garagatyi + */ +public class NoOpMachineInstance extends AbstractInstance { + public NoOpMachineInstance(Machine machine) { + super(machine); + } + + @Override + public MachineRuntimeInfoImpl getRuntime() { + return null; + } + + @Override + public LineConsumer getLogger() { + return null; + } + + @Override + public InstanceProcess getProcess(int pid) throws NotFoundException, MachineException { + return null; + } + + @Override + public List getProcesses() throws MachineException { + return Collections.emptyList(); + } + + @Override + public InstanceProcess createProcess(Command command, String outputChannel) throws MachineException { + throw new MachineException("This machine has state that doesn't support process creation"); + } + + @Override + public MachineSource saveToSnapshot() throws MachineException { + throw new MachineException("This machine has state that doesn't support saving its state"); + } + + @Override + public void destroy() throws MachineException { + } + + @Override + public InstanceNode getNode() { + return null; + } + + @Override + public String readFileContent(String filePath, int startFrom, int limit) throws MachineException { + throw new MachineException("This machine has state that doesn't support process reading files"); + } + + @Override + public void copy(Instance sourceMachine, String sourcePath, String targetPath, boolean overwriteDirNonDir) + throws MachineException { + throw new MachineException("This machine has state that doesn't support copying of files"); + } + + @Override + public void copy(String sourcePath, String targetPath) throws MachineException { + throw new MachineException("This machine has state that doesn't support copying of files"); + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/exception/EnvironmentNotRunningException.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/exception/EnvironmentNotRunningException.java new file mode 100644 index 0000000000..11770f01c1 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/exception/EnvironmentNotRunningException.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.environment.server.exception; + +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.rest.shared.dto.ServiceError; + +/** + * Exception thrown in case environment stop is called but no matching environment is running. + * + * @author Alexander Garagatyi + */ +public class EnvironmentNotRunningException extends NotFoundException { + public EnvironmentNotRunningException(String message) { + super(message); + } + + public EnvironmentNotRunningException(ServiceError serviceError) { + super(serviceError); + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidator.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidator.java index f0bef907f7..ddc17174b0 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidator.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidator.java @@ -10,21 +10,15 @@ *******************************************************************************/ package org.eclipse.che.api.workspace.server; -import com.google.common.base.Joiner; - import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.model.machine.Command; -import org.eclipse.che.api.core.model.machine.MachineConfig; -import org.eclipse.che.api.core.model.machine.ServerConf; import org.eclipse.che.api.core.model.workspace.Environment; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; -import org.eclipse.che.api.machine.server.MachineInstanceProviders; +import org.eclipse.che.api.environment.server.CheEnvironmentValidator; import javax.inject.Inject; import javax.inject.Singleton; -import java.net.MalformedURLException; -import java.net.URL; import java.util.Map; import java.util.regex.Pattern; @@ -39,15 +33,13 @@ import static java.lang.String.format; @Singleton public class DefaultWorkspaceValidator implements WorkspaceValidator { /* should contain [3, 20] characters, first and last character is letter or digit, available characters {A-Za-z0-9.-_}*/ - private static final Pattern WS_NAME = Pattern.compile("[a-zA-Z0-9][-_.a-zA-Z0-9]{1,18}[a-zA-Z0-9]"); - private static final Pattern SERVER_PORT = Pattern.compile("[1-9]+[0-9]*/(?:tcp|udp)"); - private static final Pattern SERVER_PROTOCOL = Pattern.compile("[a-z][a-z0-9-+.]*"); - - private final MachineInstanceProviders machineInstanceProviders; - + private static final Pattern WS_NAME = Pattern.compile("[a-zA-Z0-9][-_.a-zA-Z0-9]{1,18}[a-zA-Z0-9]"); + + private final CheEnvironmentValidator environmentValidator; + @Inject - public DefaultWorkspaceValidator(MachineInstanceProviders machineInstanceProviders) { - this.machineInstanceProviders = machineInstanceProviders; + public DefaultWorkspaceValidator(CheEnvironmentValidator environmentValidator) { + this.environmentValidator = environmentValidator; } @Override @@ -68,13 +60,18 @@ public class DefaultWorkspaceValidator implements WorkspaceValidator { //environments checkArgument(!isNullOrEmpty(config.getDefaultEnv()), "Workspace default environment name required"); + checkNotNull(config.getEnvironments(), "Workspace should contain at least one environment"); checkArgument(config.getEnvironments() .stream() .anyMatch(env -> config.getDefaultEnv().equals(env.getName())), "Workspace default environment configuration required"); for (Environment environment : config.getEnvironments()) { - validateEnv(environment, config.getName()); + try { + environmentValidator.validate(environment); + } catch (IllegalArgumentException e) { + throw new BadRequestException(e.getLocalizedMessage()); + } } //commands @@ -102,68 +99,6 @@ public class DefaultWorkspaceValidator implements WorkspaceValidator { } } - private void validateEnv(Environment environment, String workspaceName) throws BadRequestException { - final String envName = environment.getName(); - checkArgument(!isNullOrEmpty(envName), "Environment name should be neither null nor empty"); - - //machine configs - checkArgument(!environment.getMachineConfigs().isEmpty(), "Environment '%s' should contain at least 1 machine", envName); - - final long devCount = environment.getMachineConfigs() - .stream() - .filter(MachineConfig::isDev) - .count(); - checkArgument(devCount == 1, - "Environment should contain exactly 1 dev machine, but '%s' contains '%d'", - envName, - devCount); - for (MachineConfig machineCfg : environment.getMachineConfigs()) { - validateMachine(machineCfg, envName); - } - } - - private void validateMachine(MachineConfig machineCfg, String envName) throws BadRequestException { - checkArgument(!isNullOrEmpty(machineCfg.getName()), "Environment %s contains machine with null or empty name", envName); - checkNotNull(machineCfg.getSource(), "Environment " + envName + " contains machine without source"); - checkArgument(!(machineCfg.getSource().getContent() == null && machineCfg.getSource().getLocation() == null), - "Environment " + envName + " contains machine with source but this source doesn't define a location or content"); - - - checkArgument(machineInstanceProviders.hasProvider(machineCfg.getType()), - "Type %s of machine %s in environment %s is not supported. Supported values: %s.", - machineCfg.getType(), - machineCfg.getName(), - envName, - Joiner.on(", ").join(machineInstanceProviders.getProviderTypes())); - - if (machineCfg.getSource().getType().equals("dockerfile") && machineCfg.getSource().getLocation() != null) { - try { - final String protocol = new URL(machineCfg.getSource().getLocation()).getProtocol(); - checkArgument(protocol.equals("http") || protocol.equals("https"), - "Environment " + envName + " contains machine with invalid source location protocol: " + - machineCfg.getSource().getLocation()); - } catch (MalformedURLException e) { - throw new BadRequestException("Environment " + envName + " contains machine with invalid source location: " + - machineCfg.getSource().getLocation()); - } - } - - for (ServerConf serverConf : machineCfg.getServers()) { - checkArgument(serverConf.getPort() != null && SERVER_PORT.matcher(serverConf.getPort()).matches(), - "Machine %s contains server conf with invalid port %s", - machineCfg.getName(), - serverConf.getPort()); - checkArgument(serverConf.getProtocol() == null || SERVER_PROTOCOL.matcher(serverConf.getProtocol()).matches(), - "Machine %s contains server conf with invalid protocol %s", - machineCfg.getName(), - serverConf.getProtocol()); - } - for (Map.Entry envVariable : machineCfg.getEnvVariables().entrySet()) { - checkArgument(!isNullOrEmpty(envVariable.getKey()), "Machine %s contains environment variable with null or empty name"); - checkNotNull(envVariable.getValue(), "Machine %s contains environment variable with null value"); - } - } - /** * Checks that object reference is not null, throws {@link BadRequestException} * in the case of null {@code object} with given {@code message}. diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DtoConverter.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DtoConverter.java index 6d1cb6391b..71947920f5 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DtoConverter.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DtoConverter.java @@ -149,11 +149,7 @@ public final class DtoConverter { envDto.withRecipe(newDto(RecipeDto.class).withType(env.getRecipe().getType()) .withScript(env.getRecipe().getScript())); } - return newDto(EnvironmentDto.class).withName(env.getName()) - .withMachineConfigs(env.getMachineConfigs() - .stream() - .map(org.eclipse.che.api.machine.server.DtoConverter::asDto) - .collect(toList())); + return envDto; } /** Converts {@link WorkspaceRuntime} to {@link WorkspaceRuntimeDto}. */ @@ -183,7 +179,7 @@ public final class DtoConverter { .withType(snapshot.getType()) .withWorkspaceId(snapshot.getWorkspaceId()) .withEnvName(snapshot.getEnvName()) - .withMachineName(snapshot.getEnvName()); + .withMachineName(snapshot.getMachineName()); } private DtoConverter() {} diff --git a/plugins/plugin-machine/che-plugin-machine-ext-server/src/main/java/org/eclipse/che/ide/ext/machine/server/RecipeScriptDownloadService.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/RecipeScriptDownloadService.java similarity index 55% rename from plugins/plugin-machine/che-plugin-machine-ext-server/src/main/java/org/eclipse/che/ide/ext/machine/server/RecipeScriptDownloadService.java rename to wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/RecipeScriptDownloadService.java index 3a33afe634..a2819df5db 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-server/src/main/java/org/eclipse/che/ide/ext/machine/server/RecipeScriptDownloadService.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/RecipeScriptDownloadService.java @@ -8,13 +8,12 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.ide.ext.machine.server; - +package org.eclipse.che.api.workspace.server; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.rest.Service; -import org.eclipse.che.api.machine.server.MachineManager; +import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.machine.server.util.RecipeRetriever; import javax.inject.Inject; @@ -27,24 +26,27 @@ import javax.ws.rs.core.MediaType; /** * Service for downloading recipe script for machine. * - * @author Mihail Kuznyetsov. + * @author Mihail Kuznyetsov + * @author Alexander Garagatyi */ @Path("/recipe/script") public class RecipeScriptDownloadService extends Service { - - private final MachineManager machineManager; - private final RecipeRetriever recipeRetriever; + private final WorkspaceManager workspaceManager; + private final RecipeRetriever recipeRetriever; @Inject - public RecipeScriptDownloadService(MachineManager machineManager, RecipeRetriever recipeRetriever) { - this.machineManager = machineManager; + public RecipeScriptDownloadService(WorkspaceManager workspaceManager, RecipeRetriever recipeRetriever) { + this.workspaceManager = workspaceManager; this.recipeRetriever = recipeRetriever; } @GET - @Path("/{machineId}") + @Path("/{workspaceId}/{machineId}") @Produces(MediaType.TEXT_PLAIN) - public String getRecipeScript(@PathParam("machineId") String machineId) throws ServerException, NotFoundException { - return recipeRetriever.getRecipe(machineManager.getMachine(machineId).getConfig()).getScript(); + public String getRecipeScript(@PathParam("workspaceId") String workspaceId, + @PathParam("machineId") String machineId) throws ServerException, + NotFoundException { + Instance machineInstance = workspaceManager.getMachineInstance(workspaceId, machineId); + return recipeRetriever.getRecipe(machineInstance.getConfig()).getScript(); } } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/StripedLocks.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/StripedLocks.java new file mode 100644 index 0000000000..9b1023ee6b --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/StripedLocks.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.Striped; + +import java.io.Closeable; +import java.util.concurrent.locks.ReadWriteLock; + +/** + * Helper class to use striped locks in try-with-resources construction. + *

+ * Examples of usage: + *

+ *     StripedLocks stripedLocks = new StripedLocks(16);
+ *     try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(myKey)) {
+ *         syncedObject.write();
+ *     }
+ *
+ *     try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(myKey)) {
+ *         syncedObject.read();
+ *     }
+ *
+ *     try (StripedLocks.WriteAllLock lock = stripedLocks.acquireWriteAllLock(myKey)) {
+ *         for (ObjectToSync objectToSync : allObjectsToSync) {
+ *             objectToSync.write();
+ *         }
+ *     }
+ * 
+ * + * @author Alexander Garagatyi + */ +// TODO consider usage of plain map with locks instead of Guava's Striped +// TODO consider moving to the util module of Che core +public class StripedLocks { + private final Striped striped; + + public StripedLocks(int stripesCount) { + striped = Striped.readWriteLock(stripesCount); + } + + /** + * Acquire read lock for provided key. + */ + public ReadLock acquireReadLock(String key) { + return new ReadLock(key); + } + + /** + * Acquire write lock for provided key. + */ + public WriteLock acquireWriteLock(String key) { + return new WriteLock(key); + } + + /** + * Acquire write lock for all possible keys. + */ + public WriteAllLock acquireWriteAllLock() { + return new WriteAllLock(); + } + + /** + * Represents read lock for the provided key. + * Can be used as {@link AutoCloseable} to release lock. + */ + public class ReadLock implements Closeable { + private String key; + + private ReadLock(String key) { + this.key = key; + striped.get(key).readLock().lock(); + } + + @Override + public void close() { + striped.get(key).readLock().unlock(); + } + } + + /** + * Represents write lock for the provided key. + * Can be used as {@link AutoCloseable} to release lock. + */ + public class WriteLock implements Closeable { + private String key; + + private WriteLock(String key) { + this.key = key; + striped.get(key).readLock().lock(); + } + + @Override + public void close() { + striped.get(key).readLock().unlock(); + } + } + + /** + * Represents write lock for all possible keys. + * Can be used as {@link AutoCloseable} to release locks. + */ + public class WriteAllLock implements Closeable { + private WriteAllLock() { + for (int i = 0; i < striped.size(); i++) { + striped.getAt(i).writeLock().lock(); + } + } + + @Override + public void close() { + for (int i = 0; i < striped.size(); i++) { + striped.getAt(i).writeLock().unlock(); + } + } + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java index 577c493ea7..64269ebb70 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java @@ -17,7 +17,6 @@ import com.google.inject.Inject; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; -import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.machine.MachineConfig; @@ -25,9 +24,10 @@ 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.MachineManager; +import org.eclipse.che.api.machine.server.dao.SnapshotDao; import org.eclipse.che.api.machine.server.model.impl.MachineImpl; import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; +import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.workspace.server.WorkspaceRuntimes.RuntimeDescriptor; import org.eclipse.che.api.workspace.server.event.WorkspaceCreatedEvent; import org.eclipse.che.api.workspace.server.event.WorkspaceRemovedEvent; @@ -90,9 +90,9 @@ public class WorkspaceManager { private final WorkspaceRuntimes runtimes; private final EventService eventService; private final ExecutorService executor; - private final MachineManager machineManager; private final boolean defaultAutoSnapshot; private final boolean defaultAutoRestore; + private final SnapshotDao snapshotDao; private WorkspaceHooks hooks = new NoopWorkspaceHooks(); @@ -100,15 +100,15 @@ public class WorkspaceManager { public WorkspaceManager(WorkspaceDao workspaceDao, WorkspaceRuntimes workspaceRegistry, EventService eventService, - MachineManager machineManager, @Named("workspace.runtime.auto_snapshot") boolean defaultAutoSnapshot, - @Named("workspace.runtime.auto_restore") boolean defaultAutoRestore) { + @Named("workspace.runtime.auto_restore") boolean defaultAutoRestore, + SnapshotDao snapshotDao) { this.workspaceDao = workspaceDao; this.runtimes = workspaceRegistry; this.eventService = eventService; - this.machineManager = machineManager; this.defaultAutoSnapshot = defaultAutoSnapshot; this.defaultAutoRestore = defaultAutoRestore; + this.snapshotDao = snapshotDao; executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("WorkspaceManager-%d") .setDaemon(true) @@ -262,7 +262,9 @@ public class WorkspaceManager { public List getWorkspaces(String user) throws ServerException { requireNonNull(user, "Required non-null user id"); final List workspaces = workspaceDao.getWorkspaces(user); - workspaces.forEach(this::normalizeState); + for (WorkspaceImpl workspace : workspaces) { + normalizeState(workspace); + } return workspaces; } @@ -283,7 +285,9 @@ public class WorkspaceManager { public List getByNamespace(String namespace) throws ServerException { requireNonNull(namespace, "Required non-null namespace"); final List workspaces = workspaceDao.getByNamespace(namespace); - workspaces.forEach(this::normalizeState); + for (WorkspaceImpl workspace : workspaces) { + normalizeState(workspace); + } return workspaces; } @@ -431,7 +435,6 @@ public class WorkspaceManager { * * @param machineConfig configuration of machine to start * @param workspaceId id of workspace in which machine should be started - * @return starting machine instance * @throws NotFoundException * if machine type from recipe is unsupported * @throws NotFoundException @@ -445,21 +448,18 @@ public class WorkspaceManager { * @throws ServerException * if any other exception occurs during starting */ - public MachineImpl startMachine(MachineConfig machineConfig, String workspaceId) - throws ServerException, - ConflictException, - BadRequestException, - NotFoundException { + public void startMachine(MachineConfig machineConfig, + String workspaceId) throws ServerException, + ConflictException, + BadRequestException, + NotFoundException { final WorkspaceImpl workspace = getWorkspace(workspaceId); if (RUNNING != workspace.getStatus()) { throw new ConflictException(format("Workspace '%s' is not running, new machine can't be started", workspaceId)); } - return machineManager.createMachineAsync(machineConfig, - workspaceId, - workspace.getRuntime().getActiveEnv(), - runtimes.getMachineLogger(workspaceId, machineConfig.getName())); + performAsyncStart(machineConfig, workspaceId); } /** @@ -474,9 +474,13 @@ public class WorkspaceManager { * @throws NotFoundException * when workspace {@code workspaceId} doesn't have runtime */ - public void stopWorkspace(String workspaceId) throws ServerException, NotFoundException, ConflictException { + public void stopWorkspace(String workspaceId) throws ServerException, + NotFoundException, + ConflictException { requireNonNull(workspaceId, "Required non-null workspace id"); - performAsyncStop(normalizeState(workspaceDao.get(workspaceId))); + final WorkspaceImpl workspace = normalizeState(workspaceDao.get(workspaceId)); + checkWorkspaceIsRunning(workspace, "stop"); + performAsyncStop(workspace); } /** @@ -504,12 +508,16 @@ public class WorkspaceManager { * @throws ConflictException * when workspace is not running */ - public void createSnapshot(String workspaceId) throws NotFoundException, ServerException, ConflictException { + public void createSnapshot(String workspaceId) throws NotFoundException, + ServerException, + ConflictException { requireNonNull(workspaceId, "Required non-null workspace id"); final WorkspaceImpl workspace = normalizeState(workspaceDao.get(workspaceId)); checkWorkspaceIsRunning(workspace, "create a snapshot of"); executor.execute(ThreadLocalPropagateContext.wrap(() -> { - createSnapshotSync(workspace.getRuntime(), workspace.getNamespace(), workspaceId); + createSnapshotSync(workspace.getRuntime(), + workspace.getNamespace(), + workspaceId); })); } @@ -530,11 +538,12 @@ public class WorkspaceManager { requireNonNull(workspaceId, "Required non-null workspace id"); // check if workspace exists final WorkspaceImpl workspace = workspaceDao.get(workspaceId); - return machineManager.getSnapshots(workspace.getNamespace(), workspaceId); + return snapshotDao.findSnapshots(workspace.getNamespace(), workspaceId); } /** - * Removes all snapshots of workspace machines + * Removes all snapshots of workspace machines. + * Continues to remove snapshots even when removal of some of them fails. * * @param workspaceId workspace id to remove machine snapshots * @throws NotFoundException @@ -543,7 +552,64 @@ public class WorkspaceManager { * when any other error occurs */ public void removeSnapshots(String workspaceId) throws NotFoundException, ServerException { - machineManager.removeSnapshots(getWorkspace(workspaceId).getNamespace(), workspaceId); + List snapshots = getSnapshot(workspaceId); + for (SnapshotImpl snapshot : snapshots) { + try { + runtimes.removeSnapshot(snapshot); + snapshotDao.removeSnapshot(snapshot.getId()); + } catch (Exception e) { + LOG.error(e.getLocalizedMessage(), e); + } + } + } + + /** + * Stops machine in running workspace. + * + * @param workspaceId + * ID of workspace that owns machine + * @param machineId + * ID of machine that should be stopped + * @throws NotFoundException + * if machine is not found in running workspace + * @throws ConflictException + * if workspace is not running + * @throws ConflictException + * if machine stop is forbidden (e.g. machine is dev-machine) + * @throws ServerException + * if other error occurs + */ + public void stopMachine(String workspaceId, + String machineId) throws NotFoundException, + ServerException, + ConflictException { + requireNonNull(workspaceId, "Required non-null workspace id"); + requireNonNull(machineId, "Required non-null machine id"); + final WorkspaceImpl workspace = normalizeState(workspaceDao.get(workspaceId)); + checkWorkspaceIsRunning(workspace, format("stop machine with ID '%s' of", machineId)); + runtimes.stopMachine(workspaceId, machineId); + } + + /** + * Retrieves machine instance that allows to execute commands in a machine. + * + * @param workspaceId + * ID of workspace that owns machine + * @param machineId + * ID of requested machine + * @return instance of requested machine + * @throws NotFoundException + * if workspace is not running + * @throws NotFoundException + * if machine is not found in the workspace + */ + public Instance getMachineInstance(String workspaceId, + String machineId) throws NotFoundException, + ServerException { + requireNonNull(workspaceId, "Required non-null workspace id"); + requireNonNull(machineId, "Required non-null machine id"); + normalizeState(workspaceDao.get(workspaceId)); + return runtimes.getMachine(workspaceId, machineId); } /** Asynchronously starts given workspace. */ @@ -563,7 +629,7 @@ public class WorkspaceManager { } // WorkspaceRuntimes performs this check as well // but this check needed here because permanent workspace start performed asynchronously - // which means that even if registry won't start workspace client receives workspace object + // which means that even if workspace runtimes won't start workspace client receives workspace object // with starting status, this check prevents it and throws appropriate exception try { final RuntimeDescriptor descriptor = runtimes.get(workspace.getId()); @@ -571,7 +637,7 @@ public class WorkspaceManager { workspace.getConfig().getName(), descriptor.getRuntimeStatus())); } catch (NotFoundException ignored) { - // it is okay if workspace does not exist + // it is okay if workspace does not exist in runtimes } workspace.getAttributes().put(UPDATED_ATTRIBUTE_NAME, Long.toString(currentTimeMillis())); @@ -587,7 +653,7 @@ public class WorkspaceManager { workspace.getConfig().getName(), workspace.getId(), sessionUserNameOr("undefined")); - } catch (RuntimeException | ServerException | NotFoundException | ConflictException | ForbiddenException ex) { + } catch (RuntimeException | ApiException ex) { if (workspace.isTemporary()) { try { removeWorkspace(workspace.getId()); @@ -644,6 +710,16 @@ public class WorkspaceManager { })); } + private void performAsyncStart(MachineConfig machineConfig, String workspaceId) { + executor.execute(ThreadLocalPropagateContext.wrap(() -> { + try { + runtimes.startMachine(workspaceId, machineConfig); + } catch (ApiException e) { + LOG.error(e.getLocalizedMessage(), e); + } + })); + } + /** * Synchronously creates snapshot of the workspace. * @@ -658,7 +734,10 @@ public class WorkspaceManager { String devMachineSnapshotFailMessage = null; for (MachineImpl machine : runtime.getMachines()) { try { - machineManager.saveSync(machine.getId(), namespace, runtime.getActiveEnv()); + SnapshotImpl snapshot = runtimes.saveMachine(namespace, + workspaceId, + machine.getId()); + snapshotDao.saveSnapshot(snapshot); } catch (ApiException apiEx) { if (machine.getConfig().isDev()) { devMachineSnapshotFailMessage = apiEx.getLocalizedMessage(); @@ -679,7 +758,8 @@ public class WorkspaceManager { return devMachineSnapshotFailMessage == null; } - private void checkWorkspaceIsRunning(WorkspaceImpl workspace, String operation) throws ConflictException { + @VisibleForTesting + void checkWorkspaceIsRunning(WorkspaceImpl workspace, String operation) throws ConflictException { if (workspace.getStatus() != RUNNING) { throw new ConflictException(format("Could not %s the workspace '%s:%s' because its status is '%s'.", operation, @@ -701,7 +781,7 @@ public class WorkspaceManager { return nameIfNoUser; } - private WorkspaceImpl normalizeState(WorkspaceImpl workspace) { + private WorkspaceImpl normalizeState(WorkspaceImpl workspace) throws ServerException { try { return normalizeState(workspace, runtimes.get(workspace.getId())); } catch (NotFoundException e) { @@ -712,7 +792,7 @@ public class WorkspaceManager { private WorkspaceImpl normalizeState(WorkspaceImpl workspace, RuntimeDescriptor descriptor) { if (descriptor != null) { workspace.setStatus(descriptor.getRuntimeStatus()); - workspace.setRuntime(descriptor.getRuntime()); + workspace.setRuntime(new WorkspaceRuntimeImpl(descriptor.getRuntime())); } else { workspace.setStatus(STOPPED); } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimes.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimes.java index 61c6b49be4..5a681bf97d 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimes.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimes.java @@ -11,55 +11,51 @@ package org.eclipse.che.api.workspace.server; import com.google.common.annotations.VisibleForTesting; -import com.google.common.util.concurrent.Striped; +import com.google.common.util.concurrent.ThreadFactoryBuilder; -import org.eclipse.che.api.core.BadRequestException; +import org.eclipse.che.api.agent.server.wsagent.WsAgentLauncher; +import org.eclipse.che.api.core.ApiException; 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.Machine; import org.eclipse.che.api.core.model.machine.MachineConfig; import org.eclipse.che.api.core.model.machine.MachineLogMessage; import org.eclipse.che.api.core.model.machine.MachineStatus; +import org.eclipse.che.api.core.model.workspace.Environment; import org.eclipse.che.api.core.model.workspace.WorkspaceRuntime; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; -import org.eclipse.che.api.core.notification.EventSubscriber; -import org.eclipse.che.api.core.util.AbstractLineConsumer; -import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.core.util.AbstractMessageConsumer; +import org.eclipse.che.api.core.util.MessageConsumer; import org.eclipse.che.api.core.util.WebsocketMessageConsumer; -import org.eclipse.che.api.machine.server.MachineManager; -import org.eclipse.che.api.machine.server.exception.MachineException; -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.MachineLogMessageImpl; -import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; +import org.eclipse.che.api.environment.server.CheEnvironmentEngine; +import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; +import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; 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.shared.dto.event.WorkspaceStatusEvent; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent.EventType; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Singleton; import java.io.IOException; -import java.util.ArrayDeque; +import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; -import java.util.Queue; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.function.Predicate; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import static java.lang.String.format; import static org.eclipse.che.api.machine.shared.Constants.ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE; import static org.eclipse.che.dto.server.DtoFactory.newDto; +import static org.slf4j.LoggerFactory.getLogger; /** * Defines an internal API for managing {@link WorkspaceRuntimeImpl} instances. @@ -69,12 +65,12 @@ import static org.eclipse.che.dto.server.DtoFactory.newDto; *

All the operations performed by this component are synchronous. * *

The implementation is thread-safe and guarded by - * eagerly initialized readwrite locks produced by {@link WorkspaceRuntimes#STRIPED}. + * eagerly initialized readwrite locks produced by {@link StripedLocks}. * The component doesn't expose any api for client-side locking. * All the instances produced by this component are copies of the real data. * *

The component doesn't check if the incoming objects are in application-valid state. - * Which means that it is expected that if {@link #start(WorkspaceImpl, String)} method is called + * Which means that it is expected that if {@link #start(WorkspaceImpl, String, boolean)} method is called * then {@code WorkspaceImpl} argument is a application-valid object which contains * all the required data for performing start. * @@ -84,30 +80,28 @@ import static org.eclipse.che.dto.server.DtoFactory.newDto; @Singleton public class WorkspaceRuntimes { - private static final Logger LOG = LoggerFactory.getLogger(WorkspaceRuntimes.class); - // 16 - experimental value for stripes count, it comes from default hash map size - private static final Striped STRIPED = Striped.readWriteLock(16); + private static final Logger LOG = getLogger(WorkspaceRuntimes.class); @VisibleForTesting - final Map descriptors; + final Map workspaces; @VisibleForTesting - final Map> startQueues; - - private final MachineManager machineManager; - private final EventService eventService; - private final EventSubscriber addMachineEventSubscriber; - private final EventSubscriber removeMachineEventSubscriber; + private final EventService eventService; + private final StripedLocks stripedLocks; + private final CheEnvironmentEngine environmentEngine; + private final WsAgentLauncher wsAgentLauncher; private volatile boolean isPreDestroyInvoked; @Inject - public WorkspaceRuntimes(MachineManager machineManager, EventService eventService) { - this.machineManager = machineManager; + public WorkspaceRuntimes(EventService eventService, + CheEnvironmentEngine environmentEngine, + WsAgentLauncher wsAgentLauncher) { this.eventService = eventService; - this.descriptors = new HashMap<>(); - this.startQueues = new HashMap<>(); - this.addMachineEventSubscriber = new AddMachineEventSubscriber(); - this.removeMachineEventSubscriber = new RemoveMachineEventSubscriber(); + this.environmentEngine = environmentEngine; + this.wsAgentLauncher = wsAgentLauncher; + this.workspaces = new HashMap<>(); + // 16 - experimental value for stripes count, it comes from default hash map size + this.stripedLocks = new StripedLocks(16); } /** @@ -124,37 +118,54 @@ public class WorkspaceRuntimes { * the id of the workspace to get its runtime * @return descriptor which describes current state of the workspace runtime * @throws NotFoundException - * when workspace with given {@code workspaceId} is not running + * when workspace with given {@code workspaceId} is not found + * @throws ServerException + * if environment is in illegal state */ - public RuntimeDescriptor get(String workspaceId) throws NotFoundException { - acquireReadLock(workspaceId); - try { - final RuntimeDescriptor descriptor = descriptors.get(workspaceId); - if (descriptor == null) { - throw new NotFoundException("Workspace with id '" + workspaceId + "' is not running."); - } - return new RuntimeDescriptor(descriptor); - } finally { - releaseReadLock(workspaceId); + public RuntimeDescriptor get(String workspaceId) throws NotFoundException, + ServerException { + WorkspaceState workspaceState; + try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) { + workspaceState = workspaces.get(workspaceId); } + if (workspaceState == null) { + throw new NotFoundException("Workspace with id '" + workspaceId + "' is not running."); + } + + RuntimeDescriptor runtimeDescriptor = new RuntimeDescriptor(workspaceState.status, + new WorkspaceRuntimeImpl(workspaceState.activeEnv, + null, + Collections.emptyList(), + null)); + List machines = environmentEngine.getMachines(workspaceId); + Optional devMachineOptional = machines.stream() + .filter(machine -> machine.getConfig().isDev()) + .findAny(); + if (devMachineOptional.isPresent()) { + String projectsRoot = devMachineOptional.get().getStatus() == MachineStatus.RUNNING ? + devMachineOptional.get().getRuntime().projectsRoot() : + null; + runtimeDescriptor.setRuntime(new WorkspaceRuntimeImpl(workspaceState.activeEnv, + projectsRoot, + machines, + devMachineOptional.get())); + } else if (workspaceState.status == WorkspaceStatus.RUNNING) { + // invalid state of environment is detected + String error = format("Dev machine is not found in active environment of workspace '%s'", + workspaceId); + throw new ServerException(error); + } + + return runtimeDescriptor; } /** * Starts all machines from specified workspace environment, * creates workspace runtime instance based on that environment. * - *

Dev-machine always starts before the other machines. - * If dev-machine start failed then method will throw appropriate - * {@link ServerException}. During the start of the workspace its + *

During the start of the workspace its * runtime is visible with {@link WorkspaceStatus#STARTING} status. * - *

If {@link #stop} method executed after dev machine is started but - * another machines haven't been started yet then {@link ConflictException} - * will be thrown and start process will be interrupted. - * - *

Note that it doesn't provide any events for - * machines start, Machine API is responsible for it. - * * @param workspace * workspace which environment should be started * @param envName @@ -169,9 +180,10 @@ public class WorkspaceRuntimes { * @throws NotFoundException * when any not found exception occurs during environment start * @throws ServerException - * when component {@link #isPreDestroyInvoked is stopped} or any + * when component {@link #isPreDestroyInvoked is stopped} + * @throws ServerException * other error occurs during environment start - * @see MachineManager#createMachineSync(MachineConfig, String, String, org.eclipse.che.api.core.util.LineConsumer) + * @see CheEnvironmentEngine#start(String, Environment, boolean, MessageConsumer) * @see WorkspaceStatus#STARTING * @see WorkspaceStatus#RUNNING */ @@ -180,76 +192,83 @@ public class WorkspaceRuntimes { boolean recover) throws ServerException, ConflictException, NotFoundException { - final Optional environmentOpt = workspace.getConfig().getEnvironment(envName); + String workspaceId = workspace.getId(); + + Optional environmentOpt = workspace.getConfig().getEnvironment(envName); if (!environmentOpt.isPresent()) { throw new IllegalArgumentException(format("Workspace '%s' doesn't contain environment '%s'", - workspace.getId(), + workspaceId, envName)); } // Environment copy constructor makes deep copy of objects graph // in this way machine configs also copied from incoming values // which means that original values won't affect the values in starting queue - final EnvironmentImpl environmentCopy = new EnvironmentImpl(environmentOpt.get()); + EnvironmentImpl environmentCopy = new EnvironmentImpl(environmentOpt.get()); // This check allows to exit with an appropriate exception before blocking on lock. // The double check is required as it is still possible to get unlucky timing // between locking and starting workspace. ensurePreDestroyIsNotExecuted(); - acquireWriteLock(workspace.getId()); - try { + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) { ensurePreDestroyIsNotExecuted(); - final RuntimeDescriptor existingDescriptor = descriptors.get(workspace.getId()); - if (existingDescriptor != null) { + WorkspaceState workspaceState = workspaces.get(workspaceId); + if (workspaceState != null) { throw new ConflictException(format("Could not start workspace '%s' because its status is '%s'", workspace.getConfig().getName(), - existingDescriptor.getRuntimeStatus())); + workspaceState.status)); } - // Create a new runtime descriptor and save it with 'STARTING' status - final RuntimeDescriptor descriptor = new RuntimeDescriptor(new WorkspaceRuntimeImpl(envName)); - descriptor.setRuntimeStatus(WorkspaceStatus.STARTING); - descriptors.put(workspace.getId(), descriptor); - - // Create a new start queue with a dev machine in the queue head - final List startConfigs = environmentCopy.getMachineConfigs(); - final MachineConfigImpl devCfg = removeFirstMatching(startConfigs, MachineConfig::isDev); - startConfigs.add(0, devCfg); - startQueues.put(workspace.getId(), new ArrayDeque<>(startConfigs)); - } finally { - releaseWriteLock(workspace.getId()); + // Create a new workspace state and save it with 'STARTING' status + workspaces.put(workspaceId, new WorkspaceState(WorkspaceStatus.STARTING, envName)); } - startQueue(workspace.getId(), environmentCopy.getName(), recover); + ensurePreDestroyIsNotExecuted(); + publishWorkspaceEvent(EventType.STARTING, workspaceId, null); - return get(workspace.getId()); - } + try { + List machines = environmentEngine.start(workspaceId, + environmentCopy, + recover, + getEnvironmentLogger(workspaceId)); + Instance devMachine = getDevMachine(machines); - /** - * This method is similar to the {@link #start(WorkspaceImpl, String, boolean)} method - * except that it doesn't recover workspace and always starts a new one. - */ - public RuntimeDescriptor start(WorkspaceImpl workspace, String envName) throws ServerException, - ConflictException, - NotFoundException { - return start(workspace, envName, false); + wsAgentLauncher.startWsAgent(devMachine); + + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) { + WorkspaceState workspaceState = workspaces.get(workspaceId); + workspaceState.status = WorkspaceStatus.RUNNING; + } + // Event publication should be performed outside of the lock + // as it may take some time to notify subscribers + publishWorkspaceEvent(EventType.RUNNING, workspaceId, null); + return get(workspaceId); + } catch (ApiException e) { + try { + environmentEngine.stop(workspaceId); + } catch (Exception ex) { + LOG.error(ex.getLocalizedMessage(), ex); + } + String environmentStartError = "Start of environment " + envName + + " failed. Error: " + e.getLocalizedMessage(); + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) { + workspaces.remove(workspaceId); + } + publishWorkspaceEvent(EventType.ERROR, + workspaceId, + environmentStartError); + + throw new ServerException(environmentStartError); + } } /** * Stops running workspace runtime. * - *

Stops all running machines one by one, - * non-dev machines first. During the stop of the workspace - * its runtime is accessible with {@link WorkspaceStatus#STOPPING stopping} status. + *

Stops environment in an implementation specific way. + * During the stop of the workspace its runtime is accessible with {@link WorkspaceStatus#STOPPING stopping} status. * Workspace may be stopped only if its status is {@link WorkspaceStatus#RUNNING}. * - *

If workspace has runtime with dev-machine running - * and other machines starting then the runtime can still - * be stopped which will also interrupt starting process. - * - *

Note that it doesn't provide any events for machines stop, - * Machine API is responsible for it. - * * @param workspaceId * identifier of workspace which should be stopped * @throws NotFoundException @@ -258,7 +277,7 @@ public class WorkspaceRuntimes { * when any error occurs during workspace stopping * @throws ConflictException * when running workspace status is different from {@link WorkspaceStatus#RUNNING} - * @see MachineManager#destroy(String, boolean) + * @see CheEnvironmentEngine#stop(String) * @see WorkspaceStatus#STOPPING */ public void stop(String workspaceId) throws NotFoundException, ServerException, ConflictException { @@ -266,39 +285,38 @@ public class WorkspaceRuntimes { // The double check is required as it is still possible to get unlucky timing // between locking and stopping workspace. ensurePreDestroyIsNotExecuted(); - acquireWriteLock(workspaceId); - final WorkspaceRuntimeImpl runtime; - try { + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) { ensurePreDestroyIsNotExecuted(); - final RuntimeDescriptor descriptor = descriptors.get(workspaceId); - if (descriptor == null) { + WorkspaceState workspaceState = workspaces.get(workspaceId); + if (workspaceState == null) { throw new NotFoundException("Workspace with id '" + workspaceId + "' is not running."); } - if (descriptor.getRuntimeStatus() != WorkspaceStatus.RUNNING) { + if (workspaceState.status != WorkspaceStatus.RUNNING) { throw new ConflictException( format("Couldn't stop '%s' workspace because its status is '%s'. Workspace can be stopped only if it is 'RUNNING'", workspaceId, - descriptor.getRuntimeStatus())); + workspaceState.status)); } - // According to the WorkspaceStatus specification workspace runtime - // must visible with STOPPING status until dev-machine is not stopped - descriptor.setRuntimeStatus(WorkspaceStatus.STOPPING); - - // At this point of time starting queue must be removed - // to prevent start of another machines which are not started yet. - // In this case workspace start will be interrupted and - // interruption will be reported, machine which is currently starting(if such exists) - // will be destroyed by workspace starting thread. - startQueues.remove(workspaceId); - - // Create deep copy of the currently running workspace to prevent - // out of the lock instance modifications and stale data effects - runtime = new WorkspaceRuntimeImpl(descriptor.getRuntime()); - } finally { - releaseWriteLock(workspaceId); + workspaceState.status = WorkspaceStatus.STOPPING; + } + + publishWorkspaceEvent(EventType.STOPPING, workspaceId, null); + String error = null; + try { + environmentEngine.stop(workspaceId); + } catch (ServerException | RuntimeException e) { + error = e.getLocalizedMessage(); + } finally { + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) { + workspaces.remove(workspaceId); + } + } + if (error == null) { + publishWorkspaceEvent(EventType.STOPPED, workspaceId, null); + } else { + publishWorkspaceEvent(EventType.ERROR, workspaceId, error); } - destroyRuntime(workspaceId, runtime); } /** @@ -328,311 +346,261 @@ public class WorkspaceRuntimes { * @return true if workspace is running, otherwise false */ public boolean hasRuntime(String workspaceId) { - acquireReadLock(workspaceId); - try { - return descriptors.containsKey(workspaceId); - } finally { - releaseReadLock(workspaceId); + try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) { + return workspaces.containsKey(workspaceId); } } - @PostConstruct - private void subscribe() { - eventService.subscribe(addMachineEventSubscriber); - eventService.subscribe(removeMachineEventSubscriber); + /** + * Starts machine in running workspace. + * + * @param workspaceId + * ID of workspace that owns machine + * @param machineConfig + * config of machine that should be started + * @return running machine + * @throws ConflictException + * if environment is not running or conflicting machine already exists in the environment + * @throws ConflictException + * if environment was stopped during start of machine + * @throws ServerException + * if any other error occurs + */ + public Instance startMachine(String workspaceId, + MachineConfig machineConfig) throws ServerException, + ConflictException, + NotFoundException { + + try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) { + WorkspaceState workspaceState = workspaces.get(workspaceId); + if (workspaceState == null || workspaceState.status != WorkspaceStatus.RUNNING) { + throw new ConflictException(format("Environment of workspace '%s' is not running", workspaceId)); + } + } + + Instance instance = environmentEngine.startMachine(workspaceId, machineConfig); + + try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) { + WorkspaceState workspaceState = workspaces.get(workspaceId); + if (workspaceState == null || workspaceState.status != WorkspaceStatus.RUNNING) { + try { + environmentEngine.stopMachine(workspaceId, instance.getId()); + } catch (NotFoundException | ServerException | ConflictException e) { + LOG.error(e.getLocalizedMessage(), e); + } + throw new ConflictException(format("Environment of workspace '%s' was stopped during start of machine", + workspaceId)); + } + } + return instance; } /** - * Removes all descriptors from the in-memory storage, while - * {@link MachineManager#cleanup()} is responsible for machines destroying. + * Stops machine in a running environment. + * + * @param workspaceId + * ID of workspace that owns environment + * @param machineId + * ID of machine that should be stopped + * @throws NotFoundException + * if machine is not found in the environment + * @throws ConflictException + * if environment is not running + * @throws ConflictException + * if machine is dev and its stop is forbidden + * @throws ServerException + * if any other error occurs + */ + public void stopMachine(String workspaceId, String machineId) throws NotFoundException, + ServerException, + ConflictException { + try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) { + WorkspaceState workspaceState = workspaces.get(workspaceId); + if (workspaceState == null || workspaceState.status != WorkspaceStatus.RUNNING) { + throw new ConflictException(format("Environment of workspace '%s' is not running", workspaceId)); + } + } + environmentEngine.stopMachine(workspaceId, machineId); + } + + /** + * Save snapshot of running machine. + * + * @param namespace + * namespace of workspace + * @param workspaceId + * ID of workspace that owns machine + * @param machineId + * ID of machine that should be saved + * @return snapshot description + * @throws NotFoundException + * if machine is not running + * @throws ConflictException + * if environment of machine is not running + * @throws ServerException + * if another error occurs + */ + public SnapshotImpl saveMachine(String namespace, + String workspaceId, + String machineId) throws NotFoundException, + ServerException, + ConflictException { + + try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) { + WorkspaceState workspaceState = workspaces.get(workspaceId); + if (workspaceState == null || workspaceState.status != WorkspaceStatus.RUNNING) { + throw new ConflictException(format("Environment of workspace '%s' is not running", workspaceId)); + } + } + return environmentEngine.saveSnapshot(namespace, workspaceId, machineId); + } + + /** + * Removes implementation specific representation of snapshot. + * + * @param snapshot + * description of snapshot that should be removed + * @throws ServerException + * if error occurs + */ + public void removeSnapshot(SnapshotImpl snapshot) throws ServerException { + environmentEngine.removeSnapshot(snapshot); + } + + /** + * Finds machine {@link Instance} by specified workspace and machine IDs. + * + * @param workspaceId + * ID of workspace that owns machine + * @param machineId + * ID of requested machine + * @return requested machine + * @throws NotFoundException + * if environment or machine is not running + */ + public Instance getMachine(String workspaceId, String machineId) throws NotFoundException { + return environmentEngine.getMachine(workspaceId, machineId); + } + + /** + * Returns all workspaces with statuses of its active environment. + */ + public Map getWorkspaces() { + return new HashMap<>(workspaces); + } + + private MessageConsumer getEnvironmentLogger(String workspaceId) throws ServerException { + WebsocketMessageConsumer envMessageConsumer = + new WebsocketMessageConsumer<>(format(ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE, workspaceId)); + return new AbstractMessageConsumer() { + @Override + public void consume(MachineLogMessage message) throws IOException { + envMessageConsumer.consume(message); + } + }; + } + + /** + * Removes all workspaces from the in-memory storage, while + * {@link CheEnvironmentEngine} is responsible for environment destroying. */ @PreDestroy @VisibleForTesting void cleanup() { isPreDestroyInvoked = true; - // Unsubscribe from events - eventService.unsubscribe(addMachineEventSubscriber); - eventService.unsubscribe(removeMachineEventSubscriber); + final ExecutorService stopEnvExecutor = + Executors.newFixedThreadPool(2 * Runtime.getRuntime().availableProcessors(), + new ThreadFactoryBuilder().setNameFormat("StopEnvironment-%d") + .setDaemon(false) + .build()); + try (StripedLocks.WriteAllLock lock = stripedLocks.acquireWriteAllLock()) { + for (Map.Entry workspace : workspaces.entrySet()) { + if (workspace.getValue().status.equals(WorkspaceStatus.RUNNING) || + workspace.getValue().status.equals(WorkspaceStatus.STARTING)) { + stopEnvExecutor.execute(() -> { + try { + environmentEngine.stop(workspace.getKey()); + } catch (ServerException | NotFoundException e) { + LOG.error(e.getLocalizedMessage(), e); + } + }); + } + } - // Acquire all the locks - for (int i = 0; i < STRIPED.size(); i++) { - STRIPED.getAt(i).writeLock().lock(); + workspaces.clear(); + + stopEnvExecutor.shutdown(); } - - // clean up - descriptors.clear(); - startQueues.clear(); - - // Release all the locks - for (int i = 0; i < STRIPED.size(); i++) { - STRIPED.getAt(i).writeLock().unlock(); + try { + if (!stopEnvExecutor.awaitTermination(50, TimeUnit.SECONDS)) { + stopEnvExecutor.shutdownNow(); + if (!stopEnvExecutor.awaitTermination(10, TimeUnit.SECONDS)) { + LOG.warn("Unable terminate destroy machines pool"); + } + } + } catch (InterruptedException e) { + stopEnvExecutor.shutdownNow(); + Thread.currentThread().interrupt(); } } @VisibleForTesting - void publishEvent(EventType type, String workspaceId, String error) { + void publishWorkspaceEvent(EventType type, String workspaceId, String error) { eventService.publish(newDto(WorkspaceStatusEvent.class) .withEventType(type) .withWorkspaceId(workspaceId) .withError(error)); } - @VisibleForTesting - void cleanupStartResources(String workspaceId) { - acquireWriteLock(workspaceId); - try { - descriptors.remove(workspaceId); - startQueues.remove(workspaceId); - } finally { - releaseWriteLock(workspaceId); + private Instance getDevMachine(List machines) throws ServerException { + Optional devMachineOpt = machines.stream() + .filter(machine -> machine.getConfig().isDev()) + .findAny(); + + if (devMachineOpt.isPresent()) { + return devMachineOpt.get(); + } + throw new ServerException( + "Environment has booted but it doesn't contain dev machine. Environment has been stopped."); + } + + private void ensurePreDestroyIsNotExecuted() throws ServerException { + if (isPreDestroyInvoked) { + throw new ServerException("Could not perform operation because application server is stopping"); } } - private void removeRuntime(String workspaceId) { - acquireWriteLock(workspaceId); - try { - descriptors.remove(workspaceId); - } finally { - releaseWriteLock(workspaceId); - } - } + public static class WorkspaceState { + private WorkspaceStatus status; + private String activeEnv; - /** - * Stops workspace by destroying all its machines and removing it from the in memory storage. - */ - private void destroyRuntime(String workspaceId, - WorkspaceRuntimeImpl workspace) throws NotFoundException, ServerException { - publishEvent(EventType.STOPPING, workspaceId, null); - - // Preparing the list of machines to be destroyed, dev machine goes last - final List machines = workspace.getMachines(); - final MachineImpl devMachine = removeFirstMatching(machines, m -> m.getConfig().isDev()); - - // Synchronously destroying all non-dev machines - for (MachineImpl machine : machines) { - try { - machineManager.destroy(machine.getId(), false); - } catch (NotFoundException ignore) { - // This may happen, if machine is stopped by direct call to the Machine API - // MachineManager cleanups all the machines due to application server shutdown - // As non-dev machines don't affect runtime status, this exception is ignored - } catch (RuntimeException | MachineException ex) { - LOG.error(format("Could not destroy machine '%s' of workspace '%s'", - machine.getId(), - machine.getWorkspaceId()), - ex); - } + public WorkspaceState(WorkspaceStatus status, String activeEnv) { + this.status = status; + this.activeEnv = activeEnv; } - // Synchronously destroying dev-machine - try { - machineManager.destroy(devMachine.getId(), false); - publishEvent(EventType.STOPPED, workspaceId, null); - } catch (NotFoundException ignore) { - // This may happen, if machine is stopped by direct call to the Machine API - // MachineManager cleanups all the machines due to application server shutdown - // In this case workspace is considered as successfully stopped - publishEvent(EventType.STOPPED, workspaceId, null); - } catch (RuntimeException | ServerException ex) { - publishEvent(EventType.ERROR, workspaceId, ex.getLocalizedMessage()); - throw ex; - } finally { - removeRuntime(workspaceId); - } - } - - private void startQueue(String workspaceId, - String envName, - boolean recover) throws ServerException, - NotFoundException, - ConflictException { - publishEvent(EventType.STARTING, workspaceId, null); - - // Starting all the machines one by one by getting configs - // from the corresponding starting queue. - // Config will be null only if there are no machines left in the queue - MachineConfigImpl config = queuePeekOrFail(workspaceId); - while (config != null) { - - // According to WorkspaceStatus specification the workspace start - // is failed when dev-machine start is failed, so if any error - // occurs during machine creation and the machine is dev-machine - // then start fail is reported and start resources such as queue - // and descriptor must be cleaned up - MachineImpl machine = null; - try { - machine = startMachine(config, workspaceId, envName, recover); - } catch (RuntimeException | ServerException | ConflictException | NotFoundException x) { - if (config.isDev()) { - publishEvent(EventType.ERROR, workspaceId, x.getLocalizedMessage()); - cleanupStartResources(workspaceId); - throw x; - } - LOG.error(format("Error while creating non-dev machine '%s' in workspace '%s', environment '%s'", - config.getName(), - workspaceId, - envName), - x); - } - - // Machine destroying is an expensive operation which must be - // performed outside of the lock, this section checks if - // the workspace wasn't stopped while it is starting and sets - // polled flag to true if the workspace wasn't stopped plus - // polls the proceeded machine configuration from the queue - boolean queuePolled = false; - acquireWriteLock(workspaceId); - try { - ensurePreDestroyIsNotExecuted(); - final Queue queue = startQueues.get(workspaceId); - if (queue != null) { - queue.poll(); - queuePolled = true; - if (machine != null) { - final RuntimeDescriptor descriptor = descriptors.get(workspaceId); - if (config.isDev()) { - descriptor.getRuntime().setDevMachine(machine); - descriptor.setRuntimeStatus(WorkspaceStatus.RUNNING); - } - descriptor.getRuntime().getMachines().add(machine); - } - } - } finally { - releaseWriteLock(workspaceId); - } - - // Event publication should be performed outside of the lock - // as it may take some time to notify subscribers - if (machine != null && config.isDev()) { - publishEvent(EventType.RUNNING, workspaceId, null); - } - - // If machine config is not polled from the queue - // then workspace was stopped and newly created machine - // must be destroyed(if such exists) - if (!queuePolled) { - if (machine != null) { - machineManager.destroy(machine.getId(), false); - } - throw new ConflictException(format("Workspace '%s' start interrupted. Workspace stopped before all its machines started", - workspaceId)); - } - - config = queuePeekOrFail(workspaceId); + public String getActiveEnv() { + return activeEnv; } - // All the machines tried to start which means that queue - // should be empty and can be normally removed, but in the case of - // some unlucky timing, the workspace may be stopped and started again - // so the queue, which is guarded by the same lock as workspace descriptor - // may be initialized again with a new batch of machines to start, - // that's why queue should be removed only if it is not empty. - // On the other hand queue may not exist because workspace has been stopped - // just before queue utilization, which considered as a normal behaviour - acquireWriteLock(workspaceId); - try { - final Queue queue = startQueues.get(workspaceId); - if (queue != null && queue.isEmpty()) { - startQueues.remove(workspaceId); - } - } finally { - releaseWriteLock(workspaceId); + public WorkspaceStatus getStatus() { + return status; } - } - /** - * Gets head config from the queue associated with the given {@code workspaceId}. - * - *

Note that this method won't actually poll the queue. - * - *

Fails if workspace start was interrupted by stop(queue doesn't exist). - * - * @return machine config which is in the queue head, or null - * if there are no machine configs left - * @throws ConflictException - * when queue doesn't exist which means that {@link #stop(String)} executed - * before all the machines started - * @throws ServerException - * only if pre destroy has been invoked before peek config retrieved - */ - private MachineConfigImpl queuePeekOrFail(String workspaceId) throws ConflictException, ServerException { - acquireReadLock(workspaceId); - try { - ensurePreDestroyIsNotExecuted(); - final Queue queue = startQueues.get(workspaceId); - if (queue == null) { - throw new ConflictException( - format("Workspace '%s' start interrupted. Workspace was stopped before all its machines were started", - workspaceId)); - } - return queue.peek(); - } finally { - releaseReadLock(workspaceId); + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof WorkspaceState)) return false; + WorkspaceState that = (WorkspaceState)o; + return status == that.status && + Objects.equals(activeEnv, that.activeEnv); } - } - /** - * Starts the machine from the configuration, returns null if machine start failed. - */ - private MachineImpl startMachine(MachineConfigImpl config, - String workspaceId, - String envName, - boolean recover) throws ServerException, - NotFoundException, - ConflictException { - - LineConsumer machineLogger = getMachineLogger(workspaceId, config.getName()); - - MachineImpl machine; - try { - if (recover) { - machine = machineManager.recoverMachine(config, workspaceId, envName, machineLogger); - } else { - machine = machineManager.createMachineSync(config, workspaceId, envName, machineLogger); - } - } catch (ConflictException x) { - // The conflict is because of the already running machine - // which may be running by several reasons: - // 1. It has been running before the workspace started - // 2. It was started immediately after the workspace - // Consider the next example: - // If workspace starts machines from configurations [m1, m2, m3] - // and currently starting machine is 'm2' then it is still possible - // to use direct Machine API call to start the machine 'm3' - // which will result as a conflict for the workspace API during 'm3' start. - // This is not usual/normal behaviour but it should be handled. - // The handling logic gets the running machine instance by the 'm3' config - // and considers that the machine is started correctly, if it is impossible - // to find the corresponding machine then the fail will be reported - // and workspace runtime state will be changed according to the machine config context. - final Optional machineOpt = machineManager.getMachines() - .stream() - .filter(m -> m.getWorkspaceId().equals(workspaceId) - && m.getEnvName().equals(envName) - && m.getConfig().equals(config)) - .findAny(); - if (machineOpt.isPresent() && machineOpt.get().getStatus() == MachineStatus.RUNNING) { - machine = machineOpt.get(); - } else { - throw x; - } - } catch (BadRequestException x) { - // TODO don't throw bad request exception from machine manager - throw new IllegalArgumentException(x.getLocalizedMessage(), x); + @Override + public int hashCode() { + return Objects.hash(status, activeEnv); } - return machine; - } - - protected LineConsumer getMachineLogger(String workspaceId, String machineName) throws ServerException { - WebsocketMessageConsumer envMessageConsumer = - new WebsocketMessageConsumer<>(format(ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE, workspaceId)); - return new AbstractLineConsumer() { - @Override - public void writeLine(String line) throws IOException { - envMessageConsumer.consume(new MachineLogMessageImpl(machineName, line)); - } - }; } /** @@ -646,20 +614,21 @@ public class WorkspaceRuntimes { private WorkspaceRuntimeImpl runtime; private WorkspaceStatus status; - private RuntimeDescriptor(WorkspaceRuntimeImpl runtime) { + public RuntimeDescriptor(WorkspaceStatus workspaceStatus, + WorkspaceRuntimeImpl runtime) { + this.status = workspaceStatus; this.runtime = runtime; } - private RuntimeDescriptor(RuntimeDescriptor descriptor) { - this(new WorkspaceRuntimeImpl(descriptor.runtime)); - this.status = descriptor.status; - } - /** Returns the instance of {@code WorkspaceRuntime} described by this descriptor. */ public WorkspaceRuntimeImpl getRuntime() { return runtime; } + public void setRuntime(WorkspaceRuntimeImpl runtime) { + this.runtime = runtime; + } + /** * Returns the status of the {@code WorkspaceRuntime} described by this descriptor. * Never returns {@link WorkspaceStatus#STOPPED} status, you'll rather get {@link NotFoundException} @@ -672,180 +641,27 @@ public class WorkspaceRuntimes { private void setRuntimeStatus(WorkspaceStatus status) { this.status = status; } - } - - @VisibleForTesting - class AddMachineEventSubscriber implements EventSubscriber { - @Override - public void onEvent(MachineStatusEvent event) { - if (event.getEventType() == MachineStatusEvent.EventType.RUNNING) { - try { - final MachineImpl machine = machineManager.getMachine(event.getMachineId()); - if (!addMachine(machine)) { - machineManager.destroy(machine.getId(), true); - } - } catch (NotFoundException | MachineException x) { - LOG.warn(format("An error occurred during an attempt to add the machine '%s' to the workspace '%s'", - event.getMachineId(), - event.getWorkspaceId()), - x); - } - } - } - } - - /** - * Tries to add the machine to the WorkspaceRuntime. - * - * @return true if machine is added or will be added to the corresponding WorkspaceRuntime, - * returns false when incoming machine can't be added and should be destroyed. - */ - @VisibleForTesting - boolean addMachine(Machine machine) throws NotFoundException, MachineException { - final String workspaceId = machine.getWorkspaceId(); - - acquireWriteLock(workspaceId); - try { - final RuntimeDescriptor descriptor = descriptors.get(workspaceId); - - // Ensure that workspace runtime exists for such machine - // if it is not, then the machine should be immediately destroyed - if (descriptor == null) { - LOG.warn("Could not add machine '{}' to the workspace '{}' because workspace is in not running", - machine.getId(), - workspaceId); - return false; - } - - // This may happen when either dev-machine started by WorkspaceRuntimes, - // or dev-machine started by direct call to MachineManager before WorkspaceRuntimes - // started it. In this case WorkspaceRuntimes#startMachine will fail & then if such - // machine exists it will be added to the corresponding WorkspaceRuntime. - if (machine.getConfig().isDev()) { - if (descriptor.getRuntimeStatus() == WorkspaceStatus.STARTING) { - return true; - } - LOG.warn("Could not add another dev-machine '{}' to the workspace '{}'", - machine.getId(), - workspaceId); - return false; - } - - // When workspace is not running then started machine must be immediately destroyed - // Example: status == STARTING & machine is non-dev - if (descriptor.getRuntimeStatus() != WorkspaceStatus.RUNNING) { - LOG.warn("Could not add machine '{}' to the workspace '{}' because workspace status is '{}'", - machine.getId(), - workspaceId, - descriptor.getRuntimeStatus()); - return false; - } - - // Workspace is RUNNING and there is no start queue - // which means that machine can be added to the workspace - if (!startQueues.containsKey(workspaceId)) { - descriptor.getRuntime().getMachines().add(new MachineImpl(machine)); - return true; - } - - // These are configs of machines which are not started yet - final Queue machineConfigs = startQueues.get(workspaceId); - - // If there is no config equal to the machine config - // then the machine can be added to the workspace runtime - // otherwise it will be added later, after WorkspaceRuntimes starts it - if (!machineConfigs.stream().anyMatch(m -> m.equals(machine.getConfig()))) { - descriptor.getRuntime().getMachines().add(new MachineImpl(machine)); - } - - // All the cases are covered, in this case machine will be added - // directly by WorkspaceRuntimes, after it fails on #startMachine - return true; - } finally { - releaseWriteLock(workspaceId); - } - } - - @VisibleForTesting - class RemoveMachineEventSubscriber implements EventSubscriber { @Override - public void onEvent(MachineStatusEvent event) { - // This event subscriber doesn't handle dev-machine destroyed events - // as in that case workspace should be stopped, and stop should be asynchronous - // but WorkspaceRuntimes provides only synchronous operations. - if (event.getEventType() == MachineStatusEvent.EventType.DESTROYED && !event.isDev()) { - removeMachine(event.getMachineId(), - event.getMachineName(), - event.getWorkspaceId()); - } + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof RuntimeDescriptor)) return false; + RuntimeDescriptor that = (RuntimeDescriptor)o; + return Objects.equals(runtime, that.runtime) && + status == that.status; } - } - /** Removes machine from the workspace runtime. */ - @VisibleForTesting - void removeMachine(String machineId, String machineName, String workspaceId) { - acquireWriteLock(workspaceId); - try { - final RuntimeDescriptor descriptor = descriptors.get(workspaceId); - - // Machine can be removed only from existing runtime with 'RUNNING' status - if (descriptor == null || descriptor.getRuntimeStatus() != WorkspaceStatus.RUNNING) { - return; - } - - // Try to remove non-dev machine from the runtime machines list - // It is unusual but still possible to get the state when such machine - // doesn't exist, in this case an appropriate warning will be logged - if (!descriptor.getRuntime() - .getMachines() - .removeIf(m -> m.getConfig().getName().equals(machineName))) { - LOG.warn("An attempt to remove the machine '{}' from the workspace runtime '{}' failed. " + - "Workspace doesn't contain machine with name '{}'", - machineId, - workspaceId, - machineName); - } - } finally { - releaseWriteLock(workspaceId); + @Override + public int hashCode() { + return Objects.hash(runtime, status); } - } - private static T removeFirstMatching(List elements, Predicate predicate) { - T element = null; - for (final Iterator it = elements.iterator(); it.hasNext() && element == null; ) { - final T next = it.next(); - if (predicate.test(next)) { - element = next; - it.remove(); - } + @Override + public String toString() { + return "RuntimeDescriptor{" + + "runtime=" + runtime + + ", status=" + status + + '}'; } - return element; - } - - private void ensurePreDestroyIsNotExecuted() throws ServerException { - if (isPreDestroyInvoked) { - throw new ServerException("Could not perform operation because application server is stopping"); - } - } - - /** Short alias for acquiring read lock for the given workspace. */ - private static void acquireReadLock(String workspaceId) { - STRIPED.get(workspaceId).readLock().lock(); - } - - /** Short alias for releasing read lock for the given workspace. */ - private static void releaseReadLock(String workspaceId) { - STRIPED.get(workspaceId).readLock().unlock(); - } - - /** Short alias for acquiring write lock for the given workspace. */ - private static void acquireWriteLock(String workspaceId) { - STRIPED.get(workspaceId).writeLock().lock(); - } - - /** Short alias for releasing write lock for the given workspace. */ - private static void releaseWriteLock(String workspaceId) { - STRIPED.get(workspaceId).writeLock().unlock(); } } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java index 0f29997c5d..9367825c90 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java @@ -28,9 +28,7 @@ import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.rest.Service; import org.eclipse.che.api.core.rest.annotations.GenerateLink; import org.eclipse.che.api.machine.server.model.impl.CommandImpl; -import org.eclipse.che.api.machine.server.model.impl.MachineImpl; import org.eclipse.che.api.machine.shared.dto.CommandDto; -import org.eclipse.che.api.machine.shared.dto.MachineConfigDto; import org.eclipse.che.api.machine.shared.dto.SnapshotDto; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; @@ -53,7 +51,6 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; import java.util.List; @@ -235,9 +232,7 @@ public class WorkspaceService extends Service { NotFoundException, ConflictException, ForbiddenException { - if (!workspaceManager.getSnapshot(id).isEmpty()) { - workspaceManager.removeSnapshots(id); - } + workspaceManager.removeSnapshots(id); workspaceManager.removeWorkspace(id); } @@ -355,10 +350,12 @@ public class WorkspaceService extends Service { "The snapshot doesn't exist for the workspace"), @ApiResponse(code = 403, message = "The user is not workspace owner"), @ApiResponse(code = 500, message = "Internal server error occurred")}) - public List getSnapshot(@ApiParam("The id of the workspace") @PathParam("id") String workspaceId) throws ServerException, - BadRequestException, - NotFoundException, - ForbiddenException { + public List getSnapshot(@ApiParam("The id of the workspace") @PathParam("id") String workspaceId) + throws ServerException, + BadRequestException, + NotFoundException, + ForbiddenException { + return workspaceManager.getSnapshot(workspaceId) .stream() .map(DtoConverter::asDto) @@ -450,9 +447,9 @@ public class WorkspaceService extends Service { if (workspace.getConfig().getCommands().removeIf(command -> command.getName().equals(commandName))) { workspaceManager.updateWorkspace(id, workspace); } else { - throw new NotFoundException( - String.format("Command with name '%s' was not found in workspace '%s'", commandName, - workspace.getConfig().getName())); + throw new NotFoundException(format("Command with name '%s' was not found in workspace '%s'", + commandName, + workspace.getConfig().getName())); } } @@ -632,44 +629,6 @@ public class WorkspaceService extends Service { } } - @POST - @Path("/{id}/machine") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Create a new machine based on the configuration", - notes = "This operation can be performed only by authorized user") - @ApiResponses({@ApiResponse(code = 201, message = "The machine successfully created"), - @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"), - @ApiResponse(code = 403, message = "The user does not have access to create the new machine"), - @ApiResponse(code = 409, message = "Conflict error occurred during the machine creation" + - "(e.g. The machine with such name already exists)." + - "Workspace is not in RUNNING state"), - @ApiResponse(code = 500, message = "Internal server error occurred")}) - public Response createMachine(@ApiParam("The workspace id") - @PathParam("id") - String workspaceId, - @ApiParam(value = "The new machine configuration", required = true) - MachineConfigDto machineConfig) throws ForbiddenException, - NotFoundException, - ServerException, - ConflictException, - BadRequestException { - requiredNotNull(machineConfig, "Machine configuration"); - requiredNotNull(machineConfig.getType(), "Machine type"); - requiredNotNull(machineConfig.getSource(), "Machine source"); - requiredNotNull(machineConfig.getSource().getType(), "Machine source type"); - // definition of source should come either with a content or with location - requiredOnlyOneNotNull(machineConfig.getSource().getLocation(), machineConfig.getSource().getContent(), - "Machine source should provide either location or content"); - - final MachineImpl machine = workspaceManager.startMachine(machineConfig, workspaceId); - - return Response.status(201) - .entity(linksInjector.injectMachineLinks(org.eclipse.che.api.machine.server.DtoConverter.asDto(machine), - getServiceContext())) - .build(); - } - private static Map parseAttrs(List attributes) throws BadRequestException { if (attributes == null) { return emptyMap(); @@ -703,27 +662,6 @@ public class WorkspaceService extends Service { } } - /** - * Checks only one of the given object reference is {@code null} - * - * @param object1 - * object reference to check - * @param object2 - * object reference to check - * @param subject - * used as subject of exception message "{subject} required" - * @throws BadRequestException - * when objects are both null or have both a value reference is {@code null} - */ - private void requiredOnlyOneNotNull(Object object1, Object object2, String subject) throws BadRequestException { - if (object1 == null && object2 == null) { - throw new BadRequestException(subject + " required"); - } - if (object1 != null && object2 != null) { - throw new BadRequestException(subject + " required"); - } - } - /* * Validate composite key. * diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceServiceLinksInjector.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceServiceLinksInjector.java index edeb4d3d2d..3d80893f91 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceServiceLinksInjector.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceServiceLinksInjector.java @@ -13,7 +13,7 @@ package org.eclipse.che.api.workspace.server; import org.eclipse.che.api.core.rest.ServiceContext; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.LinkParameter; -import org.eclipse.che.api.machine.server.MachineServiceLinksInjector; +import org.eclipse.che.api.environment.server.MachineServiceLinksInjector; import org.eclipse.che.api.machine.shared.dto.MachineDto; import org.eclipse.che.api.machine.shared.dto.ServerDto; import org.eclipse.che.api.machine.shared.dto.SnapshotDto; @@ -21,7 +21,6 @@ import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceRuntimeDto; import javax.inject.Inject; -import javax.inject.Named; import javax.inject.Singleton; import javax.ws.rs.core.UriBuilder; import java.net.URI; diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/EnvironmentImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/EnvironmentImpl.java index 7dba786fa8..54fc3654fe 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/EnvironmentImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/EnvironmentImpl.java @@ -15,7 +15,6 @@ import org.eclipse.che.api.core.model.machine.Recipe; import org.eclipse.che.api.core.model.workspace.Environment; import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.machine.server.recipe.RecipeImpl; -import org.eclipse.che.commons.annotation.Nullable; import java.util.ArrayList; import java.util.List; @@ -55,12 +54,19 @@ public class EnvironmentImpl implements Environment { return name; } + public void setName(String name) { + this.name = name; + } + @Override - @Nullable public Recipe getRecipe() { return recipe; } + public void setRecipe(RecipeImpl recipe) { + this.recipe = recipe; + } + @Override public List getMachineConfigs() { if (machineConfigs == null) { @@ -69,23 +75,23 @@ public class EnvironmentImpl implements Environment { return machineConfigs; } + public void setMachineConfigs(List machineConfigs) { + this.machineConfigs = machineConfigs; + } + @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof EnvironmentImpl)) return false; - final EnvironmentImpl other = (EnvironmentImpl)obj; - return Objects.equals(name, other.name) && - Objects.equals(recipe, other.recipe) && - getMachineConfigs().equals(other.getMachineConfigs()); + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof EnvironmentImpl)) return false; + EnvironmentImpl that = (EnvironmentImpl)o; + return Objects.equals(name, that.name) && + Objects.equals(recipe, that.recipe) && + Objects.equals(machineConfigs, that.machineConfigs); } @Override public int hashCode() { - int hash = 7; - hash = hash * 31 + Objects.hashCode(name); - hash = hash * 31 + Objects.hashCode(recipe); - hash = hash * 31 + getMachineConfigs().hashCode(); - return hash; + return Objects.hash(name, recipe, machineConfigs); } @Override diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceRuntimeImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceRuntimeImpl.java index 15f832004a..0b53c40bc7 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceRuntimeImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceRuntimeImpl.java @@ -95,28 +95,22 @@ public class WorkspaceRuntimeImpl implements WorkspaceRuntime { } @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof WorkspaceRuntimeImpl)) { - return false; - } - final WorkspaceRuntimeImpl other = (WorkspaceRuntimeImpl)obj; - return Objects.equals(activeEnv, other.activeEnv) - && Objects.equals(rootFolder, other.rootFolder) - && Objects.equals(devMachine, other.devMachine) - && getMachines().equals(getMachines()); + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof WorkspaceRuntimeImpl)) return false; + WorkspaceRuntimeImpl that = (WorkspaceRuntimeImpl)o; + return Objects.equals(activeEnv, that.activeEnv) && + Objects.equals(rootFolder, that.rootFolder) && + Objects.equals(devMachine, that.devMachine) && + Objects.equals(machines, that.machines); } @Override public int hashCode() { - int hash = 7; - hash = hash * 31 + Objects.hashCode(activeEnv); - hash = 31 * hash + Objects.hashCode(rootFolder); - hash = 31 * hash + Objects.hashCode(devMachine); - hash = 31 * hash + getMachines().hashCode(); - return hash; + return Objects.hash(activeEnv, + rootFolder, + devMachine, + machines); } @Override diff --git a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/wsagent/WsAgentLauncherImplTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/agent/server/wsagent/WsAgentLauncherImplTest.java similarity index 64% rename from wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/wsagent/WsAgentLauncherImplTest.java rename to wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/agent/server/wsagent/WsAgentLauncherImplTest.java index a9fa11ed51..0af95a257e 100644 --- a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/wsagent/WsAgentLauncherImplTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/agent/server/wsagent/WsAgentLauncherImplTest.java @@ -8,7 +8,7 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.machine.server.wsagent; +package org.eclipse.che.api.agent.server.wsagent; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.NotFoundException; @@ -18,15 +18,16 @@ import org.eclipse.che.api.core.model.machine.Server; import org.eclipse.che.api.core.rest.HttpJsonRequest; import org.eclipse.che.api.core.rest.HttpJsonRequestFactory; import org.eclipse.che.api.core.rest.HttpJsonResponse; -import org.eclipse.che.api.machine.server.MachineManager; +import org.eclipse.che.api.environment.server.MachineProcessManager; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.model.impl.CommandImpl; import org.eclipse.che.api.machine.server.model.impl.MachineImpl; import org.eclipse.che.api.machine.server.model.impl.MachineRuntimeInfoImpl; import org.eclipse.che.api.machine.server.model.impl.ServerImpl; import org.eclipse.che.api.machine.shared.Constants; -import org.eclipse.che.commons.test.SelfReturningAnswer; +import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; @@ -42,31 +43,30 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @Listeners(MockitoTestNGListener.class) public class WsAgentLauncherImplTest { - private static final String WS_ID = "wsId"; - private static final String MACHINE_ID = "machineId"; - private static final String WS_AGENT_START_CMD_LINE = "cmdLine"; - private static final String WS_AGENT_PORT = Constants.WS_AGENT_PORT; - private static final long WS_AGENT_MAX_START_TIME_MS = 1000; - private static final long WS_AGENT_PING_DELAY_MS = 1; - private static final int WS_AGENT_PING_CONN_TIMEOUT_MS = 1; - private static final String WS_AGENT_SERVER_LOCATION = "ws-agent.com:456789/"; - private static final String WS_AGENT_SERVER_URL = "http://" + WS_AGENT_SERVER_LOCATION; + private static final String MACHINE_ID = "machineId"; + private static final String WORKSPACE_ID = "testWorkspaceId"; + private static final String WS_AGENT_START_CMD_LINE = "cmdLine"; + private static final String WS_AGENT_PORT = Constants.WS_AGENT_PORT; + private static final long WS_AGENT_MAX_START_TIME_MS = 1000; + private static final long WS_AGENT_PING_DELAY_MS = 1; + private static final int WS_AGENT_PING_CONN_TIMEOUT_MS = 1; + private static final String WS_AGENT_SERVER_LOCATION = "ws-agent.com:456789/"; + private static final String WS_AGENT_SERVER_URL = "http://" + WS_AGENT_SERVER_LOCATION; private static final ServerImpl SERVER = new ServerImpl("ref", "http", WS_AGENT_SERVER_LOCATION, null, WS_AGENT_SERVER_URL); - private static final String WS_AGENT_TIMED_OUT_MESSAGE = "timeout error message"; + private static final String WS_AGENT_TIMED_OUT_MESSAGE = "timeout error message"; @Mock - private MachineManager machineManager; + private MachineProcessManager machineProcessManager; @Mock private HttpJsonRequestFactory requestFactory; @Mock @@ -81,16 +81,16 @@ public class WsAgentLauncherImplTest { @BeforeMethod public void setUp() throws Exception { - wsAgentLauncher = new WsAgentLauncherImpl(() -> machineManager, + wsAgentLauncher = new WsAgentLauncherImpl(() -> machineProcessManager, requestFactory, WS_AGENT_START_CMD_LINE, WS_AGENT_MAX_START_TIME_MS, WS_AGENT_PING_DELAY_MS, WS_AGENT_PING_CONN_TIMEOUT_MS, WS_AGENT_TIMED_OUT_MESSAGE); - pingRequest = mock(HttpJsonRequest.class, new SelfReturningAnswer()); - when(machineManager.getDevMachine(WS_ID)).thenReturn(machine); + pingRequest = Mockito.mock(HttpJsonRequest.class, new SelfReturningAnswer()); when(machine.getId()).thenReturn(MACHINE_ID); + when(machine.getWorkspaceId()).thenReturn(WORKSPACE_ID); when(machine.getRuntime()).thenReturn(machineRuntime); doReturn(Collections.singletonMap(WS_AGENT_PORT, SERVER)).when(machineRuntime).getServers(); when(requestFactory.fromUrl(anyString())).thenReturn(pingRequest); @@ -100,19 +100,20 @@ public class WsAgentLauncherImplTest { @Test public void shouldStartWsAgentUsingMachineExec() throws Exception { - wsAgentLauncher.startWsAgent(WS_ID); + wsAgentLauncher.startWsAgent(machine); - verify(machineManager).exec(eq(MACHINE_ID), - eq(new CommandImpl(WsAgentLauncherImpl.WS_AGENT_PROCESS_NAME, + verify(machineProcessManager).exec(eq(WORKSPACE_ID), + eq(MACHINE_ID), + eq(new CommandImpl(WsAgentLauncherImpl.WS_AGENT_PROCESS_NAME, WS_AGENT_START_CMD_LINE, "Arbitrary")), - eq(WsAgentLauncherImpl.getWsAgentProcessOutputChannel(WS_ID))); + eq(WsAgentLauncherImpl.getWsAgentProcessOutputChannel(WORKSPACE_ID))); } @Test public void shouldPingWsAgentAfterStart() throws Exception { - wsAgentLauncher.startWsAgent(WS_ID); + wsAgentLauncher.startWsAgent(machine); verify(requestFactory).fromUrl(UriBuilder.fromUri(WS_AGENT_SERVER_URL) .build() @@ -130,7 +131,7 @@ public class WsAgentLauncherImplTest { new IOException()) .thenReturn(pingResponse); - wsAgentLauncher.startWsAgent(WS_ID); + wsAgentLauncher.startWsAgent(machine); verify(requestFactory).fromUrl(WS_AGENT_SERVER_URL); verify(pingRequest).setMethod(HttpMethod.GET); @@ -145,7 +146,7 @@ public class WsAgentLauncherImplTest { HttpURLConnection.HTTP_NO_CONTENT, HttpURLConnection.HTTP_OK); - wsAgentLauncher.startWsAgent(WS_ID); + wsAgentLauncher.startWsAgent(machine); verify(requestFactory).fromUrl(WS_AGENT_SERVER_URL); verify(pingRequest).setMethod(HttpMethod.GET); @@ -159,57 +160,58 @@ public class WsAgentLauncherImplTest { when(pingRequest.request()).thenThrow(new ServerException("")) .thenReturn(pingResponse); - wsAgentLauncher.startWsAgent(WS_ID); + wsAgentLauncher.startWsAgent(machine); verify(pingRequest, times(2)).request(); verify(pingResponse).getResponseCode(); } - @Test(expectedExceptions = NotFoundException.class, expectedExceptionsMessageRegExp = "Test exception") - public void shouldThrowNotFoundExceptionIfMachineManagerGetDevMachineForWsThrowsNotFoundException() throws Exception { - final String notExistingWsId = "notExistingWsId"; - when(machineManager.getDevMachine(notExistingWsId)).thenThrow(new NotFoundException("Test exception")); - - wsAgentLauncher.startWsAgent(notExistingWsId); - - verify(machineManager).getDevMachine(eq(notExistingWsId)); - } - - @Test(expectedExceptions = MachineException.class, expectedExceptionsMessageRegExp = "Test exception") - public void shouldThrowMachineExceptionIfMachineManagerGetDevMachineForWsThrowsMachineException() throws Exception { - final String notExistingWsId = "notExistingWsId"; - when(machineManager.getDevMachine(notExistingWsId)).thenThrow(new MachineException("Test exception")); - - wsAgentLauncher.startWsAgent(notExistingWsId); - - verify(machineManager).getDevMachine(eq(notExistingWsId)); - } - @Test(expectedExceptions = NotFoundException.class, expectedExceptionsMessageRegExp = "Test exception") public void shouldThrowNotFoundExceptionIfMachineManagerExecInDevMachineThrowsNotFoundException() throws Exception { - when(machineManager.exec(anyString(), any(Command.class), anyString())).thenThrow(new NotFoundException("Test exception")); + when(machineProcessManager.exec(anyString(), + anyString(), + any(Command.class), + anyString())) + .thenThrow(new NotFoundException("Test exception")); - wsAgentLauncher.startWsAgent(WS_ID); + wsAgentLauncher.startWsAgent(machine); - verify(machineManager).exec(anyString(), any(Command.class), anyString()); + verify(machineProcessManager).exec(anyString(), + anyString(), + any(Command.class), + anyString()); } @Test(expectedExceptions = MachineException.class, expectedExceptionsMessageRegExp = "Test exception") public void shouldThrowMachineExceptionIfMachineManagerExecInDevMachineThrowsMachineException() throws Exception { - when(machineManager.exec(anyString(), any(Command.class), anyString())).thenThrow(new MachineException("Test exception")); + when(machineProcessManager.exec(anyString(), + anyString(), + any(Command.class), + anyString())) + .thenThrow(new MachineException("Test exception")); - wsAgentLauncher.startWsAgent(WS_ID); + wsAgentLauncher.startWsAgent(machine); - verify(machineManager).exec(anyString(), any(Command.class), anyString()); + verify(machineProcessManager).exec(anyString(), + anyString(), + any(Command.class), + anyString()); } - @Test(expectedExceptions = MachineException.class, expectedExceptionsMessageRegExp = "Test exception") - public void shouldThrowMachineExceptionIfMachineManagerExecInDevMachineThrowsBadRequestException() throws Exception { - when(machineManager.exec(anyString(), any(Command.class), anyString())).thenThrow(new BadRequestException("Test exception")); + @Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = "Test exception") + public void shouldThrowExceptionIfMachineManagerExecInDevMachineThrowsBadRequestException() throws Exception { + when(machineProcessManager.exec(anyString(), + anyString(), + any(Command.class), + anyString())) + .thenThrow(new BadRequestException("Test exception")); - wsAgentLauncher.startWsAgent(WS_ID); + wsAgentLauncher.startWsAgent(machine); - verify(machineManager).exec(anyString(), any(Command.class), anyString()); + verify(machineProcessManager).exec(anyString(), + anyString(), + any(Command.class), + anyString()); } @Test(expectedExceptions = ServerException.class, @@ -217,14 +219,14 @@ public class WsAgentLauncherImplTest { public void shouldThrowMachineExceptionIfPingsWereUnsuccessfulTooLong() throws Exception { when(pingRequest.request()).thenThrow(new ServerException("")); - wsAgentLauncher.startWsAgent(WS_ID); + wsAgentLauncher.startWsAgent(machine); } - @Test(expectedExceptions = MachineException.class, + @Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = "Workspace agent server not found in dev machine.") - public void shouldThrowMachineExceptionIfwsAgentNotFound() throws Exception { + public void shouldThrowExceptionIfWsAgentNotFound() throws Exception { doReturn(Collections.emptyMap()).when(machineRuntime).getServers(); - wsAgentLauncher.startWsAgent(WS_ID); + wsAgentLauncher.startWsAgent(machine); } } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/CheEnvironmentEngineTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/CheEnvironmentEngineTest.java new file mode 100644 index 0000000000..28050705b1 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/CheEnvironmentEngineTest.java @@ -0,0 +1,484 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.environment.server; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.core.model.machine.MachineLogMessage; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.core.util.MessageConsumer; +import org.eclipse.che.api.environment.server.exception.EnvironmentNotRunningException; +import org.eclipse.che.api.machine.server.MachineInstanceProviders; +import org.eclipse.che.api.machine.server.dao.SnapshotDao; +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.machine.server.model.impl.LimitsImpl; +import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl; +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.Instance; +import org.eclipse.che.api.machine.server.spi.InstanceProvider; +import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; +import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.subject.SubjectImpl; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.eclipse.che.dto.server.DtoFactory.newDto; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +/** + * @author Alexander Garagatyi + */ +@Listeners(MockitoTestNGListener.class) +public class CheEnvironmentEngineTest { + @Mock + MessageConsumer messageConsumer; + @Mock + InstanceProvider instanceProvider; + + @Mock + SnapshotDao snapshotDao; + @Mock + MachineInstanceProviders machineInstanceProviders; + @Mock + EventService eventService; + + CheEnvironmentEngine engine; + + @BeforeMethod + public void setUp() throws Exception { + engine = spy(new CheEnvironmentEngine(snapshotDao, + machineInstanceProviders, + "/tmp", + 256, + eventService)); + + when(machineInstanceProviders.getProvider("docker")).thenReturn(instanceProvider); + when(instanceProvider.getRecipeTypes()).thenReturn(Collections.singleton("dockerfile")); + + EnvironmentContext.getCurrent().setSubject(new SubjectImpl("name", "id", "token", false)); + } + + @AfterMethod + public void tearDown() throws Exception { + EnvironmentContext.reset(); + } + + @Test + public void shouldBeAbleToGetMachinesOfEnv() throws Exception { + // given + List instances = startEnv(); + String workspaceId = instances.get(0).getWorkspaceId(); + + // when + List actualMachines = engine.getMachines(workspaceId); + + // then + assertEquals(actualMachines, instances); + } + + @Test(expectedExceptions = EnvironmentNotRunningException.class, + expectedExceptionsMessageRegExp = "Environment with ID '.*' is not found") + public void shouldThrowExceptionOnGetMachinesIfEnvironmentIsNotFound() throws Exception { + engine.getMachines("wsIdOfNotRunningEnv"); + } + + @Test + public void shouldBeAbleToGetMachineOfEnv() throws Exception { + // given + List instances = startEnv(); + Instance instance = instances.get(0); + String workspaceId = instance.getWorkspaceId(); + + // when + Instance actualInstance = engine.getMachine(workspaceId, instance.getId()); + + // then + assertEquals(actualInstance, instance); + } + + @Test(expectedExceptions = EnvironmentNotRunningException.class, + expectedExceptionsMessageRegExp = "Environment with ID '.*' is not found") + public void shouldThrowExceptionOnGetMachineIfEnvironmentIsNotFound() throws Exception { + // when + engine.getMachine("wsIdOfNotRunningEnv", "nonExistingInstanceId"); + } + + @Test(expectedExceptions = NotFoundException.class, + expectedExceptionsMessageRegExp = "Machine with ID .* is not found in the environment of workspace .*") + public void shouldThrowExceptionOnGetMachineIfMachineIsNotFound() throws Exception { + // given + List instances = startEnv(); + Instance instance = instances.get(0); + String workspaceId = instance.getWorkspaceId(); + + // when + engine.getMachine(workspaceId, "nonExistingInstanceId"); + } + + @Test + public void shouldBeAbleToStartEnvironment() throws Exception { + // given + EnvironmentImpl env = createEnv(); + String workspaceId = "wsId"; + List expectedMachines = new ArrayList<>(); + when(instanceProvider.createInstance(any(Machine.class), + any(LineConsumer.class))) + .thenAnswer(invocationOnMock -> { + Object[] arguments = invocationOnMock.getArguments(); + Machine machine = (Machine)arguments[0]; + Instance instance = spy(new NoOpMachineInstance(machine)); + expectedMachines.add(instance); + return instance; + }); + + // when + List machines = engine.start(workspaceId, env, false, messageConsumer); + + // then + assertEquals(machines, expectedMachines); + verify(instanceProvider, times(env.getMachineConfigs().size())) + .createInstance(any(Machine.class), any(LineConsumer.class)); + } + + @Test + public void envStartShouldFireEvents() throws Exception { + // when + List instances = startEnv(); + assertTrue(instances.size() > 1, "This test requires at least 2 instances in environment"); + + // then + for (Instance instance : instances) { + verify(eventService).publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.CREATING) + .withDev(instance.getConfig().isDev()) + .withMachineName(instance.getConfig().getName()) + .withMachineId(instance.getId()) + .withWorkspaceId(instance.getWorkspaceId())); + verify(eventService).publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.RUNNING) + .withDev(instance.getConfig().isDev()) + .withMachineName(instance.getConfig().getName()) + .withMachineId(instance.getId()) + .withWorkspaceId(instance.getWorkspaceId())); + } + } + + @Test(expectedExceptions = ConflictException.class, + expectedExceptionsMessageRegExp = "Environment of workspace '.*' already exists") + public void envStartShouldThrowsExceptionIfSameEnvironmentExists() throws Exception { + // given + List instances = startEnv(); + Instance instance = instances.get(0); + EnvironmentImpl env = createEnv(); + + // when + engine.start(instance.getWorkspaceId(), + env, + false, + messageConsumer); + } + + @Test + public void shouldDestroyMachinesOnEnvStop() throws Exception { + // given + List instances = startEnv(); + Instance instance = instances.get(0); + + // when + engine.stop(instance.getWorkspaceId()); + + // then + for (Instance instance1 : instances) { + verify(instance1).destroy(); + } + } + + @Test(expectedExceptions = EnvironmentNotRunningException.class, + expectedExceptionsMessageRegExp = "Environment with ID '.*' is not found") + public void shouldThrowExceptionOnEnvStopIfItIsNotRunning() throws Exception { + engine.stop("wsIdOFNonExistingEnv"); + } + + @Test + public void destroyOfMachineOnEnvStopShouldNotPreventStopOfOthers() throws Exception { + // given + List instances = startEnv(); + Instance instance = instances.get(0); + doThrow(new MachineException("test exception")).when(instance).destroy(); + assertTrue(instances.size() > 1, "This test requires at least 2 instances in environment"); + + // when + engine.stop(instance.getWorkspaceId()); + + // then + InOrder inOrder = inOrder(instances.toArray()); + for (Instance instance1 : instances) { + inOrder.verify(instance1).destroy(); + } + } + + @Test + public void shouldBeAbleToStartMachine() throws Exception { + // given + List instances = startEnv(); + String workspaceId = instances.get(0).getWorkspaceId(); + + when(engine.generateMachineId()).thenReturn("newMachineId"); + Instance newMachine = mock(Instance.class); + when(newMachine.getId()).thenReturn("newMachineId"); + when(newMachine.getWorkspaceId()).thenReturn(workspaceId); + doReturn(newMachine).when(instanceProvider).createInstance(any(Machine.class), any(LineConsumer.class)); + + MachineConfigImpl config = createConfig(false); + + // when + Instance actualInstance = engine.startMachine(workspaceId, config); + + // then + assertEquals(actualInstance, newMachine); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Machine.class); + verify(instanceProvider, times(3)).createInstance(argumentCaptor.capture(), any(LineConsumer.class)); + assertEquals(argumentCaptor.getValue().getConfig(), config); + } + + @Test(expectedExceptions = EnvironmentNotRunningException.class, + expectedExceptionsMessageRegExp = "Environment '.*' is not running") + public void shouldThrowExceptionOnMachineStartIfEnvironmentIsNotRunning() throws Exception { + MachineConfigImpl config = createConfig(false); + + // when + engine.startMachine("wsIdOfNotRunningEnv", config); + } + + @Test(expectedExceptions = ConflictException.class, + expectedExceptionsMessageRegExp = "Machine with name '.*' already exists in environment of workspace '.*'") + public void machineStartShouldThrowExceptionIfMachineWithTheSameNameAlreadyExistsInEnvironment() throws Exception { + // given + List instances = startEnv(); + Instance instance = instances.get(0); + + MachineConfigImpl config = createConfig(false); + config.setName(instance.getConfig().getName()); + + // when + engine.startMachine(instance.getWorkspaceId(), config); + } + + @Test + public void machineStartShouldPublishEvents() throws Exception { + // given + List instances = startEnv(); + Instance instance = instances.get(0); + + MachineConfigImpl config = createConfig(false); + when(engine.generateMachineId()).thenReturn("newMachineId"); + + // when + engine.startMachine(instance.getWorkspaceId(), config); + + // then + verify(eventService).publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.CREATING) + .withDev(config.isDev()) + .withMachineName(config.getName()) + .withMachineId("newMachineId") + .withWorkspaceId(instance.getWorkspaceId())); + verify(eventService).publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.RUNNING) + .withDev(config.isDev()) + .withMachineName(config.getName()) + .withMachineId("newMachineId") + .withWorkspaceId(instance.getWorkspaceId())); + } + + @Test + public void shouldBeAbleToStopMachine() throws Exception { + // given + List instances = startEnv(); + Optional instanceOpt = instances.stream() + .filter(machine -> !machine.getConfig().isDev()) + .findAny(); + assertTrue(instanceOpt.isPresent(), "Required for test non-dev machine is not found"); + Instance instance = instanceOpt.get(); + + // when + engine.stopMachine(instance.getWorkspaceId(), instance.getId()); + + // then + verify(instance).destroy(); + } + + @Test(expectedExceptions = EnvironmentNotRunningException.class, + expectedExceptionsMessageRegExp = "Environment '.*' is not running") + public void machineStopShouldThrowExceptionIfEnvDoesNotExist() throws Exception { + engine.stopMachine("wsIdOfNotRunningEnv", "testMachineID"); + } + + @Test(expectedExceptions = ConflictException.class, + expectedExceptionsMessageRegExp = "Stop of dev machine is not allowed. Please, stop whole environment") + public void devMachineStopShouldThrowException() throws Exception { + // given + List instances = startEnv(); + Optional instanceOpt = instances.stream() + .filter(machine -> machine.getConfig().isDev()) + .findAny(); + assertTrue(instanceOpt.isPresent(), "Required for test dev machine is not found"); + Instance instance = instanceOpt.get(); + + // when + engine.stopMachine(instance.getWorkspaceId(), instance.getId()); + } + + @Test(expectedExceptions = NotFoundException.class, + expectedExceptionsMessageRegExp = "Machine with ID '.*' is not found in environment of workspace '.*'") + public void machineStopOfNonExistingMachineShouldThrowsException() throws Exception { + // given + List instances = startEnv(); + Instance instance = instances.get(0); + + // when + engine.stopMachine(instance.getWorkspaceId(), "idOfNonExistingMachine"); + } + + @Test + public void machineStopShouldFireEvents() throws Exception { + // given + List instances = startEnv(); + Optional instanceOpt = instances.stream() + .filter(machine -> !machine.getConfig().isDev()) + .findAny(); + assertTrue(instanceOpt.isPresent(), "Required for test non-dev machine is not found"); + Instance instance = instanceOpt.get(); + + // when + engine.stopMachine(instance.getWorkspaceId(), instance.getId()); + + // then + verify(eventService).publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.CREATING) + .withDev(instance.getConfig().isDev()) + .withMachineName(instance.getConfig().getName()) + .withMachineId(instance.getId()) + .withWorkspaceId(instance.getWorkspaceId())); + verify(eventService).publish(newDto(MachineStatusEvent.class) + .withEventType(MachineStatusEvent.EventType.RUNNING) + .withDev(instance.getConfig().isDev()) + .withMachineName(instance.getConfig().getName()) + .withMachineId(instance.getId()) + .withWorkspaceId(instance.getWorkspaceId())); + } + + @Test + public void shouldBeAbleToSaveMachineSnapshot() throws Exception { + // given + List instances = startEnv(); + Instance instance = instances.get(0); + doReturn(new MachineSourceImpl("someType").setContent("some content")).when(instance).saveToSnapshot(); + + // when + engine.saveSnapshot("someNamespace", instance.getWorkspaceId(), instance.getId()); + + // then + verify(instance).saveToSnapshot(); + } + + @Test(expectedExceptions = EnvironmentNotRunningException.class, + expectedExceptionsMessageRegExp = "Environment .*' is not running") + public void shouldThrowExceptionOnSaveSnapshotIfEnvIsNotRunning() throws Exception { + engine.saveSnapshot("someNamespace", "wsIdOfNotRunningEnv", "someId"); + } + + @Test(expectedExceptions = NotFoundException.class, + expectedExceptionsMessageRegExp = "Machine with id '.*' is not found in environment of workspace '.*'") + public void shouldThrowExceptionOnSaveSnapshotIfMachineIsNotFound() throws Exception { + // given + List instances = startEnv(); + Instance instance = instances.get(0); + + // when + engine.saveSnapshot("someNamespace", instance.getWorkspaceId(), "idOfNonExistingMachine"); + } + + @Test + public void shouldBeAbleToRemoveSnapshot() throws Exception { + // given + SnapshotImpl snapshot = mock(SnapshotImpl.class); + MachineSourceImpl machineSource = mock(MachineSourceImpl.class); + when(snapshot.getType()).thenReturn("docker"); + when(snapshot.getMachineSource()).thenReturn(machineSource); + + // when + engine.removeSnapshot(snapshot); + + // then + verify(instanceProvider).removeInstanceSnapshot(machineSource); + } + + private List startEnv() throws Exception { + EnvironmentImpl env = createEnv(); + String workspaceId = "wsId"; + when(instanceProvider.createInstance(any(Machine.class), + any(LineConsumer.class))) + .thenAnswer(invocationOnMock -> { + Object[] arguments = invocationOnMock.getArguments(); + Machine machine = (Machine)arguments[0]; + return spy(new NoOpMachineInstance(machine)); + }); + + // when + return engine.start(workspaceId, env, false, messageConsumer); + } + + private static MachineConfigImpl createConfig(boolean isDev) { + return MachineConfigImpl.builder() + .setDev(isDev) + .setType("docker") + .setLimits(new LimitsImpl(1024)) + .setSource(new MachineSourceImpl("dockerfile").setLocation("location")) + .setName(UUID.randomUUID().toString()) + .build(); + } + + private EnvironmentImpl createEnv() { + List machines = new ArrayList<>(); + machines.add(createConfig(true)); + machines.add(createConfig(false)); + return new EnvironmentImpl("envName", + null, + machines); + } +} diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/CheEnvironmentValidatorTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/CheEnvironmentValidatorTest.java new file mode 100644 index 0000000000..7b6a473281 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/CheEnvironmentValidatorTest.java @@ -0,0 +1,345 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.environment.server; + +import org.eclipse.che.api.machine.server.MachineInstanceProviders; +import org.eclipse.che.api.machine.shared.dto.MachineConfigDto; +import org.eclipse.che.api.machine.shared.dto.MachineSourceDto; +import org.eclipse.che.api.machine.shared.dto.ServerConfDto; +import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto; +import org.eclipse.che.api.workspace.shared.dto.RecipeDto; +import org.mockito.InjectMocks; +import org.mockito.Mock; +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.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; + +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.Mockito.when; + +/** + * @author Alexander Garagatyi + */ +@Listeners(MockitoTestNGListener.class) +public class CheEnvironmentValidatorTest { + @Mock + MachineInstanceProviders machineInstanceProviders; + @InjectMocks + CheEnvironmentValidator environmentValidator; + + @BeforeMethod + public void prepare() throws Exception { + when(machineInstanceProviders.hasProvider("docker")).thenReturn(true); + when(machineInstanceProviders.getProviderTypes()).thenReturn(Arrays.asList("docker", "ssh")); + } + + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Environment name should not be neither null nor empty") + public void shouldFailValidationIfEnvNameIsNull() throws Exception { + EnvironmentDto environment = createConfig(); + environment.setName(null); + + + environmentValidator.validate(environment); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Environment name should not be neither null nor empty") + public void shouldFailValidationIfEnvNameIsEmpty() throws Exception { + EnvironmentDto environment = createConfig(); + environment.setName(""); + + + environmentValidator.validate(environment); + } + + @Test + public void shouldNotFailValidationIfEnvironmentRecipeTypeIsDocker() throws Exception { + EnvironmentDto config = createConfig(); + config.withRecipe(newDto(RecipeDto.class).withType("docker")); + + + environmentValidator.validate(config); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Environment '.*' should contain at least 1 machine") + public void shouldFailValidationIfMachinesListIsEmpty() throws Exception { + EnvironmentDto config = createConfig(); + config.withMachineConfigs(null); + + + environmentValidator.validate(config); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Environment '.*' should contain exactly 1 dev machine, but contains '0'") + public void shouldFailValidationIfNoDevMachineFound() throws Exception { + EnvironmentDto config = createConfig(); + config.getMachineConfigs() + .stream() + .filter(MachineConfigDto::isDev) + .forEach(machine -> machine.withDev(false)); + + + environmentValidator.validate(config); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Environment '.*' should contain exactly 1 dev machine, but contains '2'") + public void shouldFailValidationIf2DevMachinesFound() throws Exception { + EnvironmentDto config = createConfig(); + final Optional devMachine = config.getMachineConfigs() + .stream() + .filter(MachineConfigDto::isDev) + .findAny(); + config.getMachineConfigs() + .add(devMachine.get().withName("other-name")); + + + environmentValidator.validate(config); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Environment .* contains machine with null or empty name") + public void shouldFailValidationIfMachineNameIsNull() throws Exception { + EnvironmentDto config = createConfig(); + config.getMachineConfigs() + .get(0) + .withName(null); + + + environmentValidator.validate(config); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Environment .* contains machine with null or empty name") + public void shouldFailValidationIfMachineNameIsEmpty() throws Exception { + EnvironmentDto config = createConfig(); + config.getMachineConfigs() + .get(0) + .withName(""); + + + environmentValidator.validate(config); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Machine '.*' in environment '.*' doesn't have source") + public void shouldFailValidationIfMachineSourceIsNull() throws Exception { + EnvironmentDto config = createConfig(); + config.getMachineConfigs() + .get(0) + .withSource(null); + + + environmentValidator.validate(config); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Type 'null' of machine '.*' in environment '.*' is not supported. Supported values are: docker, ssh.") + public void shouldFailValidationIfMachineTypeIsNull() throws Exception { + EnvironmentDto config = createConfig(); + config.getMachineConfigs() + .get(0) + .withType(null); + + + environmentValidator.validate(config); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Type 'compose' of machine '.*' in environment '.*' is not supported. Supported values are: docker, ssh.") + public void shouldFailValidationIfMachineTypeIsNotDocker() throws Exception { + EnvironmentDto config = createConfig(); + config.getMachineConfigs() + .get(0) + .withType("compose"); + + + environmentValidator.validate(config); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Machine .* contains server conf with invalid port .*", + dataProvider = "invalidPortProvider") + public void shouldFailValidationIfServerConfPortIsInvalid(String invalidPort) throws Exception { + EnvironmentDto config = createConfig(); + config.getMachineConfigs() + .get(0) + .getServers() + .add(newDto(ServerConfDto.class).withPort(invalidPort)); + + + environmentValidator.validate(config); + } + + @DataProvider(name = "invalidPortProvider") + public static Object[][] invalidPortProvider() { + return new Object[][] { + {"0"}, + {"0123"}, + {"012/tcp"}, + {"8080"}, + {"8080/pct"}, + {"8080/pdu"}, + {"/tcp"}, + {"tcp"}, + {""}, + {"8080/tcp1"}, + {"8080/tcpp"}, + {"8080tcp"}, + {"8080/tc"}, + {"8080/ud"}, + {"8080/udpp"}, + {"8080/udp/"}, + {"8080/tcp/"}, + {"8080/tcp/udp"}, + {"8080/tcp/tcp"}, + {"8080/tcp/8080"}, + {null} + }; + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Machine .* contains server conf with invalid protocol .*", + dataProvider = "invalidProtocolProvider") + public void shouldFailValidationIfServerConfProtocolIsInvalid(String invalidProtocol) throws Exception { + EnvironmentDto config = createConfig(); + config.getMachineConfigs() + .get(0) + .getServers() + .add(newDto(ServerConfDto.class).withPort("8080/tcp") + .withProtocol(invalidProtocol)); + + + environmentValidator.validate(config); + } + + @DataProvider(name = "invalidProtocolProvider") + public static Object[][] invalidProtocolProvider() { + return new Object[][] { + {""}, + {"http!"}, + {"2http"}, + {"http:"}, + }; + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Machine '.*' in environment '.*' contains environment variable with null or empty name") + public void shouldFailValidationIfEnvVarNameIsNull() throws Exception { + EnvironmentDto config = createConfig(); + config.getMachineConfigs() + .get(0) + .getEnvVariables() + .put(null, "value"); + + + environmentValidator.validate(config); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Machine '.*' in environment '.*' contains environment variable with null or empty name") + public void shouldFailValidationIfEnvVarNameIsEmpty() throws Exception { + EnvironmentDto config = createConfig(); + config.getMachineConfigs() + .get(0) + .getEnvVariables() + .put("", "value"); + + + environmentValidator.validate(config); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Machine '.*' in environment '.*' contains environment variable '.*' with null value") + public void shouldFailValidationIfEnvVarValueIsNull() throws Exception { + EnvironmentDto config = createConfig(); + config.getMachineConfigs() + .get(0) + .getEnvVariables() + .put("key", null); + + + environmentValidator.validate(config); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Source of machine '.*' in environment '.*' must contain location or content") + public void shouldFailValidationIfMissingSourceLocationAndContent() throws Exception { + EnvironmentDto config = createConfig(); + config.getMachineConfigs() + .get(0) + .withSource(newDto(MachineSourceDto.class).withType("dockerfile")); + + environmentValidator.validate(config); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Environment '.*' contains machine '.*' with invalid source location: 'localhost'") + public void shouldFailValidationIfLocationIsInvalidUrl() throws Exception { + EnvironmentDto config = createConfig(); + config.getMachineConfigs() + .get(0) + .withSource(newDto(MachineSourceDto.class).withType("dockerfile").withLocation("localhost")); + + environmentValidator.validate(config); + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Environment '.*' contains machine '.*' with invalid source location protocol: ftp://localhost") + public void shouldFailValidationIfLocationHasInvalidProtocol() throws Exception { + EnvironmentDto config = createConfig(); + config.getMachineConfigs() + .get(0) + .withSource(newDto(MachineSourceDto.class).withType("dockerfile").withLocation("ftp://localhost")); + + environmentValidator.validate(config); + } + + private EnvironmentDto createConfig() { + final List serversConf = + new ArrayList<>(Arrays.asList(newDto(ServerConfDto.class).withRef("ref1") + .withPort("8080/tcp") + .withProtocol("https") + .withPath("some/path"), + newDto(ServerConfDto.class).withRef("ref2") + .withPort("9090/udp") + .withProtocol("protocol") + .withPath("/some/path"))); + MachineConfigDto devMachine = newDto(MachineConfigDto.class).withDev(true) + .withName("dev-machine") + .withType("docker") + .withSource(newDto(MachineSourceDto.class) + .withLocation("http://location") + .withType("dockerfile")) + .withServers(serversConf) + .withEnvVariables(new HashMap<>( + singletonMap("key1", "value1"))); + + return newDto(EnvironmentDto.class).withName("dev-env") + .withMachineConfigs( + new ArrayList<>(singletonList(devMachine))) + .withRecipe(null); + } +} diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/MachineProcessManagerTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/MachineProcessManagerTest.java new file mode 100644 index 0000000000..7a96b05a89 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/MachineProcessManagerTest.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.environment.server; + +import org.eclipse.che.api.core.model.machine.Command; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.machine.server.spi.Instance; +import org.eclipse.che.api.machine.server.spi.InstanceProcess; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.lang.IoUtil; +import org.eclipse.che.commons.subject.SubjectImpl; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +import java.io.File; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.ThreadPoolExecutor; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertNotNull; + +/** + * Unit tests for {@link MachineProcessManager} + * + * @author Anton Korneta + * @author Alexander Garagatyi + */ +@Listeners(MockitoTestNGListener.class) +public class MachineProcessManagerTest { + + private static final String USER_ID = "userId"; + private static final String MACHINE_ID = "machineId"; + private static final String WORKSPACE_ID = "testWorkspaceId"; + + private static final SubjectImpl CREATOR = new SubjectImpl("name", USER_ID, "token", false); + + @Mock + Instance instance; + @Mock + Command command; + @Mock + InstanceProcess instanceProcess; + @Mock + LineConsumer logConsumer; + @Mock + CheEnvironmentEngine environmentEngine; + + private MachineProcessManager manager; + + @BeforeMethod + public void setUp() throws Exception { + final EventService eventService = mock(EventService.class); + final String machineLogsDir = targetDir().resolve("logs-dir").toString(); + IoUtil.deleteRecursive(new File(machineLogsDir)); + manager = spy(new MachineProcessManager(machineLogsDir, + eventService, + environmentEngine)); + + EnvironmentContext envCont = new EnvironmentContext(); + envCont.setSubject(CREATOR); + EnvironmentContext.setCurrent(envCont); + + doReturn(logConsumer).when(manager).getProcessLogger(MACHINE_ID, 111, "outputChannel"); + when(command.getCommandLine()).thenReturn("CommandLine"); + when(command.getName()).thenReturn("CommandName"); + when(command.getType()).thenReturn("CommandType"); + when(instance.createProcess(command, "outputChannel")).thenReturn(instanceProcess); + when(instanceProcess.getPid()).thenReturn(111); + when(environmentEngine.getMachine(WORKSPACE_ID, MACHINE_ID)).thenReturn(instance); + } + + @AfterMethod + public void tearDown() throws Exception { + EnvironmentContext.reset(); + } + + @Test + public void shouldCloseProcessLoggerIfExecIsSuccess() throws Exception { + //when + manager.exec(WORKSPACE_ID, MACHINE_ID, command, "outputChannel"); + waitForExecutorIsCompletedTask(); + + //then + verify(logConsumer).close(); + } + + @Test + public void shouldCloseProcessLoggerIfExecFails() throws Exception { + //given + doThrow(Exception.class).when(instanceProcess).start(); + + //when + manager.exec(WORKSPACE_ID, MACHINE_ID, command, "outputChannel"); + waitForExecutorIsCompletedTask(); + + //then + verify(logConsumer).close(); + } + + private void waitForExecutorIsCompletedTask() throws Exception { + for (int i = 0; ((ThreadPoolExecutor)manager.executor).getCompletedTaskCount() == 0 && i < 10; i++) { + Thread.sleep(300); + } + } + + private static Path targetDir() throws Exception { + final URL url = Thread.currentThread().getContextClassLoader().getResource("."); + assertNotNull(url); + return Paths.get(url.toURI()).getParent(); + } +} diff --git a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/MachineServiceLinksInjectorTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/MachineServiceLinksInjectorTest.java similarity index 78% rename from wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/MachineServiceLinksInjectorTest.java rename to wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/MachineServiceLinksInjectorTest.java index 98887f1b81..1f85208b43 100644 --- a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/MachineServiceLinksInjectorTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/MachineServiceLinksInjectorTest.java @@ -8,7 +8,7 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.machine.server; +package org.eclipse.che.api.environment.server; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -20,7 +20,6 @@ import org.eclipse.che.api.machine.shared.dto.MachineDto; import org.eclipse.che.api.machine.shared.dto.MachineProcessDto; import org.eclipse.che.api.machine.shared.dto.MachineRuntimeInfoDto; import org.eclipse.che.api.machine.shared.dto.ServerDto; -import org.eclipse.che.api.machine.shared.dto.SnapshotDto; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.dto.server.DtoFactory; import org.everrest.core.impl.uri.UriBuilderImpl; @@ -36,17 +35,13 @@ import java.util.Set; import java.util.stream.Collectors; import static java.util.Arrays.asList; -import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL; import static org.eclipse.che.api.machine.shared.Constants.ENVIRONMENT_STATUS_CHANNEL_TEMPLATE; import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_DESTROY_MACHINE; +import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL; import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_EXECUTE_COMMAND; import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_MACHINES; -import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_MACHINE_LOGS; import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_PROCESSES; import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_PROCESS_LOGS; -import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_SNAPSHOTS; -import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_REMOVE_SNAPSHOT; -import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_SAVE_SNAPSHOT; import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_SELF; import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_STOP_PROCESS; import static org.eclipse.che.api.machine.shared.Constants.TERMINAL_REFERENCE; @@ -102,10 +97,7 @@ public class MachineServiceLinksInjectorTest { Pair.of("GET", LINK_REL_GET_MACHINES), Pair.of("POST", LINK_REL_EXECUTE_COMMAND), Pair.of("DELETE", LINK_REL_DESTROY_MACHINE), - Pair.of("GET", LINK_REL_GET_SNAPSHOTS), Pair.of("GET", LINK_REL_GET_PROCESSES), - Pair.of("POST", LINK_REL_SAVE_SNAPSHOT), - Pair.of("GET", LINK_REL_GET_MACHINE_LOGS), Pair.of("GET", LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL), Pair.of("GET", ENVIRONMENT_STATUS_CHANNEL_TEMPLATE))); @@ -115,7 +107,7 @@ public class MachineServiceLinksInjectorTest { @Test public void shouldInjectLinksIntoMachineProcessDto() { final MachineProcessDto machineProcessDto = DtoFactory.newDto(MachineProcessDto.class); - machineLinksInjector.injectLinks(machineProcessDto, "machineId", serviceContextMock); + machineLinksInjector.injectLinks(machineProcessDto, "workspaceId", "machineId", serviceContextMock); final Set> links = machineProcessDto.getLinks() .stream() .map(link -> Pair.of(link.getMethod(), link.getRel())) @@ -126,18 +118,4 @@ public class MachineServiceLinksInjectorTest { assertEquals(links, expectedLinks, "Difference " + Sets.symmetricDifference(links, expectedLinks) + "\n"); } - - @Test - public void shouldInjectLinksIntoSnapshotDto() { - final SnapshotDto snapshotDto = DtoFactory.newDto(SnapshotDto.class) - .withId("id"); - machineLinksInjector.injectLinks(snapshotDto, serviceContextMock); - final Set> links = snapshotDto.getLinks() - .stream() - .map(link -> Pair.of(link.getMethod(), link.getRel())) - .collect(Collectors.toSet()); - final Set> expectedLinks = ImmutableSet.of(Pair.of("DELETE", LINK_REL_REMOVE_SNAPSHOT)); - - assertEquals(links, expectedLinks, "Difference " + Sets.symmetricDifference(links, expectedLinks) + "\n"); - } } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/MachineServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/MachineServiceTest.java new file mode 100644 index 0000000000..e24c921d1a --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/MachineServiceTest.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.environment.server; + +import com.jayway.restassured.http.ContentType; +import com.jayway.restassured.response.Response; + +import org.eclipse.che.api.core.model.machine.MachineConfig; +import org.eclipse.che.api.core.rest.ApiExceptionMapper; +import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl; +import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl; +import org.eclipse.che.api.workspace.server.WorkspaceManager; +import org.eclipse.che.api.workspace.server.WorkspaceServiceTest; +import org.everrest.assured.EverrestJetty; +import org.mockito.Mock; +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 static com.jayway.restassured.RestAssured.given; +import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; +import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; +import static org.everrest.assured.JettyHttpServer.SECURE_PATH; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.testng.Assert.assertEquals; + +/** + * @author Alexander Garagatyi + */ +@Listeners(value = {EverrestJetty.class, MockitoTestNGListener.class}) +public class MachineServiceTest { + @SuppressWarnings("unused") + private static final ApiExceptionMapper MAPPER = new ApiExceptionMapper(); + @SuppressWarnings("unused") + private static final WorkspaceServiceTest.EnvironmentFilter FILTER = new WorkspaceServiceTest.EnvironmentFilter(); + @Mock + private WorkspaceManager wsManager; + @Mock + private MachineProcessManager machineProcessManager; + + private MachineService service; + + @BeforeMethod + public void setup() { + service = new MachineService(machineProcessManager, + new MachineServiceLinksInjector(), + wsManager); + } + + @Test(dataProvider = "illegalMachineConfigProvider") + public void shouldReturnErrorOnStartMachineIfBodyIsInvalid(MachineConfig machineConfig) throws Exception { + // given + String workspaceId = "wsId"; + + // when + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .body(machineConfig) + .contentType(ContentType.JSON) + .post(SECURE_PATH + "/workspace/" + workspaceId + "/machine"); + + // then + assertEquals(response.getStatusCode(), 400); + verify(wsManager, never()).startMachine(any(MachineConfig.class), anyString()); + } + + @DataProvider(name = "illegalMachineConfigProvider") + public static Object[][] illegalMachineConfigProvider() { + MachineConfigImpl.MachineConfigImplBuilder builder = + MachineConfigImpl.builder() + .setDev(false) + .setName("name") + .setType("type") + .setSource(new MachineSourceImpl("type1").setContent("content")); + return new Object[][] { + {builder.setType(null) + .build()}, + {builder.setSource(null) + .build()}, + {builder.setSource(new MachineSourceImpl((String)null).setContent("content")) + .build()}, + {builder.setSource(new MachineSourceImpl("type").setContent("content") + .setLocation("location")) + .build()}, + }; + } + + @Test + public void shouldStartMachine() throws Exception { + // given + String workspaceId = "wsId"; + MachineConfig machineConfig = MachineConfigImpl.builder() + .setDev(false) + .setName("name") + .setType("type") + .setSource(new MachineSourceImpl("type1").setContent("content")) + .build(); + + // when + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .body(machineConfig) + .contentType(ContentType.JSON) + .post(SECURE_PATH + "/workspace/" + workspaceId + "/machine"); + + // then + assertEquals(response.getStatusCode(), 204); + verify(wsManager).startMachine(any(MachineConfig.class), eq(workspaceId)); + } + + @Test + public void shouldStopMachine() throws Exception { + // given + String workspaceId = "wsId"; + String machineId = "mcId"; + + // when + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .delete(SECURE_PATH + "/workspace/" + workspaceId + "/machine/" + machineId); + + // then + assertEquals(response.getStatusCode(), 204); + } +} diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidatorTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidatorTest.java index e0b9922fe5..0306906ecf 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidatorTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidatorTest.java @@ -11,33 +11,26 @@ package org.eclipse.che.api.workspace.server; import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.machine.server.MachineInstanceProviders; +import org.eclipse.che.api.environment.server.CheEnvironmentValidator; import org.eclipse.che.api.machine.shared.dto.CommandDto; -import org.eclipse.che.api.machine.shared.dto.MachineConfigDto; -import org.eclipse.che.api.machine.shared.dto.MachineSourceDto; -import org.eclipse.che.api.machine.shared.dto.ServerConfDto; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto; -import org.eclipse.che.api.workspace.shared.dto.RecipeDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.mockito.InjectMocks; import org.mockito.Mock; 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.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; -import java.util.Optional; +import static java.util.Collections.emptyList; 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.Mockito.when; /** * Tests for {@link WorkspaceValidator} and {@link DefaultWorkspaceValidator} @@ -48,15 +41,9 @@ import static org.mockito.Mockito.when; public class DefaultWorkspaceValidatorTest { @Mock - private MachineInstanceProviders machineInstanceProviders; + CheEnvironmentValidator environmentValidator; @InjectMocks - private DefaultWorkspaceValidator wsValidator; - - @BeforeMethod - public void prepare() throws Exception { - when(machineInstanceProviders.hasProvider("docker")).thenReturn(true); - when(machineInstanceProviders.getProviderTypes()).thenReturn(Arrays.asList(new String[] { "docker", "ssh" })); - } + DefaultWorkspaceValidator wsValidator; @Test public void shouldValidateCorrectWorkspace() throws Exception { @@ -184,161 +171,6 @@ public class DefaultWorkspaceValidatorTest { wsValidator.validateConfig(config); } - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Environment name should be neither null nor empty") - public void shouldFailValidationIfEnvNameIsNull() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .add(newDto(EnvironmentDto.class).withName(null) - .withMachineConfigs(config.getEnvironments() - .get(0) - .getMachineConfigs())); - - - wsValidator.validateConfig(config); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Environment name should be neither null nor empty") - public void shouldFailValidationIfEnvNameIsEmpty() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .add(newDto(EnvironmentDto.class).withName("") - .withMachineConfigs(config.getEnvironments() - .get(0) - .getMachineConfigs())); - - - wsValidator.validateConfig(config); - } - - @Test - public void shouldNotFailValidationIfEnvironmentRecipeTypeIsDocker() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .withRecipe(newDto(RecipeDto.class).withType("docker")); - - - wsValidator.validateConfig(config); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Environment '.*' should contain at least 1 machine") - public void shouldFailValidationIfMachinesListIsEmpty() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .withMachineConfigs(null); - - - wsValidator.validateConfig(config); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Environment should contain exactly 1 dev machine, but '.*' contains '0'") - public void shouldFailValidationIfNoDevMachineFound() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .stream() - .filter(MachineConfigDto::isDev) - .forEach(machine -> machine.withDev(false)); - - - wsValidator.validateConfig(config); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Environment should contain exactly 1 dev machine, but '.*' contains '2'") - public void shouldFailValidationIf2DevMachinesFound() throws Exception { - final WorkspaceConfigDto config = createConfig(); - final Optional devMachine = config.getEnvironments() - .get(0) - .getMachineConfigs() - .stream() - .filter(MachineConfigDto::isDev) - .findAny(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .add(devMachine.get().withName("other-name")); - - - wsValidator.validateConfig(config); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Environment .* contains machine with null or empty name") - public void shouldFailValidationIfMachineNameIsNull() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .get(0) - .withName(null); - - - wsValidator.validateConfig(config); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Environment .* contains machine with null or empty name") - public void shouldFailValidationIfMachineNameIsEmpty() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .get(0) - .withName(""); - - - wsValidator.validateConfig(config); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Environment .* contains machine without source") - public void shouldFailValidationIfMachineSourceIsNull() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .get(0) - .withSource(null); - - - wsValidator.validateConfig(config); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Type .* of machine .* in environment .* is not supported. Supported values: docker, ssh.") - public void shouldFailValidationIfMachineTypeIsNull() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .get(0) - .withType(null); - - - wsValidator.validateConfig(config); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Type .* of machine .* in environment .* is not supported. Supported values: docker, ssh.") - public void shouldFailValidationIfMachineTypeIsNotDocker() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .get(0) - .withType("compose"); - - - wsValidator.validateConfig(config); - } - @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Workspace ws-name contains command with null or empty name") public void shouldFailValidationIfCommandNameIsNull() throws Exception { @@ -387,181 +219,12 @@ public class DefaultWorkspaceValidatorTest { wsValidator.validateConfig(config); } - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Machine .* contains server conf with invalid port .*", - dataProvider = "invalidPortProvider") - public void shouldFailValidationIfServerConfPortIsInvalid(String invalidPort) throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .get(0) - .getServers() - .add(newDto(ServerConfDto.class).withPort(invalidPort)); - - - wsValidator.validateConfig(config); - } - - @DataProvider(name = "invalidPortProvider") - public static Object[][] invalidPortProvider() { - return new Object[][] { - {"0"}, - {"0123"}, - {"012/tcp"}, - {"8080"}, - {"8080/pct"}, - {"8080/pdu"}, - {"/tcp"}, - {"tcp"}, - {""}, - {"8080/tcp1"}, - {"8080/tcpp"}, - {"8080tcp"}, - {"8080/tc"}, - {"8080/ud"}, - {"8080/udpp"}, - {"8080/udp/"}, - {"8080/tcp/"}, - {"8080/tcp/udp"}, - {"8080/tcp/tcp"}, - {"8080/tcp/8080"}, - {null} - }; - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Machine .* contains server conf with invalid protocol .*", - dataProvider = "invalidProtocolProvider") - public void shouldFailValidationIfServerConfProtocolIsInvalid(String invalidProtocol) throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .get(0) - .getServers() - .add(newDto(ServerConfDto.class).withPort("8080/tcp") - .withProtocol(invalidProtocol)); - - - wsValidator.validateConfig(config); - } - - @DataProvider(name = "invalidProtocolProvider") - public static Object[][] invalidProtocolProvider() { - return new Object[][] { - {""}, - {"http!"}, - {"2http"}, - {"http:"}, - }; - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Machine %s contains environment variable with null or empty name") - public void shouldFailValidationIfEnvVarNameIsNull() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .get(0) - .getEnvVariables() - .put(null, "value"); - - - wsValidator.validateConfig(config); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Machine %s contains environment variable with null or empty name") - public void shouldFailValidationIfEnvVarNameIsEmpty() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .get(0) - .getEnvVariables() - .put("", "value"); - - - wsValidator.validateConfig(config); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Machine %s contains environment variable with null value") - public void shouldFailValidationIfEnvVarValueIsNull() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .get(0) - .getEnvVariables() - .put("key", null); - - - wsValidator.validateConfig(config); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Environment dev-env contains machine with source but this source doesn't define a location or content") - public void shouldFailValidationIfMissingLocationOrContent() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .get(0) - .withSource(newDto(MachineSourceDto.class).withType("dockerfile")); - - wsValidator.validateConfig(config); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Environment dev-env contains machine with invalid source location: localhost") - public void shouldFailValidationIfLocationIsInvalidUrl() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .get(0) - .withSource(newDto(MachineSourceDto.class).withType("dockerfile").withLocation("localhost")); - - wsValidator.validateConfig(config); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Environment dev-env contains machine with invalid source location protocol: ftp://localhost") - public void shouldFailValidationIfLocationHasInvalidProtocol() throws Exception { - final WorkspaceConfigDto config = createConfig(); - config.getEnvironments() - .get(0) - .getMachineConfigs() - .get(0) - .withSource(newDto(MachineSourceDto.class).withType("dockerfile").withLocation("ftp://localhost")); - - wsValidator.validateConfig(config); - } - private static WorkspaceConfigDto createConfig() { final WorkspaceConfigDto workspaceConfigDto = newDto(WorkspaceConfigDto.class).withName("ws-name") .withDefaultEnv("dev-env"); - final List serversConf = new ArrayList<>(Arrays.asList(newDto(ServerConfDto.class).withRef("ref1") - .withPort("8080/tcp") - .withProtocol("https") - .withPath("some/path"), - newDto(ServerConfDto.class).withRef("ref2") - .withPort("9090/udp") - .withProtocol("protocol") - .withPath("/some/path"))); - MachineConfigDto devMachine = newDto(MachineConfigDto.class).withDev(true) - .withName("dev-machine") - .withType("docker") - .withSource(newDto(MachineSourceDto.class).withLocation("http://location") - .withType("dockerfile")) - .withServers(serversConf) - .withEnvVariables(new HashMap<>(singletonMap("key1", "value1"))); EnvironmentDto devEnv = newDto(EnvironmentDto.class).withName("dev-env") - .withMachineConfigs(new ArrayList<>(singletonList(devMachine))) + .withMachineConfigs(emptyList()) .withRecipe(null); workspaceConfigDto.setEnvironments(new ArrayList<>(singletonList(devEnv))); @@ -569,7 +232,8 @@ public class DefaultWorkspaceValidatorTest { commandDtos.add(newDto(CommandDto.class).withName("command_name") .withType("maven") .withCommandLine("mvn clean install") - .withAttributes(new HashMap<>(singletonMap("cmd-attribute-name", "cmd-attribute-value")))); + .withAttributes(new HashMap<>(singletonMap("cmd-attribute-name", + "cmd-attribute-value")))); workspaceConfigDto.setCommands(commandDtos); return workspaceConfigDto; diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java index 561a680f9e..623c1ba23a 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java @@ -15,9 +15,9 @@ import org.eclipse.che.api.core.NotFoundException; 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.core.util.LineConsumer; -import org.eclipse.che.api.machine.server.MachineManager; -import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.environment.server.MachineProcessManager; +import org.eclipse.che.api.machine.server.dao.SnapshotDao; +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.MachineRuntimeInfoImpl; @@ -37,6 +37,7 @@ 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.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; @@ -44,6 +45,7 @@ import org.testng.annotations.Listeners; import org.testng.annotations.Test; import java.util.List; +import java.util.Optional; import static com.google.common.base.Strings.isNullOrEmpty; import static java.util.Arrays.asList; @@ -62,6 +64,8 @@ import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; @@ -70,7 +74,7 @@ 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.AssertJUnit.assertTrue; +import static org.testng.Assert.assertTrue; /** * Covers main cases of {@link WorkspaceManager}. @@ -90,13 +94,15 @@ public class WorkspaceManagerTest { @Mock private WorkspaceValidator workspaceConfigValidator; @Mock - private MachineManager client; + private MachineProcessManager client; @Mock private WorkspaceHooks workspaceHooks; @Mock - private MachineManager machineManager; + private MachineProcessManager machineProcessManager; @Mock private WorkspaceRuntimes runtimes; + @Mock + private SnapshotDao snapshotDao; @Captor private ArgumentCaptor workspaceCaptor; @@ -107,9 +113,9 @@ public class WorkspaceManagerTest { workspaceManager = spy(new WorkspaceManager(workspaceDao, runtimes, eventService, - machineManager, false, - false)); + false, + snapshotDao)); workspaceManager.setHooks(workspaceHooks); when(workspaceDao.create(any(WorkspaceImpl.class))).thenAnswer(invocation -> invocation.getArguments()[0]); @@ -349,26 +355,60 @@ public class WorkspaceManagerTest { public void shouldRecoverWorkspaceWhenRecoverParameterIsNullAndAutoRestoreAttributeIsSetAndSnapshotExists() throws Exception { final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); workspace.getAttributes().put(AUTO_RESTORE_FROM_SNAPSHOT, "true"); - when(machineManager.getSnapshots(any(), any())).thenReturn(singletonList(mock(SnapshotImpl.class))); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); + SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder() + .generateId() + .setEnvName("env") + .setDev(true) + .setMachineName("machine1") + .setNamespace(workspace.getNamespace()) + .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.getNamespace(), workspace.getId())) + .thenReturn(asList(snapshot1, snapshot2)); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), "account", null); - verify(runtimes, timeout(2000)).start(workspace, workspace.getConfig().getDefaultEnv(), true); - verify(workspaceHooks, timeout(2000)).beforeStart(workspace, workspace.getConfig().getDefaultEnv(), "account"); + verify(runtimes, timeout(2000)).start(workspace, + workspace.getConfig().getDefaultEnv(), + true); + verify(workspaceHooks, timeout(2000)).beforeStart(workspace, + workspace.getConfig().getDefaultEnv(), + "account"); assertNotNull(workspace.getAttributes().get(UPDATED_ATTRIBUTE_NAME)); } @Test public void shouldRecoverWorkspaceWhenRecoverParameterIsTrueAndSnapshotExists() throws Exception { final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); - when(machineManager.getSnapshots(any(), any())).thenReturn(singletonList(mock(SnapshotImpl.class))); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); + SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder() + .generateId() + .setEnvName("env") + .setDev(true) + .setMachineName("machine1") + .setNamespace(workspace.getNamespace()) + .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.getNamespace(), workspace.getId())) + .thenReturn(asList(snapshot1, snapshot2)); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), @@ -417,7 +457,6 @@ public class WorkspaceManagerTest { public void shouldNotRecoverWorkspaceWhenRecoverParameterIsFalseAndAutoRestoreAttributeIsSetAndSnapshotExists() throws Exception { final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); workspace.getAttributes().put(AUTO_RESTORE_FROM_SNAPSHOT, "true"); - when(machineManager.getSnapshots(any(), any())).thenReturn(singletonList(mock(SnapshotImpl.class))); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); @@ -555,8 +594,6 @@ public class WorkspaceManagerTest { when(workspaceDao.get(workspace.getId())).thenReturn(workspace); final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); - // force createSnapshotSync to return true - when(machineManager.saveSync(anyString(), anyString(), anyString())).thenThrow(new MachineException("test")); workspaceManager.stopWorkspace(workspace.getId()); @@ -595,7 +632,7 @@ public class WorkspaceManagerTest { final String wsId = "workspace123"; final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE, "account"); when(workspaceDao.get(wsId)).thenReturn(workspace); - when(machineManager.getSnapshots(NAMESPACE, wsId)).thenReturn(singletonList(any())); + when(snapshotDao.findSnapshots(NAMESPACE, wsId)).thenReturn(singletonList(any())); final List snapshots = workspaceManager.getSnapshot("workspace123"); @@ -607,9 +644,9 @@ public class WorkspaceManagerTest { workspaceManager = spy(new WorkspaceManager(workspaceDao, runtimes, eventService, - machineManager, true, - false)); + false, + snapshotDao)); final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); @@ -626,13 +663,28 @@ public class WorkspaceManagerTest { workspaceManager = spy(new WorkspaceManager(workspaceDao, runtimes, eventService, - machineManager, false, - true)); + true, + snapshotDao)); final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); - when(machineManager.getSnapshots(any(), any())).thenReturn(singletonList(mock(SnapshotImpl.class))); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); + SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder() + .generateId() + .setEnvName("env") + .setDev(true) + .setMachineName("machine1") + .setNamespace(workspace.getNamespace()) + .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.getNamespace(), workspace.getId())) + .thenReturn(asList(snapshot1, snapshot2)); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), "account", null); @@ -645,14 +697,69 @@ public class WorkspaceManagerTest { String testWsId = "testWsId"; String testNamespace = "testNamespace"; WorkspaceImpl workspaceMock = mock(WorkspaceImpl.class); - doReturn(workspaceMock).when(workspaceManager).getWorkspace(testWsId); + when(workspaceDao.get(testWsId)).thenReturn(workspaceMock); when(workspaceMock.getNamespace()).thenReturn(testNamespace); + SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder() + .generateId() + .setEnvName("env") + .setDev(true) + .setMachineName("machine1") + .setNamespace(testNamespace) + .setWorkspaceId(testWsId) + .setType("docker") + .setMachineSource(new MachineSourceImpl("image")); + SnapshotImpl snapshot1 = snapshotBuilder.build(); + SnapshotImpl snapshot2 = snapshotBuilder.generateId() + .setDev(false) + .setMachineName("machine2") + .build(); + when(snapshotDao.findSnapshots(testNamespace, testWsId)).thenReturn(asList(snapshot1, snapshot2)); // when workspaceManager.removeSnapshots(testWsId); // then - verify(machineManager).removeSnapshots(testNamespace, testWsId); + InOrder runtimesInOrder = inOrder(runtimes); + runtimesInOrder.verify(runtimes).removeSnapshot(snapshot1); + runtimesInOrder.verify(runtimes).removeSnapshot(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") + .setNamespace(testNamespace) + .setWorkspaceId(testWsId) + .setType("docker") + .setMachineSource(new MachineSourceImpl("image")); + SnapshotImpl snapshot1 = snapshotBuilder.build(); + SnapshotImpl snapshot2 = snapshotBuilder.generateId() + .setDev(false) + .setMachineName("machine2") + .build(); + when(snapshotDao.findSnapshots(testNamespace, testWsId)).thenReturn(asList(snapshot1, snapshot2)); + doThrow(new SnapshotException("test")).when(snapshotDao).removeSnapshot(snapshot1.getId()); + + // when + workspaceManager.removeSnapshots(testWsId); + + // then + verify(runtimes).removeSnapshot(snapshot1); + verify(runtimes).removeSnapshot(snapshot2); + verify(snapshotDao).removeSnapshot(snapshot1.getId()); + verify(snapshotDao).removeSnapshot(snapshot2.getId()); } @Test @@ -666,15 +773,13 @@ public class WorkspaceManagerTest { when(workspaceMock.getStatus()).thenReturn(RUNNING); when(workspaceMock.getRuntime()).thenReturn(wsRuntimeMock); when(wsRuntimeMock.getActiveEnv()).thenReturn(testActiveEnv); - LineConsumer lineConsumerMock = mock(LineConsumer.class); MachineConfigImpl machineConfig = createConfig().getEnvironments().get(0).getMachineConfigs().get(0); - when(runtimes.getMachineLogger(testWsId, machineConfig.getName())).thenReturn(lineConsumerMock); // when workspaceManager.startMachine(machineConfig, testWsId); // then - verify(machineManager).createMachineAsync(eq(machineConfig), eq(testWsId), eq(testActiveEnv), eq(lineConsumerMock)); + verify(runtimes, timeout(2000)).startMachine(testWsId, machineConfig); } @Test(expectedExceptions = ConflictException.class, expectedExceptionsMessageRegExp = "Workspace .* is not running, new machine can't be started") @@ -690,16 +795,107 @@ public class WorkspaceManagerTest { workspaceManager.startMachine(machineConfig, testWsId); } + @Test + public void shouldBeAbleToCreateSnapshot() throws Exception { + // then + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + when(workspaceDao.get(workspace.getId())).thenReturn(workspace); + RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); + when(runtimes.get(any())).thenReturn(descriptor); + + // when + workspaceManager.createSnapshot(workspace.getId()); + + // then + verify(workspaceManager, timeout(1_000)).createSnapshotSync(any(WorkspaceRuntimeImpl.class), + eq(workspace.getNamespace()), + eq(workspace.getId())); + } + + @Test(expectedExceptions = ConflictException.class, + expectedExceptionsMessageRegExp = "Could not .* the workspace '.*' because its status is '.*'.") + public void shouldNotCreateSnapshotIfWorkspaceIsNotRunning() throws Exception { + // then + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + when(workspaceDao.get(workspace.getId())).thenReturn(workspace); + RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING); + when(runtimes.get(any())).thenReturn(descriptor); + + // when + workspaceManager.createSnapshot(workspace.getId()); + } + + @Test + public void shouldBeAbleToStopMachine() throws Exception { + // given + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + when(workspaceDao.get(workspace.getId())).thenReturn(workspace); + RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); + when(runtimes.get(any())).thenReturn(descriptor); + MachineImpl machine = descriptor.getRuntime().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 = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + when(workspaceDao.get(workspace.getId())).thenReturn(workspace); + RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING); + when(runtimes.get(any())).thenReturn(descriptor); + + // when + workspaceManager.stopMachine(workspace.getId(), "someId"); + } + + @Test + public void shouldBeAbleToGetMachineInstanceIfWorkspaceIsRunning() throws Exception { + // given + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + when(workspaceDao.get(workspace.getId())).thenReturn(workspace); + RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); + when(runtimes.get(any())).thenReturn(descriptor); + MachineImpl machine = descriptor.getRuntime().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 = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + when(workspaceDao.get(workspace.getId())).thenReturn(workspace); + RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING); + when(runtimes.get(any())).thenReturn(descriptor); + MachineImpl machine = descriptor.getRuntime().getMachines().get(0); + + // when + workspaceManager.getMachineInstance(workspace.getId(), machine.getId()); + + // then + verify(runtimes).getMachine(workspace.getId(), machine.getId()); + } + private RuntimeDescriptor createDescriptor(WorkspaceImpl workspace, WorkspaceStatus status) { - final WorkspaceRuntimeImpl runtime = new WorkspaceRuntimeImpl(workspace.getConfig().getDefaultEnv()); - final String env = workspace.getConfig().getDefaultEnv(); - for (MachineConfigImpl machineConfig : workspace.getConfig() - .getEnvironment(workspace.getConfig().getDefaultEnv()) - .get() - .getMachineConfigs()) { + Optional environmentOpt = workspace.getConfig().getEnvironment(workspace.getConfig().getDefaultEnv()); + assertTrue(environmentOpt.isPresent()); + EnvironmentImpl environment = environmentOpt.get(); + + final WorkspaceRuntimeImpl runtime = new WorkspaceRuntimeImpl(environment.getName()); + for (MachineConfigImpl machineConfig : environment.getMachineConfigs()) { final MachineImpl machine = MachineImpl.builder() .setConfig(machineConfig) - .setEnvName(env) + .setEnvName(environment.getName()) .setId(NameGenerator.generate("machine", 10)) .setOwner(workspace.getNamespace()) .setRuntime(new MachineRuntimeInfoImpl(emptyMap(), emptyMap(), emptyMap())) diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java index af6fd87ed4..b8d1cb6820 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java @@ -10,22 +10,25 @@ *******************************************************************************/ package org.eclipse.che.api.workspace.server; +import org.eclipse.che.api.agent.server.wsagent.WsAgentLauncher; 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.Machine; import org.eclipse.che.api.core.model.machine.MachineConfig; -import org.eclipse.che.api.core.model.machine.MachineStatus; +import org.eclipse.che.api.core.model.workspace.Environment; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; -import org.eclipse.che.api.core.util.LineConsumer; -import org.eclipse.che.api.machine.server.MachineManager; -import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.environment.server.CheEnvironmentEngine; +import org.eclipse.che.api.environment.server.NoOpMachineInstance; import org.eclipse.che.api.machine.server.model.impl.LimitsImpl; 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.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.recipe.RecipeImpl; -import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; +import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.workspace.server.WorkspaceRuntimes.RuntimeDescriptor; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; @@ -33,32 +36,25 @@ 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.shared.dto.event.WorkspaceStatusEvent.EventType; import org.eclipse.che.commons.lang.NameGenerator; -import org.eclipse.che.dto.server.DtoFactory; import org.mockito.Mock; 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.ArrayDeque; -import java.util.ArrayList; -import java.util.EnumSet; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; 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.STOPPING; 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.doAnswer; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -68,93 +64,138 @@ 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; /** * @author Yevhenii Voevodin + * @author Alexander Garagatyi */ @Listeners(MockitoTestNGListener.class) public class WorkspaceRuntimesTest { - private static final String WORKSPACE_ID = "workspace123"; - private static final String ENV_NAME = "default-env"; - - @Mock - private MachineManager machineManager; + private static String WORKSPACE_ID = "workspace123"; + private static String ENV_NAME = "default-env"; @Mock private EventService eventService; @Mock - private RuntimeDescriptor descriptor; + private CheEnvironmentEngine environmentEngine; + + @Mock + private WsAgentLauncher wsAgentLauncher; private WorkspaceRuntimes runtimes; @BeforeMethod - public void setUp() throws Exception { - when(machineManager.createMachineSync(any(), any(), any(), any(LineConsumer.class))) - .thenAnswer(invocation -> createMachine((MachineConfig)invocation.getArguments()[0])); - runtimes = new WorkspaceRuntimes(machineManager, eventService); + public void setUp(Method method) throws Exception { + runtimes = spy(new WorkspaceRuntimes(eventService, environmentEngine, wsAgentLauncher)); + + List machines = asList(createMachine(true), createMachine(false)); + when(environmentEngine.start(anyString(), + any(Environment.class), + anyBoolean(), + any())) + .thenReturn(machines); + when(environmentEngine.getMachines(WORKSPACE_ID)).thenReturn(machines); } @Test(expectedExceptions = NotFoundException.class, expectedExceptionsMessageRegExp = "Workspace with id '.*' is not running.") public void shouldThrowNotFoundExceptionIfWorkspaceRuntimeDoesNotExist() throws Exception { - runtimes.get("workspace123"); + runtimes.get(WORKSPACE_ID); + } + + @Test(expectedExceptions = ServerException.class, + expectedExceptionsMessageRegExp = "Dev machine is not found in active environment of workspace 'workspace123'") + public void shouldThrowExceptionOnGetRuntimesIfDevMachineIsMissingInTheEnvironment() throws Exception { + // given + WorkspaceImpl workspace = createWorkspace(); + + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); + when(environmentEngine.getMachines(workspace.getId())) + .thenReturn(asList(createMachine(false), createMachine(false))); + + // when + runtimes.get(workspace.getId()); + } + + @Test + public void shouldFetchMachinesFromEnvEngineOnGetRuntime() throws Exception { + // given + WorkspaceImpl workspace = createWorkspace(); + Instance devMachine = createMachine(true); + List machines = asList(devMachine, createMachine(false)); + when(environmentEngine.start(anyString(), + any(Environment.class), + anyBoolean(), + any())) + .thenReturn(machines); + when(environmentEngine.getMachines(WORKSPACE_ID)).thenReturn(machines); + + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); + + // when + RuntimeDescriptor runtimeDescriptor = runtimes.get(workspace.getId()); + + // then + RuntimeDescriptor expected = new RuntimeDescriptor(WorkspaceStatus.RUNNING, + new WorkspaceRuntimeImpl(workspace.getConfig() + .getDefaultEnv(), + devMachine.getRuntime() + .projectsRoot(), + machines, + devMachine)); + verify(environmentEngine, times(2)).getMachines(workspace.getId()); + assertEquals(runtimeDescriptor, expected); } @Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = "Could not perform operation because application server is stopping") public void shouldNotStartTheWorkspaceIfPostConstructWasIsInvoked() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); + // given + WorkspaceImpl workspace = createWorkspace(); runtimes.cleanup(); + // when runtimes.start(createWorkspace(), workspace.getConfig().getDefaultEnv(), false); } @Test - public void workspaceShouldBeInStartingStatusUntilDevMachineIsNotStarted() throws Exception { - final MachineManager machineManagerMock = mock(MachineManager.class); - final WorkspaceRuntimes runtimes = new WorkspaceRuntimes(machineManagerMock, eventService); - final WorkspaceImpl workspace = createWorkspace(); - - // check if workspace in starting status before dev machine is started - when(machineManagerMock.createMachineSync(anyObject(), anyString(), anyString(), any(LineConsumer.class))) - .thenAnswer(invocationOnMock -> { - final RuntimeDescriptor descriptor = runtimes.get(workspace.getId()); - final MachineConfig cfg = (MachineConfig)invocationOnMock.getArguments()[0]; - if (cfg.isDev()) { - assertEquals(descriptor.getRuntimeStatus(), STARTING, "Workspace status is not 'STARTING'"); - } - return createMachine((MachineConfig)invocationOnMock.getArguments()[0]); - }); - - runtimes.start(workspace, workspace.getConfig().getDefaultEnv(), false); - - verify(machineManagerMock, times(2)).createMachineSync(anyObject(), anyString(), anyString(), any(LineConsumer.class)); - } - - @Test - public void workspaceShouldNotHaveRuntimeIfDevMachineCreationFailed() throws Exception { - final MachineManager machineManagerMock = mock(MachineManager.class); - final WorkspaceRuntimes runtimes = new WorkspaceRuntimes(machineManagerMock, eventService); - final WorkspaceImpl workspaceMock = createWorkspace(); - when(machineManagerMock.createMachineSync(any(), any(), any(), any(LineConsumer.class))) - .thenThrow(new MachineException("Creation error")); + public void workspaceShouldNotHaveRuntimeIfEnvStartFails() throws Exception { + // given + when(environmentEngine.start(anyString(), + any(Environment.class), + anyBoolean(), + any())) + .thenThrow(new ServerException("Test env start error")); + WorkspaceImpl workspaceMock = createWorkspace(); try { - runtimes.start(workspaceMock, workspaceMock.getConfig().getDefaultEnv()); - } catch (MachineException ex) { + // when + runtimes.start(workspaceMock, + workspaceMock.getConfig().getDefaultEnv(), + false); + } catch (Exception ex) { + // then assertFalse(runtimes.hasRuntime(workspaceMock.getId())); } } @Test public void workspaceShouldContainAllMachinesAndBeInRunningStatusAfterSuccessfulStart() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); + // given + WorkspaceImpl workspace = createWorkspace(); - final RuntimeDescriptor runningWorkspace = runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); + // when + RuntimeDescriptor runningWorkspace = runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); + // then assertEquals(runningWorkspace.getRuntimeStatus(), RUNNING); assertNotNull(runningWorkspace.getRuntime().getDevMachine()); assertEquals(runningWorkspace.getRuntime().getMachines().size(), 2); @@ -163,558 +204,345 @@ public class WorkspaceRuntimesTest { @Test(expectedExceptions = ConflictException.class, expectedExceptionsMessageRegExp = "Could not start workspace '.*' because its status is 'RUNNING'") public void shouldNotStartWorkspaceIfItIsAlreadyRunning() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); + // given + WorkspaceImpl workspace = createWorkspace(); - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); - } - - @Test(expectedExceptions = ConflictException.class, - expectedExceptionsMessageRegExp = "Couldn't stop '.*' workspace because its status is 'STARTING'. " + - "Workspace can be stopped only if it is 'RUNNING'") - public void shouldNotStopWorkspaceIfItIsStarting() throws Exception { - final MachineManager machineManagerMock = mock(MachineManager.class); - final WorkspaceRuntimes registry = new WorkspaceRuntimes(machineManagerMock, eventService); - final WorkspaceImpl workspace = createWorkspace(); - - when(machineManagerMock.createMachineSync(any(), any(), any(), any(LineConsumer.class))).thenAnswer(invocationOnMock -> { - registry.stop(workspace.getId()); - return createMachine((MachineConfig)invocationOnMock.getArguments()[0]); - }); - - registry.start(workspace, workspace.getConfig().getDefaultEnv()); - } - - @Test - public void shouldDestroyNonDevMachineIfWorkspaceWasStoppedWhileNonDevMachineWasStarting() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); - - doAnswer(invocation -> { - final MachineConfig machineCfg = (MachineConfig)invocation.getArguments()[0]; - if (!machineCfg.isDev()) { - runtimes.stop(workspace.getId()); - } - return createMachine((MachineConfig)invocation.getArguments()[0]); - }).when(machineManager).createMachineSync(any(), anyString(), anyString(), any(LineConsumer.class)); - - try { - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); - } catch (ConflictException ex) { - assertEquals(ex.getMessage(), "Workspace '" + workspace.getId() + "' start interrupted. " + - "Workspace stopped before all its machines started"); - } - verify(machineManager, times(2)).destroy(any(), anyBoolean()); + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); + // when + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); } @Test public void testCleanup() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); + // given + WorkspaceImpl workspace = createWorkspace(); + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); runtimes.cleanup(); + // when, then assertFalse(runtimes.hasRuntime(workspace.getId())); } - @Test - public void startShouldIgnoreFailedToStartNonDevMachine() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); - - doAnswer(invocation -> { - final MachineConfig machineCfg = (MachineConfig)invocation.getArguments()[0]; - if (!machineCfg.isDev()) { - throw new MachineException("Failed to start"); - } - return createMachine((MachineConfig)invocation.getArguments()[0]); - }).when(machineManager).createMachineSync(any(), anyString(), anyString(), any(LineConsumer.class)); - - - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); - - final RuntimeDescriptor descriptor = runtimes.get(workspace.getId()); - assertEquals(descriptor.getRuntime().getMachines().size(), 1); - assertEquals(descriptor.getRuntimeStatus(), RUNNING); - verify(machineManager, times(2)).createMachineSync(any(), any(), any(), any(LineConsumer.class)); - } - - @Test - public void shouldNotDestroyNonDevMachineIfRegistryWasStoppedWhileDevMachineWasStarting() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); - - doAnswer(invocation -> { - final MachineConfig machineCfg = (MachineConfig)invocation.getArguments()[0]; - if (!machineCfg.isDev()) { - runtimes.cleanup(); - } - return createMachine((MachineConfig)invocation.getArguments()[0]); - }).when(machineManager).createMachineSync(any(), anyString(), anyString(), any(LineConsumer.class)); - - try { - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); - } catch (ServerException ex) { - assertEquals(ex.getMessage(), "Could not perform operation because application server is stopping"); - } - verify(machineManager, never()).destroy(any(), anyBoolean()); - } - - @Test - public void runtimeShouldBeInStoppingStatusIfWorkspacesDevMachineIsNotStopped() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); - - doAnswer(invocation -> { - assertEquals(runtimes.get(workspace.getId()).getRuntimeStatus(), STOPPING); - return null; - }).when(machineManager).destroy(any(), anyBoolean()); - - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); - runtimes.stop(workspace.getId()); - } - - @Test - public void runtimeStopShouldIgnoreNonDevMachineFail() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); - - doAnswer(invocation -> { - if (!runtimes.get(workspace.getId()) - .getRuntime() - .getDevMachine() - .getId() - .equals(invocation.getArguments()[0])) { - throw new MachineException("Destroy failed"); - } - return null; - }).when(machineManager).destroy(any(), anyBoolean()); - - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); - runtimes.stop(workspace.getId()); - - assertFalse(runtimes.hasRuntime(workspace.getId())); - verify(machineManager, times(2)).destroy(anyString(), anyBoolean()); - } - @Test public void shouldStopRunningWorkspace() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); + // given + WorkspaceImpl workspace = createWorkspace(); - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); + // when runtimes.stop(workspace.getId()); + // then assertFalse(runtimes.hasRuntime(workspace.getId())); } @Test(expectedExceptions = NotFoundException.class, expectedExceptionsMessageRegExp = "Workspace with id 'workspace123' is not running.") public void shouldThrowNotFoundExceptionWhenStoppingWorkspaceWhichDoesNotHaveRuntime() throws Exception { - runtimes.stop("workspace123"); + runtimes.stop(WORKSPACE_ID); } @Test public void startedRuntimeShouldBeTheSameToRuntimeTakenFromGetMethod() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); - final RuntimeDescriptor descriptor = runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); + // given + WorkspaceImpl workspace = createWorkspace(); - assertEquals(runtimes.get(workspace.getId()).getRuntime(), descriptor.getRuntime()); + // when + RuntimeDescriptor descriptorFromStartMethod = runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); + RuntimeDescriptor descriptorFromGetMethod = runtimes.get(workspace.getId()); + + // then + assertEquals(descriptorFromStartMethod, + descriptorFromGetMethod); } @Test public void startingEventShouldBePublishedBeforeStart() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); - runtimes = spy(new WorkspaceRuntimes(machineManager, eventService)); - doNothing().when(runtimes).publishEvent(any(), any(), any()); + // given + WorkspaceImpl workspace = createWorkspace(); - doAnswer(invocation -> { - verify(runtimes).publishEvent(EventType.STARTING, workspace.getId(), null); - return null; - }).when(machineManager).createMachineSync(any(), any(), any(), any(LineConsumer.class)); + // when + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); + // then + verify(runtimes).publishWorkspaceEvent(EventType.STARTING, + workspace.getId(), + null); } @Test - public void runningEventShouldBePublishedAfterDevMachineStarted() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); - runtimes = spy(new WorkspaceRuntimes(machineManager, eventService)); - doNothing().when(runtimes).publishEvent(any(), any(), any()); + public void runningEventShouldBePublishedAfterEnvStart() throws Exception { + // given + WorkspaceImpl workspace = createWorkspace(); - doAnswer(invocation -> { - final MachineConfig cfg = (MachineConfig)invocation.getArguments()[0]; - if (!cfg.isDev()) { - verify(runtimes).publishEvent(EventType.RUNNING, workspace.getId(), null); - } - return createMachine(cfg); - }).when(machineManager).createMachineSync(any(), any(), any(), any(LineConsumer.class)); + // when + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); + // then + verify(runtimes).publishWorkspaceEvent(EventType.RUNNING, + workspace.getId(), + null); } @Test public void errorEventShouldBePublishedIfDevMachineFailedToStart() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); - runtimes = spy(new WorkspaceRuntimes(machineManager, eventService)); - doNothing().when(runtimes).publishEvent(any(), any(), any()); - doNothing().when(runtimes).cleanupStartResources(any()); - - doAnswer(invocation -> { - final MachineConfig cfg = (MachineConfig)invocation.getArguments()[0]; - if (cfg.isDev()) { - throw new MachineException("Start error"); - } - return createMachine(cfg); - }).when(machineManager).createMachineSync(any(), any(), any(), any(LineConsumer.class)); + // given + WorkspaceImpl workspace = createWorkspace(); + when(environmentEngine.start(anyString(), + any(Environment.class), + anyBoolean(), + any())) + .thenReturn(singletonList(createMachine(false))); try { - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); - } catch (MachineException ex) { - verify(runtimes).publishEvent(EventType.ERROR, workspace.getId(), ex.getLocalizedMessage()); + // when + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); + + } catch (Exception e) { + // then + verify(runtimes).publishWorkspaceEvent(EventType.ERROR, + workspace.getId(), + e.getLocalizedMessage()); } } @Test public void stoppingEventShouldBePublishedBeforeStop() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); - runtimes = spy(new WorkspaceRuntimes(machineManager, eventService)); - doNothing().when(runtimes).publishEvent(any(), any(), any()); + // given + WorkspaceImpl workspace = createWorkspace(); + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); - doAnswer(invocation -> { - if ((!runtimes.get(workspace.getId()) - .getRuntime() - .getDevMachine() - .getId() - .equals(invocation.getArguments()[0]))) { - verify(runtimes).publishEvent(EventType.STOPPING, workspace.getId(), null); - } - return null; - }).when(machineManager).destroy(anyString(), anyBoolean()); - - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); + // when runtimes.stop(workspace.getId()); + + // then + verify(runtimes).publishWorkspaceEvent(EventType.STOPPING, + workspace.getId(), + null); } @Test - public void stoppedEventShouldBePublishedAfterDevMachineStopped() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); - runtimes = spy(new WorkspaceRuntimes(machineManager, eventService)); - doNothing().when(runtimes).publishEvent(any(), any(), any()); + public void stoppedEventShouldBePublishedAfterEnvStop() throws Exception { + // given + WorkspaceImpl workspace = createWorkspace(); + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); + // when runtimes.stop(workspace.getId()); - verify(runtimes).publishEvent(EventType.STOPPED, workspace.getId(), null); + // then + verify(runtimes).publishWorkspaceEvent(EventType.STOPPED, + workspace.getId(), + null); } @Test - public void errorEventShouldBePublishedIfDevMachineFailedToStop() throws Exception { - final WorkspaceImpl workspace = createWorkspace(); - runtimes = spy(new WorkspaceRuntimes(machineManager, eventService)); - doNothing().when(runtimes).publishEvent(any(), any(), any()); - - doAnswer(invocation -> { - if ((runtimes.get(workspace.getId()) - .getRuntime() - .getDevMachine() - .getId() - .equals(invocation.getArguments()[0]))) { - throw new MachineException("Stop error"); - } - return null; - }).when(machineManager).destroy(anyString(), anyBoolean()); - - runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); + public void errorEventShouldBePublishedIfEnvFailedToStop() throws Exception { + // given + WorkspaceImpl workspace = createWorkspace(); + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); try { + // when runtimes.stop(workspace.getId()); - } catch (MachineException ex) { - verify(runtimes).publishEvent(EventType.ERROR, workspace.getId(), ex.getLocalizedMessage()); + } catch (Exception e) { + // then + verify(runtimes).publishWorkspaceEvent(EventType.ERROR, + workspace.getId(), + "Test error"); } } @Test - public void shouldNotAddMachineIfWorkspaceDescriptorDoesNotExist() throws Exception { - assertFalse(runtimes.addMachine(createMachine(true)), "should not be added"); + public void shouldBeAbleToStartMachine() throws Exception { + // when + WorkspaceImpl workspace = createWorkspace(); + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); + MachineConfigImpl config = createConfig(false); + Instance instance = mock(Instance.class); + when(environmentEngine.startMachine(anyString(), any(MachineConfig.class))).thenReturn(instance); + when(instance.getConfig()).thenReturn(config); + + // when + Instance actual = runtimes.startMachine(workspace.getId(), config); + + // then + assertEquals(actual, instance); + verify(environmentEngine).startMachine(workspace.getId(), config); + } + + @Test(expectedExceptions = ConflictException.class, + expectedExceptionsMessageRegExp = "Environment of workspace '.*' is not running") + public void shouldNotStartMachineIfEnvironmentIsNotRunning() throws Exception { + // when + MachineConfigImpl config = createConfig(false); + + // when + runtimes.startMachine("someWsID", config); + + // then + verify(environmentEngine, never()).startMachine(anyString(), any(MachineConfig.class)); } @Test - public void shouldPromiseToAddMachineIfStatusIsStartingAndCreatedMachineIsDev() throws Exception { - when(descriptor.getRuntimeStatus()).thenReturn(STARTING); - runtimes.descriptors.put(WORKSPACE_ID, descriptor); + public void shouldBeAbleToStopMachine() throws Exception { + // when + WorkspaceImpl workspace = createWorkspace(); + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); - assertTrue(runtimes.addMachine(createMachine(true)), "should be added later"); + // when + runtimes.stopMachine(workspace.getId(), "testMachineId"); + + // then + verify(environmentEngine).stopMachine(workspace.getId(), "testMachineId"); } - @Test(dataProvider = "inconsistentMachines") - public void machineShouldNotBeAdded(boolean isDev, WorkspaceStatus status) throws Exception { - when(descriptor.getRuntimeStatus()).thenReturn(status); - runtimes.descriptors.put(WORKSPACE_ID, descriptor); + @Test(expectedExceptions = ConflictException.class, + expectedExceptionsMessageRegExp = "Environment of workspace '.*' is not running") + public void shouldNotStopMachineIfEnvironmentIsNotRunning() throws Exception { + // when + runtimes.stopMachine("someWsID", "someMachineId"); - assertFalse(runtimes.addMachine(createMachine(isDev)), "should not be added"); + // then + verify(environmentEngine, never()).stopMachine(anyString(), anyString()); } @Test - public void machineShouldBeAddedIfStartQueueDoesNotExist() throws Exception { - // prepare runtime - final WorkspaceRuntimeImpl runtime = mock(WorkspaceRuntimeImpl.class); - final ArrayList machines = new ArrayList<>(); - when(runtime.getMachines()).thenReturn(machines); - // prepare descriptor - when(descriptor.getRuntime()).thenReturn(runtime); - when(descriptor.getRuntimeStatus()).thenReturn(RUNNING); - // register mocks - runtimes.descriptors.put(WORKSPACE_ID, descriptor); + public void shouldBeAbleToSaveMachine() throws Exception { + // when + WorkspaceImpl workspace = createWorkspace(); + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); + SnapshotImpl snapshot = mock(SnapshotImpl.class); + when(runtimes.saveMachine(workspace.getNamespace(), workspace.getId(), "machineId")).thenReturn(snapshot); - final MachineImpl machine = createMachine(false); - assertTrue(runtimes.addMachine(machine), "should be added"); - assertFalse(machines.isEmpty()); - assertEquals(machines.get(0), machine); + // when + SnapshotImpl actualSnapshot = runtimes.saveMachine(workspace.getNamespace(), workspace.getId(), "machineId"); + + // then + assertEquals(actualSnapshot, snapshot); + verify(environmentEngine).saveSnapshot(workspace.getNamespace(), workspace.getId(), "machineId"); + } + + @Test(expectedExceptions = ConflictException.class, + expectedExceptionsMessageRegExp = "Environment of workspace '.*' is not running") + public void shouldNotSaveMachineIfEnvironmentIsNotRunning() throws Exception { + // when + runtimes.saveMachine("namespace", "workspaceId", "machineId"); + + // then + verify(environmentEngine, never()).saveSnapshot(anyString(), anyString(), anyString()); } @Test - public void machineShouldBeAddedIfStartQueueDoesNotContainIt() throws Exception { - // prepare runtime - final WorkspaceRuntimeImpl runtime = mock(WorkspaceRuntimeImpl.class); - final ArrayList machines = new ArrayList<>(); - when(runtime.getMachines()).thenReturn(machines); - // prepare descriptor - when(descriptor.getRuntime()).thenReturn(runtime); - when(descriptor.getRuntimeStatus()).thenReturn(RUNNING); - // register mocks - runtimes.descriptors.put(WORKSPACE_ID, descriptor); - runtimes.startQueues.put(WORKSPACE_ID, new ArrayDeque<>()); + public void shouldBeAbleToRemoveSnapshot() throws Exception { + // given + SnapshotImpl snapshot = mock(SnapshotImpl.class); - final MachineImpl machine = createMachine(false); - assertTrue(runtimes.addMachine(machine), "should be added"); - assertFalse(machines.isEmpty()); - assertEquals(machines.get(0), machine); + // when + runtimes.removeSnapshot(snapshot); + + // then + verify(environmentEngine).removeSnapshot(snapshot); } @Test - public void shouldPromiseThatMachineWillBeAddedIfStartQueueContainsMachine() throws Exception { - // prepare runtime - final WorkspaceRuntimeImpl runtime = mock(WorkspaceRuntimeImpl.class); - final ArrayList machines = new ArrayList<>(); - when(runtime.getMachines()).thenReturn(machines); - // prepare descriptor - when(descriptor.getRuntime()).thenReturn(runtime); - when(descriptor.getRuntimeStatus()).thenReturn(RUNNING); - // prepare queue - final ArrayDeque queue = new ArrayDeque<>(); - queue.add(createConfig(false)); - // register mocks - runtimes.descriptors.put(WORKSPACE_ID, descriptor); - runtimes.startQueues.put(WORKSPACE_ID, queue); + public void shouldBeAbleToGetMachine() throws Exception { + // given + Instance expected = createMachine(false); + when(environmentEngine.getMachine(WORKSPACE_ID, expected.getId())).thenReturn(expected); - final MachineImpl machine = createMachine(false); - assertTrue(runtimes.addMachine(machine), "should be added"); - assertTrue(machines.isEmpty()); + // when + Instance actualMachine = runtimes.getMachine(WORKSPACE_ID, expected.getId()); + + // then + assertEquals(actualMachine, expected); + verify(environmentEngine).getMachine(WORKSPACE_ID, expected.getId()); + } + + @Test(expectedExceptions = NotFoundException.class, + expectedExceptionsMessageRegExp = "test exception") + public void shouldThrowExceptionIfGetMachineFromEnvEngineThrowsException() throws Exception { + // given + Instance expected = createMachine(false); + when(environmentEngine.getMachine(WORKSPACE_ID, expected.getId())) + .thenThrow(new NotFoundException("test exception")); + + // when + runtimes.getMachine(WORKSPACE_ID, expected.getId()); + + // then + verify(environmentEngine).getMachine(WORKSPACE_ID, expected.getId()); } @Test - public void shouldDestroyMachineIfItIsNotAddedWhenEventReceived() throws Exception { - // prepare runtimes - runtimes = spy(new WorkspaceRuntimes(machineManager, eventService)); - doReturn(false).when(runtimes).addMachine(any()); - // prepare machine - final MachineImpl machine = createMachine(true); - when(machineManager.getMachine(machine.getId())).thenReturn(machine); - // prepare event - final MachineStatusEvent event = DtoFactory.newDto(MachineStatusEvent.class) - .withDev(true) - .withEventType(MachineStatusEvent.EventType.RUNNING) - .withMachineId(machine.getId()); + public void shouldBeAbleToGetAllWorkspacesWithExistingRuntime() throws Exception { + // then + Map expectedWorkspaces = new HashMap<>(); + WorkspaceImpl workspace = createWorkspace(); + runtimes.start(workspace, + workspace.getConfig().getDefaultEnv(), + false); + expectedWorkspaces.put(workspace.getId(), + new WorkspaceRuntimes.WorkspaceState(RUNNING, + workspace.getConfig().getDefaultEnv())); + WorkspaceImpl workspace2 = spy(createWorkspace()); + when(workspace2.getId()).thenReturn("testWsId"); + when(environmentEngine.getMachines(workspace2.getId())) + .thenReturn(Collections.singletonList(createMachine(true))); + runtimes.start(workspace2, + workspace2.getConfig().getDefaultEnv(), + false); + expectedWorkspaces.put(workspace2.getId(), + new WorkspaceRuntimes.WorkspaceState(RUNNING, + workspace2.getConfig().getDefaultEnv())); + // when + Map actualWorkspaces = runtimes.getWorkspaces(); - runtimes.new AddMachineEventSubscriber().onEvent(event); - - verify(machineManager).destroy(machine.getId(), true); + // then + assertEquals(actualWorkspaces, expectedWorkspaces); } - @Test(dataProvider = "machineEventTypesExceptOfRunning") - public void eventTypesExceptOfRunningShouldBeIgnoredByAddMachineSubscriber(MachineStatusEvent.EventType type) - throws Exception { - // prepare runtimes - runtimes = spy(new WorkspaceRuntimes(machineManager, eventService)); - doReturn(false).when(runtimes).addMachine(any()); - // prepare machine - final MachineImpl machine = createMachine(true); - when(machineManager.getMachine(machine.getId())).thenReturn(machine); - // prepare event - final MachineStatusEvent event = DtoFactory.newDto(MachineStatusEvent.class) - .withDev(true) - .withEventType(type) - .withMachineId(machine.getId()); - - - runtimes.new AddMachineEventSubscriber().onEvent(event); - - verify(machineManager, never()).destroy(machine.getId(), true); - } - - @Test(dataProvider = "workspaceStatusesExceptOfRunning") - public void shouldNotRemoveMachineWhenDescriptorStatusIsNotRunning(WorkspaceStatus status) throws Exception { - // prepare runtime - final WorkspaceRuntimeImpl runtime = mock(WorkspaceRuntimeImpl.class); - final MachineImpl machine = createMachine(false); - final ArrayList machines = new ArrayList<>(); - machines.add(machine); - when(runtime.getMachines()).thenReturn(machines); - // prepare descriptor - when(descriptor.getRuntime()).thenReturn(runtime); - when(descriptor.getRuntimeStatus()).thenReturn(status); - // register mocks - runtimes.descriptors.put(WORKSPACE_ID, descriptor); - - runtimes.removeMachine(machine.getId(), machine.getConfig().getName(), machine.getWorkspaceId()); - - assertFalse(machines.isEmpty(), "should not remove machine"); - } - - @Test - public void shouldNotRemoveMachineWhenMachineNameIsDifferent() throws Exception { - // prepare runtime - final WorkspaceRuntimeImpl runtime = mock(WorkspaceRuntimeImpl.class); - final MachineImpl machine = createMachine(false); - final ArrayList machines = new ArrayList<>(); - machines.add(machine); - when(runtime.getMachines()).thenReturn(machines); - // prepare descriptor - when(descriptor.getRuntime()).thenReturn(runtime); - when(descriptor.getRuntimeStatus()).thenReturn(RUNNING); - // register mocks - runtimes.descriptors.put(WORKSPACE_ID, descriptor); - - runtimes.removeMachine(machine.getId(), machine.getConfig().getName() + "2", machine.getWorkspaceId()); - - assertFalse(machines.isEmpty(), "should not remove machine"); - } - - @Test - public void shouldRemoveMachineIfStatusIsRunning() throws Exception { - // prepare runtime - final WorkspaceRuntimeImpl runtime = mock(WorkspaceRuntimeImpl.class); - final MachineImpl machine = createMachine(false); - final ArrayList machines = new ArrayList<>(); - machines.add(machine); - when(runtime.getMachines()).thenReturn(machines); - // prepare descriptor - when(descriptor.getRuntime()).thenReturn(runtime); - when(descriptor.getRuntimeStatus()).thenReturn(RUNNING); - // register mocks - runtimes.descriptors.put(WORKSPACE_ID, descriptor); - - runtimes.removeMachine(machine.getId(), machine.getConfig().getName(), machine.getWorkspaceId()); - - assertTrue(machines.isEmpty(), "should remove machine"); - } - - @Test(dataProvider = "machineEventTypesExceptOfDestroyed") - public void eventTypesExceptOfDestroyedShouldBeIgnoredByRemoveMachineSubscriber(MachineStatusEvent.EventType type) - throws Exception { - // prepare runtimes - runtimes = spy(new WorkspaceRuntimes(machineManager, eventService)); - doNothing().when(runtimes).removeMachine(anyString(), anyString(), anyString()); - // prepare event - final MachineImpl machine = createMachine(true); - final MachineStatusEvent event = DtoFactory.newDto(MachineStatusEvent.class) - .withDev(false) - .withEventType(type) - .withMachineId(machine.getId()); - - - runtimes.new RemoveMachineEventSubscriber().onEvent(event); - - verify(runtimes, never()).removeMachine(anyString(), anyString(), anyString()); - } - - @Test - public void removeMachineSubscriberShouldRemoveMachineIfItIsDevAndEventIsDestroyed() throws Exception { - // prepare runtimes - runtimes = spy(new WorkspaceRuntimes(machineManager, eventService)); - doNothing().when(runtimes).removeMachine(anyString(), anyString(), anyString()); - // prepare event - final MachineImpl machine = createMachine(true); - final MachineStatusEvent event = DtoFactory.newDto(MachineStatusEvent.class) - .withDev(false) - .withEventType(MachineStatusEvent.EventType.DESTROYED) - .withMachineId(machine.getId()) - .withMachineName(machine.getConfig().getName()) - .withWorkspaceId(machine.getWorkspaceId()); - - runtimes.new RemoveMachineEventSubscriber().onEvent(event); - - verify(runtimes).removeMachine(machine.getId(), machine.getConfig().getName(), machine.getWorkspaceId()); - } - - @Test - public void shouldReuseRunningMachineIfFailedToStart() throws Exception { - // prepare workspace - final WorkspaceImpl workspace = createWorkspace(); - // prepare machine - final MachineImpl machine = createMachine(true); - machine.setStatus(MachineStatus.RUNNING); - when(machineManager.getMachines()).thenReturn(singletonList(machine)); - // force machine manager to throw conflict exception - final RuntimeDescriptor descriptorMock = mock(RuntimeDescriptor.class); - when(descriptorMock.getRuntimeStatus()).thenReturn(WorkspaceStatus.RUNNING); - doThrow(new ConflictException("already exists")).when(machineManager).createMachineSync(eq(machine.getConfig()), - eq(machine.getWorkspaceId()), - eq(workspace.getConfig().getDefaultEnv()), - any(LineConsumer.class)); - - final RuntimeDescriptor descriptor = runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); - - assertEquals(descriptor.getRuntime().getDevMachine(), machine); - verify(machineManager).getMachines(); - } - - @DataProvider(name = "workspaceStatusesExceptOfRunning") - private Object[][] workspaceStatusesExceptOfRunning() { - final EnumSet events = EnumSet.allOf(WorkspaceStatus.class); - events.remove(WorkspaceStatus.RUNNING); - return rotate(events.toArray(new Object[events.size()])); - } - - @DataProvider(name = "machineEventTypesExceptOfRunning") - private Object[][] machineEventTypesExceptOfRunning() { - final EnumSet events = EnumSet.allOf(MachineStatusEvent.EventType.class); - events.remove(MachineStatusEvent.EventType.RUNNING); - return rotate(events.toArray(new Object[events.size()])); - } - - @DataProvider(name = "machineEventTypesExceptOfDestroyed") - private Object[][] machineEventTypesExceptOfDestroyed() { - final EnumSet events = EnumSet.allOf(MachineStatusEvent.EventType.class); - events.remove(MachineStatusEvent.EventType.DESTROYED); - return rotate(events.toArray(new Object[events.size()])); - } - - @DataProvider(name = "inconsistentMachines") - private Object[][] inconsistentMachinesProvider() { - return new Object[][] { - {true, WorkspaceStatus.RUNNING}, - {true, WorkspaceStatus.STOPPING}, - {false, WorkspaceStatus.STARTING}, - {false, WorkspaceStatus.STOPPING} - }; - } - - private static Object[][] rotate(Object[] array) { - final Object[][] result = new Object[array.length][1]; - for (int i = 0; i < array.length; i++) { - result[i] = new Object[] {array[i]}; - } - return result; - } - - private static MachineImpl createMachine(boolean isDev) { + private static Instance createMachine(boolean isDev) { return createMachine(createConfig(isDev)); } - private static MachineImpl createMachine(MachineConfig cfg) { - return MachineImpl.builder() - .setId(NameGenerator.generate("machine", 10)) - .setWorkspaceId(WORKSPACE_ID) - .setEnvName(ENV_NAME) - .setConfig(new MachineConfigImpl(cfg)) - .build(); + private static Instance createMachine(MachineConfig cfg) { + return new TestMachineInstance(MachineImpl.builder() + .setId(NameGenerator.generate("machine", 10)) + .setWorkspaceId(WORKSPACE_ID) + .setEnvName(ENV_NAME) + .setConfig(new MachineConfigImpl(cfg)) + .build()); } private static MachineConfigImpl createConfig(boolean isDev) { @@ -723,25 +551,40 @@ public class WorkspaceRuntimesTest { .setType("docker") .setLimits(new LimitsImpl(1024)) .setSource(new MachineSourceImpl("git").setLocation("location")) - .setName("dev-machine") + .setName(UUID.randomUUID().toString()) .build(); } private static WorkspaceImpl createWorkspace() { - final MachineConfigImpl devCfg = createConfig(true); - final MachineConfigImpl nonDevCfg = MachineConfigImpl.builder() + MachineConfigImpl devCfg = createConfig(true); + MachineConfigImpl nonDevCfg = MachineConfigImpl.builder() .fromConfig(devCfg) .setName("non-dev") .setDev(false) .build(); - final EnvironmentImpl environment = new EnvironmentImpl(ENV_NAME, - new RecipeImpl(), - asList(nonDevCfg, devCfg)); - final WorkspaceConfigImpl wsConfig = WorkspaceConfigImpl.builder() - .setName("test workspace") - .setEnvironments(singletonList(environment)) - .setDefaultEnv(environment.getName()) - .build(); + EnvironmentImpl environment = new EnvironmentImpl(ENV_NAME, + new RecipeImpl(), + asList(nonDevCfg, devCfg)); + WorkspaceConfigImpl wsConfig = WorkspaceConfigImpl.builder() + .setName("test workspace") + .setEnvironments(singletonList(environment)) + .setDefaultEnv(environment.getName()) + .build(); return new WorkspaceImpl(WORKSPACE_ID, "user123", wsConfig); } + + private static class TestMachineInstance extends NoOpMachineInstance { + + MachineRuntimeInfoImpl runtime; + + public TestMachineInstance(Machine machine) { + super(machine); + runtime = mock(MachineRuntimeInfoImpl.class); + } + + @Override + public MachineRuntimeInfoImpl getRuntime() { + return runtime; + } + } } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java index cadd3cc763..a72995d10a 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java @@ -22,8 +22,8 @@ import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.rest.ApiExceptionMapper; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; -import org.eclipse.che.api.machine.server.MachineManager; -import org.eclipse.che.api.machine.server.MachineServiceLinksInjector; +import org.eclipse.che.api.environment.server.MachineProcessManager; +import org.eclipse.che.api.environment.server.MachineServiceLinksInjector; import org.eclipse.che.api.machine.server.model.impl.CommandImpl; import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.machine.server.model.impl.MachineImpl; @@ -33,6 +33,7 @@ import org.eclipse.che.api.machine.server.model.impl.ServerConfImpl; import org.eclipse.che.api.machine.server.model.impl.ServerImpl; import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; import org.eclipse.che.api.machine.shared.dto.CommandDto; +import org.eclipse.che.api.machine.shared.dto.SnapshotDto; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; @@ -49,7 +50,6 @@ import org.everrest.assured.EverrestJetty; import org.everrest.core.Filter; import org.everrest.core.GenericContainerRequest; import org.everrest.core.RequestFilter; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; @@ -57,10 +57,14 @@ import org.testng.annotations.Listeners; import org.testng.annotations.Test; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import static com.jayway.restassured.RestAssured.given; import static java.lang.String.format; @@ -118,13 +122,13 @@ public class WorkspaceServiceTest { private static final EnvironmentFilter FILTER = new EnvironmentFilter(); @Mock - private WorkspaceManager wsManager; + private WorkspaceManager wsManager; @Mock - private MachineManager machineManager; + private MachineProcessManager machineProcessManager; @Mock - private WorkspaceValidator validator; - @InjectMocks - private WorkspaceService service; + private WorkspaceValidator validator; + + private WorkspaceService service; @BeforeMethod public void setup() { @@ -638,17 +642,18 @@ public class WorkspaceServiceTest { public void testWorkspaceLinks() throws Exception { // given final WorkspaceImpl workspace = createWorkspace(createConfigDto()); - final WorkspaceRuntimeImpl runtime = new WorkspaceRuntimeImpl(workspace.getConfig().getDefaultEnv()); - final MachineConfigImpl devCfg = workspace.getConfig() - .getEnvironment(workspace.getConfig().getDefaultEnv()) - .get() - .getMachineConfigs() - .iterator() - .next(); + Optional environmentOpt = workspace.getConfig().getEnvironment(workspace.getConfig().getDefaultEnv()); + assertTrue(environmentOpt.isPresent()); + EnvironmentImpl environment = environmentOpt.get(); + + final WorkspaceRuntimeImpl runtime = new WorkspaceRuntimeImpl(environment.getName()); + final MachineConfigImpl devCfg = environment.getMachineConfigs() + .iterator() + .next(); runtime.setDevMachine(new MachineImpl(devCfg, "machine123", workspace.getId(), - workspace.getConfig().getDefaultEnv(), + environment.getName(), USER_ID, MachineStatus.RUNNING, new MachineRuntimeInfoImpl(emptyMap(), @@ -696,6 +701,63 @@ public class WorkspaceServiceTest { assertNotNull(workspaceDto.getRuntime().getLink(WSAGENT_WEBSOCKET_REFERENCE), "Runtime doesn't contain wsagent.websocket link"); } + @Test + public void shouldReturnSnapshotsOnGetSnapshot() throws Exception { + // given + String workspaceId = "testWsId1"; + SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder() + .setCreationDate(System.currentTimeMillis()) + .setDescription("description") + .setDev(true) + .setEnvName("envName") + .setId("snap1") + .setMachineName("machine1") + .setMachineSource(new MachineSourceImpl("type") + .setContent("content")) + .setNamespace("namespace") + .setType("type") + .setWorkspaceId(workspaceId); + SnapshotImpl snapshot1 = snapshotBuilder.build(); + SnapshotImpl snapshot2 = snapshotBuilder.setDev(false).build(); + + List originSnapshots = Arrays.asList(snapshot1, snapshot2); + when(wsManager.getSnapshot(workspaceId)).thenReturn(originSnapshots); + + // when + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .get(SECURE_PATH + "/workspace/" + workspaceId + "/snapshot"); + + // then + assertEquals(response.getStatusCode(), 200); + List snapshotDtos = unwrapDtoList(response, SnapshotDto.class); + List newSnapshots = snapshotDtos.stream().map(SnapshotImpl::new).collect(Collectors.toList()); + originSnapshots.forEach(snapshot -> snapshot.setMachineSource(null)); + assertEquals(newSnapshots, originSnapshots); + verify(wsManager).getSnapshot(workspaceId); + } + + @Test + public void shouldReturnEmptyListIfNotSnapshotsFound() throws Exception { + // given + String workspaceId = "testWsId1"; + + when(wsManager.getSnapshot(workspaceId)).thenReturn(Collections.emptyList()); + + // when + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .get(SECURE_PATH + "/workspace/" + workspaceId + "/snapshot"); + + // then + assertEquals(response.getStatusCode(), 200); + List snapshotDtos = unwrapDtoList(response, SnapshotDto.class); + assertTrue(snapshotDtos.isEmpty()); + verify(wsManager).getSnapshot(workspaceId); + } + private static String unwrapError(Response response) { return unwrapDto(response, ServiceError.class).getMessage(); }