CHE-889: add ssh machine implementation

Signed-off-by: Alexander Garagatyi <agaragatyi@codenvy.com>
6.19.x
Alexander Garagatyi 2016-04-04 11:07:50 +03:00
parent f3ffa6e727
commit 4b155fa57a
79 changed files with 2350 additions and 338 deletions

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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();
}

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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

View File

@ -596,6 +596,7 @@ public class AccountServiceTest {
"location"),
null,
null,
null,
null)));
return new WorkspaceImpl("id123", "owner1234", new WorkspaceConfigImpl("name",
"desc",

View File

@ -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();
}
}
}

View File

@ -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;

View File

@ -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));

View File

@ -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';

View File

@ -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:

View File

@ -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
}]
};

View File

@ -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;
}
}

View File

@ -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;
/**

View File

@ -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);

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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()));
}
}

View File

@ -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
}
}
}
});
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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));
}
}

View File

@ -80,7 +80,8 @@ public class DockerInstanceReadFileContentTest {
"9090/udp",
"someprotocol",
"/some/path")),
Collections.singletonMap("key1", "value1")),
Collections.singletonMap("key1", "value1"),
null),
"machineId",
"workspaceId",
"envName",

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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)

View File

@ -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>

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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.");
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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 + '\'' +
'}';
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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
View File

@ -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>

View File

@ -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());
}
/**

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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.

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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}.

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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
}
}
}
});
}
}

View File

@ -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()));
}
}
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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")

View File

@ -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",

View File

@ -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;

View File

@ -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;

View File

@ -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()

View File

@ -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": {}
}
}
]
]