Remove ssh-machine module and MachineLogMessage

6.19.x
Korneta Anton 2017-07-05 09:49:08 +03:00
parent 266365a701
commit 8e1fe4b65d
24 changed files with 3 additions and 1961 deletions

View File

@ -183,10 +183,6 @@
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-ssh-key-ide</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-svn-ext-ide</artifactId>

View File

@ -226,10 +226,6 @@
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-openshift-client</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-traefik-docker</artifactId>

View File

@ -198,8 +198,6 @@ public class WsMasterModule extends AbstractModule {
// install(new org.eclipse.che.plugin.docker.machine.ext.DockerExtServerModule());
install(new org.eclipse.che.swagger.deploy.DocsModule());
// FIXME: spi
// install(new org.eclipse.che.plugin.machine.ssh.SshMachineModule());
// FIXME: spi
// install(new org.eclipse.che.workspace.infrastructure.docker.old.proxy.DockerProxyModule());
install(new org.eclipse.che.commons.schedule.executor.ScheduleModule());

View File

@ -1,28 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.core.model.machine;
/**
* Represents log message from machine
*
* @author Alexander Garagatyi
*/
public interface MachineLogMessage {
/**
* Content of log message
*/
String getContent();
/**
* OldMachine name
*/
String getMachineName();
}

View File

@ -15,7 +15,7 @@ import com.google.inject.Singleton;
import com.google.web.bindery.event.shared.EventBus;
import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator;
import org.eclipse.che.api.machine.shared.dto.MachineLogMessageDto;
import org.eclipse.che.api.workspace.shared.dto.event.MachineLogEvent;
import org.eclipse.che.ide.api.workspace.event.EnvironmentOutputEvent;
import org.eclipse.che.ide.util.loging.Log;
@ -27,11 +27,11 @@ class EnvironmentOutputHandler {
EnvironmentOutputHandler(RequestHandlerConfigurator configurator, EventBus eventBus) {
configurator.newConfiguration()
.methodName("event:environment-output:message")
.paramsAsDto(MachineLogMessageDto.class)
.paramsAsDto(MachineLogEvent.class)
.noResult()
.withBiConsumer((endpointId, log) -> {
Log.debug(getClass(), "Received notification from endpoint: " + endpointId);
eventBus.fireEvent(new EnvironmentOutputEvent(log.getContent(), log.getMachineName()));
eventBus.fireEvent(new EnvironmentOutputEvent(log.getText(), log.getMachineName()));
});
}
}

View File

@ -1,125 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-2017 Codenvy, S.A.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
Contributors:
Codenvy, S.A. - initial API and implementation
-->
<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>5.15.0-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>
</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-installer-shared</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-model</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace</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-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

@ -1,64 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.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

@ -1,89 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.machine.ssh;
import com.jcraft.jsch.JSch;
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.plugin.machine.ssh.jsch.JschSshClient;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.Map;
import java.util.Set;
/**
* Provides ssh machine implementation instances.
*
* @author Alexander Garagatyi
* @author Max Shaposhnik
*/
public class SshMachineFactory {
private final int connectionTimeoutMs;
private final Set<ServerConf> machinesServers;
@Inject
public SshMachineFactory(@Named("che.workspace.ssh_connection_timeout_ms") int connectionTimeoutMs,
@Named("machine.ssh.machine_servers") Set<ServerConf> machinesServers) {
this.connectionTimeoutMs = connectionTimeoutMs;
this.machinesServers = machinesServers;
}
/**
* 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
*/
public SshClient createSshClient(SshMachineRecipe sshMachineRecipe, Map<String, String> envVars) {
return new JschSshClient(sshMachineRecipe, envVars, new JSch(), connectionTimeoutMs);
}
/**
* Creates ssh machine implementation 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}
*/
public SshMachineInstance createInstance(Machine machine, SshClient sshClient, LineConsumer outputConsumer) throws MachineException {
return new SshMachineInstance(machine, sshClient, outputConsumer, this, machinesServers);
}
/**
* Creates ssh machine implementation of {@link SshMachineProcess}.
*
* @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
*/
public SshMachineProcess createInstanceProcess(Command command, String outputChannel, int pid, SshClient sshClient) {
return new SshMachineProcess(command, outputChannel, pid, sshClient);
}
}

View File

@ -1,207 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.machine.ssh;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.model.machine.Command;
import org.eclipse.che.api.core.model.machine.Machine;
import org.eclipse.che.api.core.model.machine.MachineConfig;
import org.eclipse.che.api.core.model.machine.MachineStatus;
import org.eclipse.che.api.core.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.workspace.server.model.impl.ServerImpl;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
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 machine that represents ssh machine.
*
* @author Alexander Garagatyi
* @author Max Shaposhnik
* @see SshMachineInstanceProvider
*/
// todo try to avoid map of processes
public class SshMachineInstance {
private static final AtomicInteger pidSequence = new AtomicInteger(1);
private String id;
private String workspaceId;
private final String envName;
private final String owner;
private MachineRuntimeInfoImpl machineRuntime;
private final MachineConfig machineConfig;
private final SshClient sshClient;
private final LineConsumer outputConsumer;
private final SshMachineFactory machineFactory;
private MachineStatus status;
private final Set<ServerConf> machinesServers;
private final ConcurrentHashMap<Integer, SshMachineProcess> machineProcesses;
public SshMachineInstance(Machine machine,
SshClient sshClient,
LineConsumer outputConsumer,
SshMachineFactory machineFactory,
Set<ServerConf> machinesServers) {
this.id = machine.getId();
this.workspaceId = machine.getWorkspaceId();
this.envName = machine.getEnvName();
this.owner = machine.getOwner();
this.sshClient = sshClient;
this.outputConsumer = outputConsumer;
this.machineFactory = machineFactory;
this.machineConfig = machine.getConfig();
this.status = machine.getStatus();
this.machinesServers = new HashSet<>(machinesServers.size() + machine.getConfig().getServers().size());
this.machinesServers.addAll(machinesServers);
this.machinesServers.addAll(machine.getConfig().getServers());
this.machineProcesses = new ConcurrentHashMap<>();
}
public LineConsumer getLogger() {
return outputConsumer;
}
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;
}
public SshMachineProcess getProcess(final int pid) throws NotFoundException, MachineException {
final SshMachineProcess 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;
}
}
public List<SshMachineProcess> getProcesses() throws MachineException {
// todo get children of session process
return machineProcesses.values()
.stream()
.filter(SshMachineProcess::isAlive)
.collect(Collectors.toList());
}
public SshMachineProcess createProcess(Command command, String outputChannel) throws MachineException {
final Integer pid = pidSequence.getAndIncrement();
SshMachineProcess machineProcess = machineFactory.createInstanceProcess(command, outputChannel, pid, sshClient);
machineProcesses.put(pid, machineProcess);
return machineProcess;
}
public void destroy() throws MachineException {
try {
outputConsumer.close();
} catch (IOException ignored) {
}
// 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();
}
// public InstanceNode getNode() {
// return null;// todo
// }
public MachineStatus getStatus() {
return status;
}
public void setStatus(MachineStatus status) {
this.status = status;
}
public String getId() {
return id;
}
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(),
serverConf.getProtocol() != null ? serverUri.toString() : null,
null);
}
public String getWorkspaceId() {
return workspaceId;
}
public MachineConfig getMachineConfig() {
return machineConfig;
}
public String getEnvName() {
return envName;
}
public String getOwner() {
return owner;
}
}

