diff --git a/agents/exec/pom.xml b/agents/exec/pom.xml index f17a7bf64e4..ebdd7afd1ec 100644 --- a/agents/exec/pom.xml +++ b/agents/exec/pom.xml @@ -48,14 +48,6 @@ org.eclipse.che.core che-core-api-machine - - org.eclipse.che.core - che-core-api-workspace - - - org.slf4j - slf4j-api - diff --git a/agents/exec/src/main/java/org/eclipse/che/api/agent/SshMachineExecAgentLauncher.java b/agents/exec/src/main/java/org/eclipse/che/api/agent/SshMachineExecAgentLauncher.java deleted file mode 100644 index e3a1be12858..00000000000 --- a/agents/exec/src/main/java/org/eclipse/che/api/agent/SshMachineExecAgentLauncher.java +++ /dev/null @@ -1,134 +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.agent; - -import org.eclipse.che.api.agent.server.terminal.WebsocketTerminalFilesPathProvider; -import org.eclipse.che.api.agent.shared.model.Agent; -import org.eclipse.che.api.core.ConflictException; -import org.eclipse.che.api.core.ServerException; -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.slf4j.Logger; - -import javax.inject.Inject; -import javax.inject.Named; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -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 extends ExecAgentLauncher { - 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\\] (?[\\S]+) (?[\\S]+)"); - private static final String DEFAULT_ARCHITECTURE = "linux_amd64"; - - private final WebsocketTerminalFilesPathProvider archivePathProvider; - private final String terminalLocation; - - @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) { - super(agentMaxStartTimeMs, agentPingDelayMs, terminalRunCommand); - this.archivePathProvider = terminalPathProvider; - this.terminalLocation = terminalLocation; - } - - @Override - public String getMachineType() { - return "ssh"; - } - - @Override - public void launch(Instance machine, Agent agent) throws ServerException { - try { - String architecture = detectArchitecture(machine); - machine.copy(archivePathProvider.getPath(architecture), terminalLocation); - - super.launch(machine, agent); - } catch (ConflictException e) { - // should never happen - throw new ServerException("Internal server error occurs on terminal launching."); - } - } - - private String detectArchitecture(Instance machine) throws ConflictException, MachineException { - // 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 - InstanceProcess 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; - } - } -} diff --git a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java index c9ebfbc64da..789f224879e 100644 --- a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java +++ b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java @@ -23,7 +23,6 @@ import org.eclipse.che.api.agent.LSTypeScriptAgent; import org.eclipse.che.api.agent.SshAgent; import org.eclipse.che.api.agent.SshAgentLauncher; -import org.eclipse.che.api.agent.SshMachineExecAgentLauncher; import org.eclipse.che.api.agent.UnisonAgent; import org.eclipse.che.api.agent.WsAgent; import org.eclipse.che.api.agent.WsAgentLauncher; @@ -136,7 +135,6 @@ protected void configure() { Multibinder launchers = Multibinder.newSetBinder(binder(), AgentLauncher.class); launchers.addBinding().to(WsAgentLauncher.class); launchers.addBinding().to(ExecAgentLauncher.class); - launchers.addBinding().to(SshMachineExecAgentLauncher.class); launchers.addBinding().to(SshAgentLauncher.class); bindConstant().annotatedWith(Names.named("machine.ws_agent.run_command")) diff --git a/plugins/plugin-ssh-machine/pom.xml b/plugins/plugin-ssh-machine/pom.xml index 25871e078d0..dec4bf4d204 100644 --- a/plugins/plugin-ssh-machine/pom.xml +++ b/plugins/plugin-ssh-machine/pom.xml @@ -30,6 +30,10 @@ com.google.code.gson gson + + com.google.guava + guava + com.google.inject guice @@ -54,6 +58,10 @@ javax.ws.rs javax.ws.rs-api + + org.eclipse.che.core + che-core-api-agent-shared + org.eclipse.che.core che-core-api-core @@ -66,6 +74,10 @@ org.eclipse.che.core che-core-api-model + + org.eclipse.che.core + che-core-api-workspace + org.eclipse.che.core che-core-commons-annotations @@ -74,6 +86,10 @@ org.eclipse.che.core che-core-commons-lang + + org.slf4j + slf4j-api + ch.qos.logback logback-classic diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineFactory.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineFactory.java index 5e483f5d348..2b72942a4d8 100644 --- a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineFactory.java +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineFactory.java @@ -10,22 +10,38 @@ *******************************************************************************/ package org.eclipse.che.plugin.machine.ssh; -import com.google.inject.assistedinject.Assisted; +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.api.machine.server.spi.Instance; +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 interface SshMachineFactory { +public class SshMachineFactory { + + private final int connectionTimeoutMs; + private final Set machinesServers; + + @Inject + public SshMachineFactory(@Named("che.workspace.ssh_connection_timeout_ms") int connectionTimeoutMs, + @Named("machine.ssh.machine_servers") Set machinesServers) { + this.connectionTimeoutMs = connectionTimeoutMs; + this.machinesServers = machinesServers; + } + /** * Creates {@link SshClient} to communicate with machine over SSH protocol. @@ -35,31 +51,39 @@ public interface SshMachineFactory { * @param envVars * environment variables that should be injected into machine */ - SshClient createSshClient(@Assisted SshMachineRecipe sshMachineRecipe, - @Assisted Map envVars); + public SshClient createSshClient(SshMachineRecipe sshMachineRecipe, Map envVars) { + return new JschSshClient(sshMachineRecipe, envVars, new JSch(), connectionTimeoutMs); + } /** - * Creates ssh machine implementation of {@link Instance}. + * 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} + * @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; + 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 org.eclipse.che.api.machine.server.spi.InstanceProcess}. + * 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 + * @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); + public SshMachineProcess createInstanceProcess(Command command, String outputChannel, int pid, SshClient sshClient) { + return new SshMachineProcess(command, outputChannel, pid, sshClient); + } } diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstance.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstance.java index 206f555a8c5..16809b19496 100644 --- a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstance.java +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstance.java @@ -10,24 +10,19 @@ *******************************************************************************/ 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.MachineSource; +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.machine.server.model.impl.ServerImpl; -import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.machine.server.spi.InstanceNode; -import org.eclipse.che.api.machine.server.spi.InstanceProcess; -import org.eclipse.che.api.machine.server.spi.impl.AbstractInstance; -import javax.inject.Inject; -import javax.inject.Named; import javax.ws.rs.core.UriBuilder; import java.io.IOException; import java.net.URI; @@ -44,46 +39,57 @@ import static java.util.Collections.emptyMap; /** - * Implementation of {@link Instance} that uses represents ssh machine. + * 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 extends AbstractInstance { +public class SshMachineInstance { private static final AtomicInteger pidSequence = new AtomicInteger(1); - private final SshClient sshClient; - private final LineConsumer outputConsumer; - private final SshMachineFactory machineFactory; + private String id; + private String workspaceId; + private final String envName; + private final String owner; + private MachineRuntimeInfoImpl machineRuntime; + private final MachineConfig machineConfig; - private final Set machinesServers; - private final ConcurrentHashMap machineProcesses; - private MachineRuntimeInfoImpl machineRuntime; + private final SshClient sshClient; + private final LineConsumer outputConsumer; + private final SshMachineFactory machineFactory; + private MachineStatus status; - @Inject - public SshMachineInstance(@Assisted Machine machine, - @Assisted SshClient sshClient, - @Assisted LineConsumer outputConsumer, + private final Set machinesServers; + private final ConcurrentHashMap machineProcesses; + + + public SshMachineInstance(Machine machine, + SshClient sshClient, + LineConsumer outputConsumer, SshMachineFactory machineFactory, - @Named("machine.ssh.machine_servers") Set machinesServers) { - super(machine); + Set 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<>(); } - @Override public LineConsumer getLogger() { return outputConsumer; } - @Override public MachineRuntimeInfoImpl getRuntime() { // lazy initialization if (machineRuntime == null) { @@ -103,9 +109,8 @@ public MachineRuntimeInfoImpl getRuntime() { return machineRuntime; } - @Override - public InstanceProcess getProcess(final int pid) throws NotFoundException, MachineException { - final InstanceProcess machineProcess = machineProcesses.get(pid); + 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)); } @@ -118,38 +123,26 @@ public InstanceProcess getProcess(final int pid) throws NotFoundException, Machi } } - @Override - public List getProcesses() throws MachineException { + public List getProcesses() throws MachineException { // todo get children of session process return machineProcesses.values() .stream() - .filter(InstanceProcess::isAlive) + .filter(SshMachineProcess::isAlive) .collect(Collectors.toList()); } - @Override - public InstanceProcess createProcess(Command command, String outputChannel) throws MachineException { + public SshMachineProcess createProcess(Command command, String outputChannel) throws MachineException { final Integer pid = pidSequence.getAndIncrement(); - SshMachineProcess instanceProcess = machineFactory.createInstanceProcess(command, outputChannel, pid, sshClient); + SshMachineProcess machineProcess = machineFactory.createInstanceProcess(command, outputChannel, pid, sshClient); - machineProcesses.put(pid, instanceProcess); + machineProcesses.put(pid, machineProcess); - return instanceProcess; + return machineProcess; } - /** - * Not implemented.

