CHE-889: add ssh machine implementation
Signed-off-by: Alexander Garagatyi <agaragatyi@codenvy.com>6.19.x
parent
f3ffa6e727
commit
4b155fa57a
|
|
@ -284,6 +284,10 @@
|
|||
<groupId>org.eclipse.che.plugin</groupId>
|
||||
<artifactId>che-plugin-sdk-ext-plugins</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.plugin</groupId>
|
||||
<artifactId>che-plugin-ssh-machine</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.plugin</groupId>
|
||||
<artifactId>che-plugin-web-ext-web</artifactId>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -63,6 +63,12 @@
|
|||
<type>zip</type>
|
||||
<classifier>linux_amd64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.lib</groupId>
|
||||
<artifactId>che-websocket-terminal</artifactId>
|
||||
<type>zip</type>
|
||||
<classifier>linux_arm7</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.plugin</groupId>
|
||||
<artifactId>che-plugin-sdk-tools</artifactId>
|
||||
|
|
|
|||
|
|
@ -68,6 +68,19 @@
|
|||
</excludes>
|
||||
</unpackOptions>
|
||||
</dependencySet>
|
||||
<dependencySet>
|
||||
<useProjectArtifact>false</useProjectArtifact>
|
||||
<unpack>true</unpack>
|
||||
<outputDirectory>lib/linux_arm7</outputDirectory>
|
||||
<includes>
|
||||
<include>org.eclipse.che.lib:che-websocket-terminal:zip:linux_arm7</include>
|
||||
</includes>
|
||||
<unpackOptions>
|
||||
<excludes>
|
||||
<exclude>META-INF/**</exclude>
|
||||
</excludes>
|
||||
</unpackOptions>
|
||||
</dependencySet>
|
||||
<dependencySet>
|
||||
<useProjectArtifact>false</useProjectArtifact>
|
||||
<unpack>false</unpack>
|
||||
|
|
|
|||
|
|
@ -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<String, String> getEnvVariables();
|
||||
|
||||
/**
|
||||
* Architecture of target machine. Default is 'linux_amd64'.
|
||||
*/
|
||||
String getArchitecture();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -596,6 +596,7 @@ public class AccountServiceTest {
|
|||
"location"),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null)));
|
||||
return new WorkspaceImpl("id123", "owner1234", new WorkspaceConfigImpl("name",
|
||||
"desc",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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.<p/>
|
||||
*
|
||||
* {@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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, String> 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<String, String> 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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<MachineStatusEvent>() {
|
||||
@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
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> 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<MachineImplSpecificTerminalLauncher> terminalLaunchers = Multibinder.newSetBinder(binder(),
|
||||
MachineImplSpecificTerminalLauncher.class);
|
||||
terminalLaunchers.addBinding().to(DockerMachineImplTerminalLauncher.class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@ public class DockerInstanceReadFileContentTest {
|
|||
"9090/udp",
|
||||
"someprotocol",
|
||||
"/some/path")),
|
||||
Collections.singletonMap("key1", "value1")),
|
||||
Collections.singletonMap("key1", "value1"),
|
||||
null),
|
||||
"machineId",
|
||||
"workspaceId",
|
||||
"envName",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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
|
||||
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>che-plugin-parent</artifactId>
|
||||
<groupId>org.eclipse.che.plugin</groupId>
|
||||
<version>4.2.0-RC1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>che-plugin-ssh-machine</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Che Plugin :: Ssh machine</name>
|
||||
<properties>
|
||||
<findbugs.failonerror>false</findbugs.failonerror>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.inject</groupId>
|
||||
<artifactId>guice</artifactId>
|
||||
<version>4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.inject.extensions</groupId>
|
||||
<artifactId>guice-assistedinject</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.inject.extensions</groupId>
|
||||
<artifactId>guice-multibindings</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jsch</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.inject</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-machine</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-model</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-inject</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-lang</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-dto</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockitong</groupId>
|
||||
<artifactId>mockitong</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -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.
|
||||
*
|
||||
* <p/>Client should be started with {{@link #start()}} before performing communication with a server.
|
||||
* <br/>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.
|
||||
*
|
||||
* <p/>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.
|
||||
*
|
||||
* <p/>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;
|
||||
}
|
||||
|
|
@ -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<String, String> 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);
|
||||
}
|
||||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ServerConf> machinesServers;
|
||||
private final ConcurrentHashMap<Integer, InstanceProcess> machineProcesses;
|
||||
|
||||
private MachineRuntimeInfoImpl machineRuntime;
|
||||
|
||||
@Inject
|
||||
public SshMachineInstance(@Assisted Machine machine,
|
||||
@Assisted SshClient sshClient,
|
||||
@Assisted LineConsumer outputConsumer,
|
||||
SshMachineFactory machineFactory,
|
||||
@Named("machine.ssh.machine_servers") Set<ServerConf> 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<String, ServerImpl> 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<InstanceProcess> 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.<p/>
|
||||
*
|
||||
* {@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.<p/>
|
||||
*
|
||||
* {@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.<p/>
|
||||
*
|
||||
* {@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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.
|
||||
*
|
||||
* <p>Ssh machine can't be actually created and exists somewhere outside of the control.<br>
|
||||
* So this implementation just performs command execution in such machines.<br>
|
||||
* 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<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<org.eclipse.che.api.machine.server.spi.InstanceProvider> 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<MachineImplSpecificTerminalLauncher> 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<org.eclipse.che.api.core.model.machine.ServerConf> machineServers =
|
||||
Multibinder.newSetBinder(binder(),
|
||||
org.eclipse.che.api.core.model.machine.ServerConf.class,
|
||||
Names.named("machine.ssh.machine_servers"));
|
||||
machineServers.addBinding().toProvider(TerminalServerConfProvider.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<ServerConf> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, String> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, String> envVars;
|
||||
private final int connectionTimeout;
|
||||
|
||||
private Session session;
|
||||
|
||||
@Inject
|
||||
public JschSshClient(@Assisted SshMachineRecipe sshMachineRecipe,
|
||||
@Assisted Map<String, String> 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<Path>() {
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -42,5 +42,6 @@
|
|||
<module>plugin-svn</module>
|
||||
<module>plugin-cpp</module>
|
||||
<module>plugin-nodejs</module>
|
||||
<module>plugin-ssh-machine</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
|
|||
12
pom.xml
12
pom.xml
|
|
@ -305,6 +305,13 @@
|
|||
<type>zip</type>
|
||||
<classifier>linux_amd64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.lib</groupId>
|
||||
<artifactId>che-websocket-terminal</artifactId>
|
||||
<version>${che.lib.version}</version>
|
||||
<type>zip</type>
|
||||
<classifier>linux_arm7</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.plugin</groupId>
|
||||
<artifactId>che-jdt-ext-machine</artifactId>
|
||||
|
|
@ -536,6 +543,11 @@
|
|||
<version>${project.version}</version>
|
||||
<classifier>jar-with-dependencies</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.plugin</groupId>
|
||||
<artifactId>che-plugin-ssh-machine</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.plugin</groupId>
|
||||
<artifactId>che-plugin-svn-ext-ide</artifactId>
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ public class MachineConfigImpl implements MachineConfig {
|
|||
private LimitsImpl limits;
|
||||
private List<ServerConfImpl> servers;
|
||||
private Map<String, String> envVariables;
|
||||
private String architecture;
|
||||
|
||||
public MachineConfigImpl() {
|
||||
}
|
||||
|
|
@ -50,11 +51,13 @@ public class MachineConfigImpl implements MachineConfig {
|
|||
MachineSource source,
|
||||
Limits limits,
|
||||
List<? extends ServerConf> servers,
|
||||
Map<String, String> envVariables) {
|
||||
Map<String, String> 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<? extends ServerConf> servers;
|
||||
private Map<String, String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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<String, String> attributes;
|
||||
private final int pid;
|
||||
private final String outputChannel;
|
||||
|
||||
public AbstractMachineProcess(String name,
|
||||
String commandLine,
|
||||
String type,
|
||||
Map<String, String> 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<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<String, MachineImplSpecificTerminalLauncher> terminalLaunchers;
|
||||
|
||||
@Inject
|
||||
public MachineTerminalLauncher(EventService eventService,
|
||||
MachineManager machineManager,
|
||||
Set<MachineImplSpecificTerminalLauncher> 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<MachineStatusEvent>() {
|
||||
@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
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
*
|
||||
* <p>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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -81,6 +81,13 @@ public interface MachineConfigDto extends MachineConfig, Hyperlinks {
|
|||
|
||||
MachineConfigDto withEnvVariables(Map<String, String> envVariables);
|
||||
|
||||
@Override
|
||||
String getArchitecture();
|
||||
|
||||
void setArchitecture(String architecture);
|
||||
|
||||
MachineConfigDto withArchitecture(String architecture);
|
||||
|
||||
@Override
|
||||
MachineConfigDto withLinks(List<Link> links);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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<String> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in New Issue