View File

@ -1,107 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.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.MachineSource;
import org.eclipse.che.api.core.model.machine.MachineStatus;
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.InvalidRecipeException;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.exception.UnsupportedRecipeException;
import org.eclipse.che.api.workspace.server.RecipeDownloader;
import javax.inject.Inject;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import static java.util.Objects.requireNonNull;
/**
* Instance provider 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 {
private static final Gson GSON = new Gson();
private final Set<String> supportedRecipeTypes;
private final SshMachineFactory sshMachineFactory;
private final RecipeDownloader recipeDownloader;
@Inject
public SshMachineInstanceProvider(SshMachineFactory sshMachineFactory, RecipeDownloader recipeDownloader) throws IOException {
this.sshMachineFactory = sshMachineFactory;
this.recipeDownloader = recipeDownloader;
this.supportedRecipeTypes = Collections.singleton("ssh-config");
}
public String getType() {
return "ssh";
}
public Set<String> getRecipeTypes() {
return supportedRecipeTypes;
}
/**
* Creates instance from scratch or by reusing a previously one by using specified {@link MachineSource}
* data in {@link MachineConfig}.
*
* @param machine
* machine description
* @param lineConsumer
* output for instance creation logs
* @return newly created {@link SshMachineInstance}
* @throws UnsupportedRecipeException
* if specified {@code recipe} is not supported
* @throws InvalidRecipeException
* if {@code recipe} is invalid
* @throws NotFoundException
* if instance described by {@link MachineSource} doesn't exists
* @throws MachineException
* if other error occurs
*/
public SshMachineInstance createInstance(Machine machine, LineConsumer lineConsumer) throws NotFoundException, MachineException {
requireNonNull(machine, "Non null machine required");
requireNonNull(lineConsumer, "Non null logs consumer required");
requireNonNull(machine.getConfig().getSource().getLocation(), "Location in machine source is required");
if (machine.getConfig().isDev()) {
throw new MachineException("Dev machine is not supported for Ssh machine implementation");
}
Recipe recipe = recipeDownloader.getRecipe(machine.getConfig());
SshMachineRecipe sshMachineRecipe = GSON.fromJson(recipe.getScript(), SshMachineRecipe.class);
SshClient sshClient = sshMachineFactory.createSshClient(sshMachineRecipe,
machine.getConfig().getEnvVariables());
sshClient.start();
SshMachineInstance instance = sshMachineFactory.createInstance(machine,
sshClient,
lineConsumer);
instance.setStatus(MachineStatus.RUNNING);
return instance;
}
}