- * - * {@inheritDoc} - */ - @Override - public MachineSource saveToSnapshot() throws MachineException { - throw new MachineException("Snapshot feature is unsupported for ssh machine implementation"); - } - @Override public void destroy() throws MachineException { try { outputConsumer.close(); @@ -161,34 +154,23 @@ public void destroy() throws MachineException { sshClient.stop(); } - @Override public InstanceNode getNode() { return null;// todo } - /** - * Not implemented.

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

- * - * {@inheritDoc} - */ - @Override - public void copy(Instance sourceMachine, String sourcePath, String targetPath, boolean overwrite) throws MachineException { - //todo - throw new MachineException("Copying is not implemented in ssh machine implementation"); + public void setStatus(MachineStatus status) { + this.status = status; + } + + + public String getId() { + return id; } - @Override public void copy(String sourcePath, String targetPath) throws MachineException { sshClient.copy(sourcePath, targetPath); } @@ -208,4 +190,19 @@ private ServerImpl serverConfToServer(ServerConf serverConf, UriBuilder uriBuild null); } + public String getWorkspaceId() { + return workspaceId; + } + + public MachineConfig getMachineConfig() { + return machineConfig; + } + + public String getEnvName() { + return envName; + } + + public String getOwner() { + return owner; + } } diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProvider.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProvider.java index 79c0e9a2000..a114423c64e 100644 --- a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProvider.java +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProvider.java @@ -21,10 +21,7 @@ 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.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.InstanceProvider; import org.eclipse.che.api.machine.server.util.RecipeDownloader; import javax.inject.Inject; @@ -35,7 +32,7 @@ import static java.util.Objects.requireNonNull; /** - * Implementation of {@link InstanceProvider} based on communication with machine over ssh protocol. + * Instance provider based on communication with machine over ssh protocol. * *

Ssh machine can't be actually created and exists somewhere outside of the control.
* So this implementation just performs command execution in such machines.
@@ -44,7 +41,7 @@ * @author Alexander Garagatyi */ // todo tests -public class SshMachineInstanceProvider implements InstanceProvider { +public class SshMachineInstanceProvider { private static final Gson GSON = new Gson(); private final Set supportedRecipeTypes; @@ -58,12 +55,10 @@ public SshMachineInstanceProvider(SshMachineFactory sshMachineFactory, RecipeDow this.supportedRecipeTypes = Collections.singleton("ssh-config"); } - @Override public String getType() { return "ssh"; } - @Override public Set getRecipeTypes() { return supportedRecipeTypes; } @@ -76,7 +71,7 @@ public Set getRecipeTypes() { * machine description * @param lineConsumer * output for instance creation logs - * @return newly created {@link Instance} + * @return newly created {@link SshMachineInstance} * @throws UnsupportedRecipeException * if specified {@code recipe} is not supported * @throws InvalidRecipeException @@ -86,9 +81,7 @@ public Set getRecipeTypes() { * @throws MachineException * if other error occurs */ - @Override - public Instance createInstance(Machine machine, LineConsumer lineConsumer) - throws UnsupportedRecipeException, InvalidRecipeException, NotFoundException, MachineException { + 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"); @@ -111,18 +104,4 @@ public Instance createInstance(Machine machine, LineConsumer lineConsumer) instance.setStatus(MachineStatus.RUNNING); return instance; } - - /** - * Removes snapshot of the instance in implementation specific way. - * - * @param machineSource - * contains implementation specific key of the snapshot of the instance that should be removed - * @throws SnapshotException - * if exception occurs on instance snapshot removal - */ - @Override - public void removeInstanceSnapshot(MachineSource machineSource) throws SnapshotException { - throw new SnapshotException("Snapshot feature is unsupported for ssh machine implementation"); - } - } diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineModule.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineModule.java index 140ac2e1fa8..ca20b46fc3f 100644 --- a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineModule.java +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineModule.java @@ -11,7 +11,6 @@ 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; @@ -23,20 +22,9 @@ public class SshMachineModule extends AbstractModule { @Override protected void configure() { - Multibinder machineProviderMultibinder = - Multibinder.newSetBinder(binder(), - org.eclipse.che.api.machine.server.spi.InstanceProvider.class); - machineProviderMultibinder.addBinding() - .to(SshMachineInstanceProvider.class); + bind(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)); + bind(SshMachineFactory.class); bindConstant().annotatedWith(Names.named("machine.ssh.server.terminal.location")).to("~/che"); diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineProcess.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineProcess.java index 3fa2e144f13..bc419a9d7f8 100644 --- a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineProcess.java +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineProcess.java @@ -17,24 +17,28 @@ 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 java.io.IOException; +import java.util.Map; import static java.lang.String.format; /** - * Ssh machine implementation of {@link InstanceProcess} + * Ssh machine process implementation. * * @author Alexander Garagatyi */ -public class SshMachineProcess extends AbstractMachineProcess implements InstanceProcess { - private final String commandLine; - private final SshClient sshClient; +public class SshMachineProcess { + + private final SshClient sshClient; + private final String name; + private final String commandLine; + private final String type; + private final Map attributes; + private final int pid; + private final String outputChannel; private volatile boolean started; @@ -45,13 +49,16 @@ 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; + this.name = command.getName(); + this.type = command.getType(); + this.attributes = command.getAttributes(); + this.pid = pid; + this.outputChannel = outputChannel; } - @Override public boolean isAlive() { if (!started) { return false; @@ -66,12 +73,10 @@ public boolean isAlive() { } } - @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."); @@ -89,22 +94,45 @@ public void start(LineConsumer output) throws ConflictException, MachineExceptio } } - @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())); + throw new NotFoundException(format("Process with pid %s not found", pid)); } } - @Override 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 getAttributes() { + return attributes; + } + + public String getOutputChannel() { + return outputChannel; + } + private static class PrefixingLineConsumer implements LineConsumer { private final String prefix; private final LineConsumer lineConsumer; diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/exec/SshMachineExecAgentLauncher.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/exec/SshMachineExecAgentLauncher.java new file mode 100644 index 00000000000..77e3796bff0 --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/exec/SshMachineExecAgentLauncher.java @@ -0,0 +1,215 @@ +/******************************************************************************* + * 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.api.agent.server.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.machine.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.machine.server.exception.MachineException; +import org.eclipse.che.api.machine.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\\] (?[\\S]+) (?[\\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 (MachineException 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, MachineException { + // 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 | MachineException 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; + } +} diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/exec/SshProcessLaunchedChecker.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/exec/SshProcessLaunchedChecker.java new file mode 100644 index 00000000000..6b65bcb11c5 --- /dev/null +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/exec/SshProcessLaunchedChecker.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * 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.machine.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()); + } + } +} diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschSshClient.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschSshClient.java index 7f2f9e1ba8a..914ec6d09de 100644 --- a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschSshClient.java +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/jsch/JschSshClient.java @@ -10,7 +10,6 @@ *******************************************************************************/ 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; @@ -60,8 +59,8 @@ public class JschSshClient implements SshClient { private Session session; @Inject - public JschSshClient(@Assisted SshMachineRecipe sshMachineRecipe, - @Assisted Map envVars, + public JschSshClient(SshMachineRecipe sshMachineRecipe, + Map envVars, JSch jsch, @Named("che.workspace.ssh_connection_timeout_ms") int connectionTimeoutMs) { this.envVars = envVars; diff --git a/plugins/plugin-ssh-machine/src/test/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProviderTest.java b/plugins/plugin-ssh-machine/src/test/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProviderTest.java index b9538ac77cc..012929ecf17 100644 --- a/plugins/plugin-ssh-machine/src/test/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProviderTest.java +++ b/plugins/plugin-ssh-machine/src/test/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProviderTest.java @@ -17,13 +17,11 @@ 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.util.RecipeDownloader; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -67,10 +65,6 @@ public class SshMachineInstanceProviderTest { @BeforeMethod public void setUp() throws Exception { machine = createMachine(); - SshMachineRecipe sshMachineRecipe = new SshMachineRecipe("localhost", - 22, - "user", - "password"); recipe = new RecipeImpl().withType("ssh-config") .withScript(RECIPE_SCRIPT); } @@ -85,12 +79,6 @@ public void shouldReturnCorrectRecipeTypes() throws Exception { assertEquals(provider.getRecipeTypes(), new HashSet<>(singletonList("ssh-config"))); } - @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 { @@ -114,7 +102,7 @@ public void shouldBeAbleToCreateSshMachineInstanceOnMachineCreationFromRecipe() when(sshMachineFactory.createInstance(eq(machine), eq(sshClient), any(LineConsumer.class))).thenReturn(sshMachineInstance); when(recipeDownloader.getRecipe(eq(machine.getConfig()))).thenReturn(recipe); - Instance instance = provider.createInstance(machine, LineConsumer.DEV_NULL); + SshMachineInstance instance = provider.createInstance(machine, LineConsumer.DEV_NULL); assertEquals(instance, sshMachineInstance); }