From 4b155fa57a67c2e9137b11c7dabf3a4840e878f4 Mon Sep 17 00:00:00 2001 From: Alexander Garagatyi Date: Mon, 4 Apr 2016 11:07:50 +0300 Subject: [PATCH] CHE-889: add ssh machine implementation Signed-off-by: Alexander Garagatyi --- assembly/assembly-ide-war/pom.xml | 4 + .../che/api/deploy/WsMasterModule.java | 9 +- .../WEB-INF/classes/codenvy/che.properties | 4 + assembly/assembly-main/pom.xml | 6 + .../assembly-main/src/assembly/assembly.xml | 13 + .../api/core/model/machine/MachineConfig.java | 8 + .../api/core/model/machine/MachineSource.java | 2 +- .../create/CreateWorkspacePresenter.java | 4 +- .../create/CreateWorkspacePresenterTest.java | 2 +- .../src/main/resources/predefined-stacks.json | 32 +- .../che/api/account/AccountServiceTest.java | 1 + .../che/api/local/LocalSnapshotDaoImpl.java | 4 +- .../che/api/local/LocalSnapshotDaoTest.java | 2 +- .../che/api/local/LocalWorkspaceDaoTest.java | 6 +- .../create-project.controller.js | 3 +- .../create-workspace.controller.js | 4 +- .../components/api/che-workspace.factory.js | 2 +- .../plugin/docker/machine/DockerInstance.java | 38 +-- .../docker/machine/DockerInstanceKey.java | 2 +- .../machine/DockerInstanceProvider.java | 14 +- .../machine/DockerInstanceStopDetector.java | 2 +- .../DockerMachineImplTerminalLauncher.java | 69 ++++ .../plugin/docker/machine/DockerProcess.java | 50 +-- .../ext/DockerMachineTerminalLauncher.java | 86 ----- .../machine/ext/DockerTerminalModule.java | 10 +- .../provider/WsAgentServerConfProvider.java | 2 +- .../machine/DockerInstanceProviderTest.java | 14 +- .../DockerInstanceReadFileContentTest.java | 3 +- .../DockerMachineTerminalLauncherTest.java | 79 +++-- .../machine/integration/ServiceTest.java | 2 +- .../machine/client/machine/Machine.java | 3 +- .../client/machine/MachineManagerImpl.java | 2 +- plugins/plugin-ssh-machine/pom.xml | 126 ++++++++ .../che/plugin/machine/ssh/SshClient.java | 64 ++++ .../plugin/machine/ssh/SshMachineFactory.java | 65 ++++ .../ssh/SshMachineImplTerminalLauncher.java | 99 ++++++ .../machine/ssh/SshMachineInstance.java | 201 ++++++++++++ .../ssh/SshMachineInstanceProvider.java | 102 ++++++ .../plugin/machine/ssh/SshMachineModule.java | 60 ++++ .../plugin/machine/ssh/SshMachineProcess.java | 104 ++++++ .../plugin/machine/ssh/SshMachineRecipe.java | 78 +++++ .../che/plugin/machine/ssh/SshProcess.java | 29 ++ .../ssh/TerminalServerConfProvider.java | 39 +++ .../WebsocketTerminalFilesPathProvider.java | 47 +++ .../machine/ssh/jsch/JschSshClient.java | 299 ++++++++++++++++++ .../machine/ssh/jsch/JschSshProcess.java | 76 +++++ .../machine/ssh/jsch/JschUserInfoImpl.java | 109 +++++++ .../ssh/SshMachineInstanceProviderTest.java | 142 +++++++++ plugins/pom.xml | 1 + pom.xml | 12 + .../che/api/machine/server/DtoConverter.java | 5 +- .../api/machine/server/MachineManager.java | 123 +++---- .../che/api/machine/server/MachineModule.java | 1 + .../api/machine/server/MachineService.java | 2 +- .../api/machine/server/dao/SnapshotDao.java | 2 +- .../{ => event}/InstanceStateEvent.java | 2 +- .../exception/UnsupportedRecipeException.java | 4 +- .../server/model/impl/MachineConfigImpl.java | 38 ++- .../server/{ => model}/impl/SnapshotImpl.java | 3 +- .../che/api/machine/server/spi/Instance.java | 16 +- .../machine/server/spi/InstanceProvider.java | 4 +- .../{ => spi}/impl/AbstractInstance.java | 2 +- .../spi/impl/AbstractMachineProcess.java | 83 +++++ .../{ => spi}/impl/InstanceKeyImpl.java | 2 +- .../MachineImplSpecificTerminalLauncher.java | 31 ++ .../terminal/MachineTerminalLauncher.java | 80 +++++ .../machine/server/util/RecipeDownloader.java | 86 +++++ .../machine/shared/dto/MachineConfigDto.java | 7 + .../{server => wsagent}/WsAgentLauncher.java | 2 +- .../WsAgentLauncherImpl.java | 3 +- .../machine/server/MachineManagerTest.java | 29 +- .../WsAgentLauncherImplTest.java | 4 +- .../workspace/server/WorkspaceManager.java | 2 +- .../server/DefaultWorkspaceValidatorTest.java | 2 +- .../server/WorkspaceManagerTest.java | 4 +- .../server/WorkspaceRuntimesTest.java | 1 - .../server/WorkspaceServiceTest.java | 4 +- .../server/stack/StackServiceTest.java | 5 +- .../src/test/resources/stacks.json | 6 +- 79 files changed, 2350 insertions(+), 338 deletions(-) create mode 100644 plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerMachineImplTerminalLauncher.java delete mode 100644 plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ext/DockerMachineTerminalLauncher.java create mode 100644 plugins/plugin-ssh-machine/pom.xml create mode 100644 plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshClient.java create mode 100644 plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineFactory.java create mode 100644 plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineImplTerminalLauncher.java create mode 100644 plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstance.java create mode 100644 plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProvider.java create mode 100644 plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineModule.java create mode 100644 plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineProcess.java create mode 100644 plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineRecipe.java create mode 100644 plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshProcess.java create mode 100644 plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/TerminalServerConfProvider.java create mode 100644 plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/WebsocketTerminalFilesPathProvider.java create mode 100644 plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschSshClient.java create mode 100644 plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschSshProcess.java create mode 100644 plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschUserInfoImpl.java create mode 100644 plugins/plugin-ssh-machine/src/test/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProviderTest.java rename wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/{ => event}/InstanceStateEvent.java (95%) rename wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/{ => model}/impl/SnapshotImpl.java (98%) rename wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/{ => spi}/impl/AbstractInstance.java (96%) create mode 100644 wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/impl/AbstractMachineProcess.java rename wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/{ => spi}/impl/InstanceKeyImpl.java (96%) create mode 100644 wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/MachineImplSpecificTerminalLauncher.java create mode 100644 wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/MachineTerminalLauncher.java create mode 100644 wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/util/RecipeDownloader.java rename wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/{server => wsagent}/WsAgentLauncher.java (95%) rename wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/{server => wsagent}/WsAgentLauncherImpl.java (98%) rename wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/{ => wsagent}/WsAgentLauncherImplTest.java (98%) diff --git a/assembly/assembly-ide-war/pom.xml b/assembly/assembly-ide-war/pom.xml index 7d380d3f2f..8bb9a6e8dd 100644 --- a/assembly/assembly-ide-war/pom.xml +++ b/assembly/assembly-ide-war/pom.xml @@ -284,6 +284,10 @@ org.eclipse.che.plugin che-plugin-sdk-ext-plugins + + org.eclipse.che.plugin + che-plugin-ssh-machine + org.eclipse.che.plugin che-plugin-web-ext-web diff --git a/assembly/assembly-ide-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java b/assembly/assembly-ide-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java index ae23ffeda6..de3ef706e1 100644 --- a/assembly/assembly-ide-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java +++ b/assembly/assembly-ide-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java @@ -123,7 +123,7 @@ public class WsMasterModule extends AbstractModule { bind(org.eclipse.che.api.workspace.server.stack.StackService.class); bind(org.eclipse.che.api.workspace.server.stack.StackLoader.class); - bindConstant().annotatedWith(Names.named(org.eclipse.che.api.machine.server.WsAgentLauncherImpl.WS_AGENT_PROCESS_START_COMMAND)) + bindConstant().annotatedWith(Names.named(org.eclipse.che.api.machine.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 chown -R $(id -u -n) /projects && " + "export JPDA_ADDRESS=\"4403\" && ~/che/ws-agent/bin/catalina.sh jpda run"); @@ -131,6 +131,11 @@ public class WsMasterModule extends AbstractModule { bind(WorkspaceValidator.class).to(DefaultWorkspaceValidator.class); bind(MachineStateListener.class).asEagerSingleton(); - bind(org.eclipse.che.api.machine.server.WsAgentLauncher.class).to(org.eclipse.che.api.machine.server.WsAgentLauncherImpl.class); + bind(org.eclipse.che.api.machine.wsagent.WsAgentLauncher.class) + .to(org.eclipse.che.api.machine.wsagent.WsAgentLauncherImpl.class); + + install(new org.eclipse.che.plugin.machine.ssh.SshMachineModule()); + + bind(org.eclipse.che.api.machine.server.terminal.MachineTerminalLauncher.class); } } diff --git a/assembly/assembly-ide-war/src/main/webapp/WEB-INF/classes/codenvy/che.properties b/assembly/assembly-ide-war/src/main/webapp/WEB-INF/classes/codenvy/che.properties index a0b93dca74..06b24e005e 100644 --- a/assembly/assembly-ide-war/src/main/webapp/WEB-INF/classes/codenvy/che.properties +++ b/assembly/assembly-ide-war/src/main/webapp/WEB-INF/classes/codenvy/che.properties @@ -136,3 +136,7 @@ org.everrest.asynchronous.job.timeout=10 org.everrest.asynchronous.cache.size=1024 # Path to asynchronous service org.everrest.asynchronous.service.path=/async/ + +machine.ssh.connection_timeout_ms=3000 +machine.ssh.server.terminal.path_to_archive.linux_amd64=${che.home}/lib/linux_amd64/terminal +machine.ssh.server.terminal.path_to_archive.linux_arm7=${che.home}/lib/linux_arm7/terminal diff --git a/assembly/assembly-main/pom.xml b/assembly/assembly-main/pom.xml index ce8b467c70..cceef0cc31 100644 --- a/assembly/assembly-main/pom.xml +++ b/assembly/assembly-main/pom.xml @@ -63,6 +63,12 @@ zip linux_amd64 + + org.eclipse.che.lib + che-websocket-terminal + zip + linux_arm7 + org.eclipse.che.plugin che-plugin-sdk-tools diff --git a/assembly/assembly-main/src/assembly/assembly.xml b/assembly/assembly-main/src/assembly/assembly.xml index b662848b49..72cc50521f 100644 --- a/assembly/assembly-main/src/assembly/assembly.xml +++ b/assembly/assembly-main/src/assembly/assembly.xml @@ -68,6 +68,19 @@ + + false + true + lib/linux_arm7 + + org.eclipse.che.lib:che-websocket-terminal:zip:linux_arm7 + + + + META-INF/** + + + false false diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineConfig.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineConfig.java index e2dde52d2f..f2688cd687 100644 --- a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineConfig.java +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineConfig.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.eclipse.che.api.core.model.machine; +import org.eclipse.che.commons.annotation.Nullable; + import java.util.List; import java.util.Map; @@ -41,6 +43,7 @@ public interface MachineConfig { /** * Machine limits such as RAM size. */ + @Nullable Limits getLimits(); /** @@ -54,4 +57,9 @@ public interface MachineConfig { * Get predefined environment variables of machine. */ Map getEnvVariables(); + + /** + * Architecture of target machine. Default is 'linux_amd64'. + */ + String getArchitecture(); } diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineSource.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineSource.java index 4b939602fc..d8ffa871ca 100644 --- a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineSource.java +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineSource.java @@ -16,7 +16,7 @@ package org.eclipse.che.api.core.model.machine; public interface MachineSource { /** - * Returns Recipe or Snapshot + * Returns dockerfile, image, ssh-config, etc */ String getType(); diff --git a/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/create/CreateWorkspacePresenter.java b/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/create/CreateWorkspacePresenter.java index 682de3eab3..a47df7aa94 100644 --- a/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/create/CreateWorkspacePresenter.java +++ b/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/create/CreateWorkspacePresenter.java @@ -230,7 +230,7 @@ public class CreateWorkspacePresenter implements CreateWorkspaceView.ActionDeleg .withName("ws-machine") .withType("docker") .withSource(dtoFactory.createDto(MachineSourceDto.class) - .withType("recipe") + .withType("dockerfile") .withLocation(view.getRecipeUrl())) .withDev(true) .withLimits(dtoFactory.createDto(LimitsDto.class).withRam(2048))); @@ -245,4 +245,4 @@ public class CreateWorkspacePresenter implements CreateWorkspaceView.ActionDeleg .withDefaultEnv(wsName) .withEnvironments(environments); } -} \ No newline at end of file +} diff --git a/core/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/workspace/create/CreateWorkspacePresenterTest.java b/core/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/workspace/create/CreateWorkspacePresenterTest.java index 7e6240d7f5..03db90c21e 100644 --- a/core/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/workspace/create/CreateWorkspacePresenterTest.java +++ b/core/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/workspace/create/CreateWorkspacePresenterTest.java @@ -322,7 +322,7 @@ public class CreateWorkspacePresenterTest { verify(machineConfigDto).withDev(true); verify(dtoFactory).createDto(MachineSourceDto.class); - verify(machineSourceDto).withType("recipe"); + verify(machineSourceDto).withType("dockerfile"); verify(machineSourceDto).withLocation("test"); verify(dtoFactory).createDto(EnvironmentDto.class); diff --git a/core/ide/che-core-ide-stacks/src/main/resources/predefined-stacks.json b/core/ide/che-core-ide-stacks/src/main/resources/predefined-stacks.json index d10f6cff79..574c60a4e6 100644 --- a/core/ide/che-core-ide-stacks/src/main/resources/predefined-stacks.json +++ b/core/ide/che-core-ide-stacks/src/main/resources/predefined-stacks.json @@ -43,7 +43,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -110,7 +110,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -177,7 +177,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -236,7 +236,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -311,7 +311,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -408,7 +408,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -491,7 +491,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -570,7 +570,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -648,7 +648,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -739,7 +739,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -780,7 +780,7 @@ } ], "source": { - "type": "recipe", + "type": "dockerfile", "origin": "FROM codenvy/debian_jre\nCMD tail -f /dev/null" }, "workspaceConfig": { @@ -796,7 +796,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -849,7 +849,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -862,7 +862,7 @@ "description": null }, "source": { - "type": "recipe", + "type": "dockerfile", "origin": "FROM codenvy/ubuntu_jre\nCMD tail -f /dev/null" }, "permissions": { @@ -927,7 +927,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -1009,7 +1009,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true diff --git a/core/platform-api/che-core-api-account/src/test/java/org/eclipse/che/api/account/AccountServiceTest.java b/core/platform-api/che-core-api-account/src/test/java/org/eclipse/che/api/account/AccountServiceTest.java index 62dd346777..4b30c655ff 100644 --- a/core/platform-api/che-core-api-account/src/test/java/org/eclipse/che/api/account/AccountServiceTest.java +++ b/core/platform-api/che-core-api-account/src/test/java/org/eclipse/che/api/account/AccountServiceTest.java @@ -596,6 +596,7 @@ public class AccountServiceTest { "location"), null, null, + null, null))); return new WorkspaceImpl("id123", "owner1234", new WorkspaceConfigImpl("name", "desc", diff --git a/core/platform-api/che-core-api-infrastructure-local/src/main/java/org/eclipse/che/api/local/LocalSnapshotDaoImpl.java b/core/platform-api/che-core-api-infrastructure-local/src/main/java/org/eclipse/che/api/local/LocalSnapshotDaoImpl.java index f0cc713170..81098ef933 100644 --- a/core/platform-api/che-core-api-infrastructure-local/src/main/java/org/eclipse/che/api/local/LocalSnapshotDaoImpl.java +++ b/core/platform-api/che-core-api-infrastructure-local/src/main/java/org/eclipse/che/api/local/LocalSnapshotDaoImpl.java @@ -17,7 +17,7 @@ import org.eclipse.che.api.local.storage.LocalStorage; import org.eclipse.che.api.local.storage.LocalStorageFactory; 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.impl.SnapshotImpl; +import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; import org.eclipse.che.api.machine.server.recipe.adapters.InstanceKeyAdapter; import org.eclipse.che.api.machine.server.spi.InstanceKey; @@ -114,4 +114,4 @@ public class LocalSnapshotDaoImpl implements SnapshotDao { && snapshot.getMachineName().equals(machineName)) .findFirst(); } -} \ No newline at end of file +} diff --git a/core/platform-api/che-core-api-infrastructure-local/src/test/java/org/eclipse/che/api/local/LocalSnapshotDaoTest.java b/core/platform-api/che-core-api-infrastructure-local/src/test/java/org/eclipse/che/api/local/LocalSnapshotDaoTest.java index aa435f2b64..07edb5bb5d 100644 --- a/core/platform-api/che-core-api-infrastructure-local/src/test/java/org/eclipse/che/api/local/LocalSnapshotDaoTest.java +++ b/core/platform-api/che-core-api-infrastructure-local/src/test/java/org/eclipse/che/api/local/LocalSnapshotDaoTest.java @@ -14,7 +14,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.eclipse.che.api.local.storage.LocalStorageFactory; -import org.eclipse.che.api.machine.server.impl.SnapshotImpl; +import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; import org.eclipse.che.api.machine.server.recipe.adapters.InstanceKeyAdapter; import org.eclipse.che.api.machine.server.spi.InstanceKey; import org.testng.annotations.BeforeMethod; diff --git a/core/platform-api/che-core-api-infrastructure-local/src/test/java/org/eclipse/che/api/local/LocalWorkspaceDaoTest.java b/core/platform-api/che-core-api-infrastructure-local/src/test/java/org/eclipse/che/api/local/LocalWorkspaceDaoTest.java index d3f72f00b0..6d75b789a1 100644 --- a/core/platform-api/che-core-api-infrastructure-local/src/test/java/org/eclipse/che/api/local/LocalWorkspaceDaoTest.java +++ b/core/platform-api/che-core-api-infrastructure-local/src/test/java/org/eclipse/che/api/local/LocalWorkspaceDaoTest.java @@ -108,7 +108,8 @@ public class LocalWorkspaceDaoTest { "9090/udp", "someprotocol", "/some/path")), - Collections.singletonMap("key1", "value1")); + Collections.singletonMap("key1", "value1"), + null); final MachineConfigImpl machineCfg2 = new MachineConfigImpl(false, "non-dev-machine", "machine-type-2", @@ -122,7 +123,8 @@ public class LocalWorkspaceDaoTest { "9090/udp", "someprotocol", "/some/path")), - Collections.singletonMap("key1", "value1")); + Collections.singletonMap("key1", "value1"), + null); final EnvironmentImpl env1 = new EnvironmentImpl("my-environment", recipe, asList(machineCfg1, machineCfg2)); final EnvironmentImpl env2 = new EnvironmentImpl("my-environment-2", recipe, singletonList(machineCfg1)); diff --git a/dashboard/src/app/projects/create-project/create-project.controller.js b/dashboard/src/app/projects/create-project/create-project.controller.js index 5cf7c338b9..46fc38f60f 100755 --- a/dashboard/src/app/projects/create-project/create-project.controller.js +++ b/dashboard/src/app/projects/create-project/create-project.controller.js @@ -733,8 +733,7 @@ export class CreateProjectCtrl { if ('image' === recipeSource.type) { // needs to add recipe for that script promise = this.submitRecipe('generated-' + stack.name, 'FROM ' + recipeSource.origin); - } else if ('recipe' === recipeSource.type) { - + } else if ('dockerfile' === recipeSource.type.toLowerCase()) { promise = this.submitRecipe('generated-' + stack.name, recipeSource.origin); } else { throw 'Not implemented'; diff --git a/dashboard/src/app/workspaces/create-workspace/create-workspace.controller.js b/dashboard/src/app/workspaces/create-workspace/create-workspace.controller.js index 9f38ad7b5d..189a443bc7 100644 --- a/dashboard/src/app/workspaces/create-workspace/create-workspace.controller.js +++ b/dashboard/src/app/workspaces/create-workspace/create-workspace.controller.js @@ -170,11 +170,11 @@ export class CreateWorkspaceCtrl { let recipeName = 'generated-' + stack.name; let recipeScript; // what is type of source ? - switch (recipeSource.type) { + switch (recipeSource.type.toLowerCase()) { case 'image': recipeScript = 'FROM ' + recipeSource.origin; break; - case 'recipe': + case 'dockerfile': recipeScript = recipeSource.origin; break; default: diff --git a/dashboard/src/components/api/che-workspace.factory.js b/dashboard/src/components/api/che-workspace.factory.js index ad67cb7b59..522c455003 100644 --- a/dashboard/src/components/api/che-workspace.factory.js +++ b/dashboard/src/components/api/che-workspace.factory.js @@ -189,7 +189,7 @@ export class CheWorkspace { 'name': 'ws-machine', 'limits': {'ram': memory}, 'type': 'docker', - 'source': {'location': recipeUrl, 'type': 'recipe'}, + 'source': {'location': recipeUrl, 'type': 'dockerfile'}, 'dev': true }] }; 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 e20abf6005..c93b66b1d2 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 @@ -18,7 +18,7 @@ import org.eclipse.che.api.core.model.machine.Machine; import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.api.core.util.ListLineConsumer; import org.eclipse.che.api.machine.server.exception.MachineException; -import org.eclipse.che.api.machine.server.impl.AbstractInstance; +import org.eclipse.che.api.machine.server.spi.impl.AbstractInstance; 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.InstanceKey; @@ -298,23 +298,8 @@ public class DockerInstance extends AbstractInstance { return content; } - /** - * Copies files from specified container. - * - * @param sourceMachine - * source machine - * @param sourcePath - * path to file or directory inside specified container - * @param targetPath - * path to destination file or directory inside container - * @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 - */ @Override - public void copy(Instance sourceMachine, String sourcePath, String targetPath, boolean overwrite) throws MachineException { + public void copy(Instance sourceMachine, String sourcePath, String targetPath, boolean overwriteDirNonDir) throws MachineException { if (!(sourceMachine instanceof DockerInstance)) { throw new MachineException("Unsupported copying between not docker machines"); } @@ -322,12 +307,22 @@ public class DockerInstance extends AbstractInstance { docker.putResource(container, targetPath, docker.getResource(((DockerInstance)sourceMachine).container, sourcePath), - overwrite); + overwriteDirNonDir); } catch (IOException e) { throw new MachineException(e.getLocalizedMessage()); } } + /** + * Not implemented.

+ * + * {@inheritDoc} + */ + @Override + public void copy(String sourcePath, String targetPath) throws MachineException { + throw new MachineException("Unsupported operation for docker machine implementation"); + } + /** * Removes process from the list of processes * @@ -336,4 +331,11 @@ public class DockerInstance extends AbstractInstance { void removeProcess(int pid) { machineProcesses.remove(pid); } + + /** + * Can be used for docker specific operations with machine + */ + String getContainer() { + return container; + } } diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceKey.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceKey.java index 9f643e0a8b..7938880b0c 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceKey.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceKey.java @@ -12,7 +12,7 @@ package org.eclipse.che.plugin.docker.machine; import com.google.common.collect.ImmutableMap; -import org.eclipse.che.api.machine.server.impl.InstanceKeyImpl; +import org.eclipse.che.api.machine.server.spi.impl.InstanceKeyImpl; import org.eclipse.che.api.machine.server.spi.InstanceKey; /** 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 023b63aaab..2133561908 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 @@ -26,6 +26,7 @@ import org.eclipse.che.api.core.util.SystemInfo; 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.UnsupportedRecipeException; import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.machine.server.spi.InstanceKey; import org.eclipse.che.api.machine.server.spi.InstanceProvider; @@ -66,6 +67,7 @@ import java.util.Set; import java.util.stream.Collectors; import static com.google.common.base.Strings.isNullOrEmpty; +import static java.lang.String.format; /** * Docker implementation of {@link InstanceProvider} @@ -116,7 +118,7 @@ public class DockerInstanceProvider implements InstanceProvider { this.workspaceFolderPathProvider = workspaceFolderPathProvider; this.doForcePullOnBuild = doForcePullOnBuild; this.privilegeMode = privilegeMode; - this.supportedRecipeTypes = Collections.singleton("Dockerfile"); + this.supportedRecipeTypes = Collections.singleton("dockerfile"); this.projectFolderPath = projectFolderPath; if (SystemInfo.isWindows()) { @@ -209,7 +211,7 @@ public class DockerInstanceProvider implements InstanceProvider { @Override public Instance createInstance(Recipe recipe, Machine machine, - LineConsumer creationLogsOutput) throws MachineException { + LineConsumer creationLogsOutput) throws MachineException, UnsupportedRecipeException { final Dockerfile dockerfile = parseRecipe(recipe); final String machineContainerName = generateContainerName(machine.getWorkspaceId(), machine.getConfig().getName()); @@ -276,7 +278,7 @@ public class DockerInstanceProvider implements InstanceProvider { return DockerfileParser.parse(recipe.getScript()); } catch (DockerFileException e) { LOG.debug(e.getLocalizedMessage(), e); - throw new InvalidRecipeException(String.format("Unable build docker based machine. %s", e.getMessage())); + throw new InvalidRecipeException("Unable build docker based machine. " + e.getMessage()); } } @@ -396,9 +398,9 @@ public class DockerInstanceProvider implements InstanceProvider { if (machine.getConfig().isDev()) { portsToExpose = new HashMap<>(devMachinePortsToExpose); - final String projectFolderVolume = String.format("%s:%s:Z", - workspaceFolderPathProvider.getPath(machine.getWorkspaceId()), - projectFolderPath); + final String projectFolderVolume = format("%s:%s:Z", + workspaceFolderPathProvider.getPath(machine.getWorkspaceId()), + projectFolderPath); volumes = ObjectArrays.concat(devMachineSystemVolumes, SystemInfo.isWindows() ? escapePath(projectFolderVolume) : projectFolderVolume); 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 0c51ce9528..4c62beec0d 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 @@ -15,7 +15,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.eclipse.che.api.core.notification.EventService; -import org.eclipse.che.api.machine.server.InstanceStateEvent; +import org.eclipse.che.api.machine.server.event.InstanceStateEvent; 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; 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 new file mode 100644 index 0000000000..d721eb1703 --- /dev/null +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerMachineImplTerminalLauncher.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * 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; + +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.plugin.docker.client.DockerConnector; +import org.eclipse.che.plugin.docker.client.Exec; +import org.eclipse.che.plugin.docker.client.LogMessage; + +import javax.inject.Inject; +import javax.inject.Named; +import java.io.IOException; + +/** + * Starts websocket terminal in the machine after container start + * + * @author Alexander Garagatyi + */ +public class DockerMachineImplTerminalLauncher implements MachineImplSpecificTerminalLauncher { + public static final String START_TERMINAL_COMMAND = "machine.docker.server.terminal.run_command"; + + private final DockerConnector docker; + private final String terminalStartCommand; + + @Inject + public DockerMachineImplTerminalLauncher(DockerConnector docker, + @Named(START_TERMINAL_COMMAND) String terminalStartCommand) { + this.docker = docker; + this.terminalStartCommand = terminalStartCommand; + } + + @Override + public String getMachineType() { + return "docker"; + } + + @Override + public void launchTerminal(Instance machine) throws MachineException { + if (!(machine instanceof DockerInstance)) { + throw new MachineException("Docker terminal launcher was used to launch terminal in non-docker machine."); + } + try { + final String container = ((DockerInstance)machine).getContainer(); + + final Exec exec = docker.createExec(container, true, "/bin/bash", "-c", terminalStartCommand); + + docker.startExec(exec.getId(), logMessage -> { + if (logMessage.getType() == LogMessage.Type.STDERR) { + try { + machine.getLogger().writeLine("Terminal error. %s" + logMessage.getContent()); + } catch (IOException ignore) { + } + } + }); + } catch (IOException e) { + throw new MachineException(e.getLocalizedMessage(), e); + } + } +} diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerProcess.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerProcess.java index e10d8ca2d6..76f276da8b 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerProcess.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerProcess.java @@ -19,7 +19,9 @@ import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.api.core.util.ListLineConsumer; import org.eclipse.che.api.core.util.ValueHolder; import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.machine.server.spi.impl.AbstractMachineProcess; import org.eclipse.che.api.machine.server.spi.InstanceProcess; +import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.plugin.docker.client.DockerConnector; import org.eclipse.che.plugin.docker.client.Exec; import org.eclipse.che.plugin.docker.client.LogMessage; @@ -29,7 +31,6 @@ import javax.inject.Inject; import java.io.IOException; import java.net.SocketTimeoutException; import java.util.Arrays; -import java.util.Map; import static java.lang.String.format; @@ -39,16 +40,11 @@ import static java.lang.String.format; * @author andrew00x * @author Alexander Garagatyi */ -public class DockerProcess implements InstanceProcess { +public class DockerProcess extends AbstractMachineProcess implements InstanceProcess { private final DockerConnector docker; private final String container; private final String pidFilePath; - private final int pid; private final String commandLine; - private final String commandName; - private final String commandType; - private final Map attributes; - private final String outputChannel; private volatile boolean started; @@ -56,51 +52,17 @@ public class DockerProcess implements InstanceProcess { public DockerProcess(DockerConnector docker, @Assisted Command command, @Assisted("container") String container, - @Assisted("outputChannel") String outputChannel, + @Nullable @Assisted("outputChannel") String outputChannel, @Assisted("pid_file_path") String pidFilePath, @Assisted int pid) { + super(command, pid, outputChannel); this.docker = docker; this.container = container; this.commandLine = command.getCommandLine(); - this.commandName = command.getName(); - this.commandType = command.getType(); - this.attributes = command.getAttributes(); - this.outputChannel = outputChannel; this.pidFilePath = pidFilePath; - this.pid = pid; this.started = false; } - @Override - public int getPid() { - return pid; - } - - @Override - public String getName() { - return commandName; - } - - @Override - public String getCommandLine() { - return commandLine; - } - - @Override - public String getType() { - return commandType; - } - - @Override - public Map getAttributes() { - return attributes; - } - - @Override - public String getOutputChannel() { - return outputChannel; - } - @Override public boolean isAlive() { if (!started) { @@ -173,7 +135,7 @@ public class DockerProcess implements InstanceProcess { } // 'kill -0 [pid]' is silent if process is running or print "No such process" message otherwise if (!output.getText().isEmpty()) { - throw new NotFoundException(format("Process with pid %s not found", pid)); + throw new NotFoundException(format("Process with pid %s not found", getPid())); } } diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ext/DockerMachineTerminalLauncher.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ext/DockerMachineTerminalLauncher.java deleted file mode 100644 index 90f7055b71..0000000000 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ext/DockerMachineTerminalLauncher.java +++ /dev/null @@ -1,86 +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.ext; - -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.machine.server.exception.MachineException; -import org.eclipse.che.api.machine.server.spi.Instance; -import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; -import org.eclipse.che.plugin.docker.client.DockerConnector; -import org.eclipse.che.plugin.docker.client.Exec; -import org.eclipse.che.plugin.docker.client.LogMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; -import java.io.IOException; - -/** - * Starts websocket terminal in the machine after container start - * - * @author Alexander Garagatyi - */ -@Singleton // must be eager -public class DockerMachineTerminalLauncher { - public static final String START_TERMINAL_COMMAND = "machine.server.terminal.run_command"; - - private static final Logger LOG = LoggerFactory.getLogger(DockerMachineTerminalLauncher.class); - - private final EventService eventService; - private final DockerConnector docker; - private final MachineManager machineManager; - private final String terminalStartCommand; - - @Inject - public DockerMachineTerminalLauncher(EventService eventService, - DockerConnector docker, - MachineManager machineManager, - @Named(START_TERMINAL_COMMAND) String terminalStartCommand) { - this.eventService = eventService; - this.docker = docker; - this.machineManager = machineManager; - this.terminalStartCommand = terminalStartCommand; - } - - @PostConstruct - public void start() { - eventService.subscribe(new EventSubscriber() { - @Override - public void onEvent(MachineStatusEvent event) { - if (event.getEventType() == MachineStatusEvent.EventType.RUNNING) { - try { - final Instance machine = machineManager.getInstance(event.getMachineId()); - final String containerId = machine.getRuntime().getProperties().get("id"); - - final Exec exec = docker.createExec(containerId, true, "/bin/bash", "-c", terminalStartCommand); - docker.startExec(exec.getId(), logMessage -> { - if (logMessage.getType() == LogMessage.Type.STDERR) { - try { - machine.getLogger().writeLine("Terminal error. %s" + logMessage.getContent()); - } catch (IOException ignore) { - } - } - }); - } catch (IOException | MachineException | NotFoundException e) { - LOG.error(e.getLocalizedMessage(), e); - // TODO send event that terminal is unavailable - } - } - } - }); - } -} 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 dc6eadddd2..694123646b 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,6 +15,8 @@ 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.plugin.docker.machine.DockerMachineImplTerminalLauncher; import org.eclipse.che.plugin.docker.machine.ext.provider.TerminalServerConfProvider; /** @@ -26,9 +28,7 @@ import org.eclipse.che.plugin.docker.machine.ext.provider.TerminalServerConfProv public class DockerTerminalModule extends AbstractModule { @Override protected void configure() { - bind(DockerMachineTerminalLauncher.class).asEagerSingleton(); - - bindConstant().annotatedWith(Names.named(DockerMachineTerminalLauncher.START_TERMINAL_COMMAND)) + bindConstant().annotatedWith(Names.named(DockerMachineImplTerminalLauncher.START_TERMINAL_COMMAND)) .to("mkdir -p ~/che " + "&& cp /mnt/che/terminal -R ~/che" + "&& ~/che/terminal/che-websocket-terminal -addr :4411 -cmd /bin/bash -static ~/che/terminal/"); @@ -41,5 +41,9 @@ public class DockerTerminalModule extends AbstractModule { Multibinder volumesMultibinder = Multibinder.newSetBinder(binder(), String.class, Names.named("machine.docker.machine_volumes")); volumesMultibinder.addBinding().toProvider(org.eclipse.che.plugin.docker.machine.ext.provider.TerminalVolumeProvider.class); + + Multibinder terminalLaunchers = Multibinder.newSetBinder(binder(), + MachineImplSpecificTerminalLauncher.class); + terminalLaunchers.addBinding().to(DockerMachineImplTerminalLauncher.class); } } diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ext/provider/WsAgentServerConfProvider.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ext/provider/WsAgentServerConfProvider.java index cc39c54247..5270eef2cd 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ext/provider/WsAgentServerConfProvider.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ext/provider/WsAgentServerConfProvider.java @@ -11,7 +11,7 @@ package org.eclipse.che.plugin.docker.machine.ext.provider; import org.eclipse.che.api.core.model.machine.ServerConf; -import org.eclipse.che.api.machine.server.WsAgentLauncherImpl; +import org.eclipse.che.api.machine.wsagent.WsAgentLauncherImpl; import org.eclipse.che.api.machine.server.model.impl.ServerConfImpl; import javax.inject.Inject; diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProviderTest.java b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProviderTest.java index 52dc0b0288..7ac3a83c11 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProviderTest.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProviderTest.java @@ -17,7 +17,6 @@ import org.eclipse.che.api.core.model.machine.Recipe; import org.eclipse.che.api.core.model.machine.ServerConf; import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.api.machine.server.exception.MachineException; -import org.eclipse.che.api.machine.server.impl.InstanceKeyImpl; 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; @@ -44,8 +43,6 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; -import javax.ws.rs.core.UriBuilder; -import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -146,7 +143,7 @@ public class DockerInstanceProviderTest { @Test public void shouldReturnRecipeTypesDockerfile() throws Exception { - assertEquals(dockerInstanceProvider.getRecipeTypes(), Collections.singleton("Dockerfile")); + assertEquals(dockerInstanceProvider.getRecipeTypes(), Collections.singleton("dockerfile")); } // TODO add tests for instance snapshot removal @@ -276,7 +273,8 @@ public class DockerInstanceProviderTest { new LimitsImpl(MEMORY_LIMIT_MB), asList(new ServerConfImpl("ref1", "8080", "https", null), new ServerConfImpl("ref2", "9090/udp", "someprotocol", null)), - Collections.singletonMap("key1", "value1")), + Collections.singletonMap("key1", "value1"), + null), "machineId", WORKSPACE_ID, "envName", @@ -310,7 +308,8 @@ public class DockerInstanceProviderTest { new LimitsImpl(MEMORY_LIMIT_MB), asList(new ServerConfImpl("ref1", "8080", "https", null), new ServerConfImpl("ref2", "9090/udp", "someprotocol", null)), - Collections.singletonMap("key1", "value1")), + Collections.singletonMap("key1", "value1"), + null), "machineId", WORKSPACE_ID, "envName", @@ -1723,6 +1722,7 @@ public class DockerInstanceProviderTest { "9090/udp", "someprotocol", null)), - Collections.singletonMap("key1", "value1"))); + Collections.singletonMap("key1", "value1"), + null)); } } diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceReadFileContentTest.java b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceReadFileContentTest.java index f44876e476..2996773c1d 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceReadFileContentTest.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceReadFileContentTest.java @@ -80,7 +80,8 @@ public class DockerInstanceReadFileContentTest { "9090/udp", "someprotocol", "/some/path")), - Collections.singletonMap("key1", "value1")), + Collections.singletonMap("key1", "value1"), + null), "machineId", "workspaceId", "envName", diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerMachineTerminalLauncherTest.java b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerMachineTerminalLauncherTest.java index 758235e89d..e0b19ee292 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerMachineTerminalLauncherTest.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerMachineTerminalLauncherTest.java @@ -10,50 +10,87 @@ *******************************************************************************/ package org.eclipse.che.plugin.docker.machine; -import org.eclipse.che.api.core.notification.EventService; -import org.eclipse.che.api.machine.server.MachineManager; -import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; -import org.eclipse.che.dto.server.DtoFactory; +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.plugin.docker.client.DockerConnector; -import org.eclipse.che.plugin.docker.machine.ext.DockerMachineTerminalLauncher; +import org.eclipse.che.plugin.docker.client.Exec; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; -import static org.mockito.Mockito.verifyZeroInteractions; +import java.io.IOException; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.anyVararg; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; /** * @author Max Shaposhnik (mshaposhnik@codenvy.com) - * + * @author Alexander Garagatyi */ @Listeners(value = {MockitoTestNGListener.class}) public class DockerMachineTerminalLauncherTest { + private static final String LAUNCH_COMMAND = "launch terminal"; + private static final String CONTAINER = "test container"; + private static final String EXEC_ID = "testExecId"; - private EventService eventService; @Mock - private DockerConnector docker; + private DockerConnector docker; @Mock - private MachineManager machineManager; + private Instance testMachineInstance; + @Mock + private DockerInstance dockerInstance; + @Mock + private Exec exec; - private DockerMachineTerminalLauncher launcher; + private DockerMachineImplTerminalLauncher launcher; @BeforeMethod public void setUp() throws Exception { - eventService = new EventService(); - } + launcher = new DockerMachineImplTerminalLauncher(docker, LAUNCH_COMMAND); + when(dockerInstance.getContainer()).thenReturn(CONTAINER); + when(docker.createExec(CONTAINER, true, "/bin/bash", "-c", LAUNCH_COMMAND)).thenReturn(exec); + when(exec.getId()).thenReturn(EXEC_ID); + } @Test - public void shouldSkipEventsWithStatusOtherThanRunning() { - launcher = new DockerMachineTerminalLauncher(eventService,docker,machineManager,""); - - launcher.start(); - - eventService.publish(DtoFactory.newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.ERROR)); - - verifyZeroInteractions(machineManager); + public void shouldReturnDockerMachineType() throws Exception { + assertEquals(launcher.getMachineType(), "docker"); } + @Test(expectedExceptions = MachineException.class, + expectedExceptionsMessageRegExp = "Docker terminal launcher was used to launch terminal in non-docker machine.") + public void shouldThrowExcIfNonDockerInstanceWasPassedAsArgument() throws Exception { + launcher.launchTerminal(testMachineInstance); + } + + @Test + public void shouldCreateDetachedExecWithTerminalCommandInBash() throws Exception { + launcher.launchTerminal(dockerInstance); + + verify(docker).createExec(CONTAINER, true, "/bin/bash", "-c", LAUNCH_COMMAND); + } + + @Test + public void shouldStartCreatedExec() throws Exception { + launcher.launchTerminal(dockerInstance); + + verify(docker).startExec(eq(EXEC_ID), any()); + } + + @Test(expectedExceptions = MachineException.class, + expectedExceptionsMessageRegExp = "test error") + public void shouldThrowMachineExceptionIfIOExceptionWasThrownByDocker() throws Exception { + when(docker.createExec(anyString(), anyBoolean(), anyVararg())).thenThrow(new IOException("test error")); + + launcher.launchTerminal(dockerInstance); + } } 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 index 4f36f002cc..f38d7ccd26 100644 --- 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 @@ -21,7 +21,7 @@ import org.eclipse.che.api.machine.server.MachineRegistry; import org.eclipse.che.api.machine.server.MachineService; 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.impl.SnapshotImpl; +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; diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/Machine.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/Machine.java index 5cc5cdc31a..f1da67a104 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/Machine.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/Machine.java @@ -76,7 +76,8 @@ public class Machine { String machineSourceType = machineSource.getType(); - if ("recipe".equalsIgnoreCase(machineSourceType)) { + // recipe is left for backward compatibility + if ("recipe".equalsIgnoreCase(machineSourceType) || "dockerfile".equalsIgnoreCase(machineSourceType)) { return machineSource.getLocation(); } 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 776cdd0261..3555ab28a2 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 @@ -236,7 +236,7 @@ public class MachineManagerImpl implements MachineManager, WorkspaceStoppedHandl if (isDev) { limitsDto.withRam(3072); } - MachineSourceDto sourceDto = dtoFactory.createDto(MachineSourceDto.class).withType("Recipe").withLocation(recipeURL); + MachineSourceDto sourceDto = dtoFactory.createDto(MachineSourceDto.class).withType("dockerfile").withLocation(recipeURL); MachineConfigDto configDto = dtoFactory.createDto(MachineConfigDto.class) .withDev(isDev) diff --git a/plugins/plugin-ssh-machine/pom.xml b/plugins/plugin-ssh-machine/pom.xml new file mode 100644 index 0000000000..d2ca60cdb9 --- /dev/null +++ b/plugins/plugin-ssh-machine/pom.xml @@ -0,0 +1,126 @@ + + + + 4.0.0 + + che-plugin-parent + org.eclipse.che.plugin + 4.2.0-RC1-SNAPSHOT + ../pom.xml + + che-plugin-ssh-machine + jar + Che Plugin :: Ssh machine + + false + + + + com.google.code.gson + gson + + + com.google.guava + guava + + + com.google.inject + guice + 4.0 + + + com.google.inject.extensions + guice-assistedinject + + + com.google.inject.extensions + guice-multibindings + + + com.jcraft + jsch + + + javax.inject + javax.inject + + + javax.ws.rs + javax.ws.rs-api + + + org.eclipse.che.core + che-core-api-core + + + org.eclipse.che.core + che-core-api-machine + + + org.eclipse.che.core + che-core-api-model + + + org.eclipse.che.core + che-core-commons-annotations + + + org.eclipse.che.core + che-core-commons-inject + + + org.eclipse.che.core + che-core-commons-lang + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + test + + + javax.servlet + javax.servlet-api + test + + + org.eclipse.che.core + che-core-api-dto + test + + + org.hamcrest + hamcrest-core + test + + + org.mockito + mockito-core + test + + + org.mockitong + mockitong + test + + + org.testng + testng + test + + + diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshClient.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshClient.java new file mode 100644 index 0000000000..29593fe55d --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshClient.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * 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.machine.ssh; + +import org.eclipse.che.api.machine.server.exception.MachineException; + +/** + * Client for communication with ssh machine using SSH protocol. + * + *

Client should be started with {{@link #start()}} before performing communication with a server. + *
Server should be stopped with {{@link #stop()}} after finishing of communication with a server. + * + * @author Alexander Garagatyi + */ +public interface SshClient { + + /** + * Gets address of server this SSH client is connected to. + */ + String getHost(); + + /** + * Starts ssh client. + * + *

Client should be stopped to perform connection cleanup on SSH server. + */ + void start() throws MachineException; + + /** + * Stops client to perform connection cleanup on SSH server. + */ + void stop() throws MachineException; + + /** + * Creates {@link SshProcess} that represents command that can be started over SSH protocol. + * + * @param commandLine + * command line to start over SSH + * @return ssh process, it should be started separately. + * @throws MachineException + */ + SshProcess createProcess(String commandLine) throws MachineException; + + /** + * Copies file(s) from local machine to remote machine using SSH protocol. + * + *

Copying can be performed using SCP or SFTP. + * + * @param sourcePath + * path on localhost that should be copied + * @param targetPath + * path on remote host where file(s) from sourcePath should be copied + * @throws MachineException + */ + void copy(String sourcePath, String targetPath) throws MachineException; +} diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineFactory.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineFactory.java new file mode 100644 index 0000000000..b3c40bee26 --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineFactory.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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.machine.ssh; + +import com.google.inject.assistedinject.Assisted; + +import org.eclipse.che.api.core.model.machine.Command; +import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.machine.server.spi.Instance; + +import java.util.Map; + +/** + * Provides ssh machine implementation instances. + * + * @author Alexander Garagatyi + */ +public interface SshMachineFactory { + + /** + * Creates {@link SshClient} to communicate with machine over SSH protocol. + * + * @param sshMachineRecipe + * recipe of machine + * @param envVars + * environment variables that should be injected into machine + */ + SshClient createSshClient(@Assisted SshMachineRecipe sshMachineRecipe, + @Assisted Map envVars); + + /** + * Creates ssh machine implementation of {@link Instance}. + * + * @param machine description of machine + * @param sshClient ssh client of machine + * @param outputConsumer consumer of output from container main process + * @throws MachineException if error occurs on creation of {@code Instance} + */ + SshMachineInstance createInstance(@Assisted Machine machine, + @Assisted SshClient sshClient, + @Assisted LineConsumer outputConsumer) throws MachineException; + + /** + * Creates ssh machine implementation of {@link org.eclipse.che.api.machine.server.spi.InstanceProcess}. + * + * @param command command that should be executed on process start + * @param outputChannel channel where output will be available on process execution + * @param pid virtual id of that process + * @param sshClient client to communicate with machine + */ + SshMachineProcess createInstanceProcess(@Assisted Command command, + @Assisted("outputChannel") String outputChannel, + @Assisted int pid, + @Assisted SshClient sshClient); +} 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 new file mode 100644 index 0000000000..8808da4404 --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineImplTerminalLauncher.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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.machine.ssh; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.core.util.ListLineConsumer; +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.slf4j.Logger; + +import javax.inject.Inject; +import javax.inject.Named; +import java.io.IOException; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Launch websocket terminal in ssh machines. + * + * @author Alexander Garagatyi + */ +public class SshMachineImplTerminalLauncher implements MachineImplSpecificTerminalLauncher { + private static final Logger LOG = getLogger(SshMachineImplTerminalLauncher.class); + + public static final String TERMINAL_LAUNCH_COMMAND_PROPERTY = "machine.ssh.server.terminal.run_command"; + public static final String TERMINAL_LOCATION_PROPERTY = "machine.ssh.server.terminal.location"; + + private final String runTerminalCommand; + private final String terminalLocation; + private final WebsocketTerminalFilesPathProvider archivePathProvider; + + @Inject + public SshMachineImplTerminalLauncher(@Named(TERMINAL_LAUNCH_COMMAND_PROPERTY) String runTerminalCommand, + @Named(TERMINAL_LOCATION_PROPERTY) String terminalLocation, + WebsocketTerminalFilesPathProvider terminalPathProvider) { + this.runTerminalCommand = runTerminalCommand; + this.terminalLocation = terminalLocation; + this.archivePathProvider = terminalPathProvider; + } + + @Override + public String getMachineType() { + return "ssh"; + } + + // todo stop outdated terminal + // todo check existing version of terminal, do not copy if it is up to date + @Override + public void launchTerminal(Instance machine) throws MachineException { + try { + InstanceProcess checkTerminalAlive = machine.createProcess( + new CommandImpl("check if che websocket terminal is running", + "ps ax | grep 'che-websocket-terminal' | grep -q -v 'grep che-websocket-terminal' && echo 'found' || echo 'not found'", + null), + null); + ListLineConsumer lineConsumer = new ListLineConsumer(); + checkTerminalAlive.start(lineConsumer); + String checkAliveText = lineConsumer.getText(); + + if ("not found".equals(checkAliveText)) { + machine.copy(archivePathProvider.getPath(firstNonNull(machine.getConfig().getArchitecture(), "linux_amd64")), + terminalLocation); + + InstanceProcess startTerminal = machine.createProcess(new CommandImpl("websocket terminal", + runTerminalCommand, + null), + null); + + startTerminal.start(new LineConsumer() { + @Override + public void writeLine(String line) throws IOException { + machine.getLogger().writeLine("[Terminal] " + line); + } + + @Override + public void close() throws IOException {} + }); + } else if (!"found".equals(checkAliveText)) { + LOG.error("Unexpected output of websocket terminal check. Output:" + checkAliveText); + } + } catch (ConflictException e) { + // should never happen + throw new MachineException("Internal server error occurs on terminal launching."); + } + } +} 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 new file mode 100644 index 0000000000..e364b02271 --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstance.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * 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.machine.ssh; + +import com.google.inject.assistedinject.Assisted; + +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.ServerConf; +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.model.impl.ServerImpl; +import org.eclipse.che.api.machine.server.spi.Instance; +import org.eclipse.che.api.machine.server.spi.InstanceKey; +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 javax.inject.Inject; +import javax.inject.Named; +import javax.ws.rs.core.UriBuilder; +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import static java.lang.String.format; +import static java.util.Collections.emptyMap; + +/** + * Implementation of {@link Instance} that uses represents ssh machine. + * + * @author Alexander Garagatyi + * @see SshMachineInstanceProvider + */ +// todo try to avoid map of processes +public class SshMachineInstance extends AbstractInstance { + private static final AtomicInteger pidSequence = new AtomicInteger(1); + + private final SshClient sshClient; + private final LineConsumer outputConsumer; + private final SshMachineFactory machineFactory; + private final Set machinesServers; + private final ConcurrentHashMap machineProcesses; + + private MachineRuntimeInfoImpl machineRuntime; + + @Inject + public SshMachineInstance(@Assisted Machine machine, + @Assisted SshClient sshClient, + @Assisted LineConsumer outputConsumer, + SshMachineFactory machineFactory, + @Named("machine.ssh.machine_servers") Set machinesServers) { + super(machine); + this.sshClient = sshClient; + this.outputConsumer = outputConsumer; + this.machineFactory = machineFactory; + this.machinesServers = machinesServers; + this.machineProcesses = new ConcurrentHashMap<>(); + } + + @Override + public LineConsumer getLogger() { + return outputConsumer; + } + + @Override + public MachineRuntimeInfoImpl getRuntime() { + // lazy initialization + if (machineRuntime == null) { + synchronized (this) { + if (machineRuntime == null) { + UriBuilder uriBuilder = UriBuilder.fromUri("http://" + sshClient.getHost()); + + final Map servers = new HashMap<>(); + for (ServerConf serverConf : machinesServers) { + servers.put(serverConf.getPort(), serverConfToServer(serverConf, uriBuilder.clone())); + } + machineRuntime = new MachineRuntimeInfoImpl(emptyMap(), emptyMap(), servers); + } + } + // todo get env from client + } + return machineRuntime; + } + + @Override + public InstanceProcess getProcess(final int pid) throws NotFoundException, MachineException { + final InstanceProcess machineProcess = machineProcesses.get(pid); + if (machineProcess == null) { + throw new NotFoundException(format("Process with pid %s not found", pid)); + } + try { + machineProcess.checkAlive(); + return machineProcess; + } catch (NotFoundException e) { + machineProcesses.remove(pid); + throw e; + } + } + + @Override + public List getProcesses() throws MachineException { + // todo get children of session process + return machineProcesses.values() + .stream() + .filter(InstanceProcess::isAlive) + .collect(Collectors.toList()); + + } + + @Override + public InstanceProcess createProcess(Command command, String outputChannel) throws MachineException { + final Integer pid = pidSequence.getAndIncrement(); + + SshMachineProcess instanceProcess = machineFactory.createInstanceProcess(command, outputChannel, pid, sshClient); + + machineProcesses.put(pid, instanceProcess); + + return instanceProcess; + } + + /** + * Not implemented.

+ * + * {@inheritDoc} + */ + @Override + public InstanceKey saveToSnapshot(String owner) throws MachineException { + throw new MachineException("Snapshot feature is unsupported for ssh machine implementation"); + } + + @Override + public void destroy() throws MachineException { + // session destroying stops all processes + // todo kill all processes started by code, we should get parent pid of session and kill all children + sshClient.stop(); + } + + @Override + public InstanceNode getNode() { + return null;// todo + } + + /** + * Not implemented.

+ * + * {@inheritDoc} + */ + @Override + public String readFileContent(String filePath, int startFrom, int limit) throws MachineException { + // todo + throw new MachineException("File content reading is not implemented in ssh machine implementation"); + } + + /** + * Not implemented.

+ * + * {@inheritDoc} + */ + @Override + public void copy(Instance sourceMachine, String sourcePath, String targetPath, boolean overwrite) throws MachineException { + //todo + throw new MachineException("Copying is not implemented in ssh machine implementation"); + } + + @Override + public void copy(String sourcePath, String targetPath) throws MachineException { + sshClient.copy(sourcePath, targetPath); + } + + private ServerImpl serverConfToServer(ServerConf serverConf, UriBuilder uriBuilder) { + String port = serverConf.getPort().split("/")[0]; + uriBuilder.port(Integer.parseInt(port)); + if (serverConf.getPath() != null) { + uriBuilder.path(serverConf.getPath()); + } + URI serverUri = uriBuilder.build(); + + return new ServerImpl(serverConf.getRef(), + serverConf.getProtocol(), + serverUri.getHost() + ":" + serverUri.getPort(), + serverUri.getPath(), + serverConf.getProtocol() != null ? serverUri.toString() : null); + } + +} diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProvider.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProvider.java new file mode 100644 index 0000000000..399a56b7ba --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProvider.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * 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.machine.ssh; + +import com.google.gson.Gson; + +import org.eclipse.che.api.core.NotFoundException; +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.Recipe; +import org.eclipse.che.api.core.util.LineConsumer; +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.spi.Instance; +import org.eclipse.che.api.machine.server.spi.InstanceKey; +import org.eclipse.che.api.machine.server.spi.InstanceProvider; + +import javax.inject.Inject; +import java.io.IOException; +import java.util.Collections; +import java.util.Set; + +import static java.util.Objects.requireNonNull; + +/** + * Implementation of {@link InstanceProvider} based on communication with machine over ssh protocol. + * + *

Ssh machine can't be actually created and exists somewhere outside of the control.
+ * So this implementation just performs command execution in such machines.
+ * This implementation ignores machine limits {@link MachineConfig#getLimits()}. + * + * @author Alexander Garagatyi + */ +// todo tests +public class SshMachineInstanceProvider implements InstanceProvider { + private static final Gson GSON = new Gson(); + + private final Set supportedRecipeTypes; + private final SshMachineFactory sshMachineFactory; + + @Inject + public SshMachineInstanceProvider(SshMachineFactory sshMachineFactory) throws IOException { + this.sshMachineFactory = sshMachineFactory; + this.supportedRecipeTypes = Collections.singleton("ssh-config"); + } + + @Override + public String getType() { + return "ssh"; + } + + @Override + public Set getRecipeTypes() { + return supportedRecipeTypes; + } + + @Override + public Instance createInstance(Recipe recipe, + Machine machine, + LineConsumer machineLogsConsumer) throws MachineException { + requireNonNull(machine, "Non null machine required"); + requireNonNull(machineLogsConsumer, "Non null logs consumer required"); + + if (machine.getConfig().isDev()) { + throw new MachineException("Dev machine is not supported for Ssh machine implementation"); + } + + SshMachineRecipe sshMachineRecipe = parseRecipe(recipe); + + SshClient sshClient = sshMachineFactory.createSshClient(sshMachineRecipe, + machine.getConfig().getEnvVariables()); + sshClient.start(); + + return sshMachineFactory.createInstance(machine, + sshClient, + machineLogsConsumer); + } + + @Override + public Instance createInstance(InstanceKey instanceKey, + Machine machine, + LineConsumer creationLogsOutput) throws NotFoundException, MachineException { + throw new MachineException("Snapshot feature is unsupported for ssh machine implementation"); + } + + @Override + public void removeInstanceSnapshot(InstanceKey instanceKey) throws SnapshotException { + throw new SnapshotException("Snapshot feature is unsupported for ssh machine implementation"); + } + + private SshMachineRecipe parseRecipe(Recipe recipe) { + return GSON.fromJson(recipe.getScript(), SshMachineRecipe.class); + } +} 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 new file mode 100644 index 0000000000..6910d04e8e --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineModule.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * 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.machine.ssh; + +import com.google.inject.AbstractModule; +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; + +/** + * Provides bindings needed for ssh machine implementation usage. + * + * @author Alexander Garagatyi + */ +public class SshMachineModule extends AbstractModule { + @Override + protected void configure() { + Multibinder machineProviderMultibinder = + Multibinder.newSetBinder(binder(), + org.eclipse.che.api.machine.server.spi.InstanceProvider.class); + machineProviderMultibinder.addBinding() + .to(SshMachineInstanceProvider.class); + + install(new FactoryModuleBuilder() + .implement(org.eclipse.che.api.machine.server.spi.Instance.class, + org.eclipse.che.plugin.machine.ssh.SshMachineInstance.class) + .implement(org.eclipse.che.api.machine.server.spi.InstanceProcess.class, + org.eclipse.che.plugin.machine.ssh.SshMachineProcess.class) + .implement(org.eclipse.che.plugin.machine.ssh.SshClient.class, + org.eclipse.che.plugin.machine.ssh.jsch.JschSshClient.class) + .build(SshMachineFactory.class)); + + Multibinder terminalLaunchers = + Multibinder.newSetBinder(binder(), + MachineImplSpecificTerminalLauncher.class); + terminalLaunchers.addBinding().to(SshMachineImplTerminalLauncher.class); + + bindConstant().annotatedWith(Names.named(SshMachineImplTerminalLauncher.TERMINAL_LAUNCH_COMMAND_PROPERTY)) + .to("~/che/terminal/che-websocket-terminal -addr :4411 -cmd /bin/bash -static ~/che/terminal/"); + + bindConstant().annotatedWith(Names.named(SshMachineImplTerminalLauncher.TERMINAL_LOCATION_PROPERTY)) + .to("~/che/terminal/"); + + Multibinder machineServers = + Multibinder.newSetBinder(binder(), + org.eclipse.che.api.core.model.machine.ServerConf.class, + Names.named("machine.ssh.machine_servers")); + machineServers.addBinding().toProvider(TerminalServerConfProvider.class); + } +} diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineProcess.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineProcess.java new file mode 100644 index 0000000000..9f9f54aa5e --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineProcess.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * 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.machine.ssh; + +import com.google.inject.assistedinject.Assisted; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.model.machine.Command; +import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.machine.server.spi.InstanceProcess; +import org.eclipse.che.api.machine.server.spi.impl.AbstractMachineProcess; +import org.eclipse.che.commons.annotation.Nullable; + +import javax.inject.Inject; + +import static java.lang.String.format; + +/** + * Ssh machine implementation of {@link InstanceProcess} + * + * @author Alexander Garagatyi + */ +public class SshMachineProcess extends AbstractMachineProcess implements InstanceProcess { + private final String commandLine; + private final SshClient sshClient; + + private volatile boolean started; + + private SshProcess sshProcess; + + @Inject + public SshMachineProcess(@Assisted Command command, + @Nullable @Assisted("outputChannel") String outputChannel, + @Assisted int pid, + @Assisted SshClient sshClient) { + super(command, pid, outputChannel); + this.sshClient = sshClient; + this.commandLine = command.getCommandLine(); + this.started = false; + } + + @Override + public boolean isAlive() { + if (!started) { + return false; + } + try { + checkAlive(); + return true; + } catch (MachineException | NotFoundException e) { + // when process is not found (may be finished or killed) + // when ssh is not accessible or responds in an unexpected way + return false; + } + } + + @Override + public void start() throws ConflictException, MachineException { + start(null); + } + + @Override + public void start(LineConsumer output) throws ConflictException, MachineException { + if (started) { + throw new ConflictException("Process already started."); + } + + sshProcess = sshClient.createProcess(commandLine); + + started = true; + + if (output == null) { + sshProcess.start(); + } else { + sshProcess.start(output); + } + } + + @Override + public void checkAlive() throws MachineException, NotFoundException { + if (!started) { + throw new NotFoundException("Process is not started yet"); + } + + if (sshProcess.getExitCode() != -1) { + throw new NotFoundException(format("Process with pid %s not found", getPid())); + } + } + + @Override + public void kill() throws MachineException { + sshProcess.kill(); + } +} diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineRecipe.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineRecipe.java new file mode 100644 index 0000000000..5513cb092f --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineRecipe.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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.machine.ssh; + +import java.util.Objects; + +/** + * Recipe of connection to ssh machine using ssh protocol. + * + * @author Alexander Garagatyi + */ +public class SshMachineRecipe { + + private final String host; + private final Integer port; + private final String username; + private final String password; + + public SshMachineRecipe(String host, + Integer port, + String username, + String password) { + this.host = host; + this.port = port; + this.username = username; + this.password = password; + } + + public String getHost() { + return host; + } + + public Integer getPort() { + return port; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SshMachineRecipe)) return false; + SshMachineRecipe that = (SshMachineRecipe)o; + return Objects.equals(host, that.host) && + Objects.equals(port, that.port) && + Objects.equals(username, that.username) && + Objects.equals(password, that.password); + } + + @Override + public int hashCode() { + return Objects.hash(host, port, username, password); + } + + @Override + public String toString() { + return "SshMachineRecipe{" + + "host='" + host + '\'' + + ", port=" + port + + ", username='" + username + '\'' + + ", password='" + password + '\'' + + '}'; + } +} diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshProcess.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshProcess.java new file mode 100644 index 0000000000..c715723290 --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshProcess.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.plugin.machine.ssh; + +import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.machine.server.exception.MachineException; + +/** + * Represents process created with {@link SshClient}. + * + * @author Alexander Garagatyi + */ +public interface SshProcess { + void start() throws MachineException; + + void start(LineConsumer output) throws MachineException; + + int getExitCode(); + + void kill() throws MachineException; +} diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/TerminalServerConfProvider.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/TerminalServerConfProvider.java new file mode 100644 index 0000000000..ae8c0adde5 --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/TerminalServerConfProvider.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * 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.machine.ssh; + +import org.eclipse.che.api.core.model.machine.ServerConf; +import org.eclipse.che.api.machine.server.model.impl.ServerConfImpl; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; +import java.net.URI; + +/** + * Provides server conf that describes websocket terminal server + * + * @author Alexander Garagatyi + */ +@Singleton +public class TerminalServerConfProvider implements Provider { + public static final String TERMINAL_SERVER_REFERENCE = "terminal"; + + @Inject + @Named("api.endpoint") + private URI apiEndpoint; + + @Override + public ServerConf get() { + return new ServerConfImpl(TERMINAL_SERVER_REFERENCE, "4411/tcp", apiEndpoint.getScheme(), null); + } +} diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/WebsocketTerminalFilesPathProvider.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/WebsocketTerminalFilesPathProvider.java new file mode 100644 index 0000000000..7e53bebc98 --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/WebsocketTerminalFilesPathProvider.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * 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.machine.ssh; + +import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.che.inject.ConfigurationProperties; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Map; + +import static java.util.stream.Collectors.toMap; + +/** + * Provides path to websocket terminal archive. + * + * @author Alexander Garagatyi + */ +@Singleton +public class WebsocketTerminalFilesPathProvider { + private static final String CONFIGURATION_PREFIX = "machine.ssh.server.terminal.path_to_archive."; + private static final String CONFIGURATION_PREFIX_PATTERN = "machine\\.ssh\\.server\\.terminal\\.path_to_archive\\..+"; + + private Map archivesPaths; + + @Inject + public WebsocketTerminalFilesPathProvider(ConfigurationProperties configurationProperties) { + archivesPaths = configurationProperties.getProperties(CONFIGURATION_PREFIX_PATTERN) + .entrySet() + .stream() + .collect(toMap(entry -> entry.getKey().replaceFirst(CONFIGURATION_PREFIX, ""), + Map.Entry::getValue)); + } + + @Nullable + public String getPath(String architecture) { + return archivesPaths.get(architecture); + } +} diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschSshClient.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschSshClient.java new file mode 100644 index 0000000000..db392ae78d --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschSshClient.java @@ -0,0 +1,299 @@ +/******************************************************************************* + * 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.machine.ssh.jsch; + +import com.google.inject.assistedinject.Assisted; +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpException; + +import org.eclipse.che.api.core.util.ListLineConsumer; +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.commons.lang.IoUtil; +import org.eclipse.che.plugin.machine.ssh.SshClient; +import org.eclipse.che.plugin.machine.ssh.SshMachineRecipe; + +import javax.inject.Inject; +import javax.inject.Named; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Map; + +import static java.lang.String.format; + +/** + * Client for communication with ssh machine using ssh protocol. + * + * @author Alexander Garagatyi + */ +// todo think about replacement JSch with Apace SSHD +// todo tests for ssh library that ensures that it works as expected +public class JschSshClient implements SshClient { + private final JSch jsch; + private final JschUserInfoImpl user; + private final String host; + private final int port; + private final String username; + private final Map envVars; + private final int connectionTimeout; + + private Session session; + + @Inject + public JschSshClient(@Assisted SshMachineRecipe sshMachineRecipe, + @Assisted Map envVars, + JSch jsch, + @Named("machine.ssh.connection_timeout_ms") int connectionTimeoutMs) { + this.envVars = envVars; + this.connectionTimeout = connectionTimeoutMs; + this.user = JschUserInfoImpl.builder() + .password(sshMachineRecipe.getPassword()) + .promptPassword(true) + .passphrase(null) + .promptPassphrase(false) + .promptYesNo(true) + .build(); + this.jsch = jsch; + this.host = sshMachineRecipe.getHost(); + this.port = sshMachineRecipe.getPort(); + this.username = sshMachineRecipe.getUsername(); + } + + @Override + public String getHost() { + return host; + } + + @Override + public void start() throws MachineException { + try { + session = jsch.getSession(username, host, port); + session.setUserInfo(user); + // todo remember parent pid of shell to be able to kill all processes on client stop + if (!session.isConnected()) { + session.connect(connectionTimeout); + } + } catch (JSchException e) { + throw new MachineException("Ssh machine creation failed because ssh of machine is inaccessible. Error: " + + e.getLocalizedMessage()); + } + } + + //todo add method to read env vars by client + // ChannelExec execAndGetCode = (ChannelExec)session.openChannel("execAndGetCode"); + // execAndGetCode.setCommand("env"); + //envVars.entrySet() +// .stream() +// .forEach(envVariableEntry -> execAndGetCode.setEnv(envVariableEntry.getKey(), +// envVariableEntry.getValue())); +// todo process output + + @Override + public void stop() throws MachineException { + session.disconnect(); + } + + @Override + public JschSshProcess createProcess(String commandLine) throws MachineException { + try { + ChannelExec exec = (ChannelExec)session.openChannel("exec"); + exec.setCommand(commandLine); + envVars.entrySet() + .stream() + .forEach(envVariableEntry -> exec.setEnv(envVariableEntry.getKey(), + envVariableEntry.getValue())); + return new JschSshProcess(exec); + } catch (JSchException e) { + throw new MachineException("Can't establish connection to perform command execution in ssh machine. Error: " + + e.getLocalizedMessage(), e); + } + } + + @Override + public void copy(String sourcePath, String targetPath) throws MachineException { + File source = new File(sourcePath); + if (!source.exists()) { + throw new MachineException("Source of copying '" + sourcePath + "' doesn't exist."); + } + if (source.isDirectory()) { + copyRecursively(sourcePath, targetPath); + } else { + copyFile(sourcePath, targetPath); + } + } + + private void copyRecursively(String sourceFolder, String targetFolder) throws MachineException { + // create target dir + try { + int execCode = execAndGetCode("mkdir -p " + targetFolder); + + if (execCode != 0) { + throw new MachineException(format("Creation of folder %s failed. Exit code is %s", targetFolder, execCode)); + } + } catch (JSchException e) { + throw new MachineException(format("Creation of folder %s failed. Error: %s", targetFolder, e.getLocalizedMessage())); + } + + // not normalized paths don't work + final String targetAbsolutePath = getAbsolutePath(targetFolder); + + // copy files + ChannelSftp sftp = null; + try { + sftp = (ChannelSftp)session.openChannel("sftp"); + sftp.connect(connectionTimeout); + + final ChannelSftp finalSftp = sftp; + Files.walkFileTree(Paths.get(sourceFolder), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + try { + if (!attrs.isDirectory()) { + copyFile(file.toString(), + Paths.get(targetAbsolutePath, file.getFileName().toString()).toString(), finalSftp); + } else { + finalSftp.mkdir(file.normalize().toString()); + } + } catch (MachineException | SftpException e) { + throw new IOException(format("Sftp copying of file %s failed. Error: %s", file, e.getLocalizedMessage())); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (JSchException | IOException e) { + throw new MachineException("Copying failed. Error: " + e.getLocalizedMessage()); + } finally { + if (sftp != null) { + sftp.disconnect(); + } + } + } + + private void copyFile(String sourcePath, String targetPath) throws MachineException { + ChannelSftp sftp = null; + try { + sftp = (ChannelSftp)session.openChannel("sftp"); + sftp.connect(connectionTimeout); + String absoluteTargetPath = getAbsolutePath(targetPath); + copyFile(sourcePath, absoluteTargetPath, sftp); + } catch (JSchException e) { + throw new MachineException("Sftp copying failed. Error: " + e.getLocalizedMessage()); + } finally { + if (sftp != null) { + sftp.disconnect(); + } + } + } + + private void copyFile(String sourcePath, String absoluteTargetPath, ChannelSftp channelSftp) throws MachineException { + try { + channelSftp.put(sourcePath, absoluteTargetPath); + + // apply permissions + File file = new File(sourcePath); + // read + int permissions = 4; + // execute + if (file.canExecute()) { + permissions += 1; + } + // write + if (file.canWrite()) { + permissions += 2; + } + channelSftp.chmod(permissions, absoluteTargetPath); + } catch (SftpException e) { + throw new MachineException(format("Sftp copying of file %s failed. Error: %s", + absoluteTargetPath, + e.getLocalizedMessage())); + } + } + + private String getAbsolutePath(String path) throws MachineException { + try { + return execAndGetOutput("cd " + path + "; pwd"); + } catch (JSchException | IOException | MachineException e) { + throw new MachineException("Target directory lookup failed. " + e.getLocalizedMessage()); + } + } + + private int execAndGetCode(String command) throws JSchException { + ChannelExec exec = null; + try { + exec = (ChannelExec)session.openChannel("exec"); + exec.setCommand(command); + exec.connect(connectionTimeout); + + // todo fix that + // this method is used to run commands such as "pwd" or "mkdir -p /some/path" + // And we need to wait to check if it return 0. If command is still running method returns -1. + try { + // workaround exit code -1 + Thread.sleep(1000); + } catch (InterruptedException ignored) { + } + return exec.getExitStatus(); + } finally { + if (exec != null) { + exec.disconnect(); + } + } + } + + private String execAndGetOutput(String command) throws JSchException, MachineException, IOException { + ChannelExec exec = null; + try { + exec = (ChannelExec)session.openChannel("exec"); + exec.setCommand(command); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(exec.getInputStream()))) { + + exec.connect(connectionTimeout); + + // todo fix that + // this method is used to run commands such as "pwd" or "mkdir -p /some/path" + // And we need to wait to check if it return 0. If command is still running method returns -1. + try { + // workaround exit code -1 + Thread.sleep(1000); + } catch (InterruptedException ignored) { + } + if (exec.getExitStatus() != 0) { + throw new MachineException(format("Error code: %s. Error: %s", + exec.getExitStatus(), + IoUtil.readAndCloseQuietly(exec.getErrStream()))); + } + + ListLineConsumer listLineConsumer = new ListLineConsumer(); + String line; + while ((line = reader.readLine()) != null) { + listLineConsumer.writeLine(line); + } + return listLineConsumer.getText(); + } + } finally { + if (exec != null) { + exec.disconnect(); + } + } + } +} diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschSshProcess.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschSshProcess.java new file mode 100644 index 0000000000..90fa6d2bef --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschSshProcess.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * 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.machine.ssh.jsch; + +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.JSchException; + +import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.plugin.machine.ssh.SshProcess; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * JSch implementation of {@link SshProcess} + * + * @author Alexander Garagatyi + */ +public class JschSshProcess implements SshProcess { + private final ChannelExec exec; + + public JschSshProcess(ChannelExec exec) { + this.exec = exec; + } + + @Override + public void start() throws MachineException { + try { + exec.connect(); + } catch (JSchException e) { + throw new MachineException("Ssh machine command execution error:" + e.getLocalizedMessage()); + } + } + + @Override + public void start(LineConsumer output) throws MachineException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(exec.getInputStream())); + BufferedReader errReader = new BufferedReader(new InputStreamReader(exec.getErrStream()))) { + + exec.connect(); + + String line; + while ((line = reader.readLine()) != null) { + // todo format output as it is done in docker impl + // todo use async streams? + // todo how to manage disconnections due to network failures? + output.writeLine(line); + } + while ((line = errReader.readLine()) != null) { + output.writeLine(line); + } + } catch (IOException | JSchException e) { + throw new MachineException("Ssh machine command execution error:" + e.getLocalizedMessage()); + } + } + + @Override + public int getExitCode() { + return exec.getExitStatus(); + } + + @Override + public void kill() throws MachineException { + exec.disconnect(); + } +} diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschUserInfoImpl.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschUserInfoImpl.java new file mode 100644 index 0000000000..61cf505067 --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschUserInfoImpl.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * 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.machine.ssh.jsch; + +import com.jcraft.jsch.UserInfo; + +/** + * Implementation of {@link UserInfo}. + * + * @author Alexander Garagatyi + */ +public class JschUserInfoImpl implements UserInfo { + private final String password; + private final boolean promptPassword; + private final String passphrase; + private final boolean promptPassphrase; + private final boolean promptYesNo; + + private JschUserInfoImpl(String password, + boolean promptPassword, + String passphrase, + boolean promptPassphrase, + boolean promptYesNo) { + this.password = password; + this.promptPassword = promptPassword; + this.passphrase = passphrase; + this.promptPassphrase = promptPassphrase; + this.promptYesNo = promptYesNo; + } + + public static JschUserInfoImplBuilder builder() { + return new JschUserInfoImplBuilder(); + } + + @Override + public String getPassphrase() { + return passphrase; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public boolean promptPassword(String message) { + return promptPassword; + } + + @Override + public boolean promptPassphrase(String message) { + return promptPassphrase; + } + + @Override + public boolean promptYesNo(String message) { + return promptYesNo; + } + + @Override + public void showMessage(String message) {} + + public static class JschUserInfoImplBuilder { + private String password; + private String passphrase; + private boolean promptPassword; + private boolean promptPassphrase; + private boolean promptYesNo; + + private JschUserInfoImplBuilder() {} + + public JschUserInfoImpl build() { + return new JschUserInfoImpl(password, promptPassword, passphrase, promptPassphrase, promptYesNo); + } + + public JschUserInfoImplBuilder password(String password) { + this.password = password; + return this; + } + + public JschUserInfoImplBuilder passphrase(String passphrase) { + this.passphrase = passphrase; + return this; + } + + public JschUserInfoImplBuilder promptPassword(boolean promptPassword) { + this.promptPassword = promptPassword; + return this; + } + + public JschUserInfoImplBuilder promptPassphrase(boolean promptPassphrase) { + this.promptPassphrase = promptPassphrase; + return this; + } + + public JschUserInfoImplBuilder promptYesNo(boolean promptYesNo) { + this.promptYesNo = promptYesNo; + return this; + } + } +} diff --git a/plugins/plugin-ssh-machine/src/test/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProviderTest.java b/plugins/plugin-ssh-machine/src/test/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProviderTest.java new file mode 100644 index 0000000000..7f6a026ee2 --- /dev/null +++ b/plugins/plugin-ssh-machine/src/test/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProviderTest.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.plugin.machine.ssh; + +import com.google.gson.Gson; + +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.util.LineConsumer; +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.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.recipe.RecipeImpl; +import org.eclipse.che.api.machine.server.spi.Instance; +import org.eclipse.che.api.machine.server.spi.InstanceKey; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +import java.util.Collections; +import java.util.HashSet; + +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyMap; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; + +/** + * @author Alexander Garagatyi + */ +@Listeners(MockitoTestNGListener.class) +public class SshMachineInstanceProviderTest { + @Mock + private SshMachineFactory sshMachineFactory; + @Mock + private SshClient sshClient; + @Mock + private SshMachineInstance sshMachineInstance; + + private SshMachineInstanceProvider provider; + private RecipeImpl recipe; + private MachineImpl machine; + + @BeforeMethod + public void setUp() throws Exception { + provider = new SshMachineInstanceProvider(sshMachineFactory); + machine = createMachine(); + SshMachineRecipe sshMachineRecipe = new SshMachineRecipe("localhost", + 22, + "user", + "password"); + recipe = new RecipeImpl().withType("ssh-config") + .withScript(new Gson().toJson(sshMachineRecipe)); + } + + @Test + public void shouldReturnCorrectType() throws Exception { + assertEquals(provider.getType(), "ssh"); + } + + @Test + public void shouldReturnCorrectRecipeTypes() throws Exception { + assertEquals(provider.getRecipeTypes(), new HashSet<>(singletonList("ssh-config"))); + } + + @Test(expectedExceptions = MachineException.class, + expectedExceptionsMessageRegExp = "Snapshot feature is unsupported for ssh machine implementation") + public void shouldThrowMachineExceptionOnCreateInstanceFromSnapshot() throws Exception { + InstanceKey instanceKey = () -> Collections.EMPTY_MAP; + + provider.createInstance(instanceKey, null, null); + } + + @Test(expectedExceptions = SnapshotException.class, + expectedExceptionsMessageRegExp = "Snapshot feature is unsupported for ssh machine implementation") + public void shouldThrowSnapshotExceptionOnRemoveSnapshot() throws Exception { + provider.removeInstanceSnapshot(null); + } + + @Test(expectedExceptions = MachineException.class, + expectedExceptionsMessageRegExp = "Dev machine is not supported for Ssh machine implementation") + public void shouldThrowExceptionOnDevMachineCreationFromRecipe() throws Exception { + Machine machine = createMachine(true); + + provider.createInstance(recipe, machine, LineConsumer.DEV_NULL); + } + + @Test + public void shouldBeAbleToCreateSshMachineInstanceOnMachineCreationFromRecipe() throws Exception { + when(sshMachineFactory.createSshClient(any(SshMachineRecipe.class), anyMap())).thenReturn(sshClient); + when(sshMachineFactory.createInstance(eq(machine), eq(sshClient), any(LineConsumer.class))).thenReturn(sshMachineInstance); + + Instance instance = provider.createInstance(recipe, machine, LineConsumer.DEV_NULL); + + assertEquals(instance, sshMachineInstance); + } + + private MachineImpl createMachine() { + return createMachine(false); + } + + private MachineImpl createMachine(boolean isDev) { + MachineConfig machineConfig = MachineConfigImpl.builder() + .setDev(isDev) + .setEnvVariables(singletonMap("testEnvVar1", "testEnvVarVal1")) + .setName("name1") + .setServers(singletonList(new ServerConfImpl("myref1", + "10011/tcp", + "http", + null))) + .setSource(new MachineSourceImpl("ssh-config", + "localhost:10012/recipe")) + .setType("ssh") + .build(); + return MachineImpl.builder() + .setConfig(machineConfig) + .setEnvName("env1") + .setId("id1") + .setOwner("owner1") + .setRuntime(null) + .setStatus(MachineStatus.CREATING) + .setWorkspaceId("wsId1") + .build(); + } +} diff --git a/plugins/pom.xml b/plugins/pom.xml index b0d672d594..054768b88d 100644 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -42,5 +42,6 @@ plugin-svn plugin-cpp plugin-nodejs + plugin-ssh-machine diff --git a/pom.xml b/pom.xml index 0290feb583..77748e8248 100644 --- a/pom.xml +++ b/pom.xml @@ -305,6 +305,13 @@ zip linux_amd64 + + org.eclipse.che.lib + che-websocket-terminal + ${che.lib.version} + zip + linux_arm7 + org.eclipse.che.plugin che-jdt-ext-machine @@ -536,6 +543,11 @@ ${project.version} jar-with-dependencies + + org.eclipse.che.plugin + che-plugin-ssh-machine + ${project.version} + org.eclipse.che.plugin che-plugin-svn-ext-ide diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/DtoConverter.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/DtoConverter.java index c9fa78e24a..a1d2a7f820 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/DtoConverter.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/DtoConverter.java @@ -35,8 +35,6 @@ import java.util.stream.Collectors; import static java.util.stream.Collectors.toMap; import static org.eclipse.che.dto.server.DtoFactory.newDto; -// TODO! use global registry for DTO converters - /** * Helps to convert to/from DTOs related to workspace. * @@ -56,7 +54,8 @@ public final class DtoConverter { .stream() .map(DtoConverter::asDto) .collect(Collectors.toList())) - .withEnvVariables(config.getEnvVariables()); + .withEnvVariables(config.getEnvVariables()) + .withArchitecture(config.getArchitecture()); } /** 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 index ed32c226f7..600808600f 100644 --- 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 @@ -26,26 +26,26 @@ import org.eclipse.che.api.core.model.machine.Recipe; 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.FileCleaner; 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.exception.InvalidRecipeException; +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.SnapshotException; import org.eclipse.che.api.machine.server.exception.UnsupportedRecipeException; -import org.eclipse.che.api.machine.server.impl.SnapshotImpl; +import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; 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.recipe.RecipeImpl; import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.machine.server.spi.InstanceKey; 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.util.RecipeDownloader; import org.eclipse.che.api.machine.shared.dto.event.MachineProcessEvent; import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; +import org.eclipse.che.api.machine.wsagent.WsAgentLauncher; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.IoUtil; @@ -60,12 +60,9 @@ import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; -import javax.ws.rs.core.UriBuilder; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.Reader; -import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Files; import java.util.ArrayList; @@ -76,8 +73,9 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import java.util.stream.Collectors; -import static org.eclipse.che.api.machine.server.InstanceStateEvent.Type.DIE; -import static org.eclipse.che.api.machine.server.InstanceStateEvent.Type.OOM; +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; /** @@ -99,10 +97,10 @@ public class MachineManager { private final ExecutorService executor; private final MachineRegistry machineRegistry; private final EventService eventService; - private final String apiEndpoint; private final int defaultMachineMemorySizeMB; private final MachineCleaner machineCleaner; private final WsAgentLauncher wsAgentLauncher; + private final RecipeDownloader recipeDownloader; @Inject public MachineManager(SnapshotDao snapshotDao, @@ -111,13 +109,13 @@ public class MachineManager { @Named("machine.logs.location") String machineLogsDir, EventService eventService, @Named("machine.default_mem_size_mb") int defaultMachineMemorySizeMB, - @Named("api.endpoint") String apiEndpoint, - WsAgentLauncher wsAgentLauncher) { + WsAgentLauncher wsAgentLauncher, + RecipeDownloader recipeDownloader) { this.snapshotDao = snapshotDao; this.machineInstanceProviders = machineInstanceProviders; this.eventService = eventService; - this.apiEndpoint = apiEndpoint; this.wsAgentLauncher = wsAgentLauncher; + this.recipeDownloader = recipeDownloader; this.machineLogsDir = new File(machineLogsDir); this.machineRegistry = machineRegistry; this.defaultMachineMemorySizeMB = defaultMachineMemorySizeMB; @@ -129,7 +127,7 @@ public class MachineManager { } /** - * Synchronously creates and starts machine from scratch using recipe. + * Synchronously creates and starts machine from scratch. * * @param machineConfig * configuration that contains all information needed for machine creation @@ -138,10 +136,6 @@ public class MachineManager { * @param environmentName * environment name the created machine will belongs to * @return new machine - * @throws UnsupportedRecipeException - * if recipe isn't supported - * @throws InvalidRecipeException - * if recipe is not valid * @throws NotFoundException * if machine type from recipe is unsupported * @throws NotFoundException @@ -166,11 +160,15 @@ public class MachineManager { MachineException, BadRequestException { LOG.info("Creating machine [ws = {}: env = {}: machine = {}]", workspaceId, environmentName, machineConfig.getName()); - final MachineImpl machine = createMachine(machineConfig, workspaceId, environmentName, this::createInstance, null); + final MachineImpl machine = createMachine(normalizeMachineConfig(machineConfig), + workspaceId, + environmentName, + this::createInstance, + null); LOG.info("Machine [ws = {}: env = {}: machine = {}] was successfully created, its id is '{}'", workspaceId, environmentName, - machineConfig.getName(), + machine.getConfig().getName(), machine.getId()); return machineRegistry.getMachine(machine.getId()); @@ -205,18 +203,22 @@ public class MachineManager { 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(machineConfig, workspaceId, envName, this::createInstance, snapshot); + final MachineImpl machine = createMachine(normalizeMachineConfig(machineConfig), + workspaceId, + envName, + this::createInstance, + snapshot); LOG.info("Machine [ws = {}: env = {}: machine = {}] was successfully recovered, its id '{}'", workspaceId, envName, - machineConfig.getName(), + machine.getConfig().getName(), machine.getId()); return machineRegistry.getMachine(machine.getId()); } /** - * Asynchronously creates and starts machine from scratch using recipe. + * Asynchronously creates and starts machine from scratch. * * @param machineConfig * configuration that contains all information needed for machine creation @@ -225,10 +227,6 @@ public class MachineManager { * @param environmentName * environment name the created machine will belongs to * @return new machine - * @throws UnsupportedRecipeException - * if recipe isn't supported - * @throws InvalidRecipeException - * if recipe is not valid * @throws NotFoundException * if machine type from recipe is unsupported * @throws NotFoundException @@ -252,16 +250,16 @@ public class MachineManager { ConflictException, MachineException, BadRequestException { - return createMachine(machineConfig, + return createMachine(normalizeMachineConfig(machineConfig), workspaceId, environmentName, - (instanceProvider, recipe, instanceKey, machineState, machineLogger) -> + (instanceProvider, recipe, instanceKey, machine, machineLogger) -> executor.execute(ThreadLocalPropagateContext.wrap(() -> { try { createInstance(instanceProvider, recipe, instanceKey, - machineState, + machine, machineLogger); } catch (MachineException | NotFoundException e) { LOG.error(e.getLocalizedMessage(), e); @@ -271,7 +269,7 @@ public class MachineManager { null); } - private MachineImpl createMachine(MachineConfig machineConfig, + private MachineImpl createMachine(MachineConfigImpl machineConfig, String workspaceId, String environmentName, MachineInstanceCreator instanceCreator, @@ -282,18 +280,26 @@ public class MachineManager { BadRequestException, MachineException { final InstanceProvider instanceProvider = machineInstanceProviders.getProvider(machineConfig.getType()); - final String sourceType = machineConfig.getSource().getType(); - Recipe recipe; + // 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".equals(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())); + } + + Recipe recipe = null; InstanceKey instanceKey = null; if (snapshot != null) { instanceKey = snapshot.getInstanceKey(); - } - if ("Recipe".equalsIgnoreCase(sourceType)) { - // TODO should we check that it is dockerfile? - recipe = getRecipeByLocation(machineConfig); } else { - throw new BadRequestException("Source type is unsupported " + sourceType); + recipe = recipeDownloader.getRecipe(machineConfig); } if (!MACHINE_DISPLAY_NAME_PATTERN.matcher(machineConfig.getName()).matches()) { @@ -310,9 +316,7 @@ public class MachineManager { final String creator = EnvironmentContext.getCurrent().getUser().getId(); if (machineConfig.getLimits().getRam() == 0) { - MachineConfigImpl machineConfigWithLimits = new MachineConfigImpl(machineConfig); - machineConfigWithLimits.setLimits(new LimitsImpl(defaultMachineMemorySizeMB)); - machineConfig = machineConfigWithLimits; + machineConfig.setLimits(new LimitsImpl(defaultMachineMemorySizeMB)); } final MachineImpl machine = new MachineImpl(machineConfig, @@ -400,7 +404,9 @@ public class MachineManager { } private interface MachineInstanceCreator { - void createInstance(InstanceProvider instanceProvider, Recipe recipe, InstanceKey instanceKey, Machine machineState, + void createInstance(InstanceProvider instanceProvider, + Recipe recipe, InstanceKey instanceKey, + Machine machineState, LineConsumer machineLogger) throws MachineException, NotFoundException; } @@ -952,35 +958,6 @@ public class MachineManager { } } - Recipe getRecipeByLocation(MachineConfig machineConfig) throws MachineException { - String recipeContent; - URL recipeUrl = null; - File file = null; - try { - UriBuilder targetUriBuilder = UriBuilder.fromUri(machineConfig.getSource().getLocation()); - // add user token to be able to download user's private recipe - if (machineConfig.getSource().getLocation().startsWith(apiEndpoint)) { - if (EnvironmentContext.getCurrent().getUser() != null - && EnvironmentContext.getCurrent().getUser().getToken() != null) { - targetUriBuilder.queryParam("token", EnvironmentContext.getCurrent().getUser().getToken()); - } - } - recipeUrl = targetUriBuilder.build().toURL(); - file = IoUtil.downloadFile(null, "recipe", null, recipeUrl); - recipeContent = IoUtil.readAndCloseQuietly(new FileInputStream(file)); - } catch (IOException | IllegalArgumentException e) { - throw new MachineException("Can't start machine " + machineConfig.getName() + - ". Machine recipe downloading failed. Recipe url " + recipeUrl + ". " - + e.getLocalizedMessage()); - } finally { - if (file != null) { - FileCleaner.addFile(file); - } - } - - return new RecipeImpl().withType("Dockerfile").withScript(recipeContent); - } - /** * Checks object reference is not {@code null} * @@ -1049,4 +1026,8 @@ public class MachineManager { 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/MachineModule.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineModule.java index 9b912ad5bc..24dce19c0e 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineModule.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineModule.java @@ -15,6 +15,7 @@ import com.google.inject.name.Names; import org.eclipse.che.api.machine.server.event.MachineProcessMessenger; import org.eclipse.che.api.machine.server.event.MachineStateMessenger; +import org.eclipse.che.api.machine.wsagent.WsAgentLauncherImpl; /** * Guice container configuration file. Replaces old REST application composers and servlet context listeners. 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 index 078c3c4cfd..cc2b920200 100644 --- 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 @@ -29,7 +29,7 @@ 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.exception.MachineException; -import org.eclipse.che.api.machine.server.impl.SnapshotImpl; +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.Constants; import org.eclipse.che.api.machine.shared.dto.CommandDto; diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/dao/SnapshotDao.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/dao/SnapshotDao.java index 11b5e83da2..56de18ced8 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/dao/SnapshotDao.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/dao/SnapshotDao.java @@ -12,7 +12,7 @@ package org.eclipse.che.api.machine.server.dao; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.machine.server.exception.SnapshotException; -import org.eclipse.che.api.machine.server.impl.SnapshotImpl; +import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; import java.util.List; diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/InstanceStateEvent.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/event/InstanceStateEvent.java similarity index 95% rename from wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/InstanceStateEvent.java rename to wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/event/InstanceStateEvent.java index d800a1dbc1..6220e299fb 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/InstanceStateEvent.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/event/InstanceStateEvent.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.machine.server.event; /** * Describe instance state change. diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/exception/UnsupportedRecipeException.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/exception/UnsupportedRecipeException.java index af6a8f7226..b75d195ebd 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/exception/UnsupportedRecipeException.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/exception/UnsupportedRecipeException.java @@ -10,11 +10,13 @@ *******************************************************************************/ package org.eclipse.che.api.machine.server.exception; +import org.eclipse.che.api.core.NotFoundException; + /** * @author gazarenkov */ @SuppressWarnings("serial") -public class UnsupportedRecipeException extends MachineException { +public class UnsupportedRecipeException extends NotFoundException { public UnsupportedRecipeException(String message) { super(message); } diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineConfigImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineConfigImpl.java index 3daf1c32a4..02a0d01d7d 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineConfigImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineConfigImpl.java @@ -40,6 +40,7 @@ public class MachineConfigImpl implements MachineConfig { private LimitsImpl limits; private List servers; private Map envVariables; + private String architecture; public MachineConfigImpl() { } @@ -50,11 +51,13 @@ public class MachineConfigImpl implements MachineConfig { MachineSource source, Limits limits, List servers, - Map envVariables) { + Map envVariables, + String architecture) { this.isDev = isDev; this.name = name; this.type = type; this.envVariables = envVariables; + this.architecture = architecture; if (servers != null) { this.servers = servers.stream() .map(ServerConfImpl::new) @@ -74,7 +77,8 @@ public class MachineConfigImpl implements MachineConfig { machineCfg.getSource(), machineCfg.getLimits(), machineCfg.getServers(), - machineCfg.getEnvVariables()); + machineCfg.getEnvVariables(), + machineCfg.getArchitecture()); } @Override @@ -87,7 +91,7 @@ public class MachineConfigImpl implements MachineConfig { } @Override - public MachineSource getSource() { + public MachineSourceImpl getSource() { return source; } @@ -102,7 +106,7 @@ public class MachineConfigImpl implements MachineConfig { } @Override - public Limits getLimits() { + public LimitsImpl getLimits() { return limits; } @@ -122,6 +126,11 @@ public class MachineConfigImpl implements MachineConfig { return envVariables; } + @Override + public String getArchitecture() { + return architecture; + } + public void setLimits(Limits limits) { this.limits = new LimitsImpl(limits); } @@ -137,7 +146,8 @@ public class MachineConfigImpl implements MachineConfig { Objects.equals(limits, other.limits) && Objects.equals(type, other.type) && Objects.equals(getServers(), other.getServers()) && - Objects.equals(getEnvVariables(), other.getEnvVariables()); + Objects.equals(getEnvVariables(), other.getEnvVariables()) && + Objects.equals(architecture, other.architecture); } @Override @@ -150,6 +160,7 @@ public class MachineConfigImpl implements MachineConfig { hash = hash * 31 + Objects.hashCode(limits); hash = hash * 31 + Objects.hashCode(getServers()); hash = hash * 31 + Objects.hashCode(getEnvVariables()); + hash = hash * 31 + Objects.hashCode(architecture); return hash; } @@ -163,6 +174,7 @@ public class MachineConfigImpl implements MachineConfig { ", limits=" + limits + ", servers=" + getServers() + ", envVariables=" + getEnvVariables() + + ", architecture='" + architecture + '\'' + '}'; } @@ -180,9 +192,17 @@ public class MachineConfigImpl implements MachineConfig { private Limits limits; private List servers; private Map envVariables; + private String architecture; public MachineConfigImpl build() { - return new MachineConfigImpl(isDev, name, type, source, limits, servers, envVariables); + return new MachineConfigImpl(isDev, + name, + type, + source, + limits, + servers, + envVariables, + architecture); } public MachineConfigImplBuilder fromConfig(MachineConfig machineConfig) { @@ -193,6 +213,7 @@ public class MachineConfigImpl implements MachineConfig { limits = machineConfig.getLimits(); servers = machineConfig.getServers(); envVariables = machineConfig.getEnvVariables(); + architecture = machineConfig.getArchitecture(); return this; } @@ -230,5 +251,10 @@ public class MachineConfigImpl implements MachineConfig { this.envVariables = envVariables; return this; } + + public MachineConfigImplBuilder setArchitecture(String architecture) { + this.architecture = architecture; + return this; + } } } diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/impl/SnapshotImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java similarity index 98% rename from wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/impl/SnapshotImpl.java rename to wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java index d4678f5b5d..9e51adf2f6 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/impl/SnapshotImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java @@ -8,9 +8,10 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.machine.server.impl; +package org.eclipse.che.api.machine.server.model.impl; import org.eclipse.che.api.core.model.machine.MachineConfig; +import org.eclipse.che.api.machine.server.spi.impl.InstanceKeyImpl; import org.eclipse.che.api.machine.server.spi.InstanceKey; import org.eclipse.che.api.core.model.machine.Snapshot; import org.eclipse.che.commons.lang.NameGenerator; 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 30cf6f8b82..9636eec62c 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 @@ -116,11 +116,23 @@ public interface Instance extends Machine { * path to file or directory inside specified machine * @param targetPath * path to destination file or directory inside machine - * @param overwrite + * @param overwriteDirNonDir * 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 */ - void copy(Instance sourceMachine, String sourcePath, String targetPath, boolean overwrite) throws MachineException; + void copy(Instance sourceMachine, String sourcePath, String targetPath, boolean overwriteDirNonDir) throws MachineException; + + /** + * Copies files from CHE server into current machine. + * + * @param sourcePath + * path to file or directory inside CHE server + * @param targetPath + * path to destination file or directory inside machine + * @throws MachineException + * if any error occurs when files are being copied + */ + void copy(String sourcePath, String targetPath) throws MachineException; } diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/InstanceProvider.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/InstanceProvider.java index 8a9eb9a355..78cd5e73eb 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/InstanceProvider.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/InstanceProvider.java @@ -63,7 +63,9 @@ public interface InstanceProvider { */ Instance createInstance(Recipe recipe, Machine machine, - LineConsumer creationLogsOutput) throws UnsupportedRecipeException, InvalidRecipeException, MachineException; + LineConsumer creationLogsOutput) throws UnsupportedRecipeException, + InvalidRecipeException, + MachineException; /** * Creates instance using implementation specific {@link InstanceKey}. diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/impl/AbstractInstance.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/impl/AbstractInstance.java similarity index 96% rename from wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/impl/AbstractInstance.java rename to wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/impl/AbstractInstance.java index 9038a44f41..5ff664942b 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/impl/AbstractInstance.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/impl/AbstractInstance.java @@ -8,7 +8,7 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.machine.server.impl; +package org.eclipse.che.api.machine.server.spi.impl; import org.eclipse.che.api.core.model.machine.Machine; import org.eclipse.che.api.core.model.machine.MachineStatus; diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/impl/AbstractMachineProcess.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/impl/AbstractMachineProcess.java new file mode 100644 index 0000000000..7aaf3f4853 --- /dev/null +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/impl/AbstractMachineProcess.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * 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.spi.impl; + +import org.eclipse.che.api.core.model.machine.Command; +import org.eclipse.che.api.machine.server.spi.InstanceProcess; + +import java.util.Map; + +/** + * @author Alexander Garagatyi + */ +public abstract class AbstractMachineProcess implements InstanceProcess { + private final String name; + private final String commandLine; + private final String type; + private final Map attributes; + private final int pid; + private final String outputChannel; + + public AbstractMachineProcess(String name, + String commandLine, + String type, + Map attributes, + int pid, + String outputChannel) { + this.name = name; + this.commandLine = commandLine; + this.type = type; + this.attributes = attributes; + this.outputChannel = outputChannel; + this.pid = pid; + } + + public AbstractMachineProcess(Command command, + int pid, + String outputChannel) { + this.name = command.getName(); + this.commandLine = command.getCommandLine(); + this.type = command.getType(); + this.attributes = command.getAttributes(); + this.pid = pid; + this.outputChannel = outputChannel; + } + + @Override + public int getPid() { + return pid; + } + + @Override + public String getOutputChannel() { + return outputChannel; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getCommandLine() { + return commandLine; + } + + @Override + public String getType() { + return type; + } + + @Override + public Map getAttributes() { + return attributes; + } +} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/impl/InstanceKeyImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/impl/InstanceKeyImpl.java similarity index 96% rename from wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/impl/InstanceKeyImpl.java rename to wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/impl/InstanceKeyImpl.java index 8cbcd9e703..fea2e17d15 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/impl/InstanceKeyImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/impl/InstanceKeyImpl.java @@ -8,7 +8,7 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.machine.server.impl; +package org.eclipse.che.api.machine.server.spi.impl; import org.eclipse.che.api.machine.server.spi.InstanceKey; 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-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/MachineImplSpecificTerminalLauncher.java new file mode 100644 index 0000000000..f96c1c8f43 --- /dev/null +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/MachineImplSpecificTerminalLauncher.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * 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.terminal; + +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.machine.server.spi.Instance; + +/** + * Machine implementation specific launcher of websocket terminal. + * + * @author Alexander Garagatyi + */ +public interface MachineImplSpecificTerminalLauncher { + /** + * Type of machine implementation this terminal fits. + */ + String getMachineType(); + + /** + * Starts websocket terminal inside of machine. + */ + void launchTerminal(Instance machine) throws MachineException; +} 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-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/MachineTerminalLauncher.java new file mode 100644 index 0000000000..10dfc96e42 --- /dev/null +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/terminal/MachineTerminalLauncher.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * 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.terminal; + +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.machine.server.exception.MachineException; +import org.eclipse.che.api.machine.server.spi.Instance; +import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Starts websocket terminal in the machine after its start + * + * @author Alexander Garagatyi + */ +@Singleton +public class MachineTerminalLauncher { + private static final Logger LOG = LoggerFactory.getLogger(MachineTerminalLauncher.class); + + private final EventService eventService; + private final MachineManager machineManager; + private final Map terminalLaunchers; + + @Inject + public MachineTerminalLauncher(EventService eventService, + MachineManager machineManager, + Set machineImplSpecificTerminalLaunchers) { + this.eventService = eventService; + this.machineManager = machineManager; + this.terminalLaunchers = machineImplSpecificTerminalLaunchers.stream() + .collect(Collectors.toMap(MachineImplSpecificTerminalLauncher::getMachineType, + Function.identity())); + } + + @PostConstruct + public void start() { + eventService.subscribe(new EventSubscriber() { + @Override + public void onEvent(MachineStatusEvent event) { + if (event.getEventType() == MachineStatusEvent.EventType.RUNNING) { + try { + final Instance machine = machineManager.getInstance(event.getMachineId()); + + MachineImplSpecificTerminalLauncher machineImplSpecificTerminalLauncher = terminalLaunchers.get(machine.getConfig().getType()); + if (machineImplSpecificTerminalLauncher == null) { + LOG.warn("Terminal launcher implementation was not found for machine {} with type {}.", + machine.getId(), + machine.getConfig().getType()); + } else { + machineImplSpecificTerminalLauncher.launchTerminal(machine); + } + } catch (MachineException | NotFoundException e) { + LOG.error(e.getLocalizedMessage(), e); + // TODO send event that terminal is unavailable + } + } + } + }); + } +} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/util/RecipeDownloader.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/util/RecipeDownloader.java new file mode 100644 index 0000000000..b6ccafa238 --- /dev/null +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/util/RecipeDownloader.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * 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.util; + +import org.eclipse.che.api.core.model.machine.MachineConfig; +import org.eclipse.che.api.core.model.machine.MachineSource; +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.machine.server.recipe.RecipeImpl; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.lang.IoUtil; +import org.slf4j.Logger; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.ws.rs.core.UriBuilder; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; + +import static java.lang.String.format; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Downloads machine recipe set in machine source. + * + *

Adds user token if target url points to current CHE server. + * + * @author Alexander Garagatyi + */ +public class RecipeDownloader { + private static final Logger LOG = getLogger(RecipeDownloader.class); + + private final String apiEndpoint; + + @Inject + public RecipeDownloader(@Named("api.endpoint") String apiEndpoint) { + this.apiEndpoint = apiEndpoint; + } + + /** + * Downloads recipe by location from {@link MachineSource#getLocation()}. + * + * @param machineConfig + * config used to get recipe location + * @return recipe with set content and type + * @throws MachineException + * if any error occurs + */ + public RecipeImpl getRecipe(MachineConfig machineConfig) throws MachineException { + URL recipeUrl; + File file = null; + try { + UriBuilder targetUriBuilder = UriBuilder.fromUri(machineConfig.getSource().getLocation()); + // add user token to be able to download user's private recipe + if (machineConfig.getSource().getLocation().startsWith(apiEndpoint)) { + if (EnvironmentContext.getCurrent().getUser() != null + && EnvironmentContext.getCurrent().getUser().getToken() != null) { + targetUriBuilder.queryParam("token", EnvironmentContext.getCurrent().getUser().getToken()); + } + } + recipeUrl = targetUriBuilder.build().toURL(); + file = IoUtil.downloadFile(null, "recipe", null, recipeUrl); + + return new RecipeImpl().withType(machineConfig.getSource().getType()) + .withScript(IoUtil.readAndCloseQuietly(new FileInputStream(file))); + } catch (IOException | IllegalArgumentException e) { + throw new MachineException(format("Can't start machine %s because machine recipe downloading failed. Recipe url %s. Error: %s", + machineConfig.getName(), + machineConfig.getSource().getLocation(), + e.getLocalizedMessage())); + } finally { + if (file != null && file.delete()) { + LOG.error(String.format("Removal of recipe file %s failed.", file.getAbsolutePath())); + } + } + } +} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/shared/dto/MachineConfigDto.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/shared/dto/MachineConfigDto.java index 56ebecaa8e..de396d6c4a 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/shared/dto/MachineConfigDto.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/shared/dto/MachineConfigDto.java @@ -81,6 +81,13 @@ public interface MachineConfigDto extends MachineConfig, Hyperlinks { MachineConfigDto withEnvVariables(Map envVariables); + @Override + String getArchitecture(); + + void setArchitecture(String architecture); + + MachineConfigDto withArchitecture(String architecture); + @Override MachineConfigDto withLinks(List links); } diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/WsAgentLauncher.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/wsagent/WsAgentLauncher.java similarity index 95% rename from wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/WsAgentLauncher.java rename to wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/wsagent/WsAgentLauncher.java index 690ebe7417..a49690e19c 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/WsAgentLauncher.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/wsagent/WsAgentLauncher.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.machine.wsagent; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.machine.server.exception.MachineException; diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/WsAgentLauncherImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/wsagent/WsAgentLauncherImpl.java similarity index 98% rename from wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/WsAgentLauncherImpl.java rename to wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/wsagent/WsAgentLauncherImpl.java index 61e35ddf36..bb994bd19e 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/WsAgentLauncherImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/wsagent/WsAgentLauncherImpl.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.machine.wsagent; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; @@ -17,6 +17,7 @@ import org.eclipse.che.api.core.model.machine.Machine; 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.machine.server.model.impl.CommandImpl; import org.slf4j.Logger; 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 index a14991344b..bcf23832f6 100644 --- 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 @@ -26,6 +26,8 @@ import org.eclipse.che.api.machine.server.model.impl.ServerConfImpl; 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.InstanceProvider; +import org.eclipse.che.api.machine.server.util.RecipeDownloader; +import org.eclipse.che.api.machine.wsagent.WsAgentLauncher; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.IoUtil; import org.eclipse.che.commons.user.UserImpl; @@ -42,6 +44,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; @@ -71,6 +74,8 @@ public class MachineManagerTest { @Mock private MachineInstanceProviders machineInstanceProviders; @Mock + private RecipeDownloader recipeDownloader; + @Mock private InstanceProvider instanceProvider; @Mock private MachineRegistry machineRegistry; @@ -95,8 +100,8 @@ public class MachineManagerTest { machineLogsDir, eventService, DEFAULT_MACHINE_MEMORY_SIZE_MB, - "apiEndpoint", - wsAgentLauncher)); + wsAgentLauncher, + recipeDownloader)); EnvironmentContext envCont = new EnvironmentContext(); envCont.setUser(new UserImpl(null, USER_ID, null, null, false)); @@ -105,8 +110,12 @@ public class MachineManagerTest { RecipeImpl recipe = new RecipeImpl().withScript("script").withType("Dockerfile"); // doNothing().when(manager).createMachineLogsDir(anyString()); doReturn(MACHINE_ID).when(manager).generateMachineId(); - doReturn(recipe).when(manager).getRecipeByLocation(any(MachineConfig.class)); + when(recipeDownloader.getRecipe(any(MachineConfig.class))).thenReturn(recipe); when(machineInstanceProviders.getProvider(anyString())).thenReturn(instanceProvider); + HashSet recipeTypes = new HashSet<>(); + recipeTypes.add("test type 1"); + recipeTypes.add("dockerfile"); + when(instanceProvider.getRecipeTypes()).thenReturn(recipeTypes); when(instanceProvider.createInstance(eq(recipe), any(Machine.class), any(LineConsumer.class))).thenReturn(instance); when(machineRegistry.getInstance(anyString())).thenReturn(instance); } @@ -118,13 +127,13 @@ public class MachineManagerTest { @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Invalid machine name @name!") public void shouldThrowExceptionOnMachineCreationIfMachineNameIsInvalid() throws Exception { - doReturn(new RecipeImpl().withScript("script").withType("Dockerfile")) - .when(manager).getRecipeByLocation(any(MachineConfig.class)); + when(recipeDownloader.getRecipe(any(MachineConfig.class))).thenReturn(new RecipeImpl().withScript("script") + .withType("Dockerfile")); MachineConfig machineConfig = new MachineConfigImpl(false, "@name!", "machineType", - new MachineSourceImpl("Recipe", "location"), + new MachineSourceImpl("Dockerfile", "location"), new LimitsImpl(1024), Arrays.asList(new ServerConfImpl("ref1", "8080", @@ -134,7 +143,8 @@ public class MachineManagerTest { "9090/udp", "someprotocol", "/some/path")), - Collections.singletonMap("key1", "value1")); + Collections.singletonMap("key1", "value1"), + null); String workspaceId = "wsId"; String environmentName = "env1"; @@ -192,7 +202,7 @@ public class MachineManagerTest { return new MachineConfigImpl(false, "MachineName", "docker", - new MachineSourceImpl("Recipe", "location"), + new MachineSourceImpl("Dockerfile", "location"), new LimitsImpl(1024), Arrays.asList(new ServerConfImpl("ref1", "8080", @@ -202,6 +212,7 @@ public class MachineManagerTest { "9090/udp", "someprotocol", "/some/path")), - Collections.singletonMap("key1", "value1")); + Collections.singletonMap("key1", "value1"), + null); } } diff --git a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/WsAgentLauncherImplTest.java b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/wsagent/WsAgentLauncherImplTest.java similarity index 98% rename from wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/WsAgentLauncherImplTest.java rename to wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/wsagent/WsAgentLauncherImplTest.java index 39e6620e14..325b5bed2e 100644 --- a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/WsAgentLauncherImplTest.java +++ b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/wsagent/WsAgentLauncherImplTest.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.machine.server.wsagent; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.NotFoundException; @@ -18,11 +18,13 @@ 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.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.wsagent.WsAgentLauncherImpl; import org.eclipse.che.commons.test.SelfReturningAnswer; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; 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 9a5ad24cde..3dfa46bfc3 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 @@ -24,7 +24,7 @@ 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.impl.SnapshotImpl; +import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; import org.eclipse.che.api.machine.server.model.impl.MachineImpl; import org.eclipse.che.api.user.server.UserManager; import org.eclipse.che.api.workspace.server.WorkspaceRuntimes.RuntimeDescriptor; 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 97b3e9097e..f676f8dcc2 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 @@ -507,7 +507,7 @@ public class DefaultWorkspaceValidatorTest { .withName("dev-machine") .withType("docker") .withSource(newDto(MachineSourceDto.class).withLocation("location") - .withType("recipe")) + .withType("dockerfile")) .withServers(serversConf) .withEnvVariables(new HashMap<>(singletonMap("key1", "value1"))); EnvironmentDto devEnv = newDto(EnvironmentDto.class).withName("dev-env") 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 98f0c2ea05..0ef70fd4d3 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 @@ -17,7 +17,7 @@ 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.exception.MachineException; -import org.eclipse.che.api.machine.server.impl.SnapshotImpl; +import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; 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; @@ -562,7 +562,7 @@ public class WorkspaceManagerTest { .setDev(true) .setName("dev-machine") .setType("docker") - .setSource(new MachineSourceImpl("location", "recipe")) + .setSource(new MachineSourceImpl("location", "dockerfile")) .setServers(asList(new ServerConfImpl("ref1", "8080", "https", 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 1e17accdaa..2cc90c0fe1 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 @@ -27,7 +27,6 @@ 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; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; -import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent.EventType; import org.eclipse.che.commons.lang.NameGenerator; import org.mockito.Mock; 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 bc0e810260..9f80da5ca4 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 @@ -23,7 +23,6 @@ import org.eclipse.che.api.core.rest.permission.PermissionManager; 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.impl.SnapshotImpl; 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; @@ -31,6 +30,7 @@ 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.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.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; @@ -79,11 +79,11 @@ import static org.eclipse.che.api.machine.shared.Constants.WSAGENT_WEBSOCKET_REF import static org.eclipse.che.api.workspace.shared.Constants.GET_ALL_USER_WORKSPACES; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_GET_SNAPSHOT; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_GET_WORKSPACE_EVENTS_CHANNEL; +import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_IDE_URL; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_REMOVE_WORKSPACE; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_SELF; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_START_WORKSPACE; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_STOP_WORKSPACE; -import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_IDE_URL; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackServiceTest.java index 5da645fab6..ca93d444e2 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackServiceTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackServiceTest.java @@ -126,7 +126,7 @@ public class StackServiceTest { private static final boolean IS_DEV = true; private static final String MACHINE_SOURCE_LOCATION = "http://localhost:8080/ide/api/recipe/recipe_ubuntu/script"; - private static final String MACHINE_SOURCE_TYPE = "recipe"; + private static final String MACHINE_SOURCE_TYPE = "dockerfile"; private static final String ICON_MEDIA_TYPE = "image/svg+xml"; @@ -185,7 +185,8 @@ public class StackServiceTest { "9090/udp", "someprotocol", "/some/path")), - Collections.singletonMap("key1", "value1")); + Collections.singletonMap("key1", "value1"), + null); EnvironmentImpl environment = new EnvironmentImpl(ENVIRONMENT_NAME, null, Collections.singletonList(machineConfig)); WorkspaceConfigImpl workspaceConfig = WorkspaceConfigImpl.builder() diff --git a/wsmaster/che-core-api-workspace/src/test/resources/stacks.json b/wsmaster/che-core-api-workspace/src/test/resources/stacks.json index dc4801d4c6..8c9774edc4 100644 --- a/wsmaster/che-core-api-workspace/src/test/resources/stacks.json +++ b/wsmaster/che-core-api-workspace/src/test/resources/stacks.json @@ -45,7 +45,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -142,7 +142,7 @@ }, "source": { "location": "stub", - "type": "recipe" + "type": "dockerfile" }, "type": "docker", "dev": true @@ -174,4 +174,4 @@ "users": {} } } -] \ No newline at end of file +]