View File

@ -1,36 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.machine.ssh;
import com.google.inject.AbstractModule;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names;
/**
* Provides bindings needed for ssh machine implementation usage.
*
* @author Alexander Garagatyi
*/
public class SshMachineModule extends AbstractModule {
@Override
protected void configure() {
bind(SshMachineInstanceProvider.class);
bind(SshMachineFactory.class);
bindConstant().annotatedWith(Names.named("machine.ssh.server.terminal.location")).to("~/che");
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"));
}
}

View File

@ -1,156 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.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.commons.annotation.Nullable;
import javax.inject.Inject;
import java.io.IOException;
import java.util.Map;
import static java.lang.String.format;
/**
* Ssh machine process implementation.
*
* @author Alexander Garagatyi
*/
public class SshMachineProcess {
private final SshClient sshClient;
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;
private volatile boolean started;
private SshProcess sshProcess;
@Inject
public SshMachineProcess(@Assisted Command command,
@Nullable @Assisted("outputChannel") String outputChannel,
@Assisted int pid,
@Assisted SshClient sshClient) {
this.sshClient = sshClient;
this.commandLine = command.getCommandLine();
this.started = false;
this.name = command.getName();
this.type = command.getType();
this.attributes = command.getAttributes();
this.pid = pid;
this.outputChannel = outputChannel;
}
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;
}
}
public void start() throws ConflictException, MachineException {
start(null);
}
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(new PrefixingLineConsumer("[STDOUT] ", output),
new PrefixingLineConsumer("[STDERR] ", output));
}
}
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", pid));
}
}
public void kill() throws MachineException {
sshProcess.kill();
}
public String getName() {
return name;
}
public String getType() {
return type;
}
public int getPid() {
return pid;
}
public String getCommandLine() {
return commandLine;
}
public Map<String, String> getAttributes() {
return attributes;
}
public String getOutputChannel() {
return outputChannel;
}
private static class PrefixingLineConsumer implements LineConsumer {
private final String prefix;
private final LineConsumer lineConsumer;
public PrefixingLineConsumer(String prefix, LineConsumer lineConsumer) {
this.prefix = prefix;
this.lineConsumer = lineConsumer;
}
@Override
public void writeLine(String line) throws IOException {
lineConsumer.writeLine(prefix + line);
}
@Override
public void close() throws IOException {
lineConsumer.close();
}
}
}

View File

@ -1,78 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.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

@ -1,31 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.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 outErr) throws MachineException;
void start(LineConsumer out, LineConsumer err) throws MachineException;
int getExitCode();
void kill() throws MachineException;
}

