diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/AbstractLineConsumer.java b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/AbstractLineConsumer.java new file mode 100644 index 0000000000..912b268f46 --- /dev/null +++ b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/AbstractLineConsumer.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.core.util; + +import java.io.IOException; + +/** + * No-op implementation of {@link LineConsumer} + * + * @author Alexander Garagatyi + */ +public abstract class AbstractLineConsumer implements LineConsumer { + @Override + public void writeLine(String line) throws IOException {} + + @Override + public void close() throws IOException {} +} diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/AbstractMessageConsumer.java b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/AbstractMessageConsumer.java new file mode 100644 index 0000000000..5e3b0ca38c --- /dev/null +++ b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/AbstractMessageConsumer.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.core.util; + +import java.io.IOException; + +/** + * No-op implementation of {@link MessageConsumer} + * + * @author Alexander Garagatyi + */ +public class AbstractMessageConsumer implements MessageConsumer { + @Override + public void consume(T message) throws IOException {} + + @Override + public void close() throws IOException {} +} diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/LineConsumer.java b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/LineConsumer.java index b7fca0956c..d2e2936c91 100644 --- a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/LineConsumer.java +++ b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/LineConsumer.java @@ -17,18 +17,11 @@ import java.io.IOException; * Consumes text line by line for analysing, writing, storing, etc. * * @author andrew00x + * @see AbstractLineConsumer */ public interface LineConsumer extends Closeable { /** Consumes single line. */ void writeLine(String line) throws IOException; - LineConsumer DEV_NULL = new LineConsumer() { - @Override - public void writeLine(String line) { - } - - @Override - public void close() { - } - }; + LineConsumer DEV_NULL = new AbstractLineConsumer() {}; } diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/MessageConsumer.java b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/MessageConsumer.java new file mode 100644 index 0000000000..ecd6d70b20 --- /dev/null +++ b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/MessageConsumer.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.core.util; + +import java.io.Closeable; +import java.io.IOException; + +/** + * Consumes messages one by one for analysing, writing, storing, etc. + * + * @author Alexander Garagatyi + */ +public interface MessageConsumer extends Closeable { + void consume(T message) throws IOException; +} diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/WebsocketLineConsumer.java b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/WebsocketLineConsumer.java index aeb42858a3..13a80b71a7 100644 --- a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/WebsocketLineConsumer.java +++ b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/WebsocketLineConsumer.java @@ -10,7 +10,6 @@ *******************************************************************************/ package org.eclipse.che.api.core.util; -import org.everrest.core.impl.provider.json.JsonUtils; import org.everrest.websockets.WSConnectionContext; import org.everrest.websockets.message.ChannelBroadcastMessage; import org.slf4j.Logger; @@ -36,7 +35,7 @@ public class WebsocketLineConsumer implements LineConsumer { public void writeLine(String line) throws IOException { final ChannelBroadcastMessage bm = new ChannelBroadcastMessage(); bm.setChannel(channel); - bm.setBody(JsonUtils.getJsonString(line)); + bm.setBody(line); try { WSConnectionContext.sendMessage(bm); } catch (Exception e) { diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/WebsocketMessageConsumer.java b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/WebsocketMessageConsumer.java new file mode 100644 index 0000000000..4552c8bbaf --- /dev/null +++ b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/WebsocketMessageConsumer.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.core.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.io.IOException; + +/** + * Message consumer that sends messages to specified websocket channel + * + * @author Alexander Garagatyi + */ +public class WebsocketMessageConsumer extends AbstractMessageConsumer { + private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); + + private final LineConsumer messageSender; + + public WebsocketMessageConsumer(String channel) { + this.messageSender = new WebsocketLineConsumer(channel); + } + + @Override + public void consume(T message) throws IOException { + messageSender.writeLine(GSON.toJson(message)); + } +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineLogMessage.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineLogMessage.java new file mode 100644 index 0000000000..872dac535a --- /dev/null +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineLogMessage.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.core.model.machine; + +/** + * Represents log message from machine + * + * @author Alexander Garagatyi + */ +public interface MachineLogMessage { + /** + * Content of log message + */ + String getContent(); + + /** + * Machine name + */ + String getMachineName(); +} diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/CommandOutputMessageUnmarshaller.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/CommandOutputMessageUnmarshaller.java index 845369c7d1..4a5669f720 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/CommandOutputMessageUnmarshaller.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/CommandOutputMessageUnmarshaller.java @@ -10,9 +10,6 @@ *******************************************************************************/ package org.eclipse.che.ide.api.machine; -import com.google.gwt.json.client.JSONParser; -import com.google.gwt.json.client.JSONString; - import org.eclipse.che.ide.websocket.Message; import org.eclipse.che.ide.websocket.rest.Unmarshallable; @@ -32,8 +29,7 @@ public class CommandOutputMessageUnmarshaller implements Unmarshallable @Override public void unmarshal(Message message) { - final JSONString jsonString = JSONParser.parseStrict(message.getBody()).isString(); - payload = jsonString.stringValue(); + payload = message.getBody(); if (payload.startsWith("[STDOUT]")) { payload = payload.substring(9); diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/OutputMessageUnmarshaller.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/OutputMessageUnmarshaller.java index c2783ca605..325427d407 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/OutputMessageUnmarshaller.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/OutputMessageUnmarshaller.java @@ -10,9 +10,6 @@ *******************************************************************************/ package org.eclipse.che.ide.api.machine; -import com.google.gwt.json.client.JSONParser; -import com.google.gwt.json.client.JSONString; - import org.eclipse.che.ide.websocket.Message; import org.eclipse.che.ide.websocket.rest.Unmarshallable; @@ -26,9 +23,7 @@ public class OutputMessageUnmarshaller implements Unmarshallable { @Override public void unmarshal(Message message) { - final JSONString jsonString = JSONParser.parseStrict(message.getBody()).isString(); - payload = jsonString.stringValue(); - + payload = message.getBody(); if (payload.startsWith("[STDOUT]") || payload.startsWith("[STDERR]")) { payload = payload.substring(9); } diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineImplTerminalLauncher.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineImplTerminalLauncher.java index 6e69a9a98e..ce16d62956 100644 --- a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineImplTerminalLauncher.java +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineImplTerminalLauncher.java @@ -11,7 +11,7 @@ package org.eclipse.che.plugin.machine.ssh; import org.eclipse.che.api.core.ConflictException; -import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.core.util.AbstractLineConsumer; 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; @@ -164,14 +164,11 @@ public class SshMachineImplTerminalLauncher implements MachineImplSpecificTermin null), null); - startTerminal.start(new LineConsumer() { + startTerminal.start(new AbstractLineConsumer() { @Override public void writeLine(String line) throws IOException { machine.getLogger().writeLine("[Terminal] " + line); } - - @Override - public void close() throws IOException {} }); } } diff --git a/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/Constants.java b/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/Constants.java index 636312928d..a1efed1f05 100644 --- a/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/Constants.java +++ b/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/Constants.java @@ -40,6 +40,11 @@ public class Constants { public static final String WSAGENT_WEBSOCKET_REFERENCE = "wsagent.websocket"; public static final String WSAGENT_DEBUG_REFERENCE = "wsagent.debug"; + public static final String LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL = "environment.output_channel"; + public static final String ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE = "workspace:%s:environment_output"; + public static final String LINK_REL_ENVIRONMENT_STATUS_CHANNEL = "environment.status_channel"; + public static final String ENVIRONMENT_STATUS_CHANNEL_TEMPLATE = "workspace:%s:machines_statuses"; + public static final String TERMINAL_REFERENCE = "terminal"; public static final String WS_AGENT_PORT = "4401/tcp"; diff --git a/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/dto/MachineLogMessageDto.java b/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/dto/MachineLogMessageDto.java new file mode 100644 index 0000000000..e066d7f774 --- /dev/null +++ b/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/dto/MachineLogMessageDto.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.machine.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); +} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineManager.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineManager.java index ae11fec063..575fd07955 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineManager.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineManager.java @@ -14,6 +14,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; @@ -135,6 +136,8 @@ public class MachineManager { * id of the workspace the created machine will belong to * @param environmentName * environment name the created machine will belongs to + * @param outputConsumer + * output consumer of machine * @return new machine * @throws NotFoundException * if machine type from recipe is unsupported @@ -153,7 +156,8 @@ public class MachineManager { */ public MachineImpl createMachineSync(MachineConfig machineConfig, final String workspaceId, - final String environmentName) + final String environmentName, + LineConsumer outputConsumer) throws NotFoundException, SnapshotException, ConflictException, @@ -167,7 +171,8 @@ public class MachineManager { workspaceId, environmentName, this::createInstance, - null); + null, + outputConsumer); LOG.info("Machine [ws = {}: env = {}: machine = {}] was successfully created, its id is '{}'", workspaceId, environmentName, @@ -186,6 +191,8 @@ public class MachineManager { * workspace id * @param envName * name of environment + * @param outputConsumer + * output consumer of machine * @return machine instance * @throws NotFoundException * when snapshot doesn't exist @@ -198,11 +205,15 @@ public class MachineManager { * @throws BadRequestException * when either machineConfig or workspace id, or environment name is not valid */ - public MachineImpl recoverMachine(MachineConfig machineConfig, String workspaceId, String envName) throws NotFoundException, - SnapshotException, - MachineException, - ConflictException, - BadRequestException { + public MachineImpl recoverMachine(MachineConfig machineConfig, + String workspaceId, + String envName, + LineConsumer outputConsumer) throws NotFoundException, + SnapshotException, + MachineException, + ConflictException, + BadRequestException { + final SnapshotImpl snapshot = snapshotDao.getSnapshot(workspaceId, envName, machineConfig.getName()); LOG.info("Recovering machine [ws = {}: env = {}: machine = {}] from snapshot", workspaceId, envName, machineConfig.getName()); @@ -210,7 +221,8 @@ public class MachineManager { workspaceId, envName, this::createInstance, - snapshot); + snapshot, + outputConsumer); LOG.info("Machine [ws = {}: env = {}: machine = {}] was successfully recovered, its id '{}'", workspaceId, envName, @@ -229,6 +241,8 @@ public class MachineManager { * id of the workspace the created machine will belong to * @param environmentName * environment name the created machine will belongs to + * @param outputConsumer + * output consumer of machine * @return new machine * @throws NotFoundException * if machine type from recipe is unsupported @@ -247,7 +261,8 @@ public class MachineManager { */ public MachineImpl createMachineAsync(MachineConfig machineConfig, final String workspaceId, - final String environmentName) + final String environmentName, + LineConsumer outputConsumer) throws NotFoundException, SnapshotException, ConflictException, @@ -269,14 +284,16 @@ public class MachineManager { // todo what should we do in that case? } })), - null); + null, + outputConsumer); } private MachineImpl createMachine(MachineConfigImpl machineConfig, String workspaceId, String environmentName, MachineInstanceCreator instanceCreator, - SnapshotImpl snapshot) + SnapshotImpl snapshot, + LineConsumer outputConsumer) throws NotFoundException, SnapshotException, ConflictException, @@ -329,38 +346,43 @@ public class MachineManager { null); createMachineLogsDir(machineId); - final LineConsumer machineLogger = getMachineLogger(machineId, getMachineChannels(machine.getConfig().getName(), - machine.getWorkspaceId(), - machine.getEnvName()) - .getOutput()); + final LineConsumer machineLogger = getMachineLogger(machineId, outputConsumer); try { + machineRegistry.addMachine(machine); try { - machineRegistry.addMachine(machine); instanceCreator.createInstance(instanceProvider, machine, machineLogger); } catch (MachineException ex) { if (snapshot == null) { throw ex; } if (ex.getCause() instanceof SourceNotFoundException) { - final LineConsumer logger = getMachineLogger(machineId, - getMachineChannels(machine.getConfig().getName(), - machine.getWorkspaceId(), - machine.getEnvName()).getOutput()); LOG.error("Image of snapshot for machine " + machineConfig.getName() + " not found. " + "Machine will be created from origin source"); machine.getConfig().setSource(sourceCopy); + try { + machineRegistry.remove(machineId); + } catch (NotFoundException ignored) { + // machine is already removed, should never happen + } machineRegistry.addMachine(machine); - instanceCreator.createInstance(instanceProvider, machine, logger); + instanceCreator.createInstance(instanceProvider, machine, outputConsumer); } } return machine; - } catch (ConflictException e) { + } catch (ApiException apiEx) { try { machineLogger.close(); - } catch (IOException ignored) { + } catch (IOException ioEx) { + LOG.error(ioEx.getLocalizedMessage(), ioEx); } - throw new MachineException(e.getLocalizedMessage(), e); + try { + machineRegistry.remove(machineId); + } catch (NotFoundException ignored) { + // machine is already removed + } + + throw new MachineException(apiEx.getLocalizedMessage(), apiEx); } } @@ -393,27 +415,28 @@ public class MachineManager { .withWorkspaceId(machine.getWorkspaceId()) .withMachineName(machine.getConfig().getName())); - } catch (ServerException | InterruptedException e) { - if (instance != null) { - instance.destroy(); - } - + } catch (ServerException | InterruptedException creationEx) { eventService.publish(DtoFactory.newDto(MachineStatusEvent.class) .withEventType(MachineStatusEvent.EventType.ERROR) .withMachineId(machine.getId()) .withDev(machine.getConfig().isDev()) .withWorkspaceId(machine.getWorkspaceId()) .withMachineName(machine.getConfig().getName()) - .withError(e.getLocalizedMessage())); + .withError(creationEx.getLocalizedMessage())); + try { + machineLogger.writeLine(String.format("[ERROR] %s", creationEx.getLocalizedMessage())); + } catch (IOException ioEx) { + LOG.error(ioEx.getLocalizedMessage()); + } try { - machineRegistry.remove(machine.getId()); - machineLogger.writeLine(String.format("[ERROR] %s", e.getLocalizedMessage())); - machineLogger.close(); - } catch (IOException | NotFoundException e1) { - LOG.error(e1.getLocalizedMessage()); + if (instance != null) { + instance.destroy(); + } + } catch (MachineException destroyingEx) { + LOG.error(destroyingEx.getLocalizedMessage(), destroyingEx); } - throw new MachineException(e.getLocalizedMessage(), e); + throw new MachineException(creationEx.getLocalizedMessage(), creationEx); } } @@ -930,8 +953,8 @@ public class MachineManager { } @VisibleForTesting - LineConsumer getMachineLogger(String machineId, String outputChannel) throws MachineException { - return getLogger(getMachineFileLogger(machineId), outputChannel); + LineConsumer getMachineLogger(String machineId, LineConsumer outputConsumer) throws MachineException { + return new CompositeLineConsumer(getMachineFileLogger(machineId), outputConsumer); } @VisibleForTesting @@ -946,11 +969,6 @@ public class MachineManager { return fileLogger; } - static ChannelsImpl getMachineChannels(String machineName, String workspaceId, String envName) { - return new ChannelsImpl(workspaceId + ':' + envName + ':' + machineName, - "machine:status:" + workspaceId + ':' + machineName); - } - // cleanup machine if event about instance failure comes private class MachineCleaner implements EventSubscriber { @Override diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineServiceLinksInjector.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineServiceLinksInjector.java index 1c56f69267..a0a498c67d 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineServiceLinksInjector.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineServiceLinksInjector.java @@ -16,7 +16,6 @@ import org.eclipse.che.api.core.rest.ServiceContext; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.LinkParameter; import org.eclipse.che.api.machine.shared.Constants; -import org.eclipse.che.api.machine.shared.dto.MachineConfigDto; import org.eclipse.che.api.machine.shared.dto.MachineDto; import org.eclipse.che.api.machine.shared.dto.MachineProcessDto; import org.eclipse.che.api.machine.shared.dto.ServerDto; @@ -30,11 +29,14 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import static java.util.Arrays.asList; +import static java.lang.String.format; import static java.util.Collections.singletonList; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.MediaType.TEXT_PLAIN; import static org.eclipse.che.api.core.util.LinksHelper.createLink; +import static org.eclipse.che.api.machine.shared.Constants.ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE; +import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL; +import static org.eclipse.che.api.machine.shared.Constants.ENVIRONMENT_STATUS_CHANNEL_TEMPLATE; import static org.eclipse.che.api.machine.shared.Constants.TERMINAL_REFERENCE; import static org.eclipse.che.dto.server.DtoFactory.cloneDto; import static org.eclipse.che.dto.server.DtoFactory.newDto; @@ -118,23 +120,27 @@ public class MachineServiceLinksInjector { injectTerminalLink(machine, serviceContext, links); - // add links to websocket channels - final Link machineChannelLink = createLink("GET", - serviceContext.getBaseUriBuilder() - .path("ws") - .path(machine.getWorkspaceId()) - .scheme("https".equals(getLogsUri.getScheme()) ? "wss" : "ws") - .build() - .toString(), - null); + // add workspace channel links + final Link workspaceChannelLink = createLink("GET", + serviceContext.getBaseUriBuilder() + .path("ws") + .path(machine.getWorkspaceId()) + .scheme("https".equals(getLogsUri.getScheme()) ? "wss" : "ws") + .build() + .toString(), + null); final LinkParameter channelParameter = newDto(LinkParameter.class).withName("channel") .withRequired(true); - injectMachineChannelsLinks(machine.getConfig(), - machine.getWorkspaceId(), - machine.getEnvName(), - machineChannelLink, - channelParameter); + links.add(cloneDto(workspaceChannelLink).withRel(LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL) + .withParameters(singletonList(cloneDto(channelParameter) + .withDefaultValue(format(ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE, + machine.getWorkspaceId()))))); + + links.add(cloneDto(workspaceChannelLink).withRel(ENVIRONMENT_STATUS_CHANNEL_TEMPLATE) + .withParameters(singletonList(cloneDto(channelParameter) + .withDefaultValue(format(ENVIRONMENT_STATUS_CHANNEL_TEMPLATE, + machine.getWorkspaceId()))))); return machine.withLinks(links); } @@ -157,25 +163,6 @@ public class MachineServiceLinksInjector { } } - public void injectMachineChannelsLinks(MachineConfigDto machineConfig, - String workspaceId, - String envName, - Link machineChannelLink, - LinkParameter channelParameter) { - final ChannelsImpl channels = MachineManager.getMachineChannels(machineConfig.getName(), - workspaceId, - envName); - final Link getLogsLink = cloneDto(machineChannelLink) - .withRel(org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_MACHINE_LOGS_CHANNEL) - .withParameters(singletonList(cloneDto(channelParameter).withDefaultValue(channels.getOutput()))); - - final Link getStatusLink = cloneDto(machineChannelLink) - .withRel(org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_MACHINE_STATUS_CHANNEL) - .withParameters(singletonList(cloneDto(channelParameter).withDefaultValue(channels.getStatus()))); - - machineConfig.withLinks(asList(getLogsLink, getStatusLink)); - } - public MachineProcessDto injectLinks(MachineProcessDto process, String machineId, ServiceContext serviceContext) { final UriBuilder uriBuilder = serviceContext.getServiceUriBuilder(); final List links = Lists.newArrayListWithExpectedSize(3); diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/event/MachineStateMessenger.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/event/MachineStateMessenger.java index a2a72d05ad..ce28efa922 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/event/MachineStateMessenger.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/event/MachineStateMessenger.java @@ -24,6 +24,9 @@ import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Singleton; +import static java.lang.String.format; +import static org.eclipse.che.api.machine.shared.Constants.ENVIRONMENT_STATUS_CHANNEL_TEMPLATE; + /** * Send machine state events using websocket channel to the clients * @@ -44,7 +47,7 @@ public class MachineStateMessenger implements EventSubscriber recipeTypes = new HashSet<>(); recipeTypes.add("test type 1"); @@ -168,7 +168,7 @@ public class MachineManagerTest { String workspaceId = "wsId"; String environmentName = "env1"; - manager.createMachineSync(machineConfig, workspaceId, environmentName); + manager.createMachineSync(machineConfig, workspaceId, environmentName, logConsumer); } @Test @@ -186,7 +186,7 @@ public class MachineManagerTest { MachineStatus.CREATING, null); - manager.createMachineSync(machineConfig, WS_ID, ENVIRONMENT_NAME); + manager.createMachineSync(machineConfig, WS_ID, ENVIRONMENT_NAME, logConsumer); verify(machineRegistry).addMachine(eq(expectedMachine)); } @@ -198,7 +198,7 @@ public class MachineManagerTest { .setDev(true) .build(); - manager.createMachineSync(machineConfig, WS_ID, ENVIRONMENT_NAME); + manager.createMachineSync(machineConfig, WS_ID, ENVIRONMENT_NAME, logConsumer); verify(wsAgentLauncher).startWsAgent(WS_ID); } @@ -207,7 +207,7 @@ public class MachineManagerTest { public void shouldNotCallWsAgentLauncherAfterNonDevMachineStart() throws Exception { final MachineConfigImpl machineConfig = createMachineConfig(); - manager.createMachineSync(machineConfig, WS_ID, ENVIRONMENT_NAME); + manager.createMachineSync(machineConfig, WS_ID, ENVIRONMENT_NAME, logConsumer); verify(wsAgentLauncher, never()).startWsAgent(WS_ID); } @@ -233,7 +233,7 @@ public class MachineManagerTest { waitForExecutorIsCompletedTask(); //then - verify(processLogger).close(); + verify(logConsumer).close(); } @Test @@ -246,7 +246,7 @@ public class MachineManagerTest { waitForExecutorIsCompletedTask(); //then - verify(processLogger).close(); + verify(logConsumer).close(); } @Test(expectedExceptions = MachineException.class) @@ -255,14 +255,14 @@ public class MachineManagerTest { MachineConfig machineConfig = mock(MachineConfig.class); MachineSource machineSource = mock(MachineSource.class); LineConsumer machineLogger = mock(LineConsumer.class); - doReturn(machineLogger).when(manager).getMachineLogger(MACHINE_ID, "outputChannel"); + doReturn(machineLogger).when(manager).getMachineLogger(eq(MACHINE_ID), any(LineConsumer.class)); when(machineConfig.getSource()).thenReturn(machineSource); when(machineConfig.getName()).thenReturn("Name"); when(machineSource.getType()).thenReturn("dockerfile"); doThrow(ConflictException.class).when(machineRegistry).addMachine(any()); //when - manager.createMachineSync(machineConfig, "workspaceId", "environmentName"); + manager.createMachineSync(machineConfig, "workspaceId", "environmentName", logConsumer); //then verify(machineLogger).close(); @@ -285,7 +285,7 @@ public class MachineManagerTest { when(instanceProvider.createInstance(eq(machine), any(LineConsumer.class))).thenThrow(new SourceNotFoundException("")); when(machineRegistry.getMachine(MACHINE_ID)).thenReturn(machine); - final MachineImpl result = manager.recoverMachine(config, WS_ID, ENVIRONMENT_NAME); + final MachineImpl result = manager.recoverMachine(config, WS_ID, ENVIRONMENT_NAME, logConsumer); machine.getConfig().setSource(config.getSource()); assertEquals(result, machine); @@ -299,7 +299,7 @@ public class MachineManagerTest { when(instanceProvider.createInstance(any(MachineImpl.class), any(LineConsumer.class))).thenThrow(new MachineException("")); - manager.recoverMachine(config, WS_ID, ENVIRONMENT_NAME); + manager.recoverMachine(config, WS_ID, ENVIRONMENT_NAME, logConsumer); } private void waitForExecutorIsCompletedTask() throws Exception { diff --git a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/MachineServiceLinksInjectorTest.java b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/MachineServiceLinksInjectorTest.java index 524bb589b9..98887f1b81 100644 --- a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/MachineServiceLinksInjectorTest.java +++ b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/MachineServiceLinksInjectorTest.java @@ -36,6 +36,8 @@ import java.util.Set; import java.util.stream.Collectors; import static java.util.Arrays.asList; +import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL; +import static org.eclipse.che.api.machine.shared.Constants.ENVIRONMENT_STATUS_CHANNEL_TEMPLATE; import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_DESTROY_MACHINE; import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_EXECUTE_COMMAND; import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_MACHINES; @@ -103,7 +105,9 @@ public class MachineServiceLinksInjectorTest { Pair.of("GET", LINK_REL_GET_SNAPSHOTS), Pair.of("GET", LINK_REL_GET_PROCESSES), Pair.of("POST", LINK_REL_SAVE_SNAPSHOT), - Pair.of("GET", LINK_REL_GET_MACHINE_LOGS))); + Pair.of("GET", LINK_REL_GET_MACHINE_LOGS), + Pair.of("GET", LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL), + Pair.of("GET", ENVIRONMENT_STATUS_CHANNEL_TEMPLATE))); assertEquals(links, expectedLinks, "Difference " + Sets.symmetricDifference(links, expectedLinks) + "\n"); } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java index 2050b41dcf..577c493ea7 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java @@ -15,10 +15,12 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.inject.Inject; import org.eclipse.che.api.core.ApiException; +import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.machine.MachineConfig; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; @@ -424,6 +426,42 @@ public class WorkspaceManager { return normalizeState(workspace); } + /** + * Starts machine in running workspace + * + * @param machineConfig configuration of machine to start + * @param workspaceId id of workspace in which machine should be started + * @return starting machine instance + * @throws NotFoundException + * if machine type from recipe is unsupported + * @throws NotFoundException + * if no instance provider implementation found for provided machine type + * @throws ConflictException + * if machine with given name already exists + * @throws ConflictException + * if workspace is not in RUNNING state + * @throws BadRequestException + * if machine name is invalid + * @throws ServerException + * if any other exception occurs during starting + */ + public MachineImpl startMachine(MachineConfig machineConfig, String workspaceId) + throws ServerException, + ConflictException, + BadRequestException, + NotFoundException { + + final WorkspaceImpl workspace = getWorkspace(workspaceId); + if (RUNNING != workspace.getStatus()) { + throw new ConflictException(format("Workspace '%s' is not running, new machine can't be started", workspaceId)); + } + + return machineManager.createMachineAsync(machineConfig, + workspaceId, + workspace.getRuntime().getActiveEnv(), + runtimes.getMachineLogger(workspaceId, machineConfig.getName())); + } + /** * Asynchronously stops the workspace. * @@ -495,6 +533,19 @@ public class WorkspaceManager { return machineManager.getSnapshots(workspace.getNamespace(), workspaceId); } + /** + * Removes all snapshots of workspace machines + * + * @param workspaceId workspace id to remove machine snapshots + * @throws NotFoundException + * when workspace with given id doesn't exists + * @throws ServerException + * when any other error occurs + */ + public void removeSnapshots(String workspaceId) throws NotFoundException, ServerException { + machineManager.removeSnapshots(getWorkspace(workspaceId).getNamespace(), workspaceId); + } + /** Asynchronously starts given workspace. */ @VisibleForTesting WorkspaceImpl performAsyncStart(WorkspaceImpl workspace, diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimes.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimes.java index 751fa98cce..61c6b49be4 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimes.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimes.java @@ -19,15 +19,20 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; 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.MachineLogMessage; import org.eclipse.che.api.core.model.machine.MachineStatus; import org.eclipse.che.api.core.model.workspace.WorkspaceRuntime; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; +import org.eclipse.che.api.core.util.AbstractLineConsumer; +import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.core.util.WebsocketMessageConsumer; import org.eclipse.che.api.machine.server.MachineManager; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.machine.server.model.impl.MachineImpl; +import org.eclipse.che.api.machine.server.model.impl.MachineLogMessageImpl; import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; @@ -41,6 +46,7 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Singleton; +import java.io.IOException; import java.util.ArrayDeque; import java.util.HashMap; import java.util.Iterator; @@ -52,6 +58,7 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.function.Predicate; import static java.lang.String.format; +import static org.eclipse.che.api.machine.shared.Constants.ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE; import static org.eclipse.che.dto.server.DtoFactory.newDto; /** @@ -164,7 +171,7 @@ public class WorkspaceRuntimes { * @throws ServerException * when component {@link #isPreDestroyInvoked is stopped} or any * other error occurs during environment start - * @see MachineManager#createMachineSync(MachineConfig, String, String) + * @see MachineManager#createMachineSync(MachineConfig, String, String, org.eclipse.che.api.core.util.LineConsumer) * @see WorkspaceStatus#STARTING * @see WorkspaceStatus#RUNNING */ @@ -574,12 +581,15 @@ public class WorkspaceRuntimes { boolean recover) throws ServerException, NotFoundException, ConflictException { + + LineConsumer machineLogger = getMachineLogger(workspaceId, config.getName()); + MachineImpl machine; try { if (recover) { - machine = machineManager.recoverMachine(config, workspaceId, envName); + machine = machineManager.recoverMachine(config, workspaceId, envName, machineLogger); } else { - machine = machineManager.createMachineSync(config, workspaceId, envName); + machine = machineManager.createMachineSync(config, workspaceId, envName, machineLogger); } } catch (ConflictException x) { // The conflict is because of the already running machine @@ -614,6 +624,17 @@ public class WorkspaceRuntimes { return machine; } + protected LineConsumer getMachineLogger(String workspaceId, String machineName) throws ServerException { + WebsocketMessageConsumer envMessageConsumer = + new WebsocketMessageConsumer<>(format(ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE, workspaceId)); + return new AbstractLineConsumer() { + @Override + public void writeLine(String line) throws IOException { + envMessageConsumer.consume(new MachineLogMessageImpl(machineName, line)); + } + }; + } + /** * Wrapper for the {@link WorkspaceRuntime} instance. * Knows the state of the started workspace runtime, diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java index 477f792255..0f29997c5d 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java @@ -27,7 +27,6 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.rest.Service; import org.eclipse.che.api.core.rest.annotations.GenerateLink; -import org.eclipse.che.api.machine.server.MachineManager; import org.eclipse.che.api.machine.server.model.impl.CommandImpl; import org.eclipse.che.api.machine.server.model.impl.MachineImpl; import org.eclipse.che.api.machine.shared.dto.CommandDto; @@ -81,7 +80,6 @@ public class WorkspaceService extends Service { private final WorkspaceManager workspaceManager; private final WorkspaceValidator validator; - private final MachineManager machineManager; private final WorkspaceServiceLinksInjector linksInjector; @Context @@ -89,11 +87,9 @@ public class WorkspaceService extends Service { @Inject public WorkspaceService(WorkspaceManager workspaceManager, - MachineManager machineManager, WorkspaceValidator validator, WorkspaceServiceLinksInjector workspaceServiceLinksInjector) { this.workspaceManager = workspaceManager; - this.machineManager = machineManager; this.validator = validator; this.linksInjector = workspaceServiceLinksInjector; } @@ -240,7 +236,7 @@ public class WorkspaceService extends Service { ConflictException, ForbiddenException { if (!workspaceManager.getSnapshot(id).isEmpty()) { - machineManager.removeSnapshots(workspaceManager.getWorkspace(id).getNamespace(), id); + workspaceManager.removeSnapshots(id); } workspaceManager.removeWorkspace(id); } @@ -646,7 +642,8 @@ public class WorkspaceService extends Service { @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"), @ApiResponse(code = 403, message = "The user does not have access to create the new machine"), @ApiResponse(code = 409, message = "Conflict error occurred during the machine creation" + - "(e.g. The machine with such name already exists)"), + "(e.g. The machine with such name already exists)." + + "Workspace is not in RUNNING state"), @ApiResponse(code = 500, message = "Internal server error occurred")}) public Response createMachine(@ApiParam("The workspace id") @PathParam("id") @@ -665,14 +662,7 @@ public class WorkspaceService extends Service { requiredOnlyOneNotNull(machineConfig.getSource().getLocation(), machineConfig.getSource().getContent(), "Machine source should provide either location or content"); - final WorkspaceImpl workspace = workspaceManager.getWorkspace(workspaceId); - if (workspace.getRuntime() == null) { - throw new NotFoundException(format("Workspace '%s' is not running, new machine can't be started", workspaceId)); - } - - final MachineImpl machine = machineManager.createMachineAsync(machineConfig, - workspaceId, - workspace.getRuntime().getActiveEnv()); + final MachineImpl machine = workspaceManager.startMachine(machineConfig, workspaceId); return Response.status(201) .entity(linksInjector.injectMachineLinks(org.eclipse.che.api.machine.server.DtoConverter.asDto(machine), diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceServiceLinksInjector.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceServiceLinksInjector.java index f14175235f..edeb4d3d2d 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceServiceLinksInjector.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceServiceLinksInjector.java @@ -14,11 +14,9 @@ import org.eclipse.che.api.core.rest.ServiceContext; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.LinkParameter; import org.eclipse.che.api.machine.server.MachineServiceLinksInjector; -import org.eclipse.che.api.machine.shared.dto.MachineConfigDto; import org.eclipse.che.api.machine.shared.dto.MachineDto; import org.eclipse.che.api.machine.shared.dto.ServerDto; import org.eclipse.che.api.machine.shared.dto.SnapshotDto; -import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceRuntimeDto; @@ -31,12 +29,17 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import static java.lang.String.format; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.MediaType.TEXT_HTML; import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.RUNNING; import static org.eclipse.che.api.core.util.LinksHelper.createLink; +import static org.eclipse.che.api.machine.shared.Constants.ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE; +import static org.eclipse.che.api.machine.shared.Constants.ENVIRONMENT_STATUS_CHANNEL_TEMPLATE; +import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL; +import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_ENVIRONMENT_STATUS_CHANNEL; import static org.eclipse.che.api.machine.shared.Constants.TERMINAL_REFERENCE; import static org.eclipse.che.api.machine.shared.Constants.WSAGENT_REFERENCE; import static org.eclipse.che.api.machine.shared.Constants.WSAGENT_WEBSOCKET_REFERENCE; @@ -116,7 +119,7 @@ public class WorkspaceServiceLinksInjector { .build(); links.add(createLink("GET", ideUri.toString(), TEXT_HTML, LINK_REL_IDE_URL)); - // add workspace channel link + // add workspace channel links final Link workspaceChannelLink = createLink("GET", serviceContext.getBaseUriBuilder() .path("ws") @@ -132,14 +135,16 @@ public class WorkspaceServiceLinksInjector { .withParameters(singletonList( cloneDto(channelParameter).withDefaultValue("workspace:" + workspace.getId())))); - // add machine channels links to machines configs - workspace.getConfig() - .getEnvironments() - .stream() - .forEach(environmentDto -> injectMachineChannelsLinks(environmentDto, - workspace.getId(), - workspaceChannelLink, - channelParameter)); + links.add(cloneDto(workspaceChannelLink).withRel(LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL) + .withParameters(singletonList(cloneDto(channelParameter) + .withDefaultValue(format(ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE, + workspace.getId()))))); + + links.add(cloneDto(workspaceChannelLink).withRel(LINK_REL_ENVIRONMENT_STATUS_CHANNEL) + .withParameters(singletonList(cloneDto(channelParameter) + .withDefaultValue(format(ENVIRONMENT_STATUS_CHANNEL_TEMPLATE, + workspace.getId()))))); + // add links for running workspace injectRuntimeLinks(workspace, ideUri, uriBuilder); return workspace.withLinks(links); @@ -233,17 +238,4 @@ public class WorkspaceServiceLinksInjector { public MachineDto injectMachineLinks(MachineDto machine, ServiceContext serviceContext) { return machineLinksInjector.injectLinks(machine, serviceContext); } - - private void injectMachineChannelsLinks(EnvironmentDto environmentDto, - String workspaceId, - Link machineChannelLink, - LinkParameter channelParameter) { - for (MachineConfigDto machineConfigDto : environmentDto.getMachineConfigs()) { - machineLinksInjector.injectMachineChannelsLinks(machineConfigDto, - workspaceId, - environmentDto.getName(), - machineChannelLink, - channelParameter); - } - } } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java index 726fc9bae9..561a680f9e 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java @@ -15,6 +15,7 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.api.machine.server.MachineManager; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl; @@ -59,6 +60,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -637,6 +639,57 @@ public class WorkspaceManagerTest { verify(runtimes, timeout(2000)).start(workspace, workspace.getConfig().getDefaultEnv(), true); } + @Test + public void shouldBeAbleToRemoveMachinesSnapshots() throws Exception { + // given + String testWsId = "testWsId"; + String testNamespace = "testNamespace"; + WorkspaceImpl workspaceMock = mock(WorkspaceImpl.class); + doReturn(workspaceMock).when(workspaceManager).getWorkspace(testWsId); + when(workspaceMock.getNamespace()).thenReturn(testNamespace); + + // when + workspaceManager.removeSnapshots(testWsId); + + // then + verify(machineManager).removeSnapshots(testNamespace, testWsId); + } + + @Test + public void shouldBeAbleToStartMachineInRunningWs() throws Exception { + // given + String testWsId = "testWsId"; + String testActiveEnv = "testActiveEnv"; + WorkspaceImpl workspaceMock = mock(WorkspaceImpl.class); + doReturn(workspaceMock).when(workspaceManager).getWorkspace(testWsId); + WorkspaceRuntimeImpl wsRuntimeMock = mock(WorkspaceRuntimeImpl.class); + when(workspaceMock.getStatus()).thenReturn(RUNNING); + when(workspaceMock.getRuntime()).thenReturn(wsRuntimeMock); + when(wsRuntimeMock.getActiveEnv()).thenReturn(testActiveEnv); + LineConsumer lineConsumerMock = mock(LineConsumer.class); + MachineConfigImpl machineConfig = createConfig().getEnvironments().get(0).getMachineConfigs().get(0); + when(runtimes.getMachineLogger(testWsId, machineConfig.getName())).thenReturn(lineConsumerMock); + + // when + workspaceManager.startMachine(machineConfig, testWsId); + + // then + verify(machineManager).createMachineAsync(eq(machineConfig), eq(testWsId), eq(testActiveEnv), eq(lineConsumerMock)); + } + + @Test(expectedExceptions = ConflictException.class, expectedExceptionsMessageRegExp = "Workspace .* is not running, new machine can't be started") + public void shouldThrowExceptionOnStartMachineInNonRunningWs() throws Exception { + // given + String testWsId = "testWsId"; + WorkspaceImpl workspaceMock = mock(WorkspaceImpl.class); + doReturn(workspaceMock).when(workspaceManager).getWorkspace(testWsId); + when(workspaceMock.getStatus()).thenReturn(STARTING); + MachineConfigImpl machineConfig = createConfig().getEnvironments().get(0).getMachineConfigs().get(0); + + // when + workspaceManager.startMachine(machineConfig, testWsId); + } + private RuntimeDescriptor createDescriptor(WorkspaceImpl workspace, WorkspaceStatus status) { final WorkspaceRuntimeImpl runtime = new WorkspaceRuntimeImpl(workspace.getConfig().getDefaultEnv()); final String env = workspace.getConfig().getDefaultEnv(); diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java index 09c897e146..af6fd87ed4 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java @@ -17,6 +17,7 @@ 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.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.api.machine.server.MachineManager; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.model.impl.LimitsImpl; @@ -53,6 +54,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -90,7 +92,7 @@ public class WorkspaceRuntimesTest { @BeforeMethod public void setUp() throws Exception { - when(machineManager.createMachineSync(any(), any(), any())) + when(machineManager.createMachineSync(any(), any(), any(), any(LineConsumer.class))) .thenAnswer(invocation -> createMachine((MachineConfig)invocation.getArguments()[0])); runtimes = new WorkspaceRuntimes(machineManager, eventService); } @@ -117,7 +119,7 @@ public class WorkspaceRuntimesTest { final WorkspaceImpl workspace = createWorkspace(); // check if workspace in starting status before dev machine is started - when(machineManagerMock.createMachineSync(anyObject(), anyString(), anyString())) + when(machineManagerMock.createMachineSync(anyObject(), anyString(), anyString(), any(LineConsumer.class))) .thenAnswer(invocationOnMock -> { final RuntimeDescriptor descriptor = runtimes.get(workspace.getId()); final MachineConfig cfg = (MachineConfig)invocationOnMock.getArguments()[0]; @@ -129,7 +131,7 @@ public class WorkspaceRuntimesTest { runtimes.start(workspace, workspace.getConfig().getDefaultEnv(), false); - verify(machineManagerMock, times(2)).createMachineSync(anyObject(), anyString(), anyString()); + verify(machineManagerMock, times(2)).createMachineSync(anyObject(), anyString(), anyString(), any(LineConsumer.class)); } @Test @@ -137,7 +139,7 @@ public class WorkspaceRuntimesTest { final MachineManager machineManagerMock = mock(MachineManager.class); final WorkspaceRuntimes runtimes = new WorkspaceRuntimes(machineManagerMock, eventService); final WorkspaceImpl workspaceMock = createWorkspace(); - when(machineManagerMock.createMachineSync(any(), any(), any())) + when(machineManagerMock.createMachineSync(any(), any(), any(), any(LineConsumer.class))) .thenThrow(new MachineException("Creation error")); try { @@ -175,7 +177,7 @@ public class WorkspaceRuntimesTest { final WorkspaceRuntimes registry = new WorkspaceRuntimes(machineManagerMock, eventService); final WorkspaceImpl workspace = createWorkspace(); - when(machineManagerMock.createMachineSync(any(), any(), any())).thenAnswer(invocationOnMock -> { + when(machineManagerMock.createMachineSync(any(), any(), any(), any(LineConsumer.class))).thenAnswer(invocationOnMock -> { registry.stop(workspace.getId()); return createMachine((MachineConfig)invocationOnMock.getArguments()[0]); }); @@ -193,7 +195,7 @@ public class WorkspaceRuntimesTest { runtimes.stop(workspace.getId()); } return createMachine((MachineConfig)invocation.getArguments()[0]); - }).when(machineManager).createMachineSync(any(), anyString(), anyString()); + }).when(machineManager).createMachineSync(any(), anyString(), anyString(), any(LineConsumer.class)); try { runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); @@ -224,7 +226,7 @@ public class WorkspaceRuntimesTest { throw new MachineException("Failed to start"); } return createMachine((MachineConfig)invocation.getArguments()[0]); - }).when(machineManager).createMachineSync(any(), anyString(), anyString()); + }).when(machineManager).createMachineSync(any(), anyString(), anyString(), any(LineConsumer.class)); runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); @@ -232,7 +234,7 @@ public class WorkspaceRuntimesTest { final RuntimeDescriptor descriptor = runtimes.get(workspace.getId()); assertEquals(descriptor.getRuntime().getMachines().size(), 1); assertEquals(descriptor.getRuntimeStatus(), RUNNING); - verify(machineManager, times(2)).createMachineSync(any(), any(), any()); + verify(machineManager, times(2)).createMachineSync(any(), any(), any(), any(LineConsumer.class)); } @Test @@ -245,7 +247,7 @@ public class WorkspaceRuntimesTest { runtimes.cleanup(); } return createMachine((MachineConfig)invocation.getArguments()[0]); - }).when(machineManager).createMachineSync(any(), anyString(), anyString()); + }).when(machineManager).createMachineSync(any(), anyString(), anyString(), any(LineConsumer.class)); try { runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); @@ -323,7 +325,7 @@ public class WorkspaceRuntimesTest { doAnswer(invocation -> { verify(runtimes).publishEvent(EventType.STARTING, workspace.getId(), null); return null; - }).when(machineManager).createMachineSync(any(), any(), any()); + }).when(machineManager).createMachineSync(any(), any(), any(), any(LineConsumer.class)); runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); } @@ -340,7 +342,7 @@ public class WorkspaceRuntimesTest { verify(runtimes).publishEvent(EventType.RUNNING, workspace.getId(), null); } return createMachine(cfg); - }).when(machineManager).createMachineSync(any(), any(), any()); + }).when(machineManager).createMachineSync(any(), any(), any(), any(LineConsumer.class)); runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); } @@ -358,7 +360,7 @@ public class WorkspaceRuntimesTest { throw new MachineException("Start error"); } return createMachine(cfg); - }).when(machineManager).createMachineSync(any(), any(), any()); + }).when(machineManager).createMachineSync(any(), any(), any(), any(LineConsumer.class)); try { runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); @@ -652,9 +654,10 @@ public class WorkspaceRuntimesTest { // force machine manager to throw conflict exception final RuntimeDescriptor descriptorMock = mock(RuntimeDescriptor.class); when(descriptorMock.getRuntimeStatus()).thenReturn(WorkspaceStatus.RUNNING); - doThrow(new ConflictException("already exists")).when(machineManager).createMachineSync(machine.getConfig(), - machine.getWorkspaceId(), - workspace.getConfig().getDefaultEnv()); + doThrow(new ConflictException("already exists")).when(machineManager).createMachineSync(eq(machine.getConfig()), + eq(machine.getWorkspaceId()), + eq(workspace.getConfig().getDefaultEnv()), + any(LineConsumer.class)); final RuntimeDescriptor descriptor = runtimes.start(workspace, workspace.getConfig().getDefaultEnv()); diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java index 09c7f25013..cadd3cc763 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java @@ -73,6 +73,8 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.RUNNING; import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STARTING; +import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL; +import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_ENVIRONMENT_STATUS_CHANNEL; import static org.eclipse.che.api.machine.shared.Constants.WSAGENT_REFERENCE; import static org.eclipse.che.api.machine.shared.Constants.WSAGENT_WEBSOCKET_REFERENCE; import static org.eclipse.che.api.workspace.shared.Constants.GET_ALL_USER_WORKSPACES; @@ -127,7 +129,6 @@ public class WorkspaceServiceTest { @BeforeMethod public void setup() { service = new WorkspaceService(wsManager, - machineManager, validator, new WorkspaceServiceLinksInjector(new MachineServiceLinksInjector())); } @@ -295,7 +296,7 @@ public class WorkspaceServiceTest { .delete(SECURE_PATH + "/workspace/" + workspace.getId()); assertEquals(response.getStatusCode(), 204); - verify(machineManager).removeSnapshots(NAMESPACE, workspace.getId()); + verify(wsManager).removeSnapshots(workspace.getId()); verify(wsManager).removeWorkspace(workspace.getId()); } @@ -681,7 +682,9 @@ public class WorkspaceServiceTest { LINK_REL_GET_SNAPSHOT, LINK_REL_GET_WORKSPACE_EVENTS_CHANNEL, LINK_REL_IDE_URL, - LINK_REL_SELF)); + LINK_REL_SELF, + LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL, + LINK_REL_ENVIRONMENT_STATUS_CHANNEL)); assertTrue(actualRels.equals(expectedRels), format("Links difference: '%s'. \n" + "Returned links: '%s', \n" + "Expected links: '%s'.",