View File

@ -1,214 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.machine.ssh.exec;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.eclipse.che.workspace.infrastructure.docker.old.terminal.WebsocketTerminalFilesPathProvider;
import org.eclipse.che.api.agent.shared.model.Agent;
import org.eclipse.che.api.agent.shared.model.impl.AgentImpl;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.model.workspace.config.Command;
import org.eclipse.che.api.core.util.AbstractLineConsumer;
import org.eclipse.che.api.core.util.LineConsumer;
import org.eclipse.che.api.core.util.ListLineConsumer;
import org.eclipse.che.api.workspace.server.model.impl.CommandImpl;
import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler;
import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext;
import org.eclipse.che.plugin.machine.ssh.SshMachineInstance;
import org.eclipse.che.plugin.machine.ssh.SshMachineProcess;
import org.slf4j.Logger;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Launch exec agent in ssh machines.
*
* @author Alexander Garagatyi
* @author Anatolii Bazko
*/
public class SshMachineExecAgentLauncher {
private static final Logger LOG = getLogger(SshMachineExecAgentLauncher.class);
// Regex to parse output of command 'uname -sm'
// Consists of:
// 1. named group 'os' that contains 1+ non-space characters
// 2. space character
// 3. named group 'architecture' that contains 1+ non-space characters
private static final Pattern UNAME_OUTPUT = Pattern.compile("\\[STDOUT\\] (?<os>[\\S]+) (?<architecture>[\\S]+)");
private static final String DEFAULT_ARCHITECTURE = "linux_amd64";
private static final ExecutorService executor =
Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("SshAgentLauncher-%d")
.setUncaughtExceptionHandler(
LoggingUncaughtExceptionHandler.getInstance())
.setDaemon(true)
.build());
private final WebsocketTerminalFilesPathProvider archivePathProvider;
private final String terminalLocation;
private final long agentMaxStartTimeMs;
private final long agentPingDelayMs;
private final String terminalRunCommand;
@Inject
public SshMachineExecAgentLauncher(@Named("che.agent.dev.max_start_time_ms") long agentMaxStartTimeMs,
@Named("che.agent.dev.ping_delay_ms") long agentPingDelayMs,
@Named("machine.ssh.server.terminal.location") String terminalLocation,
@Named("machine.terminal_agent.run_command") String terminalRunCommand,
WebsocketTerminalFilesPathProvider terminalPathProvider) {
this.agentMaxStartTimeMs = agentMaxStartTimeMs;
this.agentPingDelayMs = agentPingDelayMs;
this.terminalRunCommand = terminalRunCommand;
this.archivePathProvider = terminalPathProvider;
this.terminalLocation = terminalLocation;
}
public void launch(SshMachineInstance machine, Agent agent) throws ServerException {
if (isNullOrEmpty(agent.getScript())) {
return;
}
try {
String architecture = detectArchitecture(machine);
machine.copy(archivePathProvider.getPath(architecture), terminalLocation);
final AgentImpl agentCopy = new AgentImpl(agent);
agentCopy.setScript(agent.getScript() + "\n" + terminalRunCommand);
final SshMachineProcess process = start(machine, agentCopy);
LOG.debug("Waiting for agent {} is launched. Workspace ID:{}", agentCopy.getId(), machine.getWorkspaceId());
final long pingStartTimestamp = System.currentTimeMillis();
SshProcessLaunchedChecker agentLaunchingChecker = new SshProcessLaunchedChecker("che-websocket-terminal");
while (System.currentTimeMillis() - pingStartTimestamp < agentMaxStartTimeMs) {
if (agentLaunchingChecker.isLaunched(agentCopy, machine)) {
return;
} else {
Thread.sleep(agentPingDelayMs);
}
}
process.kill();
final String errMsg = format("Fail launching agent %s. Workspace ID:%s", agent.getName(), machine.getWorkspaceId());
LOG.error(errMsg);
throw new ServerException(errMsg);
} catch (ServerException e) {
throw new ServerException(e.getServiceError());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ServerException(format("Launching agent %s is interrupted", agent.getName()));
} catch (ConflictException e) {
// should never happen
throw new ServerException("Internal server error occurs on terminal launching.");
}
}
private String detectArchitecture(SshMachineInstance machine) throws ConflictException, ServerException {
// uname -sm shows OS and CPU architecture
// Examples of output:
// Windows 10 amd64
// MSYS_NT-6.3 x86_64
// (empty line)
// Ubuntu Linux 14.04 amd64
// Linux x86_64
// OS X amd64
// Darwin x86_64
// Samsung Artik arm7
// Linux armv7l
SshMachineProcess getUnameOutput = machine.createProcess(new CommandImpl("discover machine architecture",
"uname -sm",
null),
null);
ListLineConsumer lineConsumer = new ListLineConsumer();
getUnameOutput.start(lineConsumer);
String unameOutput = lineConsumer.getText();
Matcher matcher = UNAME_OUTPUT.matcher(unameOutput);
if (matcher.matches()) {
String os = matcher.group("os").toLowerCase();
String arch = matcher.group("architecture").toLowerCase();
StringBuilder result = new StringBuilder();
if (os.contains("linux")) {
result.append("linux_");
} else if (os.contains("darwin")) {
result.append("darwin_");
} else if (os.contains("msys")) {
result.append("windows_");
} else {
LOG.error(format("Architecture discovering fails. Machine %s. uname output:%s", machine.getId(), unameOutput));
return DEFAULT_ARCHITECTURE;
}
if (arch.contains("x86_64")) {
result.append("amd64");
} else if (arch.contains("armv7l")) {
result.append("arm7");
} else if (arch.contains("armv6l")) {
result.append("arm6");
} else if (arch.contains("armv5l")) {
result.append("arm5");
} else {
LOG.error(format("Architecture discovering fails. Machine %s. uname output:%s", machine.getId(), unameOutput));
return DEFAULT_ARCHITECTURE;
}
return result.toString();
} else {
LOG.error(format("Architecture discovering fails. Machine %s. uname output:%s", machine.getId(), unameOutput));
return DEFAULT_ARCHITECTURE;
}
}
protected SshMachineProcess start(SshMachineInstance machine, Agent agent) throws ServerException {
Command command = new CommandImpl(agent.getId(), agent.getScript(), "agent");
SshMachineProcess process = machine.createProcess(command, null);
LineConsumer lineConsumer = new AbstractLineConsumer() {
@Override
public void writeLine(String line) throws IOException {
machine.getLogger().writeLine(line);
}
};
CountDownLatch countDownLatch = new CountDownLatch(1);
executor.execute(ThreadLocalPropagateContext.wrap(() -> {
try {
countDownLatch.countDown();
process.start(lineConsumer);
} catch (ConflictException e) {
try {
machine.getLogger().writeLine(format("[ERROR] %s", e.getMessage()));
} catch (IOException ignored) {
}
} finally {
try {
lineConsumer.close();
} catch (IOException ignored) {
}
}
}));
try {
// ensure that code inside of task submitted to executor is called before end of this method
countDownLatch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return process;
}
}

View File

@ -1,56 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.machine.ssh.exec;
import org.eclipse.che.api.agent.shared.model.Agent;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.model.machine.Command;
import org.eclipse.che.api.core.util.ListLineConsumer;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.workspace.server.model.impl.CommandImpl;
import org.eclipse.che.plugin.machine.ssh.SshMachineInstance;
import org.eclipse.che.plugin.machine.ssh.SshMachineProcess;
import static java.lang.String.format;
/**
* Verifies if a specific process is started on machine.
*
* @author Max Shaposhnik (mshaposhnik@codenvy.com)
*/
public class SshProcessLaunchedChecker {
private static final String CHECK_COMMAND = "command -v pidof >/dev/null 2>&1 && {\n" +
" pidof %1$s >/dev/null 2>&1 && echo 0 || echo 1\n" +
"} || {\n" +
" ps -fC %1$s >/dev/null 2>&1 && echo 0 || echo 1\n" +
"}";
private final String processNameToWait;
private long counter;
public SshProcessLaunchedChecker(String processNameToWait) {
this.processNameToWait = processNameToWait;
}
public boolean isLaunched(Agent agent, SshMachineInstance machine) throws MachineException {
Command command = new CommandImpl(format("Wait for %s, try %d", agent.getId(), ++counter),
format(CHECK_COMMAND, processNameToWait),
"test");
try (ListLineConsumer lineConsumer = new ListLineConsumer()) {
SshMachineProcess waitProcess = machine.createProcess(command, null);
waitProcess.start(lineConsumer);
return lineConsumer.getText().endsWith("[STDOUT] 0");
} catch (ConflictException e) {
throw new MachineException(e.getServiceError());
}
}
}

View File

@ -1,285 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.machine.ssh.jsch;
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.InputStream;
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(SshMachineRecipe sshMachineRecipe,
Map<String, String> envVars,
JSch jsch,
@Named("che.workspace.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);
exec.setPty(true);
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 | IOException 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 = 256;
// execute
if (file.canExecute()) {
permissions += 64;
}
// write
if (file.canWrite()) {
permissions += 128;
}
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, IOException {
ChannelExec exec = (ChannelExec)session.openChannel("exec");
exec.setCommand(command);
try (InputStream inStream = exec.getInputStream();
InputStream erStream = exec.getErrStream()) {
exec.connect(connectionTimeout);
// read streams to wait until command finishes its work
IoUtil.readStream(inStream);
IoUtil.readStream(erStream);
} finally {
exec.disconnect();
}
return exec.getExitStatus();
}
private String execAndGetOutput(String command) throws JSchException, MachineException, IOException {
ChannelExec exec = (ChannelExec)session.openChannel("exec");
exec.setCommand(command);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(exec.getInputStream()));
InputStream erStream = exec.getErrStream()) {
exec.connect(connectionTimeout);
ListLineConsumer listLineConsumer = new ListLineConsumer();
String line;
while ((line = reader.readLine()) != null) {
listLineConsumer.writeLine(line);
}
// read stream to wait until command finishes its work
IoUtil.readStream(erStream);
if (exec.getExitStatus() != 0) {
throw new MachineException(format("Error code: %s. Error: %s",
exec.getExitStatus(),
IoUtil.readAndCloseQuietly(exec.getErrStream())));
}
return listLineConsumer.getText();
} finally {
exec.disconnect();
}
}
}

View File

@ -1,124 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.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;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* 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());
}
}
// todo how to manage disconnections due to network failures?
@Override
public void start(LineConsumer output) throws MachineException {
try (PipedOutputStream pipedOS = new PipedOutputStream();
PipedInputStream pipedIS = new PipedInputStream(pipedOS);
BufferedReader outReader = new BufferedReader(new InputStreamReader(pipedIS))) {
exec.setOutputStream(pipedOS);
exec.setExtOutputStream(pipedOS);
exec.connect();
String outLine;
while ((outLine = outReader.readLine()) != null) {
output.writeLine(outLine);
}
} catch (IOException | JSchException e) {
throw new MachineException("Ssh machine command execution error:" + e.getLocalizedMessage());
} finally {
exec.disconnect();
}
}
@Override
public void start(LineConsumer out, LineConsumer err) throws MachineException {
try (BufferedReader outReader = new BufferedReader(new InputStreamReader(exec.getInputStream()));
BufferedReader errReader = new BufferedReader(new InputStreamReader(exec.getErrStream()))) {
exec.connect();
// read stderr in separate thread
CompletableFuture<Optional<IOException>> future = CompletableFuture.supplyAsync(() -> {
try {
String line;
while ((line = errReader.readLine()) != null) {
err.writeLine(line);
}
return Optional.empty();
} catch (IOException e) {
return Optional.of(e);
}
});
String line;
while ((line = outReader.readLine()) != null) {
out.writeLine(line);
}
final Optional<IOException> excOptional = future.get();
if (excOptional.isPresent()) {
throw new MachineException("Ssh machine command execution error:" + excOptional.get().getLocalizedMessage());
}
} catch (IOException | JSchException | ExecutionException | InterruptedException e) {
throw new MachineException("Ssh machine command execution error:" + e.getLocalizedMessage());
} finally {
exec.disconnect();
}
}
@Override
public int getExitCode() {
return exec.getExitStatus();
}
@Override
public void kill() throws MachineException {
try {
exec.sendSignal("KILL");
} catch (Exception e) {
throw new MachineException("Ssh machine signal sending error:" + e.getLocalizedMessage());
} finally {
exec.disconnect();
}
}
}

View File

@ -1,109 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.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

@ -1,136 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.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.model.impl.MachineConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.MachineImpl;
import org.eclipse.che.api.workspace.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.workspace.server.RecipeDownloader;
import org.mockito.InjectMocks;
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.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 {
private static final String RECIPE_SCRIPT = new Gson().toJson(new SshMachineRecipe("localhost", 22, "user", "password"));
@Mock
private RecipeDownloader recipeDownloader;
@Mock
private SshMachineFactory sshMachineFactory;
@Mock
private SshClient sshClient;
@Mock
private SshMachineInstance sshMachineInstance;
@InjectMocks
private SshMachineInstanceProvider provider;
private RecipeImpl recipe;
private MachineImpl machine;
@BeforeMethod
public void setUp() throws Exception {
machine = createMachine();
recipe = new RecipeImpl().withType("ssh-config")
.withScript(RECIPE_SCRIPT);
}
@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 = "Dev machine is not supported for Ssh machine implementation")
public void shouldThrowExceptionOnDevMachineCreationFromRecipe() throws Exception {
Machine machine = createMachine(true);
provider.createInstance(machine, LineConsumer.DEV_NULL);
}
@Test(expectedExceptions = NullPointerException.class,
expectedExceptionsMessageRegExp = "Location in machine source is required")
public void shouldThrowExceptionInvalidMachineConfigSource() throws Exception {
MachineImpl machine = createMachine(true);
machine.getConfig().setSource(new MachineSourceImpl("ssh-config").setContent("hello"));
provider.createInstance(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);
when(recipeDownloader.getRecipe(eq(machine.getConfig()))).thenReturn(recipe);
SshMachineInstance instance = provider.createInstance(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").setLocation("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

@ -1,69 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.machine.ssh;
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.MachineRuntimeInfo;
import org.eclipse.che.api.core.util.LineConsumer;
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.HashSet;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for{@link SshMachineInstance}
*
* @author Igor Vinokur
*/
@Listeners(MockitoTestNGListener.class)
public class SshMachineInstanceTest {
@Mock
private Machine machine;
@Mock
private SshClient sshClient;
@Mock
private LineConsumer outputConsumer;
private SshMachineInstance sshMachineInstance;
@BeforeMethod
public void setUp() {
when(machine.getConfig()).thenReturn(mock(MachineConfig.class));
when(machine.getEnvName()).thenReturn("EnvName");
when(machine.getId()).thenReturn("Id");
when(machine.getOwner()).thenReturn("Owner");
when(machine.getRuntime()).thenReturn(mock(MachineRuntimeInfo.class));
when(machine.getWorkspaceId()).thenReturn("WorkspaceId");
sshMachineInstance = new SshMachineInstance(machine,
sshClient,
outputConsumer,
mock(SshMachineFactory.class),
new HashSet<>());
}
@Test
public void shouldCloseOutputConsumerAndStopClientOnDestroy() throws Exception {
sshMachineInstance.destroy();
verify(outputConsumer).close();
verify(sshClient).stop();
}
}

View File

@ -49,7 +49,6 @@
<module>plugin-nodejs</module>
<module>plugin-nodejs-debugger</module>
<module>plugin-php</module>
<!-- module>plugin-ssh-machine</module-->
<module>plugin-languageserver</module>
<module>plugin-urlfactory</module>
<module>plugin-json</module>

View File

@ -924,11 +924,6 @@
<artifactId>che-plugin-ssh-key-server</artifactId>
<version>${che.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-ssh-machine</artifactId>
<version>${che.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-svn-ext-ide</artifactId>

View File

@ -1,28 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.machine.shared.dto;
import org.eclipse.che.api.core.model.machine.MachineLogMessage;
import org.eclipse.che.dto.shared.DTO;
/**
* @author Alexander Garagatyi
*/
@DTO
public interface MachineLogMessageDto extends MachineLogMessage {
void setContent(String content);
MachineLogMessageDto withContent(String content);
void setMachineName(String machineName);
MachineLogMessageDto withMachineName(String machineName);
}