CHE-1370: refactor workspace environment bottstrapping and handling (#2108)

Signed-off-by: Alexander Garagatyi <agaragatyi@codenvy.com>
6.19.x
Alexander Garagatyi 2016-08-16 16:23:08 +03:00 committed by GitHub
parent cf56410975
commit 0e9718e016
101 changed files with 4950 additions and 4761 deletions

View File

@ -15,7 +15,6 @@ import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names;
import org.eclipse.che.api.machine.shared.Constants;
import org.eclipse.che.api.user.server.ProfileService;
import org.eclipse.che.inject.DynaModule;
/** @author andrew00x */
@ -62,7 +61,7 @@ public class WsMasterModule extends AbstractModule {
.toInstance("predefined-recipes.json");
bindConstant().annotatedWith(Names.named(org.eclipse.che.api.machine.server.wsagent.WsAgentLauncherImpl.WS_AGENT_PROCESS_START_COMMAND))
bindConstant().annotatedWith(Names.named(org.eclipse.che.api.agent.server.wsagent.WsAgentLauncherImpl.WS_AGENT_PROCESS_START_COMMAND))
.to("rm -rf ~/che && mkdir -p ~/che && unzip -qq /mnt/che/ws-agent.zip -d ~/che/ws-agent && " +
"sudo sh -c \"chown -R $(id -u -n) /projects || true\" && " +
"export JPDA_ADDRESS=\"4403\" && ~/che/ws-agent/bin/catalina.sh jpda run");
@ -75,10 +74,10 @@ public class WsMasterModule extends AbstractModule {
bind(org.eclipse.che.api.workspace.server.event.MachineStateListener.class).asEagerSingleton();
bind(org.eclipse.che.api.machine.server.wsagent.WsAgentLauncher.class)
.to(org.eclipse.che.api.machine.server.wsagent.WsAgentLauncherImpl.class);
bind(org.eclipse.che.api.agent.server.wsagent.WsAgentLauncher.class)
.to(org.eclipse.che.api.agent.server.wsagent.WsAgentLauncherImpl.class);
bind(org.eclipse.che.api.machine.server.terminal.MachineTerminalLauncher.class);
bind(org.eclipse.che.api.agent.server.terminal.MachineTerminalLauncher.class);
bind(org.eclipse.che.api.deploy.WsMasterAnalyticsAddresser.class);
Multibinder<org.eclipse.che.api.machine.server.spi.InstanceProvider> machineImageProviderMultibinder =

View File

@ -18,8 +18,7 @@ import org.eclipse.che.api.core.rest.annotations.Required;
import org.eclipse.che.api.core.rest.annotations.Valid;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.api.core.rest.shared.dto.ServiceDescriptor;
import org.eclipse.che.commons.test.SelfReturningAnswer;
import org.eclipse.che.dto.server.DtoFactory;
import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer;
import org.everrest.assured.EverrestJetty;
import org.mockito.Mock;
import org.mockito.Spy;
@ -144,4 +143,4 @@ public class RemoteServiceDescriptorTest {
private String getServerUrl(ITestContext ctx) {
return "http://localhost:" + ctx.getAttribute(EverrestJetty.JETTY_PORT) + "/rest";
}
}
}

View File

@ -26,6 +26,4 @@ public interface Recipe {
* Returns recipe script, which is used to instantiate new machine
*/
String getScript();
}
}

View File

@ -10,16 +10,8 @@
*******************************************************************************/
package org.eclipse.che.api.core.model.workspace;
import org.eclipse.che.api.core.model.machine.MachineStatus;
/**
* Defines the contract between workspace and its dev-machine.
*
* <p>Workspace status itself shows the state of the workspace dev-machine.
* If workspace environment which is running contains not only the dev-machine, those machines
* states won't affect workspace status at all.
*
* <p>{@link MachineStatus} is responsible for states of machines different from the dev-machine.
* Defines the contract between workspace and its active environment.
*
* <p>Workspace is rather part of the {@link Workspace} than {@link WorkspaceRuntime} or {@link WorkspaceConfig},
* as it shows the state of <b>certain</b> user's workspace and exists <b>earlier</b> than runtime workspace instance
@ -31,7 +23,7 @@ import org.eclipse.che.api.core.model.machine.MachineStatus;
public enum WorkspaceStatus {
/**
* Workspace considered as starting if and only if its dev-machine is booting(creating).
* Workspace considered as starting if and only if its active environment is booting.
*
* <p>Workspace becomes starting only if it was {@link #STOPPED}.
* The status map:
@ -39,45 +31,39 @@ public enum WorkspaceStatus {
* STOPPED -> <b>STARTING</b> -> RUNNING (normal behaviour)
* STOPPED -> <b>STARTING</b> -> STOPPED (failed to start)
* </pre>
*
* @see MachineStatus#CREATING
*/
STARTING,
/**
* Workspace considered as running if and only if its dev-machine was successfully started and it is running.
* Workspace considered as running if and only if its environment is running.
*
* <p>Workspace becomes running after it was {@link #STARTING}.
* The status map:
* <pre>
* STARTING -> <b>RUNNING</b> -> STOPPING (normal behaviour)
* STARTING -> <b>RUNNING</b> -> STOPPED (dev-machine was interrupted)
* STARTING -> <b>RUNNING</b> -> STOPPED (environment start was interrupted)
* </pre>
*
* @see MachineStatus#RUNNING
*/
RUNNING,
/**
* Workspace considered as stopping if and only if its dev-machine is shutting down(destroying).
* Workspace considered as stopping if and only if its active environment is shutting down.
*
* <p>Workspace is in stopping status only if it was in {@link #RUNNING} status before.
* The status map:
* <pre>
* RUNNING -> <b>STOPPING</b> -> STOPPED (normal behaviour)/(error while stopping)
* </pre>
*
* @see MachineStatus#DESTROYING
*/
STOPPING,
/**
* Workspace considered as stopped when:
* <ul>
* <li>Dev-machine was successfully destroyed(stopped)</li>
* <li>Error occurred while dev-machine was stopping</li>
* <li>Environment was successfully stopped</li>
* <li>Error occurred while environment was stopping</li>
* <li>Dev-machine failed to start</li>
* <li>Running dev-machine was interrupted by internal problem(e.g. OOM)</li>
* <li>Running environment machine was stopped by internal problem(e.g. OOM of a machine)</li>
* <li>Workspace hasn't been started yet(e.g stopped is the status of the user's workspace instance without its runtime)</li>
* </ul>
*
@ -85,7 +71,7 @@ public enum WorkspaceStatus {
* <pre>
* STOPPING -> <b>STOPPED</b> (normal behaviour)/(error while stopping)
* STARTING -> <b>STOPPED</b> (failed to start)
* RUNNING -> <b>STOPPED</b> (dev-machine was interrupted)
* RUNNING -> <b>STOPPED</b> (environment machine was interrupted)
* </pre>
*/
STOPPED

View File

@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.commons.test;
package org.eclipse.che.commons.test.mockito.answer;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;

View File

@ -0,0 +1,160 @@
/*******************************************************************************
* 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.commons.test.mockito.answer;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Answer class that helps to lock execution of test in separate threads for testing purposes.
* <p/>
* It can hold waiting thread until this answer is called.<br/>
* It can hold waiting thread that uses mock until answering to mock call is allowed.
*
* Here is an example of complex test that ensures that (for example) locked area is not called
* when other thread try to access it.
* <pre class="code"><code class="java">
* // given
* WaitingAnswer<Void> waitingAnswer = new WaitingAnswer<>();
* doAnswer(waitingAnswer).when(someClassUsedInTestedClass).someMethod(eq(param1), eq(param2));
*
* // start doing something in a separate thread
* executor.execute(() -> testedClass.doSomething());
* // wait until separate thread call answer to find the moment when critical area is occupied
* // to make test fast wait not more that provided timeout
* waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS);
*
* // when
* try {
* // start doing something in current thread
* testedClass.doSomething()
* // this area should not be reachable until answer is completed!
* fail("Error message");
* } finally {
* // In this case exception can be suppressed
* // Test is simplified to provide clean example
*
* // then
* // complete waiting answer
* waitingAnswer.completeAnswer();
* // ensure that someMethod is called only once to confirm that testedClass.doSomething()
* // doesn't call it this particular test
* verify(someClassUsedInTestedClass, timeout(100).times(1)).someMethod(any(), any());
* }
* </code></pre>
*
* @author Alexander Garagatyi
*/
public class WaitingAnswer<T> implements Answer<T> {
private final CountDownLatch answerIsCalledLatch;
private final CountDownLatch answerResultIsUnlockedLatch;
private long maxWaitingTime;
private TimeUnit maxWaitingUnit;
private T result;
private volatile String error;
public WaitingAnswer() {
this.maxWaitingTime = 1;
this.maxWaitingUnit = TimeUnit.SECONDS;
this.result = null;
this.answerIsCalledLatch = new CountDownLatch(1);
this.answerResultIsUnlockedLatch = new CountDownLatch(1);
this.error = null;
}
public WaitingAnswer(long maxWaitingTime, TimeUnit maxWaitingUnit) {
this();
this.maxWaitingTime = maxWaitingTime;
this.maxWaitingUnit = maxWaitingUnit;
}
public WaitingAnswer(T result) {
this();
this.result = result;
}
public WaitingAnswer(T result,
long maxWaitingTime,
TimeUnit maxWaitingUnit) {
this();
this.result = result;
this.maxWaitingTime = maxWaitingTime;
this.maxWaitingUnit = maxWaitingUnit;
}
/**
* Waits until answer is called in method {@link #answer(InvocationOnMock)}.
*
* @param maxWaitingTime
* max time to wait
* @param maxWaitingUnit
* time unit of the max waiting time argument
* @throws Exception
* if the waiting time elapsed before this answer is called
* @see #answer(InvocationOnMock)
*/
public void waitAnswerCall(long maxWaitingTime, TimeUnit maxWaitingUnit) throws Exception {
if (!answerIsCalledLatch.await(maxWaitingTime, maxWaitingUnit)) {
error = "Waiting time elapsed but answer is not called";
throw new Exception(error);
}
}
/**
* Stops process of waiting returning result of answer in method {@link #answer(InvocationOnMock)}.
*
* @throws Exception
* if this answer waiting time elapsed before this method is called
* @see #answer(InvocationOnMock)
*/
public void completeAnswer() throws Exception {
answerResultIsUnlockedLatch.countDown();
if (error != null) {
throw new Exception(error);
}
}
/**
* Stops waiting until answer is called in method {@link #waitAnswerCall(long, TimeUnit)} and
* then waits until method {@link #completeAnswer()} is called.
*
* @param invocationOnMock
* see {@link Answer#answer(InvocationOnMock)}
* @return returns answer result if provided in constructor or null otherwise
* @throws Exception
* if answer call or answer result waiting time is elapsed
* @throws Throwable
* in the same cases as in {@link Answer#answer(InvocationOnMock)}
* @see #waitAnswerCall(long, TimeUnit)
* @see #completeAnswer()
* @see Answer#answer(InvocationOnMock)
*/
@Override
public T answer(InvocationOnMock invocationOnMock) throws Throwable {
// report start of answer call
answerIsCalledLatch.countDown();
if (error != null) {
throw new Exception(error);
}
// wait until another thread unlocks returning of answer
if (!answerResultIsUnlockedLatch.await(maxWaitingTime, maxWaitingUnit)) {
error = "Waiting time elapsed but completeAnswer is not called";
throw new Exception(error);
}
return result;
}
}

View File

@ -30,11 +30,13 @@ public interface MachineServiceClient {
/**
* Get machine information by it's id.
*
* @param workspaceId
* ID of workspace
* @param machineId
* ID of the machine
* @return a promise that resolves to the {@link MachineDto}, or rejects with an error
*/
Promise<MachineDto> getMachine(@NotNull String machineId);
Promise<MachineDto> getMachine(@NotNull String workspaceId, @NotNull String machineId);
/**
* Returns list of machines which are bounded to the specified workspace.
@ -48,15 +50,19 @@ public interface MachineServiceClient {
/**
* Destroy machine with the specified ID.
*
* @param workspaceId
* ID of workspace
* @param machineId
* ID of machine that should be destroyed
* @return a promise that will resolve when the machine has been destroyed, or rejects with an error
*/
Promise<Void> destroyMachine(@NotNull String machineId);
Promise<Void> destroyMachine(@NotNull String workspaceId, @NotNull String machineId);
/**
* Execute a command in machine.
*
* @param workspaceId
* ID of workspace
* @param machineId
* ID of the machine where command should be executed
* @param command
@ -65,42 +71,34 @@ public interface MachineServiceClient {
* websocket chanel for execution logs
* @return a promise that resolves to the {@link MachineProcessDto}, or rejects with an error
*/
Promise<MachineProcessDto> executeCommand(@NotNull String machineId,
Promise<MachineProcessDto> executeCommand(@NotNull String workspaceId,
@NotNull String machineId,
@NotNull Command command,
@Nullable String outputChannel);
/**
* Get processes from the specified machine.
*
* @param workspaceId
* ID of workspace
* @param machineId
* ID of machine to get processes information from
* @return a promise that will provide a list of {@link MachineProcessDto}s for the given machine ID
*/
Promise<List<MachineProcessDto>> getProcesses(@NotNull String machineId);
Promise<List<MachineProcessDto>> getProcesses(@NotNull String workspaceId, @NotNull String machineId);
/**
* Stop process in machine.
*
* @param workspaceId
* ID of workspace
* @param machineId
* ID of the machine where process should be stopped
* @param processId
* ID of the process to stop
* @return a promise that will resolve when the process has been stopped, or rejects with an error
*/
Promise<Void> stopProcess(@NotNull String machineId, int processId);
/**
* Get file content.
*
* @param machineId
* ID of the machine
* @param path
* path to file on machine instance
* @param startFrom
* line number to start reading from
* @param limit
* limitation on line
* @return a promise that will provide the file content, or rejects with an error
*/
Promise<String> getFileContent(@NotNull String machineId, @NotNull String path, int startFrom, int limit);
Promise<Void> stopProcess(@NotNull String workspaceId,
@NotNull String machineId,
int processId);
}

View File

@ -20,7 +20,6 @@ import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.ide.rest.AsyncRequestFactory;
import org.eclipse.che.ide.rest.DtoUnmarshallerFactory;
import org.eclipse.che.ide.rest.RestContext;
import org.eclipse.che.ide.rest.StringUnmarshaller;
import org.eclipse.che.ide.ui.loaders.request.LoaderFactory;
import javax.validation.constraints.NotNull;
@ -50,12 +49,14 @@ public class MachineServiceClientImpl implements MachineServiceClient {
this.dtoUnmarshallerFactory = dtoUnmarshallerFactory;
this.asyncRequestFactory = asyncRequestFactory;
this.loaderFactory = loaderFactory;
this.baseHttpUrl = restContext + "/machine";
this.baseHttpUrl = restContext + "/workspace/";
}
@Override
public Promise<MachineDto> getMachine(@NotNull final String machineId) {
return asyncRequestFactory.createGetRequest(baseHttpUrl + '/' + machineId)
public Promise<MachineDto> getMachine(@NotNull final String workspaceId,
@NotNull final String machineId) {
return asyncRequestFactory.createGetRequest(baseHttpUrl + workspaceId +
"/machine/" + machineId)
.header(ACCEPT, APPLICATION_JSON)
.loader(loaderFactory.newLoader("Getting info about machine..."))
.send(dtoUnmarshallerFactory.newUnmarshaller(MachineDto.class));
@ -63,50 +64,57 @@ public class MachineServiceClientImpl implements MachineServiceClient {
@Override
public Promise<List<MachineDto>> getMachines(@NotNull String workspaceId) {
return asyncRequestFactory.createGetRequest(baseHttpUrl + "?workspace=" + workspaceId)
return asyncRequestFactory.createGetRequest(baseHttpUrl + workspaceId + "/machine")
.header(ACCEPT, APPLICATION_JSON)
.loader(loaderFactory.newLoader("Getting info about bound machines..."))
.send(dtoUnmarshallerFactory.newListUnmarshaller(MachineDto.class));
}
@Override
public Promise<Void> destroyMachine(@NotNull final String machineId) {
return asyncRequestFactory.createRequest(DELETE, baseHttpUrl + '/' + machineId, null, false)
public Promise<Void> destroyMachine(@NotNull final String workspaceId,
@NotNull final String machineId) {
return asyncRequestFactory.createRequest(DELETE,
baseHttpUrl + workspaceId +
"/machine/" + machineId,
null,
false)
.loader(loaderFactory.newLoader("Destroying machine..."))
.send();
}
@Override
public Promise<MachineProcessDto> executeCommand(@NotNull final String machineId,
public Promise<MachineProcessDto> executeCommand(@NotNull final String workspaceId,
@NotNull final String machineId,
@NotNull final Command command,
@Nullable final String outputChannel) {
return asyncRequestFactory.createPostRequest(baseHttpUrl + '/' + machineId + "/command?outputChannel=" + outputChannel, command)
return asyncRequestFactory.createPostRequest(baseHttpUrl + workspaceId +
"/machine/" + machineId +
"/command?outputChannel=" + outputChannel,
command)
.header(ACCEPT, APPLICATION_JSON)
.loader(loaderFactory.newLoader("Executing command..."))
.send(dtoUnmarshallerFactory.newUnmarshaller(MachineProcessDto.class));
}
@Override
public Promise<List<MachineProcessDto>> getProcesses(@NotNull final String machineId) {
return asyncRequestFactory.createGetRequest(baseHttpUrl + "/" + machineId + "/process")
public Promise<List<MachineProcessDto>> getProcesses(@NotNull final String workspaceId,
@NotNull final String machineId) {
return asyncRequestFactory.createGetRequest(baseHttpUrl + workspaceId +
"/machine/" + machineId +
"/process")
.header(ACCEPT, APPLICATION_JSON)
.loader(loaderFactory.newLoader("Getting machine processes..."))
.send(dtoUnmarshallerFactory.newListUnmarshaller(MachineProcessDto.class));
}
@Override
public Promise<Void> stopProcess(@NotNull final String machineId, final int processId) {
return asyncRequestFactory.createDeleteRequest(baseHttpUrl + '/' + machineId + "/process/" + processId)
public Promise<Void> stopProcess(@NotNull final String workspaceId,
@NotNull final String machineId,
final int processId) {
return asyncRequestFactory.createDeleteRequest(baseHttpUrl + workspaceId +
"/machine/" + machineId +
"/process/" + processId)
.loader(loaderFactory.newLoader("Stopping process..."))
.send();
}
@Override
public Promise<String> getFileContent(@NotNull final String machineId, final @NotNull String path, final int startFrom,
final int limit) {
String url = baseHttpUrl + "/" + machineId + "/filepath/" + path + "?startFrom=" + startFrom + "&limit=" + limit;
return asyncRequestFactory.createGetRequest(url)
.loader(loaderFactory.newLoader("Loading file content..."))
.send(new StringUnmarshaller());
}
}

View File

@ -27,7 +27,7 @@ import org.eclipse.che.ide.api.workspace.WorkspaceServiceClient;
import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto;
import org.eclipse.che.commons.test.SelfReturningAnswer;
import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.workspace.DefaultWorkspaceComponent;
import org.eclipse.che.ide.workspace.WorkspaceComponent;

View File

@ -18,7 +18,7 @@ import com.google.gson.GsonBuilder;
import org.eclipse.che.commons.json.JsonParseException;
import org.eclipse.che.commons.lang.ws.rs.ExtMediaType;
import org.eclipse.che.commons.test.SelfReturningAnswer;
import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer;
import org.eclipse.che.dto.server.DtoFactory;
import org.eclipse.che.plugin.docker.client.connection.CloseConnectionInputStream;
import org.eclipse.che.plugin.docker.client.connection.DockerConnection;

View File

@ -211,11 +211,11 @@ public class DockerInstance extends AbstractInstance {
}
@Override
public MachineSource saveToSnapshot(String owner) throws MachineException {
public MachineSource saveToSnapshot() throws MachineException {
try {
String image = generateRepository();
if(!snapshotUseRegistry) {
commitContainer(owner, image, LATEST_TAG);
commitContainer(image, LATEST_TAG);
return new DockerMachineSource(image).withTag(LATEST_TAG);
}
@ -224,7 +224,7 @@ public class DockerInstance extends AbstractInstance {
.withTag(LATEST_TAG);
final String fullRepo = pushParams.getFullRepo();
commitContainer(owner, fullRepo, LATEST_TAG);
commitContainer(fullRepo, LATEST_TAG);
//TODO fix this workaround. Docker image is not visible after commit when using swarm
Thread.sleep(2000);
final ProgressLineFormatterImpl lineFormatter = new ProgressLineFormatterImpl();
@ -246,10 +246,9 @@ public class DockerInstance extends AbstractInstance {
}
@VisibleForTesting
void commitContainer(String owner, String repository, String tag) throws IOException {
void commitContainer(String repository, String tag) throws IOException {
String comment = format("Suspended at %1$ta %1$tb %1$td %1$tT %1$tZ %1$tY",
System.currentTimeMillis());
comment = owner == null ? comment : comment + " by " + owner;
// !! We SHOULD NOT pause container before commit because all execs will fail
// to push image to private registry it should be tagged with registry in repo name
// https://docs.docker.com/reference/api/docker_remote_api_v1.16/#push-an-image-on-the-registry

View File

@ -79,7 +79,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -506,7 +505,7 @@ public class DockerInstanceProvider implements InstanceProvider {
final String imageName,
final LineConsumer outputConsumer)
throws MachineException {
Optional<String> containerIdOptional = null;
String containerIdCopy = null;
try {
final Map<String, Map<String, String>> portsToExpose;
final String[] volumes;
@ -558,7 +557,7 @@ public class DockerInstanceProvider implements InstanceProvider {
final String containerId = docker.createContainer(CreateContainerParams.create(config)
.withContainerName(containerName))
.getId();
containerIdOptional = Optional.ofNullable(containerId);
containerIdCopy = containerId;
docker.startContainer(StartContainerParams.create(containerId));
@ -593,7 +592,9 @@ public class DockerInstanceProvider implements InstanceProvider {
machine.getId(), containerId, node.getHost());
}
dockerInstanceStopDetector.startDetection(containerId, machine.getId());
dockerInstanceStopDetector.startDetection(containerId,
machine.getId(),
machine.getWorkspaceId());
return dockerMachineFactory.createInstance(machine,
containerId,
@ -601,18 +602,17 @@ public class DockerInstanceProvider implements InstanceProvider {
node,
outputConsumer);
} catch (IOException e) {
cleanUpContainer(containerIdOptional);
cleanUpContainer(containerIdCopy);
throw new MachineException(e.getLocalizedMessage(), e);
} catch (MachineException e) {
cleanUpContainer(containerIdOptional);
cleanUpContainer(containerIdCopy);
throw e;
}
}
private void cleanUpContainer(Optional<String> containerIdOptional) {
private void cleanUpContainer(@Nullable String containerId) {
try {
if (containerIdOptional.isPresent()) {
String containerId = containerIdOptional.get();
if (containerId != null) {
docker.removeContainer(RemoveContainerParams.create(containerId).withRemoveVolumes(true).withForce(true));
}
} catch (Exception ex) {

View File

@ -16,6 +16,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.machine.server.event.InstanceStateEvent;
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.plugin.docker.client.DockerConnector;
import org.eclipse.che.plugin.docker.client.MessageProcessor;
import org.eclipse.che.plugin.docker.client.json.Event;
@ -43,10 +44,10 @@ import java.util.concurrent.TimeUnit;
public class DockerInstanceStopDetector {
private static final Logger LOG = LoggerFactory.getLogger(DockerInstanceStopDetector.class);
private final EventService eventService;
private final DockerConnector dockerConnector;
private final ExecutorService executorService;
private final Map<String, String> instances;
private final EventService eventService;
private final DockerConnector dockerConnector;
private final ExecutorService executorService;
private final Map<String, Pair<String, String>> instances;
/*
Helps differentiate container main process OOM from other processes OOM
Algorithm:
@ -59,7 +60,7 @@ public class DockerInstanceStopDetector {
That's why cache expires in X seconds.
X was set as 10 empirically.
*/
private final Cache<String, String> containersOomTimestamps;
private final Cache<String, String> containersOomTimestamps;
private long lastProcessedEventDate = 0;
@ -84,9 +85,13 @@ public class DockerInstanceStopDetector {
* id of a container to start detection for
* @param machineId
* id of a machine which container implements
* @param workspaceId
* id of a workspace that owns machine
*/
public void startDetection(String containerId, String machineId) {
instances.put(containerId, machineId);
public void startDetection(String containerId,
String machineId,
String workspaceId) {
instances.put(containerId, Pair.of(machineId, workspaceId));
}
/**
@ -140,9 +145,11 @@ public class DockerInstanceStopDetector {
} else {
instanceStateChangeType = InstanceStateEvent.Type.DIE;
}
final String instanceId = instances.get(message.getId());
if (instanceId != null) {
eventService.publish(new InstanceStateEvent(instanceId, instanceStateChangeType));
Pair<String, String> instanceIds = instances.get(message.getId());
if (instanceIds != null) {
eventService.publish(new InstanceStateEvent(instanceIds.first,
instanceIds.second,
instanceStateChangeType));
lastProcessedEventDate = message.getTime();
}
break;

View File

@ -12,7 +12,7 @@ package org.eclipse.che.plugin.docker.machine;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.terminal.MachineImplSpecificTerminalLauncher;
import org.eclipse.che.api.agent.server.terminal.MachineImplSpecificTerminalLauncher;
import org.eclipse.che.plugin.docker.client.DockerConnector;
import org.eclipse.che.plugin.docker.client.Exec;
import org.eclipse.che.plugin.docker.client.LogMessage;

View File

@ -13,7 +13,8 @@ package org.eclipse.che.plugin.docker.machine.cleaner;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.che.api.machine.server.MachineRegistry;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.environment.server.CheEnvironmentEngine;
import org.eclipse.che.commons.schedule.ScheduleRate;
import org.eclipse.che.plugin.docker.client.DockerConnector;
import org.eclipse.che.plugin.docker.client.json.ContainerListEntry;
@ -39,15 +40,16 @@ public class DockerContainerCleaner implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(DockerContainerCleaner.class);
private final MachineRegistry machineRegistry;
// TODO replace with WorkspaceManager
private final CheEnvironmentEngine environmentEngine;
private final DockerConnector dockerConnector;
private final DockerContainerNameGenerator nameGenerator;
@Inject
public DockerContainerCleaner(MachineRegistry machineRegistry,
public DockerContainerCleaner(CheEnvironmentEngine environmentEngine,
DockerConnector dockerConnector,
DockerContainerNameGenerator nameGenerator) {
this.machineRegistry = machineRegistry;
this.environmentEngine = environmentEngine;
this.dockerConnector = dockerConnector;
this.nameGenerator = nameGenerator;
}
@ -60,8 +62,17 @@ public class DockerContainerCleaner implements Runnable {
try {
for (ContainerListEntry container : dockerConnector.listContainers()) {
Optional<ContainerNameInfo> optional = nameGenerator.parse(container.getNames()[0]);
if (optional.isPresent() && !machineRegistry.isExist(optional.get().getMachineId())) {
cleanUp(container);
if (optional.isPresent()) {
try {
// container is orphaned if not found exception is thrown
environmentEngine.getMachine(optional.get().getWorkspaceId(),
optional.get().getMachineId());
} catch (NotFoundException e) {
cleanUp(container);
} catch (Exception e) {
LOG.error("Failed to clean up inactive container. " + e.getLocalizedMessage(), e);
}
}
}
} catch (IOException e) {

View File

@ -15,7 +15,7 @@ import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names;
import org.eclipse.che.api.core.model.machine.ServerConf;
import org.eclipse.che.api.machine.server.terminal.MachineImplSpecificTerminalLauncher;
import org.eclipse.che.api.agent.server.terminal.MachineImplSpecificTerminalLauncher;
import org.eclipse.che.plugin.docker.machine.DockerMachineImplTerminalLauncher;
import org.eclipse.che.plugin.docker.machine.ext.provider.TerminalServerConfProvider;

View File

@ -15,7 +15,7 @@ import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names;
import org.eclipse.che.api.machine.server.MachineService;
import org.eclipse.che.api.environment.server.MachineService;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.spi.InstanceProcess;
import org.eclipse.che.plugin.docker.machine.DockerInstance;

View File

@ -139,9 +139,9 @@ public class DockerInstanceTest {
@Test
public void shouldCreateDockerImageLocally() throws Exception {
final String comment = format("Suspended at %1$ta %1$tb %1$td %1$tT %1$tZ %1$tY",
System.currentTimeMillis()) + " by " + OWNER;
System.currentTimeMillis());
dockerInstance.commitContainer(OWNER, REPOSITORY, TAG);
dockerInstance.commitContainer(REPOSITORY, TAG);
verify(dockerConnectorMock, times(1)).commit(CommitParams.create(CONTAINER)
.withRepository(REPOSITORY)
@ -151,7 +151,7 @@ public class DockerInstanceTest {
@Test
public void shouldSaveDockerInstanceStateIntoLocalImage() throws Exception {
final MachineSource result = dockerInstance.saveToSnapshot(OWNER);
final MachineSource result = dockerInstance.saveToSnapshot();
assertTrue(result instanceof DockerMachineSource);
DockerMachineSource dockerMachineSource = (DockerMachineSource) result;
@ -173,7 +173,7 @@ public class DockerInstanceTest {
dockerInstance = getDockerInstance(getMachine(), REGISTRY, CONTAINER, IMAGE, true);
when(dockerConnectorMock.push(any(PushParams.class), any(ProgressMonitor.class))).thenReturn(digest);
final MachineSource result = dockerInstance.saveToSnapshot(OWNER);
final MachineSource result = dockerInstance.saveToSnapshot();
assertTrue(result instanceof DockerMachineSource);
DockerMachineSource dockerMachineSource = (DockerMachineSource) result;
@ -186,7 +186,7 @@ public class DockerInstanceTest {
public void shouldThrowMachineExceptionWhenDockerCommitFailed() throws Exception{
when(dockerConnectorMock.commit(any(CommitParams.class))).thenThrow(new IOException("err"));
dockerInstance.saveToSnapshot(OWNER);
dockerInstance.saveToSnapshot();
}
@Test(expectedExceptions = MachineException.class)
@ -195,7 +195,7 @@ public class DockerInstanceTest {
when(dockerConnectorMock.push(any(PushParams.class),
any(ProgressMonitor.class))).thenThrow(new IOException("err"));
dockerInstance.saveToSnapshot(OWNER);
dockerInstance.saveToSnapshot();
}
private DockerInstance getDockerInstance() {

View File

@ -10,9 +10,10 @@
*******************************************************************************/
package org.eclipse.che.plugin.docker.machine.cleaner;
import org.eclipse.che.api.machine.server.MachineRegistry;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.environment.server.CheEnvironmentEngine;
import org.eclipse.che.api.machine.server.model.impl.MachineImpl;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.plugin.docker.client.DockerConnector;
import org.eclipse.che.plugin.docker.client.json.ContainerListEntry;
import org.eclipse.che.plugin.docker.client.params.RemoveContainerParams;
@ -28,8 +29,8 @@ import org.testng.annotations.Test;
import java.io.IOException;
import java.util.Optional;
import static java.util.Optional.of;
import static java.util.Arrays.asList;
import static java.util.Optional.of;
import static org.eclipse.che.plugin.docker.machine.DockerContainerNameGenerator.ContainerNameInfo;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.never;
@ -64,12 +65,14 @@ public class DockerContainerCleanerTest {
private static final String RUNNING_STATUS = "Up 6 hour ago";
@Mock
private MachineRegistry machineRegistry;
private CheEnvironmentEngine environmentEngine;
@Mock
private DockerConnector dockerConnector;
@Mock
private DockerContainerNameGenerator nameGenerator;
@Mock
private Instance instance;
@Mock
private MachineImpl machineImpl1;
@Mock
@ -93,8 +96,9 @@ public class DockerContainerCleanerTest {
private DockerContainerCleaner cleaner;
@BeforeMethod
public void setUp() throws MachineException, IOException {
when(machineRegistry.isExist(machineId1)).thenReturn(true);
public void setUp() throws Exception {
when(environmentEngine.getMachine(workspaceId1, machineId1)).thenReturn(instance);
when(environmentEngine.getMachine(workspaceId2, machineId2)).thenThrow(new NotFoundException("test"));
when(machineImpl1.getId()).thenReturn(machineId1);
when(machineImpl1.getWorkspaceId()).thenReturn(workspaceId1);
@ -121,27 +125,30 @@ public class DockerContainerCleanerTest {
when(containerNameInfo2.getMachineId()).thenReturn(machineId2);
when(containerNameInfo2.getWorkspaceId()).thenReturn(workspaceId2);
when(containerNameInfo3.getMachineId()).thenReturn(machineId2);
when(containerNameInfo3.getWorkspaceId()).thenReturn(workspaceId2);
}
@Test
public void cleanerShouldKillAndRemoveContainerIfThisContainerIsRunningAndContainerNameInfoIsNotEmptyAndContainerIsNotExistInTheAPI()
throws MachineException, IOException {
throws Exception {
cleaner.run();
verify(dockerConnector).listContainers();
verify(nameGenerator, times(3)).parse(anyString());
verify(machineRegistry, times(3)).isExist(anyString());
verify(environmentEngine, times(3)).getMachine(anyString(), anyString());
verify(dockerConnector, times(2)).killContainer(anyString());
verify(dockerConnector, times(2)).removeContainer(Matchers.<RemoveContainerParams>anyObject());
verify(dockerConnector, times(2)).removeContainer(Matchers.anyObject());
verify(dockerConnector, never()).killContainer(containerId1);
verify(dockerConnector, never()).removeContainer(RemoveContainerParams.create(containerId1).withForce(true).withRemoveVolumes(true));
}
@Test
public void cleanerShouldRemoveButShouldNotKillContainerWithStatusNotRunning() throws IOException, MachineException {
public void cleanerShouldRemoveButShouldNotKillContainerWithStatusNotRunning() throws Exception {
when(container2.getStatus()).thenReturn(EXITED_STATUS);
cleaner.run();
@ -150,14 +157,14 @@ public class DockerContainerCleanerTest {
}
@Test
public void cleanerShouldNotKillAndRemoveContainerIfMachineManagerDetectedExistingThisContainerInTheAPI() throws IOException {
when(machineRegistry.isExist(anyString())).thenReturn(true);
public void cleanerShouldNotKillAndRemoveContainerIfMachineManagerDetectedExistingThisContainerInTheAPI() throws Exception {
when(environmentEngine.getMachine(anyString(), anyString())).thenReturn(instance);
cleaner.run();
verify(dockerConnector, never()).killContainer(anyString());
verify(dockerConnector, never()).removeContainer(Matchers.<RemoveContainerParams>anyObject());
verify(dockerConnector, never()).removeContainer(Matchers.anyObject());
}
@Test
@ -168,6 +175,6 @@ public class DockerContainerCleanerTest {
verify(dockerConnector, never()).killContainer(anyString());
verify(dockerConnector, never()).removeContainer(Matchers.<RemoveContainerParams>anyObject());
verify(dockerConnector, never()).removeContainer(Matchers.anyObject());
}
}

View File

@ -1,462 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.docker.machine.integration;
/*import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.model.machine.MachineStatus;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.util.LineConsumer;
import org.eclipse.che.api.core.util.ValueHolder;
import org.eclipse.che.api.machine.server.MachineInstanceProviders;
import org.eclipse.che.api.machine.server.MachineManager;
import org.eclipse.che.api.machine.server.MachineRegistry;
import org.eclipse.che.api.machine.server.MachineService;
import org.eclipse.che.api.machine.server.spi.SnapshotDao;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.spi.impl.SnapshotImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineStateImpl;
import org.eclipse.che.api.machine.server.spi.InstanceProvider;
import org.eclipse.che.api.machine.shared.dto.CommandDto;
import org.eclipse.che.api.machine.shared.dto.MachineProcessDto;
import org.eclipse.che.api.machine.shared.dto.recipe.MachineRecipe;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.user.User;
import org.eclipse.che.commons.user.UserImpl;
import org.eclipse.che.inject.ConfigurationProperties;
import org.eclipse.che.plugin.docker.client.DockerConnector;
import org.eclipse.che.plugin.docker.client.InitialAuthConfig;
import org.eclipse.che.plugin.docker.client.ProgressLineFormatterImpl;
import org.eclipse.che.plugin.docker.client.json.ContainerConfig;
import org.eclipse.che.plugin.docker.client.json.HostConfig;
import org.eclipse.che.plugin.docker.client.json.PortBinding;
import org.eclipse.che.plugin.docker.machine.DockerInstanceKey;
import org.eclipse.che.plugin.docker.machine.DockerInstanceProvider;
import org.eclipse.che.plugin.docker.machine.DockerInstanceStopDetector;
import org.eclipse.che.plugin.docker.machine.DockerMachineFactory;
import org.eclipse.che.plugin.docker.machine.node.DockerNode;
import org.eclipse.che.plugin.docker.machine.TestDockerMachineFactory;
import org.eclipse.che.plugin.docker.machine.WorkspaceFolderNodePathProvider;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static java.util.Collections.singletonMap;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;*/
// TODO rework authentication
// TODO check removeSnapshotTest with https and password
// TODO bind, unbind
// TODO should we check result of tests with native calls?
/**
* @author Alexander Garagatyi
*/
//@Listeners(value = {MockitoTestNGListener.class})
public class ServiceTest {
/* private static final String USER = "userId";
private static final String SNAPSHOT_ID = "someSnapshotId";
private static LineConsumer lineConsumer = new StdErrLineConsumer();
// set in method {@link saveSnapshotTest}
// used in methods {@link createMachineFromSnapshotTest} and {@link removeSnapshotTest}
private DockerInstanceKey pushedImage;
private SnapshotDao snapshotDao;
private MachineRegistry machineRegistry;
private DockerConnector docker;
private MachineManager machineManager;
private MachineService machineService;
private String registryContainerId;
@Mock
private WorkspaceFolderNodePathProvider workspaceFolderNodePathProvider;
@Mock
private ConfigurationProperties configurationProperties;
@Mock
private DockerInstanceStopDetector dockerInstanceStopDetector;
private DockerMachineFactory dockerMachineFactory;
@BeforeClass
public void setUpClass() throws Exception {
when(configurationProperties.getProperties(anyString())).thenReturn(Collections.<String, String>emptyMap());
InitialAuthConfig authConfigs = new InitialAuthConfig(configurationProperties);
docker = new DockerConnector(authConfigs);
machineRegistry = new MachineRegistry();
assertTrue(pull("registry", "latest", null));
dockerMachineFactory = new TestDockerMachineFactory(docker);
final ContainerConfig containerConfig = new ContainerConfig()
.withImage("registry")
.withExposedPorts(singletonMap("5000/tcp", Collections.<String, String>emptyMap()))
.withHostConfig(new HostConfig().withPortBindings(
singletonMap("5000/tcp", new PortBinding[]{new PortBinding().withHostPort("5000")})));
registryContainerId = docker.createContainer(containerConfig, null).getUserId();
docker.startContainer(registryContainerId, null);
}
@AfterClass
public void tearDownClass() throws IOException {
docker.killContainer(registryContainerId);
docker.removeContainer(registryContainerId, true, false);
}
@BeforeMethod
public void setUp() throws Exception {
snapshotDao = mock(SnapshotDao.class);
DockerNode dockerNode = mock(DockerNode.class);
EventService eventService = mock(EventService.class);
RuntimeWorkspaceRegistry runtimeWorkspaceRegistry = mock(RuntimeWorkspaceRegistry.class);
EnvironmentContext envCont = new EnvironmentContext();
envCont.setUser(new UserImpl("user", null, null, null, false));
EnvironmentContext.setCurrent(envCont);
RuntimeWorkspaceImpl runtimeWorkspaceImpl = mock(RuntimeWorkspaceImpl.class);
when(runtimeWorkspaceRegistry.get(any())).thenReturn(runtimeWorkspaceImpl);
when(runtimeWorkspaceImpl.getName()).thenReturn("workspace");
InstanceProvider dockerInstanceProvider = new DockerInstanceProvider(docker,
dockerMachineFactory,
dockerInstanceStopDetector,
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
null,
"fake",
workspaceFolderNodePathProvider);
machineManager = new MachineManager(snapshotDao,
machineRegistry,
new MachineInstanceProviders(Collections.singleton(dockerInstanceProvider)),
"/tmp",
eventService,
100);
machineService = spy(new MachineService(machineManager));
EnvironmentContext.getCurrent().setUser(new User() {
@Override
public String getName() {
return null;
}
@Override
public boolean isMemberOf(String s) {
return false;
}
@Override
public String getToken() {
return null;
}
@Override
public String getUserId() {
return USER;
}
@Override
public boolean isTemporary() {
return false;
}
});
when(dockerNode.getProjectsFolder()).thenReturn(System.getProperty("user.dir"));
}
@AfterMethod
public void tearDown() throws Exception {
for (MachineStateImpl machine : new ArrayList<>(machineManager.getMachinesStates())) {
machineManager.destroy(machine.getUserId(), false);
}
EnvironmentContext.reset();
}
@Test
public void createFromRecipeTest() throws Exception {
final MachineStateDescriptor machine = machineService.createMachineFromRecipe(
newDto(RecipeMachineCreationMetadata.class)
.withType("docker")
.withDisplayName("MachineDisplayName")
.withWorkspaceId("wsId")
.withRecipe(newDto(MachineRecipe.class)
.withType("Dockerfile")
.withScript("FROM ubuntu\nCMD tail -f /dev/null\n")));
waitMachineIsRunning(machine.getUserId());
}
@Test(dependsOnMethods = "saveSnapshotTest", enabled = false)
public void createMachineFromSnapshotTest() throws Exception {
// remove local copy of image to check pulling
docker.removeImage(pushedImage.getImageId(), true);
SnapshotImpl snapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(SNAPSHOT_ID)).thenReturn(snapshot);
when(snapshot.getType()).thenReturn("docker");
when(snapshot.getWorkspaceId()).thenReturn("wsId");
when(snapshot.getInstanceKey()).thenReturn(pushedImage);
when(snapshot.getNamespace()).thenReturn(USER);
final MachineStateDescriptor machine = machineService
.createMachineFromSnapshot(newDto(SnapshotMachineCreationMetadata.class).withSnapshotId(SNAPSHOT_ID));
waitMachineIsRunning(machine.getUserId());
}
@Test
public void getMachineTest() throws Exception {
final MachineImpl machine = createMachineAndWaitRunningState();
final MachineDescriptor machineById = machineService.getMachineById(machine.getUserId());
assertEquals(machineById.getUserId(), machine.getUserId());
}
@Test
public void getMachinesTest() throws Exception {
Set<String> expected = new HashSet<>();
expected.add(createMachineAndWaitRunningState().getUserId());
expected.add(createMachineAndWaitRunningState().getUserId());
Set<String> actual = machineManager.getMachinesStates()
.stream()
.map(MachineImpl::getUserId)
.collect(Collectors.toSet());
assertEquals(actual, expected);
}
@Test
public void destroyMachineTest() throws Exception {
final MachineImpl machine = createMachineAndWaitRunningState();
machineService.destroyMachine(machine.getUserId());
assertEquals(machineService.getMachineStateById(machine.getUserId()).getStatus(), MachineStatus.DESTROYING);
int counter = 0;
while (++counter < 1000) {
try {
machineManager.getMachine(machine.getUserId());
} catch (NotFoundException e) {
return;
}
Thread.sleep(500);
}
fail();
}
@Test(enabled = false)// TODO Add ability to check when snapshot creation is finishes or fails
public void saveSnapshotTest() throws Exception {
final MachineImpl machine = createMachineAndWaitRunningState();
// use machine manager instead of machine service because it returns future with snapshot
// that allows check operation result
final SnapshotImpl snapshot = machineManager.save(machine.getUserId(), USER, "test description");
for (int i = 0; snapshot.getInstanceKey() == null && i < 10; ++i) {
Thread.sleep(500);
}
assertNotNull(snapshot.getInstanceKey());
final DockerInstanceKey instanceKey = (DockerInstanceKey)snapshot.getInstanceKey();
final boolean pullIsSuccessful = pull(instanceKey.getRepository(), instanceKey.getTag(), instanceKey.getRegistry());
assertTrue(pullIsSuccessful);
pushedImage = instanceKey;
}
// depends on saveSnapshotTest to be able to remove image from registry
// actually doesn't depend on createMachineFromSnapshotTest,
// but this test will fail createMachineFromSnapshotTest if called before
@Test(dependsOnMethods = {"saveSnapshotTest", "createMachineFromSnapshotTest"}, enabled = false)// TODO
public void removeSnapshotTest() throws Exception {
SnapshotImpl snapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(SNAPSHOT_ID)).thenReturn(snapshot);
when(snapshot.getType()).thenReturn("docker");
when(snapshot.getNamespace()).thenReturn(USER);
when(snapshot.getInstanceKey()).thenReturn(pushedImage);
machineService.removeSnapshot(SNAPSHOT_ID);
verify(snapshotDao).removeSnapshot(SNAPSHOT_ID);
try {
final boolean isPullSuccessful = pull(pushedImage.getRepository(), pushedImage.getTag(), pushedImage.getRegistry());
assertFalse(isPullSuccessful);
} catch (Exception e) {
fail(e.getLocalizedMessage(), e);
}
}
@Test
public void executeTest() throws Exception {
final MachineImpl machine = createMachineAndWaitRunningState();
String commandInMachine = "echo \"command in machine\" && tail -f /dev/null";
machineService.executeCommandInMachine(machine.getUserId(),
DtoFactory.newDto(CommandDto.class).withCommandLine(commandInMachine),
null);
Thread.sleep(500);
final List<MachineProcessDto> processes = machineService.getProcesses(machine.getUserId());
assertEquals(processes.size(), 1);
assertEquals(processes.get(0).getCommandLine(), commandInMachine);
}
@Test
public void getProcessesTest() throws Exception {
final MachineImpl machine = createMachineAndWaitRunningState();
Set<String> commands = new HashSet<>(2);
commands.add("tail -f /dev/null");
commands.add("sleep 10000");
for (String command : commands) {
machineService.executeCommandInMachine(machine.getUserId(), DtoFactory.newDto(CommandDto.class).withCommandLine(command), null);
}
Thread.sleep(500);
final List<MachineProcessDto> processes = machineService.getProcesses(machine.getUserId());
assertEquals(processes.size(), 2);
Set<String> actualCommandLines = new HashSet<>(2);
for (MachineProcessDto process : processes) {
assertTrue(process.getPid() > 0);
actualCommandLines.add(process.getCommandLine());
}
assertEquals(actualCommandLines, commands);
}
@Test
public void stopProcessTest() throws Exception {
final MachineImpl machine = createMachineAndWaitRunningState();
String commandInMachine = "echo \"command in machine\" && tail -f /dev/null";
machineService.executeCommandInMachine(machine.getUserId(),
DtoFactory.newDto(CommandDto.class).withCommandLine(commandInMachine),
null);
Thread.sleep(500);
final List<MachineProcessDto> processes = machineService.getProcesses(machine.getUserId());
assertEquals(processes.size(), 1);
assertEquals(processes.get(0).getCommandLine(), commandInMachine);
machineService.stopProcess(machine.getUserId(), processes.get(0).getPid());
assertTrue(machineService.getProcesses(machine.getUserId()).isEmpty());
}
@Test(expectedExceptions = NotFoundException.class, expectedExceptionsMessageRegExp = "Process with pid .* not found")
public void shouldThrowNotFoundExceptionOnProcessKillIfProcessPidMissing() throws Exception {
final MachineImpl machine = createMachineAndWaitRunningState();
String commandInMachine = "echo \"command in machine\" && tail -f /dev/null";
machineService.executeCommandInMachine(machine.getUserId(),
DtoFactory.newDto(CommandDto.class).withCommandLine(commandInMachine),
null);
Thread.sleep(500);
final List<MachineProcessDto> processes = machineService.getProcesses(machine.getUserId());
assertEquals(processes.size(), 1);
assertEquals(processes.get(0).getCommandLine(), commandInMachine);
machineService.stopProcess(machine.getUserId(), processes.get(0).getPid() + 100);
}
private MachineImpl createMachineAndWaitRunningState() throws Exception {
final MachineImpl machine = machineManager.create(newDto(RecipeMachineCreationMetadata.class)
.withWorkspaceId("wsId")
.withType("docker")
.withDisplayName("MachineDisplayName")
.withRecipe(newDto(MachineRecipe.class)
.withType("Dockerfile")
.withScript(
"FROM ubuntu\nCMD tail -f " +
"/dev/null\n"))
.withDev(false)
.withDisplayName("displayName" + System.currentTimeMillis())
, false);
waitMachineIsRunning(machine.getUserId());
return machine;
}
private void waitMachineIsRunning(String machineId) throws NotFoundException, InterruptedException, MachineException {
while (MachineStatus.RUNNING != machineManager.getMachineState(machineId).getStatus()) {
Thread.sleep(500);
}
}
private boolean pull(String image, String tag, String registry) throws Exception {
final ValueHolder<Boolean> isSuccessfulValueHolder = new ValueHolder<>(true);
final ProgressLineFormatterImpl progressLineFormatter = new ProgressLineFormatterImpl();
docker.pull(image, tag, registry, currentProgressStatus -> {
try {
if (currentProgressStatus.getError() != null) {
isSuccessfulValueHolder.set(false);
}
lineConsumer.writeLine(progressLineFormatter.format(currentProgressStatus));
} catch (IOException ignored) {
}
});
return isSuccessfulValueHolder.get();
}
private static class StdErrLineConsumer implements LineConsumer {
@Override
public void writeLine(String line) throws IOException {
System.err.println(line);
}
@Override
public void close() throws IOException {
}
}*/
}

View File

@ -13,12 +13,15 @@ package org.eclipse.che.plugin.docker.machine.local.interceptor;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names;
import com.google.inject.spi.ConstructorBinding;
import org.aopalliance.intercept.MethodInvocation;
import org.eclipse.che.api.core.model.machine.MachineConfig;
import org.eclipse.che.api.core.model.machine.Recipe;
import org.eclipse.che.api.machine.server.dao.SnapshotDao;
import org.eclipse.che.api.machine.server.spi.InstanceProvider;
import org.eclipse.che.api.machine.server.util.RecipeDownloader;
import org.eclipse.che.api.machine.server.util.RecipeRetriever;
import org.eclipse.che.api.workspace.server.WorkspaceManager;
@ -187,6 +190,12 @@ public class EnableOfflineDockerMachineBuildInterceptorTest {
bind(WorkspaceManager.class).toInstance(workspaceManager);
bind(RecipeRetriever.class).toInstance(recipeRetriever);
bind(RecipeDownloader.class).toInstance(mock(RecipeDownloader.class));
bind(SnapshotDao.class).toInstance(mock(SnapshotDao.class));
Multibinder<InstanceProvider> machineImageProviderMultibinder =
Multibinder.newSetBinder(binder(),
org.eclipse.che.api.machine.server.spi.InstanceProvider.class);
machineImageProviderMultibinder.addBinding()
.to(org.eclipse.che.plugin.docker.machine.DockerInstanceProvider.class);
bindConstant().annotatedWith(Names.named("machine.docker.privilege_mode")).to(false);
bindConstant().annotatedWith(Names.named("machine.docker.pull_image")).to(true);
@ -195,6 +204,8 @@ public class EnableOfflineDockerMachineBuildInterceptorTest {
bindConstant().annotatedWith(Names.named("che.machine.projects.internal.storage")).to("/tmp");
bindConstant().annotatedWith(Names.named("machine.docker.machine_extra_hosts")).to("");
bindConstant().annotatedWith(Names.named("che.workspace.storage")).to("/tmp");
bindConstant().annotatedWith(Names.named("machine.default_mem_size_mb")).to("1024");
bindConstant().annotatedWith(Names.named("machine.logs.location")).to("/tmp");
install(new DockerMachineModule());
install(new AllowOfflineMachineCreationModule());

View File

@ -84,7 +84,8 @@ public class JavaDebugConfigurationPagePresenter implements JavaDebugConfigurati
}
private void setPortsList() {
machineServiceClient.getMachine(appContext.getDevMachine().getId()).then(new Operation<MachineDto>() {
machineServiceClient.getMachine(appContext.getWorkspaceId(),
appContext.getDevMachine().getId()).then(new Operation<MachineDto>() {
@Override
public void apply(MachineDto machineDto) throws OperationException {
Machine machine = entityFactory.createMachine(machineDto);

View File

@ -78,7 +78,7 @@ public class JavaDebugConfigurationPagePresenterTest {
@Test
public void testGo() throws Exception {
when(machineServiceClient.getMachine(anyString())).thenReturn(mock(Promise.class));
when(machineServiceClient.getMachine(anyString(), anyString())).thenReturn(mock(Promise.class));
AcceptsOneWidget container = Mockito.mock(AcceptsOneWidget.class);

View File

@ -35,7 +35,7 @@ public class RecipeScriptDownloadServiceClientImpl implements RecipeScriptDownlo
@Override
public Promise<String> getRecipeScript(Machine machine) {
return asyncRequestFactory
.createGetRequest(restContext + "/recipe/script/" + machine.getId())
.createGetRequest(restContext + "/recipe/script/" + machine.getWorkspaceId() + "/" + machine.getId())
.send(new StringUnmarshaller());
}
}

View File

@ -119,7 +119,11 @@ public class CommandManager {
.withCommandLine(arg)
.withType(configuration.getType().getId());
final Promise<MachineProcessDto> processPromise = machineServiceClient.executeCommand(machine.getId(), command, outputChannel);
final Promise<MachineProcessDto> processPromise =
machineServiceClient.executeCommand(machine.getWorkspaceId(),
machine.getId(),
command,
outputChannel);
processPromise.then(new Operation<MachineProcessDto>() {
@Override
public void apply(MachineProcessDto process) throws OperationException {

View File

@ -15,9 +15,6 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.web.bindery.event.shared.EventBus;
import org.eclipse.che.ide.api.machine.MachineServiceClient;
import org.eclipse.che.ide.api.machine.events.WsAgentStateEvent;
import org.eclipse.che.ide.api.machine.events.WsAgentStateHandler;
import org.eclipse.che.api.machine.shared.dto.MachineDto;
import org.eclipse.che.api.machine.shared.dto.ServerDto;
import org.eclipse.che.api.promises.client.Operation;
@ -25,6 +22,9 @@ import org.eclipse.che.api.promises.client.OperationException;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.js.Promises;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.machine.MachineServiceClient;
import org.eclipse.che.ide.api.machine.events.WsAgentStateEvent;
import org.eclipse.che.ide.api.machine.events.WsAgentStateHandler;
import java.util.Map;
import java.util.Set;
@ -70,7 +70,7 @@ public class ServerPortProvider implements WsAgentStateHandler {
private void registerProviders() {
String devMachineId = appContext.getDevMachine().getId();
if (devMachineId != null) {
machineServiceClient.getMachine(devMachineId).then(registerProviders);
machineServiceClient.getMachine(appContext.getWorkspaceId(), devMachineId).then(registerProviders);
}
}

View File

@ -146,7 +146,8 @@ public class MachineManagerImpl implements MachineManager {
@Override
public Promise<Void> destroyMachine(final Machine machineState) {
return machineServiceClient.destroyMachine(machineState.getId()).then(new Operation<Void>() {
return machineServiceClient.destroyMachine(machineState.getWorkspaceId(),
machineState.getId()).then(new Operation<Void>() {
@Override
public void apply(Void arg) throws OperationException {
eventBus.fireEvent(new MachineStateEvent(machineState, DESTROYED));

View File

@ -66,13 +66,14 @@ public class MachineStatusNotifier implements MachineStatusChangedEvent.Handler
public void onMachineStatusChanged(final MachineStatusChangedEvent event) {
final String machineName = event.getMachineName();
final String machineId = event.getMachineId();
final String workspaceId = event.getWorkspaceId();
switch (event.getEventType()) {
case CREATING:
getMachine(machineId).then(notifyMachineCreating());
getMachine(workspaceId, machineId).then(notifyMachineCreating());
break;
case RUNNING:
getMachine(machineId).then(notifyMachineRunning());
getMachine(workspaceId, machineId).then(notifyMachineRunning());
break;
case DESTROYED:
notificationManager.notify(locale.notificationMachineDestroyed(machineName), SUCCESS, EMERGE_MODE);
@ -83,8 +84,8 @@ public class MachineStatusNotifier implements MachineStatusChangedEvent.Handler
}
}
private Promise<MachineDto> getMachine(final String machineId) {
return machineServiceClient.getMachine(machineId).catchError(new Operation<PromiseError>() {
private Promise<MachineDto> getMachine(final String workspaceId, final String machineId) {
return machineServiceClient.getMachine(workspaceId, machineId).catchError(new Operation<PromiseError>() {
@Override
public void apply(PromiseError arg) throws OperationException {
notificationManager.notify(locale.failedToFindMachine(machineId));

View File

@ -136,7 +136,8 @@ public class CreateMachinePresenter implements CreateMachineView.ActionDelegate
final String recipeURL = view.getRecipeURL();
if (appContext.getDevMachine() != null) {
machineServiceClient.getMachine(appContext.getDevMachine().getId()).then(new Operation<MachineDto>() {
machineServiceClient.getMachine(appContext.getWorkspaceId(),
appContext.getDevMachine().getId()).then(new Operation<MachineDto>() {
@Override
public void apply(MachineDto machine) throws OperationException {
machineManager.destroyMachine(machine);

View File

@ -20,8 +20,8 @@ import org.eclipse.che.api.machine.shared.dto.MachineProcessDto;
import org.eclipse.che.api.machine.shared.dto.event.MachineProcessEvent;
import org.eclipse.che.api.promises.client.Operation;
import org.eclipse.che.api.promises.client.OperationException;
import org.eclipse.che.ide.api.machine.MachineServiceClient;
import org.eclipse.che.ide.api.machine.CommandOutputMessageUnmarshaller;
import org.eclipse.che.ide.api.machine.MachineServiceClient;
import org.eclipse.che.ide.extension.machine.client.MachineResources;
import org.eclipse.che.ide.extension.machine.client.command.CommandConfiguration;
import org.eclipse.che.ide.extension.machine.client.command.CommandManager;
@ -231,7 +231,9 @@ public class CommandOutputConsolePresenter implements CommandOutputConsole, Outp
@Override
public void stop() {
machineServiceClient.stopProcess(machine.getId(), pid);
machineServiceClient.stopProcess(machine.getWorkspaceId(),
machine.getId(),
pid);
}
@Override
@ -249,7 +251,9 @@ public class CommandOutputConsolePresenter implements CommandOutputConsole, Outp
if (isFinished()) {
commandManager.executeCommand(commandConfiguration, machine);
} else {
machineServiceClient.stopProcess(machine.getId(), pid).then(new Operation<Void>() {
machineServiceClient.stopProcess(machine.getWorkspaceId(),
machine.getId(),
pid).then(new Operation<Void>() {
@Override
public void apply(Void arg) throws OperationException {
commandManager.executeCommand(commandConfiguration, machine);

View File

@ -50,8 +50,8 @@ public class ProcessesPresenter implements TabPresenter, ProcessesView.ActionDel
* @param machineId
* machine identifier for which need get processes
*/
public void showProcesses(@NotNull String machineId) {
Promise<List<MachineProcessDto>> processesPromise = service.getProcesses(machineId);
public void showProcesses(@NotNull String workspaceId, @NotNull String machineId) {
Promise<List<MachineProcessDto>> processesPromise = service.getProcesses(workspaceId, machineId);
processesPromise.then(new Operation<List<MachineProcessDto>>() {
@Override

View File

@ -17,19 +17,19 @@ import com.google.inject.Singleton;
import com.google.web.bindery.event.shared.EventBus;
import org.eclipse.che.api.core.model.machine.MachineStatus;
import org.eclipse.che.ide.api.machine.MachineServiceClient;
import org.eclipse.che.api.machine.shared.dto.MachineDto;
import org.eclipse.che.api.promises.client.Operation;
import org.eclipse.che.api.promises.client.OperationException;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.PromiseError;
import org.eclipse.che.ide.api.workspace.event.WorkspaceStartedEvent;
import org.eclipse.che.ide.api.workspace.event.WorkspaceStoppedEvent;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.event.ActivePartChangedEvent;
import org.eclipse.che.ide.api.event.ActivePartChangedHandler;
import org.eclipse.che.ide.api.machine.MachineServiceClient;
import org.eclipse.che.ide.api.parts.base.BasePresenter;
import org.eclipse.che.ide.api.workspace.event.WorkspaceStartedEvent;
import org.eclipse.che.ide.api.workspace.event.WorkspaceStoppedEvent;
import org.eclipse.che.ide.extension.machine.client.MachineLocalizationConstant;
import org.eclipse.che.ide.extension.machine.client.MachineResources;
import org.eclipse.che.ide.extension.machine.client.inject.factories.EntityFactory;
@ -163,7 +163,8 @@ public class MachinePanelPresenter extends BasePresenter implements MachinePanel
return;
}
service.getMachine(selectedMachine.getId()).then(new Operation<MachineDto>() {
service.getMachine(selectedMachine.getWorkspaceId(),
selectedMachine.getId()).then(new Operation<MachineDto>() {
@Override
public void apply(MachineDto machineDto) throws OperationException {
if (machineDto.getStatus() == MachineStatus.RUNNING) {

View File

@ -25,5 +25,5 @@ public interface AddTerminalClickHandler {
* @param machineId
* id of machine in which the terminal will be added
*/
void onAddTerminalClick(@NotNull String machineId);
void onAddTerminalClick(@NotNull String workspaceId, @NotNull String machineId);
}

View File

@ -322,7 +322,8 @@ public class ConsolesPanelPresenter implements ConsolesPanelView.ActionDelegate,
}
private void restoreState(final org.eclipse.che.api.core.model.machine.Machine machine) {
machineService.getProcesses(machine.getId()).then(new Operation<List<MachineProcessDto>>() {
machineService.getProcesses(machine.getWorkspaceId(),
machine.getId()).then(new Operation<List<MachineProcessDto>>() {
@Override
public void apply(List<MachineProcessDto> arg) throws OperationException {
for (MachineProcessDto machineProcessDto : arg) {
@ -418,17 +419,17 @@ public class ConsolesPanelPresenter implements ConsolesPanelView.ActionDelegate,
if (selectedTreeNode == null) {
if (appContext.getDevMachine() != null) {
onAddTerminal(appContext.getDevMachine().getId());
onAddTerminal(appContext.getWorkspaceId(), appContext.getDevMachine().getId());
}
return;
}
if (selectedTreeNode.getType() == MACHINE_NODE) {
onAddTerminal(selectedTreeNode.getId());
onAddTerminal(appContext.getWorkspaceId(), selectedTreeNode.getId());
} else {
if (selectedTreeNode.getParent() != null &&
selectedTreeNode.getParent().getType() == MACHINE_NODE) {
onAddTerminal(appContext.getDevMachine().getId());
onAddTerminal(appContext.getWorkspaceId(), appContext.getDevMachine().getId());
}
}
}
@ -440,8 +441,8 @@ public class ConsolesPanelPresenter implements ConsolesPanelView.ActionDelegate,
* id of machine in which the terminal will be added
*/
@Override
public void onAddTerminal(@NotNull final String machineId) {
machineService.getMachine(machineId).then(new Operation<MachineDto>() {
public void onAddTerminal(@NotNull final String workspaceId, @NotNull final String machineId) {
machineService.getMachine(workspaceId, machineId).then(new Operation<MachineDto>() {
@Override
public void apply(MachineDto arg) throws OperationException {
org.eclipse.che.ide.extension.machine.client.machine.Machine machine = entityFactory.createMachine(arg);

View File

@ -89,7 +89,7 @@ public interface ConsolesPanelView extends View<ConsolesPanelView.ActionDelegate
* @param machineId
* id of machine in which the terminal will be added
*/
void onAddTerminal(@NotNull String machineId);
void onAddTerminal(@NotNull String workspaceId, @NotNull String machineId);
/**
* Will be called when user clicks 'Preview Ssh' button

View File

@ -91,8 +91,8 @@ public class ConsolesPanelViewImpl extends Composite implements ConsolesPanelVie
renderer.setAddTerminalClickHandler(new AddTerminalClickHandler() {
@Override
public void onAddTerminalClick(@NotNull String machineId) {
delegate.onAddTerminal(machineId);
public void onAddTerminalClick(@NotNull String workspaceId, @NotNull String machineId) {
delegate.onAddTerminal(workspaceId, machineId);
}
});

View File

@ -171,7 +171,7 @@ public class ProcessTreeRenderer implements NodeRenderer<ProcessTreeNode> {
event.preventDefault();
if (addTerminalClickHandler != null) {
addTerminalClickHandler.onAddTerminalClick(machine.getId());
addTerminalClickHandler.onAddTerminalClick(machine.getWorkspaceId(), machine.getId());
}
}
}, true);

View File

@ -143,7 +143,8 @@ public class DockerCategoryPresenter implements CategoryPage, TargetManager, Doc
return;
}
machineService.destroyMachine(machine.getId()).then(new Operation<Void>() {
machineService.destroyMachine(machine.getWorkspaceId(),
machine.getId()).then(new Operation<Void>() {
@Override
public void apply(Void arg) throws OperationException {
eventBus.fireEvent(new MachineStateEvent(machine, MachineStateEvent.MachineAction.DESTROYED));

View File

@ -462,7 +462,8 @@ public class SshCategoryPresenter implements CategoryPage, TargetManager, SshVie
}
sshView.setConnectButtonText(null);
machineService.destroyMachine(machine.getId()).then(new Operation<Void>() {
machineService.destroyMachine(machine.getWorkspaceId(),
machine.getId()).then(new Operation<Void>() {
@Override
public void apply(Void arg) throws OperationException {
eventBus.fireEvent(new MachineStateEvent(machine, MachineStateEvent.MachineAction.DESTROYED));
@ -509,7 +510,8 @@ public class SshCategoryPresenter implements CategoryPage, TargetManager, SshVie
return;
}
machineService.destroyMachine(machine.getId()).then(new Operation<Void>() {
machineService.destroyMachine(machine.getWorkspaceId(),
machine.getId()).then(new Operation<Void>() {
@Override
public void apply(Void arg) throws OperationException {
eventBus.fireEvent(new MachineStateEvent(machine, MachineStateEvent.MachineAction.DESTROYED));
@ -557,14 +559,14 @@ public class SshCategoryPresenter implements CategoryPage, TargetManager, SshVie
/**
* Ensures machine is started.
*/
private void onConnected(final String machineId) {
private void onConnected(final String workspaceId, final String machineId) {
// There is a little bug in machine service on the server side.
// The machine info is updated with a little delay after running a machine.
// Using timer must fix the problem.
new Timer() {
@Override
public void run() {
machineService.getMachine(machineId).then(new Operation<MachineDto>() {
machineService.getMachine(workspaceId, machineId).then(new Operation<MachineDto>() {
@Override
public void apply(MachineDto machineDto) throws OperationException {
if (machineDto.getStatus() == RUNNING) {
@ -644,7 +646,7 @@ public class SshCategoryPresenter implements CategoryPage, TargetManager, SshVie
if (MachineStatusEvent.EventType.RUNNING == event.getEventType()
&& connectNotification != null && connectTargetName != null
&& connectTargetName.equals(event.getMachineName())) {
onConnected(event.getMachineId());
onConnected(event.getWorkspaceId(), event.getMachineId());
return;
}

View File

@ -108,6 +108,7 @@ public class MachineManagerImplTest {
@Test
public void checkUseValidSource() throws OperationException {
final String ID = "id";
final String WORKSPACE_ID = "testWorkspaceId";
final String DISPLAY_NAME = "my-display-name";
final boolean IS_DEV = true;
@ -117,9 +118,10 @@ public class MachineManagerImplTest {
org.eclipse.che.api.core.model.machine.Machine machineState = mock(org.eclipse.che.api.core.model.machine.Machine.class);
when(machineState.getId()).thenReturn(ID);
when(machineState.getWorkspaceId()).thenReturn(WORKSPACE_ID);
Promise<Void> promise = mock(Promise.class);
Promise<Void> promiseThen = mock(Promise.class);
when(machineServiceClient.destroyMachine(eq(ID))).thenReturn(promise);
when(machineServiceClient.destroyMachine(eq(WORKSPACE_ID), eq(ID))).thenReturn(promise);
when(promise.then(Matchers.<Operation<Void>>anyObject())).thenReturn(promiseThen);
MachineSource machineSource = mock(MachineSource.class);

View File

@ -54,6 +54,7 @@ import static org.mockito.Mockito.when;
public class MachineStateNotifierTest {
private static final String MACHINE_NAME = "machineName";
private static final String MACHINE_ID = "machineId";
private static final String WORKSPACE_ID = "workspaceId";
//constructor mocks
@Mock
@ -90,8 +91,9 @@ public class MachineStateNotifierTest {
when(machine.getConfig()).thenReturn(machineConfig);
when(machineConfig.getName()).thenReturn(MACHINE_NAME);
when(machineStatusChangedEvent.getMachineId()).thenReturn(MACHINE_ID);
when(machineStatusChangedEvent.getWorkspaceId()).thenReturn(WORKSPACE_ID);
when(machineStatusChangedEvent.getMachineName()).thenReturn(MACHINE_NAME);
when(machineServiceClient.getMachine(MACHINE_ID)).thenReturn(machinePromise);
when(machineServiceClient.getMachine(WORKSPACE_ID, MACHINE_ID)).thenReturn(machinePromise);
when(machinePromise.then(Matchers.<Operation<MachineDto>>anyObject())).thenReturn(machinePromise);
when(machinePromise.catchError(Matchers.<Operation<PromiseError>>anyObject())).thenReturn(machinePromise);
}

View File

@ -10,14 +10,14 @@
*******************************************************************************/
package org.eclipse.che.ide.extension.machine.client.machine.create;
import org.eclipse.che.ide.api.machine.DevMachine;
import org.eclipse.che.ide.api.machine.MachineManager;
import org.eclipse.che.ide.api.machine.MachineServiceClient;
import org.eclipse.che.api.machine.shared.dto.MachineDto;
import org.eclipse.che.ide.api.project.ProjectTypeServiceClient;
import org.eclipse.che.api.promises.client.Operation;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.machine.DevMachine;
import org.eclipse.che.ide.api.machine.MachineManager;
import org.eclipse.che.ide.api.machine.MachineServiceClient;
import org.eclipse.che.ide.api.project.ProjectTypeServiceClient;
import org.eclipse.che.ide.extension.machine.client.inject.factories.EntityFactory;
import org.junit.Before;
import org.junit.Test;
@ -42,6 +42,7 @@ public class CreateMachinePresenterTest {
private final static String RECIPE_URL = "http://www.host.com/recipe";
private final static String MACHINE_NAME = "machine";
private final static String WORKSPACE_ID = "testWorkspace123";
private final static String SOME_TEXT = "someText";
@ -122,15 +123,17 @@ public class CreateMachinePresenterTest {
public void shouldReplaceDevMachine() throws Exception {
DevMachine devMachine = mock(DevMachine.class);
when(appContext.getDevMachine()).thenReturn(devMachine);
when(appContext.getWorkspaceId()).thenReturn(WORKSPACE_ID);
when(devMachine.getId()).thenReturn(SOME_TEXT);
when(machineServiceClient.getMachine(SOME_TEXT)).thenReturn(machineDescriptorPromise);
when(devMachine.getWorkspace()).thenReturn(WORKSPACE_ID);
when(machineServiceClient.getMachine(WORKSPACE_ID, SOME_TEXT)).thenReturn(machineDescriptorPromise);
presenter.onReplaceDevMachineClicked();
verify(view).getMachineName();
verify(view).getRecipeURL();
verify(appContext, times(2)).getDevMachine();
verify(machineServiceClient).getMachine(SOME_TEXT);
verify(machineServiceClient).getMachine(WORKSPACE_ID, SOME_TEXT);
verify(machineDescriptorPromise).then(machineCaptor.capture());
machineCaptor.getValue().apply(mock(MachineDto.class));
verify(machineManager).destroyMachine(any(MachineDto.class));
@ -141,7 +144,7 @@ public class CreateMachinePresenterTest {
@Test
public void shouldStartNewDevMachine() throws Exception {
when(appContext.getDevMachine()).thenReturn(null);
when(machineServiceClient.getMachine(SOME_TEXT)).thenReturn(machineDescriptorPromise);
when(machineServiceClient.getMachine(WORKSPACE_ID, SOME_TEXT)).thenReturn(machineDescriptorPromise);
presenter.onReplaceDevMachineClicked();
@ -150,7 +153,7 @@ public class CreateMachinePresenterTest {
verify(appContext).getDevMachine();
verify(machineManager).startDevMachine(eq(RECIPE_URL), eq(MACHINE_NAME));
verify(view).close();
verify(machineServiceClient, never()).getMachine(SOME_TEXT);
verify(machineServiceClient, never()).getMachine(WORKSPACE_ID, SOME_TEXT);
verify(machineManager, never()).destroyMachine(any(MachineDto.class));
verify(machineManager).startDevMachine(eq(RECIPE_URL), eq(MACHINE_NAME));
}

View File

@ -38,6 +38,7 @@ import static org.mockito.Mockito.when;
public class ProcessesPresenterTest {
private static final String MACHINE_ID = "someId";
private static final String WORKSPACE_ID = "wsId";
//constructor mocks
@Mock
@ -66,11 +67,11 @@ public class ProcessesPresenterTest {
@Test
public void processesShouldBeGot() throws Exception {
when(service.getProcesses(MACHINE_ID)).thenReturn(processPromise);
when(service.getProcesses(WORKSPACE_ID, MACHINE_ID)).thenReturn(processPromise);
presenter.showProcesses(MACHINE_ID);
presenter.showProcesses(WORKSPACE_ID, MACHINE_ID);
verify(service).getProcesses(MACHINE_ID);
verify(service).getProcesses(WORKSPACE_ID, MACHINE_ID);
verify(processPromise).then(operationCaptor.capture());
operationCaptor.getValue().apply(descriptors);

View File

@ -162,7 +162,7 @@ public class MachinePanelPresenterTest {
when(service.getMachines(anyString())).thenReturn(machinesPromise);
when(machinesPromise.then(Matchers.<Operation<List<MachineDto>>>anyObject())).thenReturn(machinesPromise);
when(service.getMachine(anyString())).thenReturn(machinePromise);
when(service.getMachine(anyString(), anyString())).thenReturn(machinePromise);
when(machinePromise.then(Matchers.<Operation<MachineDto>>anyObject())).thenReturn(machinePromise);
when(appContext.getWorkspace()).thenReturn(usersWorkspaceDto);
@ -239,7 +239,7 @@ public class MachinePanelPresenterTest {
verify(appliance).showAppliance(machine1);
verify(service, never()).getMachine(anyString());
verify(service, never()).getMachine(anyString(), anyString());
assertThat(selectedMachine1, is(equalTo(presenter.getSelectedMachineState())));
assertThat(presenter.isMachineRunning(), is(true));

View File

@ -155,10 +155,10 @@ public class ConsolesPanelPresenterTest {
when(appContext.getDevMachine()).thenReturn(devMachine);
when(machineService.getMachines(anyString())).thenReturn(machinesPromise);
when(machineService.getMachine(anyString())).thenReturn(machinePromise);
when(machineService.getMachine(anyString(), anyString())).thenReturn(machinePromise);
when(machinePromise.then(Matchers.<Operation<MachineDto>>anyObject())).thenReturn(machinePromise);
when(machineService.getProcesses(anyString())).thenReturn(processesPromise);
when(machineService.getProcesses(anyString(), anyString())).thenReturn(processesPromise);
when(processesPromise.then(Matchers.<Operation<List<MachineProcessDto>>>anyObject())).thenReturn(processesPromise);
when(commandConsoleFactory.create(anyString())).thenReturn(mock(OutputConsole.class));
@ -334,7 +334,7 @@ public class ConsolesPanelPresenterTest {
when(terminal.getView()).thenReturn(terminalWidget);
presenter.addCommandOutput(MACHINE_ID, outputConsole);
presenter.onAddTerminal(MACHINE_ID);
presenter.onAddTerminal(WORKSPACE_ID, MACHINE_ID);
verify(machinePromise).then(machineCaptor.capture());
machineCaptor.getValue().apply(machineDto);
@ -409,7 +409,7 @@ public class ConsolesPanelPresenterTest {
IsWidget terminalWidget = mock(IsWidget.class);
when(terminal.getView()).thenReturn(terminalWidget);
presenter.onAddTerminal(MACHINE_ID);
presenter.onAddTerminal(WORKSPACE_ID, MACHINE_ID);
verify(machinePromise).then(machineCaptor.capture());
machineCaptor.getValue().apply(machineDto);

View File

@ -111,9 +111,10 @@ public class DockerCategoryPresenterTest {
when(target.getName()).thenReturn(deletingTargetName);
when(targetsTreeManager.getMachineByName(deletingTargetName)).thenReturn(machine);
when(machine.getId()).thenReturn(deletingMachineId);
when(machine.getWorkspaceId()).thenReturn("WS_ID");
when(machine.getStatus()).thenReturn(RUNNING);
when(machineLocale.targetsViewDeleteConfirm(deletingTargetName)).thenReturn(deleteProposal);
when(machineService.destroyMachine(deletingMachineId)).thenReturn(promise);
when(machineService.destroyMachine("WS_ID", deletingMachineId)).thenReturn(promise);
arbitraryCategoryPresenter.onDeleteClicked(target);
@ -124,7 +125,7 @@ public class DockerCategoryPresenterTest {
confirmCaptor.getValue().accepted();
verify(targetsTreeManager).getMachineByName(deletingTargetName);
verify(machineService).destroyMachine(deletingMachineId);
verify(machineService).destroyMachine("WS_ID", deletingMachineId);
operationSuccessCapture.getValue().apply(null);

View File

@ -32,10 +32,6 @@
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-core</artifactId>
@ -56,6 +52,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-ssh</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-inject</artifactId>

View File

@ -12,7 +12,7 @@ package org.eclipse.che.ide.ext.machine.server;
import com.google.inject.AbstractModule;
import org.eclipse.che.api.machine.server.MachineService;
import org.eclipse.che.api.workspace.server.RecipeScriptDownloadService;
import org.eclipse.che.ide.ext.machine.server.ssh.KeysInjector;
import org.eclipse.che.inject.DynaModule;

View File

@ -14,7 +14,7 @@ import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.notification.EventSubscriber;
import org.eclipse.che.api.machine.server.MachineManager;
import org.eclipse.che.api.environment.server.CheEnvironmentEngine;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent;
import org.eclipse.che.api.ssh.server.SshManager;
@ -43,20 +43,21 @@ import java.util.stream.Collectors;
public class KeysInjector {
private static final Logger LOG = LoggerFactory.getLogger(KeysInjector.class);
private final EventService eventService;
private final DockerConnector docker;
private final MachineManager machineManager;
private final SshManager sshManager;
private final EventService eventService;
private final DockerConnector docker;
private final SshManager sshManager;
// TODO replace with WorkspaceManager
private final CheEnvironmentEngine environmentEngine;
@Inject
public KeysInjector(EventService eventService,
DockerConnector docker,
MachineManager machineManager,
SshManager sshManager) {
SshManager sshManager,
CheEnvironmentEngine environmentEngine) {
this.eventService = eventService;
this.docker = docker;
this.machineManager = machineManager;
this.sshManager = sshManager;
this.environmentEngine = environmentEngine;
}
@PostConstruct
@ -66,7 +67,8 @@ public class KeysInjector {
public void onEvent(MachineStatusEvent event) {
if (event.getEventType() == MachineStatusEvent.EventType.RUNNING) {
try {
final Instance machine = machineManager.getInstance(event.getMachineId());
final Instance machine = environmentEngine.getMachine(event.getWorkspaceId(),
event.getMachineId());
List<SshPairImpl> sshPairs = sshManager.getPairs(machine.getOwner(), "machine");
final List<String> publicKeys = sshPairs.stream()
.filter(sshPair -> sshPair.getPublicKey() != null)

View File

@ -14,7 +14,7 @@ import org.eclipse.che.api.core.model.machine.MachineRuntimeInfo;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.notification.EventSubscriber;
import org.eclipse.che.api.core.util.LineConsumer;
import org.eclipse.che.api.machine.server.MachineManager;
import org.eclipse.che.api.environment.server.CheEnvironmentEngine;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent;
import org.eclipse.che.api.ssh.server.SshManager;
@ -56,6 +56,7 @@ import static org.testng.Assert.assertEquals;
*/
@Listeners(MockitoTestNGListener.class)
public class KeysInjectorTest {
private static final String WORKSPACE_ID = "workspace123";
private static final String MACHINE_ID = "machine123";
private static final String OWNER_ID = "user123";
private static final String CONTAINER_ID = "container123";
@ -82,7 +83,7 @@ public class KeysInjectorTest {
@Mock
DockerConnector docker;
@Mock
MachineManager machineManager;
CheEnvironmentEngine environmentEngine;
@Mock
SshManager sshManager;
@ -97,7 +98,7 @@ public class KeysInjectorTest {
metadataProperties.put("id", CONTAINER_ID);
when(machineRuntime.getProperties()).thenReturn(metadataProperties);
when(machineManager.getInstance(MACHINE_ID)).thenReturn(instance);
when(environmentEngine.getMachine(WORKSPACE_ID, MACHINE_ID)).thenReturn(instance);
when(instance.getOwner()).thenReturn(OWNER_ID);
when(instance.getRuntime()).thenReturn(machineRuntime);
when(instance.getLogger()).thenReturn(lineConsumer);
@ -114,7 +115,7 @@ public class KeysInjectorTest {
public void shouldNotDoAnythingIfEventTypeDoesNotEqualToRunning() {
subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.DESTROYING));
verifyZeroInteractions(docker, machineManager, sshManager);
verifyZeroInteractions(docker, environmentEngine, sshManager);
}
@Test
@ -122,11 +123,12 @@ public class KeysInjectorTest {
when(sshManager.getPairs(anyString(), anyString())).thenReturn(Collections.emptyList());
subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING)
.withMachineId(MACHINE_ID));
.withMachineId(MACHINE_ID)
.withWorkspaceId(WORKSPACE_ID));
verify(machineManager).getInstance(eq(MACHINE_ID));
verify(environmentEngine).getMachine(eq(WORKSPACE_ID), eq(MACHINE_ID));
verify(sshManager).getPairs(eq(OWNER_ID), eq("machine"));
verifyZeroInteractions(docker, machineManager, sshManager);
verifyZeroInteractions(docker, environmentEngine, sshManager);
}
@Test
@ -135,11 +137,12 @@ public class KeysInjectorTest {
.thenReturn(Collections.singletonList(new SshPairImpl("machine", "myPair", null, null)));
subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING)
.withMachineId(MACHINE_ID));
.withMachineId(MACHINE_ID)
.withWorkspaceId(WORKSPACE_ID));
verify(machineManager).getInstance(eq(MACHINE_ID));
verify(environmentEngine).getMachine(eq(WORKSPACE_ID), eq(MACHINE_ID));
verify(sshManager).getPairs(eq(OWNER_ID), eq("machine"));
verifyZeroInteractions(docker, machineManager, sshManager);
verifyZeroInteractions(docker, environmentEngine, sshManager);
}
@Test
@ -149,9 +152,10 @@ public class KeysInjectorTest {
new SshPairImpl("machine", "myPair", "publicKey2", null)));
subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING)
.withMachineId(MACHINE_ID));
.withMachineId(MACHINE_ID)
.withWorkspaceId(WORKSPACE_ID));
verify(machineManager).getInstance(eq(MACHINE_ID));
verify(environmentEngine).getMachine(eq(WORKSPACE_ID), eq(MACHINE_ID));
verify(sshManager).getPairs(eq(OWNER_ID), eq("machine"));
ArgumentCaptor<CreateExecParams> argumentCaptor = ArgumentCaptor.forClass(CreateExecParams.class);
@ -160,7 +164,7 @@ public class KeysInjectorTest {
"&& echo 'publicKey1' >> ~/.ssh/authorized_keys" +
"&& echo 'publicKey2' >> ~/.ssh/authorized_keys"});
verify(docker).startExec(eq(StartExecParams.create(EXEC_ID)), anyObject());
verifyZeroInteractions(docker, machineManager, sshManager);
verifyZeroInteractions(docker, environmentEngine, sshManager);
}
@Test
@ -171,7 +175,8 @@ public class KeysInjectorTest {
when(logMessage.getContent()).thenReturn("FAILED");
subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING)
.withMachineId(MACHINE_ID));
.withMachineId(MACHINE_ID)
.withWorkspaceId(WORKSPACE_ID));
verify(docker).startExec(eq(StartExecParams.create(EXEC_ID)), messageProcessorCaptor.capture());
final MessageProcessor<LogMessage> value = messageProcessorCaptor.getValue();

View File

@ -23,7 +23,7 @@ import org.eclipse.che.api.project.server.type.ProjectTypeRegistry;
import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto;
import org.eclipse.che.commons.test.SelfReturningAnswer;
import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer;
import org.eclipse.che.dto.server.DtoFactory;
import org.eclipse.che.plugin.java.server.projecttype.JavaProjectType;
import org.eclipse.che.plugin.java.server.projecttype.JavaValueProviderFactory;

View File

@ -66,6 +66,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-model</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-annotations</artifactId>

View File

@ -10,6 +10,8 @@
*******************************************************************************/
package org.eclipse.che.plugin.machine.ssh;
import org.eclipse.che.api.agent.server.terminal.MachineImplSpecificTerminalLauncher;
import org.eclipse.che.api.agent.server.terminal.WebsocketTerminalFilesPathProvider;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.util.AbstractLineConsumer;
import org.eclipse.che.api.core.util.ListLineConsumer;
@ -17,8 +19,6 @@ import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.model.impl.CommandImpl;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.spi.InstanceProcess;
import org.eclipse.che.api.machine.server.terminal.MachineImplSpecificTerminalLauncher;
import org.eclipse.che.api.machine.server.terminal.WebsocketTerminalFilesPathProvider;
import org.slf4j.Logger;
import javax.inject.Inject;

View File

@ -141,7 +141,7 @@ public class SshMachineInstance extends AbstractInstance {
* {@inheritDoc}
*/
@Override
public MachineSource saveToSnapshot(String owner) throws MachineException {
public MachineSource saveToSnapshot() throws MachineException {
throw new MachineException("Snapshot feature is unsupported for ssh machine implementation");
}

View File

@ -15,7 +15,7 @@ import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names;
import org.eclipse.che.api.machine.server.terminal.MachineImplSpecificTerminalLauncher;
import org.eclipse.che.api.agent.server.terminal.MachineImplSpecificTerminalLauncher;
/**
* Provides bindings needed for ssh machine implementation usage.

View File

@ -54,7 +54,7 @@ import org.eclipse.che.commons.json.JsonHelper;
import org.eclipse.che.commons.lang.IoUtil;
import org.eclipse.che.commons.lang.ws.rs.ExtMediaType;
import org.eclipse.che.commons.subject.SubjectImpl;
import org.eclipse.che.commons.test.SelfReturningAnswer;
import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer;
import org.eclipse.che.dto.server.DtoFactory;
import org.everrest.core.ApplicationContext;
import org.everrest.core.ResourceBinder;

View File

@ -17,7 +17,7 @@ import org.eclipse.che.api.core.rest.HttpJsonRequest;
import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;
import org.eclipse.che.api.core.rest.HttpJsonResponse;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.commons.test.SelfReturningAnswer;
import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer;
import org.eclipse.che.dto.server.DtoFactory;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;

View File

@ -22,19 +22,13 @@ public class Constants {
public static final String LINK_REL_UPDATE_RECIPE = "update recipe";
public static final String LINK_REL_SELF = "self link";
public static final String LINK_REL_GET_MACHINE = "get machine";
public static final String LINK_REL_GET_MACHINES = "get machines";
public static final String LINK_REL_DESTROY_MACHINE = "destroy machine";
public static final String LINK_REL_GET_SNAPSHOTS = "get snapshots";
public static final String LINK_REL_SAVE_SNAPSHOT = "save snapshot";
public static final String LINK_REL_REMOVE_SNAPSHOT = "remove snapshot";
public static final String LINK_REL_EXECUTE_COMMAND = "execute command";
public static final String LINK_REL_GET_PROCESSES = "get processes";
public static final String LINK_REL_STOP_PROCESS = "stop process";
public static final String LINK_REL_GET_MACHINE_LOGS = "get machine logs";
public static final String LINK_REL_GET_PROCESS_LOGS = "get process logs";
public static final String LINK_REL_GET_MACHINE_LOGS_CHANNEL = "get machine logs channel";
public static final String LINK_REL_GET_MACHINE_STATUS_CHANNEL = "get machine status channel";
public static final String WSAGENT_REFERENCE = "wsagent";
public static final String WSAGENT_WEBSOCKET_REFERENCE = "wsagent.websocket";

View File

@ -37,10 +37,6 @@
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
@ -77,10 +73,6 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-inject</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-lang</artifactId>

View File

@ -1,52 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.machine.server;
/**
* @author Alexander Garagatyi
*/
public class ChannelsImpl {
private final String outputChannel;
private final String statusChannel;
public ChannelsImpl(String outputChannel, String statusChannel) {
this.outputChannel = outputChannel;
this.statusChannel = statusChannel;
}
public String getOutput() {
return outputChannel;
}
public String getStatus() {
return statusChannel;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ChannelsImpl)) return false;
ChannelsImpl channels = (ChannelsImpl)o;
if (outputChannel != null ? !outputChannel.equals(channels.outputChannel) : channels.outputChannel != null) return false;
return !(statusChannel != null ? !statusChannel.equals(channels.statusChannel) : channels.statusChannel != null);
}
@Override
public int hashCode() {
int result = outputChannel != null ? outputChannel.hashCode() : 0;
result = 31 * result + (statusChannel != null ? statusChannel.hashCode() : 0);
return result;
}
}

View File

@ -1,191 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.machine.server;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.model.impl.MachineImpl;
import org.eclipse.che.api.machine.server.spi.Instance;
import javax.inject.Singleton;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
/**
* Holds active machines
*
* @author Alexander Garagatyi
*/
@Singleton
public class MachineRegistry {
// TODO add locks per workspace or machine
private final HashMap<String, Instance> instances;
private final HashMap<String, MachineImpl> machines;
public MachineRegistry() {
instances = new HashMap<>();
machines = new HashMap<>();
}
//TODO return unmodifiable lists
/**
* Get all active machines
*
* @throws MachineException
* if any error occurs
*/
public synchronized List<MachineImpl> getMachines() throws MachineException {
final List<MachineImpl> list = new ArrayList<>(machines.size() + instances.size());
list.addAll(machines.values());
list.addAll(instances.values().stream().map(this::toMachine).collect(Collectors.toList()));
return Collections.unmodifiableList(list);
}
/**
* Get machine by ID, machine can be in running or not
*
* @param machineId
* id of machine
* @throws NotFoundException
* if machine was not found
* @throws MachineException
* if other error occurs
*/
public synchronized MachineImpl getMachine(String machineId) throws NotFoundException, MachineException {
MachineImpl machine = machines.get(machineId);
if (machine == null) {
final Instance instance = instances.get(machineId);
if (instance == null) {
throw new NotFoundException("Machine " + machineId + " is not found");
}
machine = toMachine(instance);
}
return machine;
}
/**
* Return true if machine with unique {@code machineId} is exist, or false otherwise.
*
* @param machineId
* unique machine identifier
*/
public synchronized boolean isExist(String machineId) {
return machines.containsKey(machineId) || instances.containsKey(machineId);
}
/**
* Get dev machine of specific workspace. Dev machine should be in RUNNING state
*
* @param workspaceId
* id of workspace
* @throws NotFoundException
* if dev machine was not found or it is not in RUNNING
* @throws MachineException
* if other error occurs
*/
public synchronized MachineImpl getDevMachine(String workspaceId) throws NotFoundException, MachineException {
for (Instance instance : instances.values()) {
if (instance.getWorkspaceId().equals(workspaceId) && instance.getConfig().isDev()) {
return toMachine(instance);
}
}
throw new NotFoundException("Dev machine of workspace " + workspaceId + " is not running.");
}
/**
* Get machine by id. It should be in RUNNING state
*
* @param machineId
* id of machine
* @return machine with specified id
* @throws NotFoundException
* if machine with specified id not found
* @throws MachineException
* if other error occurs
*/
public synchronized Instance getInstance(String machineId) throws NotFoundException, MachineException {
final Instance instance = instances.get(machineId);
if (instance == null) {
throw new NotFoundException("Machine " + machineId + " is not found");
} else {
return instance;
}
}
/**
* Add not yet running machine
*
* @param machine
* machine
* @throws ConflictException
* if machine with the same ID already exists
* @throws MachineException
* if any other error occurs
*/
public synchronized void addMachine(MachineImpl machine) throws MachineException, ConflictException {
if (machines.containsKey(machine.getId())) {
throw new ConflictException("Machine with id " + machine.getId() + " is already exist");
}
machines.put(machine.getId(), machine);
}
/**
* Replace not running machine with instance of running machine
*
* @param instance
* running machine
* @throws NotFoundException
* if not running machine is not found
* @throws MachineException
* if any other error occurs
*/
public synchronized void update(Instance instance) throws NotFoundException, MachineException {
if (!instances.containsKey(instance.getId()) && !machines.containsKey(instance.getId())) {
throw new NotFoundException("Machine " + instance.getId() + " not found");
} else {
instances.put(instance.getId(), instance);
machines.remove(instance.getId());
}
}
/**
* Remove machine by id
*
* @param machineId
* id of machine that should be removed
* @throws NotFoundException
* if machine with specified id not found
*/
public synchronized void remove(String machineId) throws NotFoundException {
final Instance instance = instances.remove(machineId);
final MachineImpl machine = machines.remove(machineId);
if (null == instance && null == machine) {
throw new NotFoundException("Machine " + machineId + " is not found");
}
}
private MachineImpl toMachine(Instance instance) {
return new MachineImpl(instance.getConfig(),
instance.getId(),
instance.getWorkspaceId(),
instance.getEnvName(),
instance.getOwner(),
instance.getStatus(),
instance.getRuntime());
}
}

View File

@ -1,438 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.machine.server;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import com.google.common.io.CharStreams;
import org.eclipse.che.api.core.BadRequestException;
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.Machine;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.shared.dto.CommandDto;
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.NewSnapshotDescriptor;
import org.eclipse.che.api.machine.shared.dto.SnapshotDto;
import org.eclipse.che.commons.env.EnvironmentContext;
import javax.inject.Inject;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.io.Reader;
import java.util.List;
import java.util.stream.Collectors;
/**
* Machine API
*
* @author Alexander Garagatyi
* @author Anton Korneta
*/
@Api(value = "/machine", description = "Machine REST API")
@Path("/machine")
public class MachineService extends Service {
private MachineManager machineManager;
private final MachineServiceLinksInjector linksInjector;
@Inject
public MachineService(MachineManager machineManager, MachineServiceLinksInjector linksInjector) {
this.machineManager = machineManager;
this.linksInjector = linksInjector;
}
@GET
@Path("/{machineId}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get machine by ID")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains requested machine entity"),
@ApiResponse(code = 404, message = "Machine with specified id does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public MachineDto getMachineById(@ApiParam(value = "Machine ID")
@PathParam("machineId")
String machineId)
throws ServerException,
ForbiddenException,
NotFoundException {
final Machine machine = machineManager.getMachine(machineId);
return linksInjector.injectLinks(DtoConverter.asDto(machine), getServiceContext());
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get all machines of workspace with specified ID",
response = MachineDto.class,
responseContainer = "List")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains requested list of machine entities"),
@ApiResponse(code = 400, message = "Workspace ID is not specified"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public List<MachineDto> getMachines(@ApiParam(value = "Workspace ID", required = true)
@QueryParam("workspace")
String workspaceId)
throws ServerException,
BadRequestException {
requiredNotNull(workspaceId, "Parameter workspace");
return machineManager.getMachines(workspaceId)
.stream()
.map(DtoConverter::asDto)
.map(machineDto -> linksInjector.injectLinks(machineDto, getServiceContext()))
.collect(Collectors.toList());
}
@DELETE
@Path("/{machineId}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Destroy machine")
@ApiResponses({@ApiResponse(code = 204, message = "Machine was successfully destroyed"),
@ApiResponse(code = 404, message = "Machine with specified id does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public void destroyMachine(@ApiParam(value = "Machine ID")
@PathParam("machineId")
String machineId)
throws NotFoundException,
ServerException,
ForbiddenException {
machineManager.destroy(machineId, true);
}
@GET
@Path("/snapshot")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get all snapshots of machines in workspace",
response = SnapshotDto.class,
responseContainer = "List")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains requested list of snapshot entities"),
@ApiResponse(code = 400, message = "Workspace ID is not specified"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public List<SnapshotDto> getSnapshots(@ApiParam(value = "Workspace ID", required = true)
@QueryParam("workspace")
String workspaceId)
throws ServerException,
BadRequestException {
requiredNotNull(workspaceId, "Parameter workspace");
final List<SnapshotImpl> snapshots = machineManager.getSnapshots(EnvironmentContext.getCurrent().getSubject().getUserId(), workspaceId);
return snapshots.stream()
.map(DtoConverter::asDto)
.map(snapshotDto -> linksInjector.injectLinks(snapshotDto, getServiceContext()))
.collect(Collectors.toList());
}
@POST
@Path("/{machineId}/snapshot")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Save snapshot of machine")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains requested snapshot entity"),
@ApiResponse(code = 400, message = "Snapshot description is not specified"),
@ApiResponse(code = 404, message = "Snapshot with specified ID does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public SnapshotDto saveSnapshot(@ApiParam(value = "Machine ID")
@PathParam("machineId")
String machineId,
@ApiParam(value = "Snapshot description", required = true)
NewSnapshotDescriptor newSnapshotDescriptor)
throws NotFoundException,
ServerException,
ForbiddenException,
BadRequestException {
requiredNotNull(newSnapshotDescriptor, "Snapshot description");
return linksInjector.injectLinks(DtoConverter.asDto(machineManager.save(machineId,
EnvironmentContext.getCurrent()
.getSubject()
.getUserId(),
newSnapshotDescriptor.getDescription())),
getServiceContext());
}
@DELETE
@Path("/snapshot/{snapshotId}")
@ApiOperation(value = "Remove snapshot of machine")
@ApiResponses({@ApiResponse(code = 204, message = "Snapshot was successfully removed"),
@ApiResponse(code = 404, message = "Snapshot with specified ID does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public void removeSnapshot(@ApiParam(value = "Snapshot ID")
@PathParam("snapshotId")
String snapshotId)
throws ForbiddenException,
NotFoundException,
ServerException {
machineManager.removeSnapshot(snapshotId);
}
@POST
@Path("/{machineId}/command")
@Consumes(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Start specified command in machine")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains entity of created machine process"),
@ApiResponse(code = 400, message = "Command entity is invalid"),
@ApiResponse(code = 404, message = "Machine with specified ID does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public MachineProcessDto executeCommandInMachine(@ApiParam(value = "Machine ID")
@PathParam("machineId")
String machineId,
@ApiParam(value = "Command to execute", required = true)
final CommandDto command,
@ApiParam(value = "Channel for command output", required = false)
@QueryParam("outputChannel")
String outputChannel)
throws NotFoundException,
ServerException,
ForbiddenException,
BadRequestException {
requiredNotNull(command, "Command description");
requiredNotNull(command.getCommandLine(), "Commandline");
return linksInjector.injectLinks(DtoConverter.asDto(machineManager.exec(machineId, command, outputChannel)),
machineId,
getServiceContext());
}
@GET
@Path("/{machineId}/process")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get processes of machine",
response = MachineProcessDto.class,
responseContainer = "List")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains machine process entities"),
@ApiResponse(code = 404, message = "Machine with specified ID does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public List<MachineProcessDto> getProcesses(@ApiParam(value = "Machine ID")
@PathParam("machineId")
String machineId)
throws NotFoundException,
ServerException,
ForbiddenException {
return machineManager.getProcesses(machineId)
.stream()
.map(DtoConverter::asDto)
.map(machineProcess -> linksInjector.injectLinks(machineProcess,
machineId,
getServiceContext()))
.collect(Collectors.toList());
}
@DELETE
@Path("/{machineId}/process/{processId}")
@ApiOperation(value = "Stop process in machine")
@ApiResponses({@ApiResponse(code = 204, message = "Process was successfully stopped"),
@ApiResponse(code = 404, message = "Machine with specified ID does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public void stopProcess(@ApiParam(value = "Machine ID")
@PathParam("machineId")
String machineId,
@ApiParam(value = "Process ID")
@PathParam("processId")
int processId)
throws NotFoundException,
ForbiddenException,
ServerException {
machineManager.stopProcess(machineId, processId);
}
@GET
@Path("/{machineId}/logs")
@Produces(MediaType.TEXT_PLAIN)
@ApiOperation(value = "Get logs of machine")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains logs"),
@ApiResponse(code = 404, message = "Machine with specified ID does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public void getMachineLogs(@ApiParam(value = "Machine ID")
@PathParam("machineId")
String machineId,
@Context
HttpServletResponse httpServletResponse)
throws NotFoundException,
ForbiddenException,
ServerException,
IOException {
addLogsToResponse(machineManager.getMachineLogReader(machineId), httpServletResponse);
}
@GET
@Path("/{machineId}/process/{pid}/logs")
@Produces(MediaType.TEXT_PLAIN)
@ApiOperation(value = "Get logs of machine process")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains logs"),
@ApiResponse(code = 404, message = "Machine or process with specified ID does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public void getProcessLogs(@ApiParam(value = "Machine ID")
@PathParam("machineId")
String machineId,
@ApiParam(value = "Process ID")
@PathParam("pid")
int pid,
@Context
HttpServletResponse httpServletResponse)
throws NotFoundException,
ForbiddenException,
ServerException,
IOException {
addLogsToResponse(machineManager.getProcessLogReader(machineId, pid), httpServletResponse);
}
/**
* Reads file content by specified file path.
*
* @param path
* path to file on machine instance
* @param startFrom
* line number to start reading from
* @param limit
* limitation on line if not specified will used 2000 lines
* @return file content.
* @throws MachineException
* if any error occurs with file reading
*/
@GET
@Path("/{machineId}/filepath/{path:.*}")
@Produces(MediaType.TEXT_PLAIN)
@ApiOperation(value = "Get content of file in machine")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains file content"),
@ApiResponse(code = 404, message = "Machine with specified ID does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public String getFileContent(@ApiParam(value = "Machine ID")
@PathParam("machineId")
String machineId,
@ApiParam(value = "Path of file")
@PathParam("path")
String path,
@ApiParam(value = "From line")
@QueryParam("startFrom")
@DefaultValue("1")
Integer startFrom,
@ApiParam(value = "Number of lines")
@QueryParam("limit")
@DefaultValue("2000")
Integer limit)
throws NotFoundException,
ForbiddenException,
ServerException {
final Instance machine = machineManager.getInstance(machineId);
return machine.readFileContent(path, startFrom, limit);
}
/**
* Copies files from specified machine into current machine.
*
* @param sourceMachineId
* source machine id
* @param targetMachineId
* target machine id
* @param sourcePath
* path to file or directory inside specified machine
* @param targetPath
* path to destination file or directory inside machine
* @param overwrite
* If "false" then it will be an error if unpacking the given content would cause
* an existing directory to be replaced with a non-directory and vice versa.
* @throws MachineException
* if any error occurs when files are being copied
* @throws NotFoundException
* if any error occurs with getting source machine
*/
@POST
@Path("/copy")
@ApiOperation(value = "Copy files from one machine to another")
@ApiResponses({@ApiResponse(code = 200, message = "Files were copied successfully"),
@ApiResponse(code = 400, message = "Machine ID or path is not specified"),
@ApiResponse(code = 404, message = "Source machine does not exist"),
@ApiResponse(code = 404, message = "Target machine does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public void copyFilesBetweenMachines(@ApiParam(value = "Source machine ID", required = true)
@QueryParam("sourceMachineId")
String sourceMachineId,
@ApiParam(value = "Target machine ID", required = true)
@QueryParam("targetMachineId")
String targetMachineId,
@ApiParam(value = "Source path", required = true)
@QueryParam("sourcePath")
String sourcePath,
@ApiParam(value = "Target path", required = true)
@QueryParam("targetPath")
String targetPath,
@ApiParam(value = "Is files overwriting allowed")
@QueryParam("overwrite")
@DefaultValue("false")
Boolean overwrite)
throws NotFoundException,
ServerException,
ForbiddenException,
BadRequestException {
requiredNotNull(sourceMachineId, "Source machine id");
requiredNotNull(targetMachineId, "Target machine id");
requiredNotNull(sourcePath, "Source path");
requiredNotNull(targetPath, "Target path");
final Instance sourceMachine = machineManager.getInstance(sourceMachineId);
final Instance targetMachine = machineManager.getInstance(targetMachineId);
targetMachine.copy(sourceMachine, sourcePath, targetPath, overwrite);
}
private void addLogsToResponse(Reader logsReader, HttpServletResponse httpServletResponse) throws IOException {
// Response is written directly to the servlet request stream
httpServletResponse.setContentType("text/plain");
CharStreams.copy(logsReader, httpServletResponse.getWriter());
httpServletResponse.getWriter().flush();
}
/**
* Checks object reference is not {@code null}
*
* @param object
* object reference to check
* @param subject
* used as subject of exception message "{subject} required"
* @throws BadRequestException
* when object reference is {@code null}
*/
private void requiredNotNull(Object object, String subject) throws BadRequestException {
if (object == null) {
throw new BadRequestException(subject + " required");
}
}
}

View File

@ -26,10 +26,14 @@ public class InstanceStateEvent {
}
private String machineId;
private String workspaceId;
private Type type;
public InstanceStateEvent(String machineId, Type type) {
public InstanceStateEvent(String machineId,
String workspaceId,
Type type) {
this.machineId = machineId;
this.workspaceId = workspaceId;
this.type = type;
}
@ -40,4 +44,8 @@ public class InstanceStateEvent {
public Type getType() {
return type;
}
public String getWorkspaceId() {
return workspaceId;
}
}

View File

@ -126,7 +126,7 @@ public class SnapshotImpl implements Snapshot {
return this.isDev;
}
public void setMachineSourceImpl(MachineSource machineSource) {
public void setMachineSource(MachineSource machineSource) {
this.machineSource = machineSource != null ? new MachineSourceImpl(machineSource) : null;
}
@ -192,16 +192,16 @@ public class SnapshotImpl implements Snapshot {
*/
public static class SnapshotBuilder {
private String workspaceId;
private String machineName;
private String envName;
private String id;
private String type;
private String namespace;
private String description;
private String workspaceId;
private String machineName;
private String envName;
private String id;
private String type;
private String namespace;
private String description;
private MachineSource machineSource;
private boolean isDev;
private long creationDate;
private boolean isDev;
private long creationDate;
public SnapshotBuilder fromConfig(MachineConfig machineConfig) {
machineName = machineConfig.getName();

View File

@ -71,13 +71,11 @@ public interface Instance extends Machine {
/**
* Save state of the instance
*
* @param owner
* id of the user that is owner of the snapshot
* @return {@code InstanceSnapshotKey} that describe implementation specific keys of snapshot
* @throws MachineException
* if error occurs on storing state of the instance
*/
MachineSource saveToSnapshot(String owner) throws MachineException;
MachineSource saveToSnapshot() throws MachineException;
/**
* Destroy instance

View File

@ -1,345 +0,0 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.machine.server;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.model.machine.Command;
import org.eclipse.che.api.core.model.machine.Limits;
import org.eclipse.che.api.core.model.machine.Machine;
import org.eclipse.che.api.core.model.machine.MachineConfig;
import org.eclipse.che.api.core.model.machine.MachineSource;
import org.eclipse.che.api.core.model.machine.MachineStatus;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.util.LineConsumer;
import org.eclipse.che.api.machine.server.dao.SnapshotDao;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.exception.SourceNotFoundException;
import org.eclipse.che.api.machine.server.model.impl.LimitsImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl;
import org.eclipse.che.api.machine.server.model.impl.ServerConfImpl;
import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl;
import org.eclipse.che.api.machine.server.recipe.RecipeImpl;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.spi.InstanceProcess;
import org.eclipse.che.api.machine.server.spi.InstanceProvider;
import org.eclipse.che.api.machine.server.wsagent.WsAgentLauncher;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.lang.IoUtil;
import org.eclipse.che.commons.subject.SubjectImpl;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import java.io.File;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.concurrent.ThreadPoolExecutor;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
/**
* Unit tests for {@link MachineManager}
*
* @author Anton Korneta
* @author Alexander Garagatyi
*/
@Listeners(MockitoTestNGListener.class)
public class MachineManagerTest {
private static final int DEFAULT_MACHINE_MEMORY_SIZE_MB = 1000;
private static final String WS_ID = "testWsId";
private static final String ENVIRONMENT_NAME = "testEnvName";
private static final String USER_ID = "userId";
private static final String MACHINE_ID = "machineId";
private static final String NAMESPACE = "namespace";
private static final SubjectImpl CREATOR = new SubjectImpl("name", USER_ID, "token", false);
@Mock
private MachineInstanceProviders machineInstanceProviders;
@Mock
private InstanceProvider instanceProvider;
@Mock
private MachineRegistry machineRegistry;
@Mock
private WsAgentLauncher wsAgentLauncher;
@Mock
private Instance instance;
@Mock
private Limits limits;
@Mock
private Command command;
@Mock
private InstanceProcess instanceProcess;
@Mock
private LineConsumer logConsumer;
@Mock
private SnapshotDao snapshotDao;
private MachineManager manager;
@BeforeMethod
public void setUp() throws Exception {
final EventService eventService = mock(EventService.class);
final String machineLogsDir = targetDir().resolve("logs-dir").toString();
IoUtil.deleteRecursive(new File(machineLogsDir));
manager = spy(new MachineManager(snapshotDao,
machineRegistry,
machineInstanceProviders,
machineLogsDir,
eventService,
DEFAULT_MACHINE_MEMORY_SIZE_MB,
wsAgentLauncher));
EnvironmentContext envCont = new EnvironmentContext();
envCont.setSubject(CREATOR);
EnvironmentContext.setCurrent(envCont);
RecipeImpl recipe = new RecipeImpl().withScript("script").withType("Dockerfile");
// doNothing().when(manager).createMachineLogsDir(anyString());
doReturn(MACHINE_ID).when(manager).generateMachineId();
doReturn(logConsumer).when(manager).getProcessLogger(MACHINE_ID, 111, "outputChannel");
when(machineInstanceProviders.getProvider(anyString())).thenReturn(instanceProvider);
HashSet<String> recipeTypes = new HashSet<>();
recipeTypes.add("test type 1");
recipeTypes.add("snapshot");
recipeTypes.add("dockerfile");
when(instanceProvider.getRecipeTypes()).thenReturn(recipeTypes);
when(instanceProvider.createInstance(any(Machine.class), any(LineConsumer.class))).thenReturn(instance);
when(machineRegistry.getInstance(anyString())).thenReturn(instance);
when(command.getCommandLine()).thenReturn("CommandLine");
when(command.getName()).thenReturn("CommandName");
when(command.getType()).thenReturn("CommandType");
when(machineRegistry.getInstance(MACHINE_ID)).thenReturn(instance);
when(instance.createProcess(command, "outputChannel")).thenReturn(instanceProcess);
when(instanceProcess.getPid()).thenReturn(111);
}
@AfterMethod
public void tearDown() throws Exception {
EnvironmentContext.reset();
}
@Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Invalid machine name @name!")
public void shouldThrowExceptionOnMachineCreationIfMachineNameIsInvalid() throws Exception {
MachineConfig machineConfig = new MachineConfigImpl(false,
"@name!",
"machineType",
new MachineSourceImpl("Dockerfile").setLocation("location"),
new LimitsImpl(1024),
Arrays.asList(new ServerConfImpl("ref1",
"8080",
"https",
"some/path"),
new ServerConfImpl("ref2",
"9090/udp",
"someprotocol",
"/some/path")),
Collections.singletonMap("key1", "value1"));
String workspaceId = "wsId";
String environmentName = "env1";
manager.createMachineSync(machineConfig, workspaceId, environmentName, logConsumer);
}
@Test
public void shouldBeAbleToCreateMachineWithValidName() throws Exception {
String expectedName = "validMachineName";
final MachineConfigImpl machineConfig = MachineConfigImpl.builder()
.fromConfig(createMachineConfig())
.setName(expectedName)
.build();
MachineImpl expectedMachine = new MachineImpl(machineConfig,
MACHINE_ID,
WS_ID,
ENVIRONMENT_NAME,
USER_ID,
MachineStatus.CREATING,
null);
manager.createMachineSync(machineConfig, WS_ID, ENVIRONMENT_NAME, logConsumer);
verify(machineRegistry).addMachine(eq(expectedMachine));
}
@Test
public void shouldCallWsAgentLauncherAfterDevMachineStart() throws Exception {
final MachineConfigImpl machineConfig = MachineConfigImpl.builder()
.fromConfig(createMachineConfig())
.setDev(true)
.build();
manager.createMachineSync(machineConfig, WS_ID, ENVIRONMENT_NAME, logConsumer);
verify(wsAgentLauncher).startWsAgent(WS_ID);
}
@Test
public void shouldNotCallWsAgentLauncherAfterNonDevMachineStart() throws Exception {
final MachineConfigImpl machineConfig = createMachineConfig();
manager.createMachineSync(machineConfig, WS_ID, ENVIRONMENT_NAME, logConsumer);
verify(wsAgentLauncher, never()).startWsAgent(WS_ID);
}
@Test
public void shouldRemoveMachineFromRegistryIfInstanceDestroyingFailsOnDestroy() throws Exception {
final MachineConfigImpl machineConfig = createMachineConfig();
when(instance.getConfig()).thenReturn(machineConfig);
when(instance.getWorkspaceId()).thenReturn(WS_ID);
doThrow(new MachineException("test")).when(instance).destroy();
try {
manager.destroy(MACHINE_ID, false);
} catch (Exception e) {
verify(machineRegistry).remove(MACHINE_ID);
}
}
@Test
public void shouldCloseProcessLoggerIfExecIsSuccess() throws Exception {
//when
manager.exec(MACHINE_ID, command, "outputChannel");
waitForExecutorIsCompletedTask();
//then
verify(logConsumer).close();
}
@Test
public void shouldCloseProcessLoggerIfExecFails() throws Exception {
//given
doThrow(Exception.class).when(instanceProcess).start();
//when
manager.exec(MACHINE_ID, command, "outputChannel");
waitForExecutorIsCompletedTask();
//then
verify(logConsumer).close();
}
@Test(expectedExceptions = MachineException.class)
public void shouldCloseMachineLoggerIfMachineCreationFails() throws Exception {
//given
MachineConfig machineConfig = mock(MachineConfig.class);
MachineSource machineSource = mock(MachineSource.class);
LineConsumer machineLogger = mock(LineConsumer.class);
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", logConsumer);
//then
verify(machineLogger).close();
}
@Test
public void shouldCreateMachineFromOriginalSourceWhenMachineRecoverFails() throws Exception {
final SnapshotImpl snapshot = createSnapshot();
final MachineConfigImpl config = createMachineConfig();
when(manager.generateMachineId()).thenReturn(MACHINE_ID);
final MachineImpl machine = new MachineImpl(config,
MACHINE_ID,
WS_ID,
ENVIRONMENT_NAME,
CREATOR.getUserId(),
MachineStatus.CREATING,
null);
machine.getConfig().setSource(snapshot.getMachineSource());
when(snapshotDao.getSnapshot(WS_ID, ENVIRONMENT_NAME, config.getName())).thenReturn(snapshot);
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, logConsumer);
machine.getConfig().setSource(config.getSource());
assertEquals(result, machine);
}
@Test(expectedExceptions = MachineException.class)
public void shouldThrowExceptionWhenMachineCreationFromOriginSourceFailed() throws Exception {
final MachineConfigImpl config = createMachineConfig();
when(manager.generateMachineId()).thenReturn(MACHINE_ID);
when(instanceProvider.createInstance(any(MachineImpl.class),
any(LineConsumer.class))).thenThrow(new MachineException(""));
manager.recoverMachine(config, WS_ID, ENVIRONMENT_NAME, logConsumer);
}
private void waitForExecutorIsCompletedTask() throws Exception {
for (int i = 0; ((ThreadPoolExecutor)manager.executor).getCompletedTaskCount() == 0 && i < 10; i++) {
Thread.sleep(300);
}
}
private static Path targetDir() throws Exception {
final URL url = Thread.currentThread().getContextClassLoader().getResource(".");
assertNotNull(url);
return Paths.get(url.toURI()).getParent();
}
private MachineConfigImpl createMachineConfig() {
return new MachineConfigImpl(false,
"MachineName",
"docker",
new MachineSourceImpl("Dockerfile").setLocation("location"),
new LimitsImpl(1024),
Arrays.asList(new ServerConfImpl("ref1",
"8080",
"https",
"some/path"),
new ServerConfImpl("ref2",
"9090/udp",
"someprotocol",
"/some/path")),
Collections.singletonMap("key1", "value1"));
}
private SnapshotImpl createSnapshot() {
return SnapshotImpl.builder()
.setId("snapshot12")
.fromConfig(createMachineConfig())
.setWorkspaceId(WS_ID)
.setEnvName(ENVIRONMENT_NAME)
.setNamespace(NAMESPACE)
.setMachineSource(new MachineSourceImpl("snapshot").setLocation("location"))
.setDev(true)
.build();
}
}

View File

@ -11,7 +11,6 @@
package org.eclipse.che.api.workspace.shared.dto;
import org.eclipse.che.api.core.factory.FactoryParameter;
import org.eclipse.che.api.core.model.machine.Recipe;
import org.eclipse.che.api.core.model.workspace.Environment;
import org.eclipse.che.api.machine.shared.dto.MachineConfigDto;
import org.eclipse.che.dto.shared.DTO;

View File

@ -53,6 +53,10 @@
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-core</artifactId>
@ -81,6 +85,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-inject</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-lang</artifactId>
@ -113,6 +121,11 @@
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.everrest</groupId>
<artifactId>everrest-assured</artifactId>

View File

@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.machine.server.terminal;
package org.eclipse.che.api.agent.server.terminal;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.spi.Instance;

View File

@ -8,14 +8,14 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.machine.server.terminal;
package org.eclipse.che.api.agent.server.terminal;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.notification.EventSubscriber;
import org.eclipse.che.api.machine.server.MachineManager;
import org.eclipse.che.api.environment.server.CheEnvironmentEngine;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent;
@ -43,19 +43,20 @@ public class MachineTerminalLauncher {
private static final Logger LOG = LoggerFactory.getLogger(MachineTerminalLauncher.class);
private final EventService eventService;
private final MachineManager machineManager;
// TODO replace with WorkspaceManager
private final CheEnvironmentEngine cheEnvironmentEngine;
private final Map<String, MachineImplSpecificTerminalLauncher> terminalLaunchers;
private final ExecutorService executor;
@Inject
public MachineTerminalLauncher(EventService eventService,
MachineManager machineManager,
Set<MachineImplSpecificTerminalLauncher> machineImplLaunchers) {
Set<MachineImplSpecificTerminalLauncher> machineImplLaunchers,
CheEnvironmentEngine cheEnvironmentEngine) {
this.eventService = eventService;
this.machineManager = machineManager;
this.terminalLaunchers = machineImplLaunchers.stream()
.collect(toMap(MachineImplSpecificTerminalLauncher::getMachineType,
Function.identity()));
this.cheEnvironmentEngine = cheEnvironmentEngine;
this.executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("MachineTerminalLauncher-%d")
.setDaemon(true)
.build());
@ -70,10 +71,11 @@ public class MachineTerminalLauncher {
if (event.getEventType() == MachineStatusEvent.EventType.RUNNING) {
executor.execute(() -> {
try {
final Instance machine = machineManager.getInstance(event.getMachineId());
final Instance machine = cheEnvironmentEngine.getMachine(event.getWorkspaceId(),
event.getMachineId());
MachineImplSpecificTerminalLauncher terminalLauncher = terminalLaunchers.get(machine.getConfig()
.getType());
MachineImplSpecificTerminalLauncher terminalLauncher =
terminalLaunchers.get(machine.getConfig().getType());
if (terminalLauncher == null) {
LOG.warn("Terminal launcher implementation was not found for machine {} with type {}.",
machine.getId(),

View File

@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.machine.server.terminal;
package org.eclipse.che.api.agent.server.terminal;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.inject.ConfigurationProperties;

View File

@ -8,10 +8,11 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.machine.server.wsagent;
package org.eclipse.che.api.agent.server.wsagent;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.model.machine.Machine;
/**
* Starts ws agent in the machine and wait until ws agent sends notification about its start
@ -19,5 +20,6 @@ import org.eclipse.che.api.machine.server.exception.MachineException;
* @author Alexander Garagatyi
*/
public interface WsAgentLauncher {
void startWsAgent(String workspaceId) throws NotFoundException, MachineException, InterruptedException;
void startWsAgent(Machine devMachine) throws NotFoundException,
ServerException;
}

View File

@ -8,18 +8,18 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.machine.server.wsagent;
package org.eclipse.che.api.agent.server.wsagent;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.BadRequestException;
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.Server;
import org.eclipse.che.api.core.rest.HttpJsonRequest;
import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;
import org.eclipse.che.api.core.rest.HttpJsonResponse;
import org.eclipse.che.api.machine.server.MachineManager;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.environment.server.MachineProcessManager;
import org.eclipse.che.api.machine.server.model.impl.CommandImpl;
import org.eclipse.che.api.machine.shared.Constants;
import org.slf4j.Logger;
@ -49,23 +49,23 @@ public class WsAgentLauncherImpl implements WsAgentLauncher {
private static final String WS_AGENT_PROCESS_OUTPUT_CHANNEL = "workspace:%s:ext-server:output";
private static final String WS_AGENT_SERVER_NOT_FOUND_ERROR = "Workspace agent server not found in dev machine.";
private final Provider<MachineManager> machineManagerProvider;
private final HttpJsonRequestFactory httpJsonRequestFactory;
private final String wsAgentStartCommandLine;
private final long wsAgentMaxStartTimeMs;
private final long wsAgentPingDelayMs;
private final int wsAgentPingConnectionTimeoutMs;
private final String pingTimedOutErrorMessage;
private final Provider<MachineProcessManager> machineProcessManagerProvider;
private final HttpJsonRequestFactory httpJsonRequestFactory;
private final String wsAgentStartCommandLine;
private final long wsAgentMaxStartTimeMs;
private final long wsAgentPingDelayMs;
private final int wsAgentPingConnectionTimeoutMs;
private final String pingTimedOutErrorMessage;
@Inject
public WsAgentLauncherImpl(Provider<MachineManager> machineManagerProvider,
public WsAgentLauncherImpl(Provider<MachineProcessManager> machineProcessManagerProvider,
HttpJsonRequestFactory httpJsonRequestFactory,
@Named(WS_AGENT_PROCESS_START_COMMAND) String wsAgentStartCommandLine,
@Named("machine.ws_agent.max_start_time_ms") long wsAgentMaxStartTimeMs,
@Named("machine.ws_agent.ping_delay_ms") long wsAgentPingDelayMs,
@Named("machine.ws_agent.ping_conn_timeout_ms") int wsAgentPingConnectionTimeoutMs,
@Named("machine.ws_agent.ping_timed_out_error_msg") String pingTimedOutErrorMessage) {
this.machineManagerProvider = machineManagerProvider;
this.machineProcessManagerProvider = machineProcessManagerProvider;
this.httpJsonRequestFactory = httpJsonRequestFactory;
this.wsAgentStartCommandLine = wsAgentStartCommandLine;
this.wsAgentMaxStartTimeMs = wsAgentMaxStartTimeMs;
@ -79,17 +79,20 @@ public class WsAgentLauncherImpl implements WsAgentLauncher {
}
@Override
public void startWsAgent(String workspaceId) throws NotFoundException, MachineException, InterruptedException {
final Machine devMachine = getMachineManager().getDevMachine(workspaceId);
public void startWsAgent(Machine devMachine) throws NotFoundException,
ServerException {
final HttpJsonRequest wsAgentPingRequest = createPingRequest(devMachine);
final String wsAgentPingUrl = wsAgentPingRequest.getUrl();
try {
getMachineManager().exec(devMachine.getId(),
new CommandImpl(WS_AGENT_PROCESS_NAME, wsAgentStartCommandLine, "Arbitrary"),
getWsAgentProcessOutputChannel(workspaceId));
machineProcessManagerProvider.get().exec(devMachine.getWorkspaceId(),
devMachine.getId(),
new CommandImpl(WS_AGENT_PROCESS_NAME,
wsAgentStartCommandLine,
"Arbitrary"),
getWsAgentProcessOutputChannel(devMachine.getWorkspaceId()));
final long pingStartTimestamp = System.currentTimeMillis();
LOG.debug("Starts pinging ws agent. Workspace ID:{}. Url:{}. Timestamp:{}",
workspaceId,
devMachine.getWorkspaceId(),
wsAgentPingUrl,
pingStartTimestamp);
@ -101,20 +104,23 @@ public class WsAgentLauncherImpl implements WsAgentLauncher {
}
}
} catch (BadRequestException wsAgentLaunchingExc) {
throw new MachineException(wsAgentLaunchingExc.getLocalizedMessage(), wsAgentLaunchingExc);
throw new ServerException(wsAgentLaunchingExc.getLocalizedMessage(), wsAgentLaunchingExc);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ServerException("Ws agent pinging is interrupted");
}
LOG.error("Fail pinging ws agent. Workspace ID:{}. Url:{}. Timestamp:{}", workspaceId, wsAgentPingUrl);
throw new MachineException(pingTimedOutErrorMessage);
LOG.error("Fail pinging ws agent. Workspace ID:{}. Url:{}. Timestamp:{}", devMachine.getWorkspaceId(), wsAgentPingUrl);
throw new ServerException(pingTimedOutErrorMessage);
}
// forms the ping request based on information about the machine.
protected HttpJsonRequest createPingRequest(Machine machine) throws MachineException {
protected HttpJsonRequest createPingRequest(Machine machine) throws ServerException {
Map<String, ? extends Server> servers = machine.getRuntime().getServers();
Server wsAgentServer = servers.get(Constants.WS_AGENT_PORT);
if (wsAgentServer == null) {
LOG.error("{} WorkspaceId: {}, DevMachine Id: {}, found servers: {}",
WS_AGENT_SERVER_NOT_FOUND_ERROR, machine.getWorkspaceId(), machine.getId(), servers);
throw new MachineException(WS_AGENT_SERVER_NOT_FOUND_ERROR);
throw new ServerException(WS_AGENT_SERVER_NOT_FOUND_ERROR);
}
String wsAgentPingUrl = wsAgentServer.getUrl();
// since everrest mapped on the slash in case of it absence
@ -127,7 +133,7 @@ public class WsAgentLauncherImpl implements WsAgentLauncher {
.setTimeout(wsAgentPingConnectionTimeoutMs);
}
private boolean pingWsAgent(HttpJsonRequest wsAgentPingRequest) throws MachineException {
private boolean pingWsAgent(HttpJsonRequest wsAgentPingRequest) throws ServerException {
try {
final HttpJsonResponse pingResponse = wsAgentPingRequest.request();
if (pingResponse.getResponseCode() == HttpURLConnection.HTTP_OK) {
@ -137,8 +143,4 @@ public class WsAgentLauncherImpl implements WsAgentLauncher {
}
return false;
}
private MachineManager getMachineManager() {
return machineManagerProvider.get();
}
}

View File

@ -0,0 +1,955 @@
/*******************************************************************************
* 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.environment.server;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.ConflictException;
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.machine.MachineLogMessage;
import org.eclipse.che.api.core.model.machine.MachineSource;
import org.eclipse.che.api.core.model.machine.MachineStatus;
import org.eclipse.che.api.core.model.workspace.Environment;
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.CompositeLineConsumer;
import org.eclipse.che.api.core.util.FileLineConsumer;
import org.eclipse.che.api.core.util.LineConsumer;
import org.eclipse.che.api.core.util.MessageConsumer;
import org.eclipse.che.api.environment.server.exception.EnvironmentNotRunningException;
import org.eclipse.che.api.machine.server.MachineInstanceProviders;
import org.eclipse.che.api.machine.server.dao.SnapshotDao;
import org.eclipse.che.api.machine.server.event.InstanceStateEvent;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.exception.SourceNotFoundException;
import org.eclipse.che.api.machine.server.model.impl.LimitsImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineLogMessageImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl;
import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.spi.InstanceProvider;
import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent;
import org.eclipse.che.api.workspace.server.StripedLocks;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.lang.IoUtil;
import org.eclipse.che.commons.lang.NameGenerator;
import org.slf4j.Logger;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static java.lang.String.format;
import static org.eclipse.che.api.machine.server.event.InstanceStateEvent.Type.DIE;
import static org.eclipse.che.api.machine.server.event.InstanceStateEvent.Type.OOM;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Facade for implementation specific operations with environment runtimes.
*
* @author Alexander Garagatyi
* @author Yevhenii Voevodin
*/
@Singleton
public class CheEnvironmentEngine {
private static final Logger LOG = getLogger(CheEnvironmentEngine.class);
private final Map<String, EnvironmentHolder> environments;
private final StripedLocks stripedLocks;
private final SnapshotDao snapshotDao;
private final File machineLogsDir;
private final MachineInstanceProviders machineInstanceProviders;
private final int defaultMachineMemorySizeMB;
private final EventService eventService;
private volatile boolean isPreDestroyInvoked;
@Inject
public CheEnvironmentEngine(SnapshotDao snapshotDao,
MachineInstanceProviders machineInstanceProviders,
@Named("machine.logs.location") String machineLogsDir,
@Named("machine.default_mem_size_mb") int defaultMachineMemorySizeMB,
EventService eventService) {
this.eventService = eventService;
this.environments = new ConcurrentHashMap<>();
this.snapshotDao = snapshotDao;
this.machineInstanceProviders = machineInstanceProviders;
this.machineLogsDir = new File(machineLogsDir);
this.defaultMachineMemorySizeMB = defaultMachineMemorySizeMB;
// 16 - experimental value for stripes count, it comes from default hash map size
this.stripedLocks = new StripedLocks(16);
eventService.subscribe(new MachineCleaner());
}
/**
* Returns all machines from environment of specific workspace.
*
* @param workspaceId
* ID of workspace that owns environment machines
* @return list of machines
* @throws EnvironmentNotRunningException
* if environment is not running
*/
public List<Instance> getMachines(String workspaceId) throws EnvironmentNotRunningException {
EnvironmentHolder environment;
try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) {
environment = environments.get(workspaceId);
if (environment == null) {
throw new EnvironmentNotRunningException("Environment with ID '" + workspaceId + "' is not found");
}
return new ArrayList<>(environment.machines);
}
}
/**
* Returns specific machine from environment of specific workspace.
*
* @param workspaceId
* ID of workspace that owns environment machines
* @param machineId
* ID of requested machine
* @return requested machine
* @throws EnvironmentNotRunningException
* if environment is not running
* @throws NotFoundException
* if machine is not found in the environment
*/
public Instance getMachine(String workspaceId, String machineId) throws NotFoundException {
EnvironmentHolder environment;
try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) {
environment = environments.get(workspaceId);
}
if (environment == null) {
throw new EnvironmentNotRunningException("Environment with ID '" + workspaceId + "' is not found");
}
return environment.machines.stream()
.filter(instance -> instance.getId().equals(machineId))
.findAny()
.orElseThrow(() -> new NotFoundException(
format("Machine with ID '%s' is not found in the environment of workspace '%s'",
machineId, workspaceId)));
}
/**
* Starts provided environment.
* <p/>
* Environment starts if and only all machines in environment definition start successfully.<br/>
* Otherwise exception is thrown by this method.<br/>
* It is not defined whether environment start fails right after first failure or in the end of the process.<br/>
* Starting order of machines is not guarantied. Machines can start sequentially or in parallel.
*
* @param workspaceId
* ID of workspace that owns provided environment
* @param env
* environment to start
* @param recover
* whether machines from environment should be recovered or not
* @param messageConsumer
* consumer of log messages from machines in the environment
* @return list of running machines of this environment
* @throws ServerException
* if other error occurs
*/
public List<Instance> start(String workspaceId,
Environment env,
boolean recover,
MessageConsumer<MachineLogMessage> messageConsumer) throws ServerException,
ConflictException {
// Create a new start queue with a dev machine in the queue head
List<MachineConfigImpl> startConfigs = env.getMachineConfigs()
.stream()
.map(MachineConfigImpl::new)
.collect(Collectors.toList());
final MachineConfigImpl devCfg = removeFirstMatching(startConfigs, MachineConfig::isDev);
startConfigs.add(0, devCfg);
EnvironmentHolder environmentHolder = new EnvironmentHolder(new ArrayDeque<>(startConfigs),
new CopyOnWriteArrayList<>(),
messageConsumer,
EnvStatus.STARTING,
env.getName());
try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
if (environments.putIfAbsent(workspaceId, environmentHolder) != null) {
throw new ConflictException(format("Environment of workspace '%s' already exists", workspaceId));
}
}
startQueue(workspaceId, env.getName(), recover, messageConsumer);
try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
environmentHolder = environments.get(workspaceId);
// possible only if environment was stopped during its start
if (environmentHolder == null) {
throw new ServerException("Environment start was interrupted by environment stopping");
}
environmentHolder.status = EnvStatus.RUNNING;
return new ArrayList<>(environmentHolder.machines);
}
}
/**
* Stops running environment of specified workspace.
*
* @param workspaceId
* ID of workspace that owns environment
* @throws EnvironmentNotRunningException
* when environment is not running
* @throws ServerException
* if other error occurs
*/
public void stop(String workspaceId) throws EnvironmentNotRunningException,
ServerException {
List<Instance> machinesCopy = null;
try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
EnvironmentHolder environmentHolder = environments.get(workspaceId);
if (environmentHolder == null || environmentHolder.status != EnvStatus.RUNNING) {
throw new EnvironmentNotRunningException("Environment with ID '" + workspaceId + "' is not found");
}
environments.remove(workspaceId);
List<Instance> machines = environmentHolder.machines;
if (machines != null && !machines.isEmpty()) {
machinesCopy = new ArrayList<>(machines);
}
}
// long operation - perform out of lock
if (machinesCopy != null) {
stopMachines(machinesCopy);
}
}
/**
* Starts machine in running environment.
*
* @param workspaceId
* ID of workspace that owns environment in which machine should be started
* @param machineConfig
* configuration of machine that should be started
* @return running machine
* @throws EnvironmentNotRunningException
* if environment is not running
* @throws ConflictException
* if machine with the same name already exists in the environment
* @throws ServerException
* if any other error occurs
*/
public Instance startMachine(String workspaceId,
MachineConfig machineConfig) throws ServerException,
EnvironmentNotRunningException,
ConflictException {
MachineConfig machineConfigCopy = new MachineConfigImpl(machineConfig);
EnvironmentHolder environmentHolder;
try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) {
environmentHolder = environments.get(workspaceId);
if (environmentHolder == null || environmentHolder.status != EnvStatus.RUNNING) {
throw new EnvironmentNotRunningException(format("Environment '%s' is not running", workspaceId));
}
for (Instance machine : environmentHolder.machines) {
if (machine.getConfig().getName().equals(machineConfigCopy.getName())) {
throw new ConflictException(
format("Machine with name '%s' already exists in environment of workspace '%s'",
machineConfigCopy.getName(), workspaceId));
}
}
}
String machineId = generateMachineId();
final String creator = EnvironmentContext.getCurrent().getSubject().getUserId();
Instance instance = null;
try {
addMachine(workspaceId,
machineId,
environmentHolder.name,
creator,
machineConfigCopy);
eventService.publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.CREATING)
.withDev(machineConfigCopy.isDev())
.withMachineName(machineConfigCopy.getName())
.withMachineId(machineId)
.withWorkspaceId(workspaceId));
instance = startMachineInstance(machineConfigCopy,
workspaceId,
machineId,
environmentHolder.name,
creator,
false,
environmentHolder.logger);
replaceMachine(instance);
eventService.publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.RUNNING)
.withDev(machineConfigCopy.isDev())
.withMachineName(machineConfigCopy.getName())
.withMachineId(instance.getId())
.withWorkspaceId(workspaceId));
return instance;
} catch (Exception e) {
removeMachine(workspaceId, machineId);
if (instance != null) {
try {
instance.destroy();
} catch (Exception destroyingExc) {
LOG.error(destroyingExc.getLocalizedMessage(), destroyingExc);
}
}
eventService.publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.ERROR)
.withDev(machineConfigCopy.isDev())
.withMachineName(machineConfigCopy.getName())
.withMachineId(machineId)
.withWorkspaceId(workspaceId));
throw e;
}
}
/**
* Stops machine in running environment.
*
* @param workspaceId
* ID of workspace of environment that owns machine
* @param machineId
* ID of machine that should be stopped
* @throws NotFoundException
* if machine in not found in environment
* @throws EnvironmentNotRunningException
* if environment is not running
* @throws ConflictException
* if stop of dev machine is requested
* @throws ServerException
* if other error occurs
*/
public void stopMachine(String workspaceId, String machineId) throws NotFoundException,
ServerException,
ConflictException {
Instance targetMachine = null;
try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
EnvironmentHolder environmentHolder = environments.get(workspaceId);
if (environmentHolder == null || environmentHolder.status != EnvStatus.RUNNING) {
throw new EnvironmentNotRunningException(format("Environment '%s' is not running", workspaceId));
}
for (Instance machine : environmentHolder.machines) {
if (machine.getId().equals(machineId)) {
if (machine.getConfig().isDev()) {
throw new ConflictException(
"Stop of dev machine is not allowed. Please, stop whole environment");
}
targetMachine = machine;
break;
}
}
environmentHolder.machines.remove(targetMachine);
}
if (targetMachine == null) {
throw new NotFoundException(format("Machine with ID '%s' is not found in environment of workspace '%s'",
machineId, workspaceId));
}
// out of lock to prevent blocking on event processing by subscribers
eventService.publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.DESTROYING)
.withDev(targetMachine.getConfig().isDev())
.withMachineName(targetMachine.getConfig().getName())
.withMachineId(machineId)
.withWorkspaceId(workspaceId));
targetMachine.destroy();
eventService.publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.DESTROYED)
.withDev(targetMachine.getConfig().isDev())
.withMachineName(targetMachine.getConfig().getName())
.withMachineId(machineId)
.withWorkspaceId(workspaceId));
}
/**
* Saves machine into snapshot.
*
* @param namespace namespace of the workspace
* @param workspaceId ID of workspace that owns environment
* @param machineId ID of machine to save
* @return snapshot
* @throws EnvironmentNotRunningException
* if environment of machine is not running
* @throws NotFoundException
* if machine is not running
* @throws ServerException
* if another error occurs
*/
public SnapshotImpl saveSnapshot(String namespace,
String workspaceId,
String machineId) throws ServerException,
NotFoundException {
EnvironmentHolder environmentHolder;
SnapshotImpl snapshot = null;
Instance instance = null;
try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) {
environmentHolder = environments.get(workspaceId);
if (environmentHolder == null || environmentHolder.status != EnvStatus.RUNNING) {
throw new EnvironmentNotRunningException(format("Environment '%s' is not running", workspaceId));
}
for (Instance machine : environmentHolder.machines) {
if (machine.getId().equals(machineId)) {
instance = machine;
snapshot = SnapshotImpl.builder()
.generateId()
.setType(machine.getConfig().getType())
.setNamespace(namespace)
.setWorkspaceId(machine.getWorkspaceId())
.setDescription(machine.getEnvName())
.setDev(machine.getConfig().isDev())
.setEnvName(machine.getEnvName())
.setMachineName(machine.getConfig().getName())
.useCurrentCreationDate()
.build();
}
}
}
if (instance == null) {
throw new NotFoundException(format("Machine with id '%s' is not found in environment of workspace '%s'",
machineId, workspaceId));
}
try {
MachineSource machineSource = instance.saveToSnapshot();
snapshot.setMachineSource(machineSource);
return snapshot;
} catch (ServerException e) {
try {
instance.getLogger().writeLine("Snapshot storing failed. " + e.getLocalizedMessage());
} catch (IOException ignore) {
}
throw e;
}
}
/**
* Removes snapshot of machine.
*
* @param snapshot
* description of snapshot that should be removed
* @throws ServerException
* if error occurs on snapshot removal
*/
public void removeSnapshot(SnapshotImpl snapshot) throws ServerException {
final String instanceType = snapshot.getType();
try {
final InstanceProvider instanceProvider = machineInstanceProviders.getProvider(instanceType);
instanceProvider.removeInstanceSnapshot(snapshot.getMachineSource());
} catch (NotFoundException e) {
throw new ServerException(e.getLocalizedMessage(), e);
}
}
/**
* Starts all machine from machine queue of environment.
*/
private void startQueue(String workspaceId,
String envName,
boolean recover,
MessageConsumer<MachineLogMessage> messageConsumer) throws ServerException {
// Starting all machines in environment one by one by getting configs
// from the corresponding starting queue.
// Config will be null only if there are no machines left in the queue
MachineConfigImpl config = queuePeekOrFail(workspaceId);
while (config != null) {
// Environment start is failed when any machine start is failed, so if any error
// occurs during machine creation then environment start fail is reported and
// start resources such as queue and descriptor must be cleaned up
try {
String machineId = generateMachineId();
final String creator = EnvironmentContext.getCurrent().getSubject().getUserId();
addMachine(workspaceId,
machineId,
envName,
creator,
config);
eventService.publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.CREATING)
.withDev(config.isDev())
.withMachineName(config.getName())
.withWorkspaceId(workspaceId)
.withMachineId(machineId));
Instance machine = startMachineInstance(config,
workspaceId,
machineId,
envName,
creator,
recover,
messageConsumer);
// Machine destroying is an expensive operation which must be
// performed outside of the lock, this section checks if
// the environment wasn't stopped while it is starting and sets
// polled flag to true if the environment wasn't stopped.
// Also polls the proceeded machine configuration from the queue
boolean queuePolled = false;
try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
ensurePreDestroyIsNotExecuted();
EnvironmentHolder environmentHolder = environments.get(workspaceId);
if (environmentHolder != null) {
final Queue<MachineConfigImpl> queue = environmentHolder.startQueue;
if (queue != null) {
queue.poll();
queuePolled = true;
}
}
}
// If machine config is not polled from the queue
// then environment was stopped and newly created machine
// must be destroyed
if (!queuePolled) {
try {
eventService.publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.DESTROYING)
.withDev(config.isDev())
.withMachineName(config.getName())
.withMachineId(machine.getId())
.withWorkspaceId(workspaceId));
machine.destroy();
eventService.publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.DESTROYED)
.withDev(config.isDev())
.withMachineName(config.getName())
.withMachineId(machine.getId())
.withWorkspaceId(workspaceId));
} catch (MachineException e) {
LOG.error(e.getLocalizedMessage(), e);
}
throw new ServerException("Workspace '" + workspaceId +
"' start interrupted. Workspace stopped before all its machines started");
}
replaceMachine(machine);
eventService.publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.RUNNING)
.withDev(config.isDev())
.withMachineName(config.getName())
.withMachineId(machine.getId())
.withWorkspaceId(workspaceId));
config = queuePeekOrFail(workspaceId);
} catch (RuntimeException | ServerException e) {
EnvironmentHolder env;
try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
env = environments.remove(workspaceId);
}
try {
stopMachines(env.machines);
} catch (Exception remEx) {
LOG.error(remEx.getLocalizedMessage(), remEx);
}
throw new ServerException(e.getLocalizedMessage(), e);
}
}
}
private void addMachine(String workspaceId,
String machineId,
String envName,
String creator,
MachineConfig machineConfig) throws ServerException {
Instance machine = new NoOpMachineInstance(MachineImpl.builder()
.setConfig(machineConfig)
.setId(machineId)
.setWorkspaceId(workspaceId)
.setStatus(MachineStatus.CREATING)
.setEnvName(envName)
.setOwner(creator)
.build());
try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
ensurePreDestroyIsNotExecuted();
EnvironmentHolder environmentHolder = environments.get(workspaceId);
if (environmentHolder != null && environmentHolder.status != EnvStatus.STOPPING) {
environmentHolder.machines.add(machine);
} else {
throw new ServerException(
format("Can't add machine into environment. Environment of workspace '%s' is missing",
workspaceId));
}
}
}
private void removeMachine(String workspaceId,
String machineId) {
try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
EnvironmentHolder environmentHolder = environments.get(workspaceId);
if (environmentHolder != null) {
removeFirstMatching(environmentHolder.machines, m -> m.getId().equals(machineId));
}
}
}
private void replaceMachine(Instance machine) throws ServerException {
try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(machine.getWorkspaceId())) {
ensurePreDestroyIsNotExecuted();
EnvironmentHolder environmentHolder = environments.get(machine.getWorkspaceId());
if (environmentHolder != null) {
for (int i = 0; i < environmentHolder.machines.size(); i++) {
if (environmentHolder.machines.get(i).getId().equals(machine.getId())) {
environmentHolder.machines.set(i, machine);
return;
}
}
}
}
// if this area is reachable then environment/machine is not found and machine should be stopped
try {
machine.destroy();
} catch (MachineException e) {
LOG.error(e.getLocalizedMessage(), e);
}
// should not happen
throw new ServerException(format(
"Machine with ID '%s' and name '%s' has been stopped because its configuration is not found in the environment of workspace '%s'",
machine.getId(), machine.getConfig().getName(), machine.getWorkspaceId()));
}
/**
* Gets head config from the queue associated with the given {@code workspaceId}.
*
* <p>Note that this method won't actually poll the queue.
*
* <p>Fails if environment start was interrupted by stop(queue doesn't exist).
*
* @return machine config which is in the queue head, or null
* if there are no machine configs left
* @throws ServerException
* if queue doesn't exist which means that {@link #stop(String)} executed
* before all the machines started
* @throws ServerException
* if pre destroy has been invoked before peek config retrieved
*/
private MachineConfigImpl queuePeekOrFail(String workspaceId) throws ServerException {
try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) {
ensurePreDestroyIsNotExecuted();
EnvironmentHolder environmentHolder = environments.get(workspaceId);
if (environmentHolder == null || environmentHolder.startQueue == null) {
throw new ServerException("Workspace " + workspaceId +
" start interrupted. Workspace was stopped before all its machines were started");
}
return environmentHolder.startQueue.peek();
}
}
private Instance startMachineInstance(MachineConfig originMachineConfig,
String workspaceId,
String machineId,
String environmentName,
String creator,
boolean recover,
MessageConsumer<MachineLogMessage> environmentLogger)
throws ServerException {
final MachineImpl machine = new MachineImpl(originMachineConfig,
machineId,
workspaceId,
environmentName,
creator,
MachineStatus.CREATING,
null);
if ("recipe".equalsIgnoreCase(machine.getConfig().getSource().getType())) {
machine.getConfig().getSource().setType("dockerfile");
}
if (originMachineConfig.getLimits().getRam() == 0) {
machine.getConfig().setLimits(new LimitsImpl(defaultMachineMemorySizeMB));
}
final MachineSourceImpl sourceCopy = machine.getConfig().getSource();
LineConsumer machineLogger = null;
try {
machineLogger = getMachineLogger(environmentLogger, machineId, originMachineConfig.getName());
if (recover) {
final SnapshotImpl snapshot = snapshotDao.getSnapshot(workspaceId,
environmentName,
machine.getConfig().getName());
machine.getConfig().setSource(snapshot.getMachineSource());
}
final InstanceProvider instanceProvider =
machineInstanceProviders.getProvider(machine.getConfig().getType());
if (!instanceProvider.getRecipeTypes().contains(machine.getConfig()
.getSource()
.getType()
.toLowerCase())) {
throw new ServerException(format("Recipe type %s of %s machine is unsupported",
machine.getConfig().getSource().getType(),
machine.getConfig().getName()));
}
try {
Instance instance;
try {
instance = instanceProvider.createInstance(machine, machineLogger);
} catch (SourceNotFoundException e) {
if (recover) {
LOG.error("Image of snapshot for machine " + machine.getConfig().getName() + " not found. " +
"Machine will be created from origin source");
machine.getConfig().setSource(sourceCopy);
instance = instanceProvider.createInstance(machine, machineLogger);
} else {
throw e;
}
}
instance.setStatus(MachineStatus.RUNNING);
return instance;
} catch (ApiException creationEx) {
try {
machineLogger.writeLine("[ERROR] " + creationEx.getLocalizedMessage());
} catch (IOException ioEx) {
LOG.error(ioEx.getLocalizedMessage());
}
throw new MachineException(creationEx.getLocalizedMessage(), creationEx);
}
} catch (ApiException apiEx) {
if (machineLogger != null) {
try {
machineLogger.close();
} catch (IOException ioEx) {
LOG.error(ioEx.getLocalizedMessage(), ioEx);
}
}
throw new MachineException(apiEx.getLocalizedMessage(), apiEx);
}
}
/**
* Stops workspace by destroying all its machines and removing it from in memory storage.
*/
private void stopMachines(List<Instance> machines) {
for (Instance machine : machines) {
try {
machine.destroy();
} catch (RuntimeException | MachineException ex) {
LOG.error(format("Could not destroy machine '%s' of workspace '%s'",
machine.getId(),
machine.getWorkspaceId()),
ex);
}
}
}
@SuppressWarnings("unused")
@PostConstruct
private void createLogsDir() {
if (!(machineLogsDir.exists() || machineLogsDir.mkdirs())) {
throw new IllegalStateException("Unable create directory " + machineLogsDir.getAbsolutePath());
}
}
/**
* Removes all descriptors from the in-memory storage, while
* {@link MachineProcessManager#cleanup()} is responsible for machines destroying.
*/
@PreDestroy
@VisibleForTesting
void cleanup() {
isPreDestroyInvoked = true;
final java.io.File[] files = machineLogsDir.listFiles();
if (files != null && files.length > 0) {
for (java.io.File f : files) {
if (!IoUtil.deleteRecursive(f)) {
LOG.warn("Failed delete {}", f);
}
}
}
}
private LineConsumer getMachineLogger(MessageConsumer<MachineLogMessage> environmentLogger,
String machineId,
String machineName) throws ServerException {
createMachineLogsDir(machineId);
LineConsumer lineConsumer = new AbstractLineConsumer() {
@Override
public void writeLine(String line) throws IOException {
environmentLogger.consume(new MachineLogMessageImpl(machineName, line));
}
};
try {
return new CompositeLineConsumer(new FileLineConsumer(getMachineLogsFile(machineId)),
lineConsumer);
} catch (IOException e) {
throw new MachineException(format("Unable create log file '%s' for machine '%s'.",
e.getLocalizedMessage(),
machineId));
}
}
private void createMachineLogsDir(String machineId) throws MachineException {
File dir = new File(machineLogsDir, machineId);
if (!dir.exists() && !dir.mkdirs()) {
throw new MachineException("Can't create folder for the logs of machine");
}
}
private File getMachineLogsFile(String machineId) {
return new File(new File(machineLogsDir, machineId), "machineId.logs");
}
@VisibleForTesting
String generateMachineId() {
return NameGenerator.generate("machine", 16);
}
private static <T> T removeFirstMatching(List<? extends T> elements, Predicate<T> predicate) {
T element = null;
for (final Iterator<? extends T> it = elements.iterator(); it.hasNext() && element == null; ) {
final T next = it.next();
if (predicate.test(next)) {
element = next;
it.remove();
}
}
return element;
}
private void ensurePreDestroyIsNotExecuted() throws ServerException {
if (isPreDestroyInvoked) {
throw new ServerException("Could not perform operation because application server is stopping");
}
}
private enum EnvStatus {
STARTING,
RUNNING,
STOPPING
}
private static class EnvironmentHolder {
Queue<MachineConfigImpl> startQueue;
List<Instance> machines;
EnvStatus status;
MessageConsumer<MachineLogMessage> logger;
String name;
EnvironmentHolder(Queue<MachineConfigImpl> startQueue,
List<Instance> machines,
MessageConsumer<MachineLogMessage> envLogger,
EnvStatus envStatus,
String name) {
this.startQueue = startQueue;
this.machines = machines;
this.logger = envLogger;
this.status = envStatus;
this.name = name;
}
public EnvironmentHolder(EnvironmentHolder environmentHolder) {
this.startQueue = environmentHolder.startQueue;
this.machines = environmentHolder.machines;
this.logger = environmentHolder.logger;
this.status = environmentHolder.status;
this.name = environmentHolder.name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof EnvironmentHolder)) return false;
EnvironmentHolder that = (EnvironmentHolder)o;
return Objects.equals(startQueue, that.startQueue) &&
Objects.equals(machines, that.machines) &&
status == that.status &&
Objects.equals(logger, that.logger) &&
Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(startQueue, machines, status, logger, name);
}
}
// cleanup machine if event about instance failure comes
private class MachineCleaner implements EventSubscriber<InstanceStateEvent> {
@Override
public void onEvent(InstanceStateEvent event) {
if ((event.getType() == OOM) || (event.getType() == DIE)) {
EnvironmentHolder environmentHolder;
try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock("workspaceId")) {
environmentHolder = environments.get(event.getWorkspaceId());
}
if (environmentHolder != null) {
for (Instance instance : environmentHolder.machines) {
if (instance.getId().equals(event.getMachineId())) {
String message = "Machine is destroyed. ";
if (event.getType() == OOM) {
message = message +
"The processes in this machine need more RAM. This machine started with " +
instance.getConfig().getLimits().getRam() +
"MB. Create a new machine configuration that allocates additional RAM or increase " +
"the workspace RAM limit in the user dashboard.";
}
try {
if (!Strings.isNullOrEmpty(message)) {
instance.getLogger().writeLine(message);
}
} catch (IOException ignore) {
}
eventService.publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.DESTROYED)
.withDev(instance.getConfig().isDev())
.withMachineId(instance.getId())
.withWorkspaceId(instance.getWorkspaceId())
.withMachineName(instance.getConfig().getName()));
}
}
}
}
}
}
}

View File

@ -0,0 +1,159 @@
/*******************************************************************************
* 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.environment.server;
import com.google.common.base.Joiner;
import org.eclipse.che.api.core.model.machine.MachineConfig;
import org.eclipse.che.api.core.model.machine.ServerConf;
import org.eclipse.che.api.core.model.workspace.Environment;
import org.eclipse.che.api.machine.server.MachineInstanceProviders;
import javax.inject.Inject;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.regex.Pattern;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
/**
* Validates description of environment of workspace.
*
* @author Alexander Garagatyi
*/
public class CheEnvironmentValidator {
/* machine name must contain only {a-zA-Z0-9_-} characters and it's needed for validation machine names */
private static final Pattern MACHINE_NAME_PATTERN = Pattern.compile("^/?[a-zA-Z0-9_-]+$");
private static final Pattern SERVER_PORT = Pattern.compile("[1-9]+[0-9]*/(?:tcp|udp)");
private static final Pattern SERVER_PROTOCOL = Pattern.compile("[a-z][a-z0-9-+.]*");
private final MachineInstanceProviders machineInstanceProviders;
@Inject
public CheEnvironmentValidator(MachineInstanceProviders machineInstanceProviders) {
this.machineInstanceProviders = machineInstanceProviders;
}
public void validate(Environment env) throws IllegalArgumentException {
String envName = env.getName();
checkArgument(envName != null && !envName.isEmpty(),
"Environment name should not be neither null nor empty");
checkArgument(env.getMachineConfigs() != null && !env.getMachineConfigs().isEmpty(),
"Environment '%s' should contain at least 1 machine",
envName);
final long devCount = env.getMachineConfigs()
.stream()
.filter(MachineConfig::isDev)
.count();
checkArgument(devCount == 1,
"Environment '%s' should contain exactly 1 dev machine, but contains '%d'",
envName,
devCount);
for (MachineConfig machineCfg : env.getMachineConfigs()) {
validateMachine(machineCfg, envName);
}
}
private void validateMachine(MachineConfig machineCfg, String envName) throws IllegalArgumentException {
String machineName = machineCfg.getName();
checkArgument(!isNullOrEmpty(machineName), "Environment '%s' contains machine with null or empty name", envName);
checkArgument(MACHINE_NAME_PATTERN.matcher(machineName).matches(),
"Environment '%s' contains machine with invalid name '%s'", envName, machineName);
checkNotNull(machineCfg.getSource(), "Machine '%s' in environment '%s' doesn't have source", machineName, envName);
checkArgument(machineCfg.getSource().getContent() != null || machineCfg.getSource().getLocation() != null,
"Source of machine '%s' in environment '%s' must contain location or content", machineName, envName);
checkArgument(machineCfg.getSource().getContent() == null || machineCfg.getSource().getLocation() == null,
"Source of machine '%s' in environment '%s' contains mutually exclusive fields location and content",
machineName, envName);
checkArgument(machineInstanceProviders.hasProvider(machineCfg.getType()),
"Type '%s' of machine '%s' in environment '%s' is not supported. Supported values are: %s.",
machineCfg.getType(),
machineName,
envName,
Joiner.on(", ").join(machineInstanceProviders.getProviderTypes()));
if (machineCfg.getSource().getType().equals("dockerfile") && machineCfg.getSource().getLocation() != null) {
try {
final String protocol = new URL(machineCfg.getSource().getLocation()).getProtocol();
checkArgument(protocol.equals("http") || protocol.equals("https"),
"Environment '%s' contains machine '%s' with invalid source location protocol: %s",
envName,
machineName,
machineCfg.getSource().getLocation());
} catch (MalformedURLException e) {
throw new IllegalArgumentException(format("Environment '%s' contains machine '%s' with invalid source location: '%s'",
envName,
machineName,
machineCfg.getSource().getLocation()));
}
}
for (ServerConf serverConf : machineCfg.getServers()) {
checkArgument(serverConf.getPort() != null && SERVER_PORT.matcher(serverConf.getPort()).matches(),
"Machine '%s' in environment '%s' contains server conf with invalid port '%s'",
machineName,
envName,
serverConf.getPort());
checkArgument(serverConf.getProtocol() == null || SERVER_PROTOCOL.matcher(serverConf.getProtocol()).matches(),
"Machine '%s' in environment '%s' contains server conf with invalid protocol '%s'",
machineName,
envName,
serverConf.getProtocol());
}
for (Map.Entry<String, String> envVariable : machineCfg.getEnvVariables().entrySet()) {
checkArgument(!isNullOrEmpty(envVariable.getKey()),
"Machine '%s' in environment '%s' contains environment variable with null or empty name",
machineName,
envName);
checkNotNull(envVariable.getValue(),
"Machine '%s' in environment '%s' contains environment variable '%s' with null value",
machineName,
envName,
envVariable.getKey());
}
}
/**
* Checks that object reference is not null, throws {@link IllegalArgumentException} otherwise.
*
* <p>Exception uses error message built from error message template and error message parameters.
*/
private static void checkNotNull(Object object, String errorMessageTemplate, Object... errorMessageParams) {
if (object == null) {
throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageParams));
}
}
/**
* Checks that expression is true, throws {@link IllegalArgumentException} otherwise.
*
* <p>Exception uses error message built from error message template and error message parameters.
*/
private static void checkArgument(boolean expression, String errorMessage) {
if (!expression) {
throw new IllegalArgumentException(errorMessage);
}
}
/**
* Checks that expression is true, throws {@link IllegalArgumentException} otherwise.
*
* <p>Exception uses error message built from error message template and error message parameters.
*/
private static void checkArgument(boolean expression, String errorMessageTemplate, Object... errorMessageParams)
throws IllegalArgumentException {
if (!expression) {
throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageParams));
}
}
}

View File

@ -0,0 +1,282 @@
/*******************************************************************************
* 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.environment.server;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
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.model.machine.Command;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.util.CompositeLineConsumer;
import org.eclipse.che.api.core.util.FileLineConsumer;
import org.eclipse.che.api.core.util.LineConsumer;
import org.eclipse.che.api.core.util.WebsocketLineConsumer;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.spi.InstanceProcess;
import org.eclipse.che.api.machine.shared.dto.event.MachineProcessEvent;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
/**
* Facade for Machine process operations.
*
* @author gazarenkov
* @author Alexander Garagatyi
* @author Yevhenii Voevodin
*/
@Singleton
public class MachineProcessManager {
private static final Logger LOG = LoggerFactory.getLogger(MachineProcessManager.class);
private final File machineLogsDir;
private final CheEnvironmentEngine environmentEngine;
private final EventService eventService;
@VisibleForTesting
final ExecutorService executor;
@Inject
public MachineProcessManager(@Named("machine.logs.location") String machineLogsDir,
EventService eventService,
CheEnvironmentEngine environmentEngine) {
this.eventService = eventService;
this.machineLogsDir = new File(machineLogsDir);
this.environmentEngine = environmentEngine;
executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("MachineProcessManager-%d")
.setDaemon(false)
.build());
}
/**
* Execute a command in machine
*
* @param machineId
* id of the machine where command should be executed
* @param command
* command that should be executed in the machine
* @return {@link org.eclipse.che.api.machine.server.spi.InstanceProcess} that represents started process in machine
* @throws NotFoundException
* if machine with specified id not found
* @throws BadRequestException
* if value of required parameter is invalid
* @throws MachineException
* if other error occur
*/
public InstanceProcess exec(String workspaceId,
String machineId,
Command command,
@Nullable String outputChannel)
throws NotFoundException, MachineException, BadRequestException {
requiredNotNull(machineId, "Machine ID is required");
requiredNotNull(command, "Command is required");
requiredNotNull(command.getCommandLine(), "Command line is required");
requiredNotNull(command.getName(), "Command name is required");
requiredNotNull(command.getType(), "Command type is required");
final Instance machine = environmentEngine.getMachine(workspaceId, machineId);
final InstanceProcess instanceProcess = machine.createProcess(command, outputChannel);
final int pid = instanceProcess.getPid();
final LineConsumer processLogger = getProcessLogger(machineId, pid, outputChannel);
executor.execute(ThreadLocalPropagateContext.wrap(() -> {
try {
eventService.publish(newDto(MachineProcessEvent.class)
.withEventType(MachineProcessEvent.EventType.STARTED)
.withMachineId(machineId)
.withProcessId(pid));
instanceProcess.start(processLogger);
eventService.publish(newDto(MachineProcessEvent.class)
.withEventType(MachineProcessEvent.EventType.STOPPED)
.withMachineId(machineId)
.withProcessId(pid));
} catch (ConflictException | MachineException error) {
eventService.publish(newDto(MachineProcessEvent.class)
.withEventType(MachineProcessEvent.EventType.ERROR)
.withMachineId(machineId)
.withProcessId(pid)
.withError(error.getLocalizedMessage()));
try {
processLogger.writeLine(String.format("[ERROR] %s", error.getMessage()));
} catch (IOException ignored) {
}
} finally {
try {
processLogger.close();
} catch (IOException ignored) {
}
}
}));
return instanceProcess;
}
/**
* Get list of active processes from specific machine
*
* @param machineId
* id of machine to get processes information from
* @return list of {@link org.eclipse.che.api.machine.server.spi.InstanceProcess}
* @throws NotFoundException
* if machine with specified id not found
* @throws MachineException
* if other error occur
*/
public List<InstanceProcess> getProcesses(String workspaceId, String machineId) throws NotFoundException, MachineException {
return environmentEngine.getMachine(workspaceId, machineId).getProcesses();
}
/**
* Stop process in machine
*
* @param machineId
* if of the machine where process should be stopped
* @param pid
* id of the process that should be stopped in machine
* @throws NotFoundException
* if machine or process with specified id not found
* @throws ForbiddenException
* if process is finished already
* @throws MachineException
* if other error occur
*/
public void stopProcess(String workspaceId,
String machineId,
int pid) throws NotFoundException, MachineException, ForbiddenException {
final InstanceProcess process = environmentEngine.getMachine(workspaceId, machineId).getProcess(pid);
if (!process.isAlive()) {
throw new ForbiddenException("Process finished already");
}
process.kill();
eventService.publish(newDto(MachineProcessEvent.class)
.withEventType(MachineProcessEvent.EventType.STOPPED)
.withMachineId(machineId)
.withProcessId(pid));
}
/**
* Gets process reader from machine by specified id.
*
* @param machineId
* machine id whose process reader will be returned
* @param pid
* process id
* @return reader for specified process on machine
* @throws NotFoundException
* if machine with specified id not found
* @throws MachineException
* if other error occur
*/
public Reader getProcessLogReader(String machineId, int pid) throws NotFoundException, MachineException {
final File processLogsFile = getProcessLogsFile(machineId, pid);
if (processLogsFile.isFile()) {
try {
return Files.newBufferedReader(processLogsFile.toPath(), Charset.defaultCharset());
} catch (IOException e) {
throw new MachineException(
String.format("Unable read log file for process '%s' of machine '%s'. %s", pid, machineId, e.getMessage()));
}
}
throw new NotFoundException(String.format("Logs for process '%s' of machine '%s' are not available", pid, machineId));
}
private File getProcessLogsFile(String machineId, int pid) {
return new File(new File(machineLogsDir, machineId), Integer.toString(pid));
}
private FileLineConsumer getProcessFileLogger(String machineId, int pid) throws MachineException {
try {
return new FileLineConsumer(getProcessLogsFile(machineId, pid));
} catch (IOException e) {
throw new MachineException(
String.format("Unable create log file for process '%s' of machine '%s'. %s", pid, machineId, e.getMessage()));
}
}
@VisibleForTesting
LineConsumer getProcessLogger(String machineId, int pid, String outputChannel) throws MachineException {
return getLogger(getProcessFileLogger(machineId, pid), outputChannel);
}
private LineConsumer getLogger(LineConsumer fileLogger, String outputChannel) throws MachineException {
if (outputChannel != null) {
return new CompositeLineConsumer(fileLogger, new WebsocketLineConsumer(outputChannel));
}
return fileLogger;
}
/**
* Checks object reference is not {@code null}
*
* @param object
* object reference to check
* @param message
* used as subject of exception message "{subject} required"
* @throws org.eclipse.che.api.core.BadRequestException
* when object reference is {@code null}
*/
private void requiredNotNull(Object object, String message) throws BadRequestException {
if (object == null) {
throw new BadRequestException(message + " required");
}
}
@PreDestroy
private void cleanup() {
boolean interrupted = false;
executor.shutdown();
try {
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
executor.shutdownNow();
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
LOG.warn("Unable terminate main pool");
}
}
} catch (InterruptedException e) {
interrupted = true;
executor.shutdownNow();
}
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}

View File

@ -0,0 +1,333 @@
/*******************************************************************************
* 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.environment.server;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import com.google.common.io.CharStreams;
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.Machine;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.machine.server.DtoConverter;
import org.eclipse.che.api.machine.shared.dto.CommandDto;
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.workspace.server.WorkspaceManager;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
import javax.inject.Inject;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.io.Reader;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* Machine API
*
* @author Alexander Garagatyi
* @author Anton Korneta
*/
@Api(value = "/machine", description = "Machine REST API")
@Path("/workspace/{workspaceId}/machine")
public class MachineService extends Service {
private final MachineProcessManager machineProcessManager;
private final MachineServiceLinksInjector linksInjector;
private final WorkspaceManager workspaceManager;
@Inject
public MachineService(MachineProcessManager machineProcessManager,
MachineServiceLinksInjector linksInjector,
WorkspaceManager workspaceManager) {
this.machineProcessManager = machineProcessManager;
this.linksInjector = linksInjector;
this.workspaceManager = workspaceManager;
}
@GET
@Path("/{machineId}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get machine by ID")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains requested machine entity"),
@ApiResponse(code = 404, message = "Machine with specified id does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
@Deprecated
public MachineDto getMachineById(@ApiParam(value = "Workspace ID")
@PathParam("workspaceId")
String workspaceId,
@ApiParam(value = "Machine ID")
@PathParam("machineId")
String machineId)
throws ServerException,
ForbiddenException,
NotFoundException {
final Machine machine = workspaceManager.getMachineInstance(workspaceId, machineId);
return linksInjector.injectLinks(DtoConverter.asDto(machine), getServiceContext());
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get all machines of workspace with specified ID",
response = MachineDto.class,
responseContainer = "List")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains requested list of machine entities"),
@ApiResponse(code = 400, message = "Workspace ID is not specified"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
@Deprecated
public List<MachineDto> getMachines(@ApiParam(value = "Workspace ID")
@PathParam("workspaceId")
String workspaceId)
throws ServerException,
BadRequestException,
NotFoundException {
requiredNotNull(workspaceId, "Parameter workspace");
WorkspaceImpl workspace = workspaceManager.getWorkspace(workspaceId);
if (workspace.getRuntime() == null) {
return Collections.emptyList();
} else {
return workspace.getRuntime()
.getMachines()
.stream()
.map(DtoConverter::asDto)
.map(machineDto -> linksInjector.injectLinks(machineDto, getServiceContext()))
.collect(Collectors.toList());
}
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Create a new machine based on the configuration",
notes = "This operation can be performed only by authorized user")
@ApiResponses({@ApiResponse(code = 204, message = "The machine successfully created"),
@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)." +
"Workspace is not in RUNNING state"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public void startMachine(@ApiParam("The workspace id")
@PathParam("workspaceId")
String workspaceId,
@ApiParam(value = "The new machine configuration", required = true)
MachineConfigDto machineConfig) throws ForbiddenException,
NotFoundException,
ServerException,
ConflictException,
BadRequestException {
requiredNotNull(machineConfig, "Machine configuration");
requiredNotNull(machineConfig.getType(), "Machine type");
requiredNotNull(machineConfig.getSource(), "Machine source");
requiredNotNull(machineConfig.getSource().getType(), "Machine source type");
// definition of source should come either with a content or with location
requiredOnlyOneNotNull(machineConfig.getSource().getLocation(), machineConfig.getSource().getContent(),
"Machine source should provide either location or content");
workspaceManager.startMachine(machineConfig, workspaceId);
}
@DELETE
@Path("/{machineId}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Stop machine")
@ApiResponses({@ApiResponse(code = 204, message = "Machine was successfully stopped"),
@ApiResponse(code = 404, message = "Machine with specified id does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public void stopMachine(@ApiParam(value = "Workspace ID")
@PathParam("workspaceId") String workspaceId,
@ApiParam(value = "Machine ID")
@PathParam("machineId") String machineId) throws NotFoundException,
ServerException,
ForbiddenException,
ConflictException {
workspaceManager.stopMachine(workspaceId, machineId);
}
@POST
@Path("/{machineId}/command")
@Consumes(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Start specified command in machine")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains entity of created machine process"),
@ApiResponse(code = 400, message = "Command entity is invalid"),
@ApiResponse(code = 404, message = "Machine with specified ID does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public MachineProcessDto executeCommandInMachine(@ApiParam(value = "Workspace ID")
@PathParam("workspaceId")
String workspaceId,
@ApiParam(value = "Machine ID")
@PathParam("machineId")
String machineId,
@ApiParam(value = "Command to execute", required = true)
final CommandDto command,
@ApiParam(value = "Channel for command output")
@QueryParam("outputChannel")
String outputChannel)
throws NotFoundException,
ServerException,
ForbiddenException,
BadRequestException {
requiredNotNull(command, "Command description");
requiredNotNull(command.getCommandLine(), "Commandline");
return linksInjector.injectLinks(DtoConverter.asDto(machineProcessManager.exec(workspaceId,
machineId,
command,
outputChannel)),
workspaceId,
machineId,
getServiceContext());
}
@GET
@Path("/{machineId}/process")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get processes of machine",
response = MachineProcessDto.class,
responseContainer = "List")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains machine process entities"),
@ApiResponse(code = 404, message = "Machine with specified ID does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public List<MachineProcessDto> getProcesses(@ApiParam(value = "Workspace ID")
@PathParam("workspaceId")
String workspaceId,
@ApiParam(value = "Machine ID")
@PathParam("machineId")
String machineId)
throws NotFoundException,
ServerException,
ForbiddenException {
return machineProcessManager.getProcesses(workspaceId, machineId)
.stream()
.map(DtoConverter::asDto)
.map(machineProcess -> linksInjector.injectLinks(machineProcess,
workspaceId,
machineId,
getServiceContext()))
.collect(Collectors.toList());
}
@DELETE
@Path("/{machineId}/process/{processId}")
@ApiOperation(value = "Stop process in machine")
@ApiResponses({@ApiResponse(code = 204, message = "Process was successfully stopped"),
@ApiResponse(code = 404, message = "Machine with specified ID does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public void stopProcess(@ApiParam(value = "Workspace ID")
@PathParam("workspaceId")
String workspaceId,
@ApiParam(value = "Machine ID")
@PathParam("machineId")
String machineId,
@ApiParam(value = "Process ID")
@PathParam("processId")
int processId)
throws NotFoundException,
ForbiddenException,
ServerException {
machineProcessManager.stopProcess(workspaceId, machineId, processId);
}
@GET
@Path("/{machineId}/process/{pid}/logs")
@Produces(MediaType.TEXT_PLAIN)
@ApiOperation(value = "Get logs of machine process")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains logs"),
@ApiResponse(code = 404, message = "Machine or process with specified ID does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public void getProcessLogs(@ApiParam(value = "Workspace ID")
@PathParam("workspaceId")
String workspaceId,
@ApiParam(value = "Machine ID")
@PathParam("machineId")
String machineId,
@ApiParam(value = "Process ID")
@PathParam("pid")
int pid,
@Context
HttpServletResponse httpServletResponse)
throws NotFoundException,
ForbiddenException,
ServerException,
IOException {
addLogsToResponse(machineProcessManager.getProcessLogReader(machineId, pid), httpServletResponse);
}
/**
* Checks only one of the given object reference is {@code null}
*
* @param object1
* object reference to check
* @param object2
* object reference to check
* @param subject
* used as subject of exception message "{subject} required"
* @throws BadRequestException
* when objects are both null or have both a value reference is {@code null}
*/
private void requiredOnlyOneNotNull(Object object1, Object object2, String subject) throws BadRequestException {
if (object1 == null && object2 == null) {
throw new BadRequestException(subject + " required");
}
if (object1 != null && object2 != null) {
throw new BadRequestException(subject + " required");
}
}
private void addLogsToResponse(Reader logsReader, HttpServletResponse httpServletResponse) throws IOException {
// Response is written directly to the servlet request stream
httpServletResponse.setContentType("text/plain");
CharStreams.copy(logsReader, httpServletResponse.getWriter());
httpServletResponse.getWriter().flush();
}
/**
* Checks object reference is not {@code null}
*
* @param object
* object reference to check
* @param subject
* used as subject of exception message "{subject} required"
* @throws BadRequestException
* when object reference is {@code null}
*/
private void requiredNotNull(Object object, String subject) throws BadRequestException {
if (object == null) {
throw new BadRequestException(subject + " required");
}
}
}

View File

@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.machine.server;
package org.eclipse.che.api.environment.server;
import com.google.common.collect.Lists;
@ -19,7 +19,6 @@ import org.eclipse.che.api.machine.shared.Constants;
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;
import org.eclipse.che.api.machine.shared.dto.SnapshotDto;
import javax.inject.Singleton;
import javax.ws.rs.HttpMethod;
@ -35,8 +34,8 @@ 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.LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL;
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;
@ -56,67 +55,41 @@ public class MachineServiceLinksInjector {
links.add(createLink(HttpMethod.GET,
uriBuilder.clone()
.path(MachineService.class, "getMachineById")
.build(machine.getId())
.build(machine.getWorkspaceId(), machine.getId())
.toString(),
APPLICATION_JSON,
"self link"));
links.add(createLink(HttpMethod.GET,
uriBuilder.clone()
.path(MachineService.class, "getMachines")
.build()
.build(machine.getWorkspaceId())
.toString(),
null,
APPLICATION_JSON,
Constants.LINK_REL_GET_MACHINES,
newDto(LinkParameter.class).withName("workspace")
.withRequired(true)
.withDefaultValue(machine.getWorkspaceId())));
Constants.LINK_REL_GET_MACHINES));
links.add(createLink(HttpMethod.DELETE,
uriBuilder.clone()
.path(MachineService.class, "destroyMachine")
.build(machine.getId())
.path(MachineService.class, "stopMachine")
.build(machine.getWorkspaceId(), machine.getId())
.toString(),
Constants.LINK_REL_DESTROY_MACHINE));
links.add(createLink(HttpMethod.GET,
uriBuilder.clone()
.path(MachineService.class, "getSnapshots")
.build()
.toString(),
null,
APPLICATION_JSON,
Constants.LINK_REL_GET_SNAPSHOTS,
newDto(LinkParameter.class).withName("workspace")
.withRequired(true)
.withDefaultValue(machine.getWorkspaceId())));
links.add(createLink(HttpMethod.POST,
uriBuilder.clone()
.path(MachineService.class, "saveSnapshot")
.build(machine.getId())
.toString(),
APPLICATION_JSON,
APPLICATION_JSON,
Constants.LINK_REL_SAVE_SNAPSHOT));
links.add(createLink(HttpMethod.POST,
uriBuilder.clone()
.path(MachineService.class, "executeCommandInMachine")
.build(machine.getId())
.build(machine.getWorkspaceId(), machine.getId())
.toString(),
APPLICATION_JSON,
APPLICATION_JSON,
Constants.LINK_REL_EXECUTE_COMMAND,
newDto(LinkParameter.class).withName("outputChannel")
.withRequired(false)));
URI getProcessesUri = uriBuilder.clone()
.path(MachineService.class, "getProcesses")
.build(machine.getWorkspaceId(), machine.getId());
links.add(createLink(HttpMethod.GET,
uriBuilder.clone()
.path(MachineService.class, "getProcesses")
.build(machine.getId())
.toString(),
getProcessesUri.toString(),
APPLICATION_JSON,
Constants.LINK_REL_GET_PROCESSES));
final URI getLogsUri = uriBuilder.clone()
.path(MachineService.class, "getMachineLogs")
.build(machine.getId());
links.add(createLink(HttpMethod.GET, getLogsUri.toString(), TEXT_PLAIN, Constants.LINK_REL_GET_MACHINE_LOGS));
injectTerminalLink(machine, serviceContext, links);
@ -125,7 +98,7 @@ public class MachineServiceLinksInjector {
serviceContext.getBaseUriBuilder()
.path("ws")
.path(machine.getWorkspaceId())
.scheme("https".equals(getLogsUri.getScheme()) ? "wss" : "ws")
.scheme("https".equals(getProcessesUri.getScheme()) ? "wss" : "ws")
.build()
.toString(),
null);
@ -163,41 +136,39 @@ public class MachineServiceLinksInjector {
}
}
public MachineProcessDto injectLinks(MachineProcessDto process, String machineId, ServiceContext serviceContext) {
public MachineProcessDto injectLinks(MachineProcessDto process,
String workspaceId,
String machineId,
ServiceContext serviceContext) {
final UriBuilder uriBuilder = serviceContext.getServiceUriBuilder();
final List<Link> links = Lists.newArrayListWithExpectedSize(3);
links.add(createLink(HttpMethod.DELETE,
uriBuilder.clone()
.path(MachineService.class, "stopProcess")
.build(machineId, process.getPid())
.build(workspaceId,
machineId,
process.getPid())
.toString(),
Constants.LINK_REL_STOP_PROCESS));
links.add(createLink(HttpMethod.GET,
uriBuilder.clone()
.path(MachineService.class, "getProcessLogs")
.build(machineId, process.getPid())
.build(workspaceId,
machineId,
process.getPid())
.toString(),
TEXT_PLAIN,
Constants.LINK_REL_GET_PROCESS_LOGS));
links.add(createLink(HttpMethod.GET,
uriBuilder.clone()
.path(MachineService.class, "getProcesses")
.build(machineId)
.build(workspaceId,
machineId)
.toString(),
APPLICATION_JSON,
Constants.LINK_REL_GET_PROCESSES));
return process.withLinks(links);
}
public SnapshotDto injectLinks(SnapshotDto snapshot, ServiceContext serviceContext) {
final UriBuilder uriBuilder = serviceContext.getServiceUriBuilder();
return snapshot.withLinks(singletonList(createLink(HttpMethod.DELETE,
uriBuilder.clone()
.path(MachineService.class, "removeSnapshot")
.build(snapshot.getId())
.toString(),
Constants.LINK_REL_REMOVE_SNAPSHOT)));
}
}

View File

@ -0,0 +1,90 @@
/*******************************************************************************
* 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.environment.server;
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.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.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 java.util.Collections;
import java.util.List;
/**
* @author Alexander Garagatyi
*/
public class NoOpMachineInstance extends AbstractInstance {
public NoOpMachineInstance(Machine machine) {
super(machine);
}
@Override
public MachineRuntimeInfoImpl getRuntime() {
return null;
}
@Override
public LineConsumer getLogger() {
return null;
}
@Override
public InstanceProcess getProcess(int pid) throws NotFoundException, MachineException {
return null;
}
@Override
public List<InstanceProcess> getProcesses() throws MachineException {
return Collections.emptyList();
}
@Override
public InstanceProcess createProcess(Command command, String outputChannel) throws MachineException {
throw new MachineException("This machine has state that doesn't support process creation");
}
@Override
public MachineSource saveToSnapshot() throws MachineException {
throw new MachineException("This machine has state that doesn't support saving its state");
}
@Override
public void destroy() throws MachineException {
}
@Override
public InstanceNode getNode() {
return null;
}
@Override
public String readFileContent(String filePath, int startFrom, int limit) throws MachineException {
throw new MachineException("This machine has state that doesn't support process reading files");
}
@Override
public void copy(Instance sourceMachine, String sourcePath, String targetPath, boolean overwriteDirNonDir)
throws MachineException {
throw new MachineException("This machine has state that doesn't support copying of files");
}
@Override
public void copy(String sourcePath, String targetPath) throws MachineException {
throw new MachineException("This machine has state that doesn't support copying of files");
}
}

View File

@ -0,0 +1,29 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.environment.server.exception;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.rest.shared.dto.ServiceError;
/**
* Exception thrown in case environment stop is called but no matching environment is running.
*
* @author Alexander Garagatyi
*/
public class EnvironmentNotRunningException extends NotFoundException {
public EnvironmentNotRunningException(String message) {
super(message);
}
public EnvironmentNotRunningException(ServiceError serviceError) {
super(serviceError);
}
}

View File

@ -10,21 +10,15 @@
*******************************************************************************/
package org.eclipse.che.api.workspace.server;
import com.google.common.base.Joiner;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.model.machine.Command;
import org.eclipse.che.api.core.model.machine.MachineConfig;
import org.eclipse.che.api.core.model.machine.ServerConf;
import org.eclipse.che.api.core.model.workspace.Environment;
import org.eclipse.che.api.core.model.workspace.Workspace;
import org.eclipse.che.api.core.model.workspace.WorkspaceConfig;
import org.eclipse.che.api.machine.server.MachineInstanceProviders;
import org.eclipse.che.api.environment.server.CheEnvironmentValidator;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.regex.Pattern;
@ -39,15 +33,13 @@ import static java.lang.String.format;
@Singleton
public class DefaultWorkspaceValidator implements WorkspaceValidator {
/* should contain [3, 20] characters, first and last character is letter or digit, available characters {A-Za-z0-9.-_}*/
private static final Pattern WS_NAME = Pattern.compile("[a-zA-Z0-9][-_.a-zA-Z0-9]{1,18}[a-zA-Z0-9]");
private static final Pattern SERVER_PORT = Pattern.compile("[1-9]+[0-9]*/(?:tcp|udp)");
private static final Pattern SERVER_PROTOCOL = Pattern.compile("[a-z][a-z0-9-+.]*");
private final MachineInstanceProviders machineInstanceProviders;
private static final Pattern WS_NAME = Pattern.compile("[a-zA-Z0-9][-_.a-zA-Z0-9]{1,18}[a-zA-Z0-9]");
private final CheEnvironmentValidator environmentValidator;
@Inject
public DefaultWorkspaceValidator(MachineInstanceProviders machineInstanceProviders) {
this.machineInstanceProviders = machineInstanceProviders;
public DefaultWorkspaceValidator(CheEnvironmentValidator environmentValidator) {
this.environmentValidator = environmentValidator;
}
@Override
@ -68,13 +60,18 @@ public class DefaultWorkspaceValidator implements WorkspaceValidator {
//environments
checkArgument(!isNullOrEmpty(config.getDefaultEnv()), "Workspace default environment name required");
checkNotNull(config.getEnvironments(), "Workspace should contain at least one environment");
checkArgument(config.getEnvironments()
.stream()
.anyMatch(env -> config.getDefaultEnv().equals(env.getName())),
"Workspace default environment configuration required");
for (Environment environment : config.getEnvironments()) {
validateEnv(environment, config.getName());
try {
environmentValidator.validate(environment);
} catch (IllegalArgumentException e) {
throw new BadRequestException(e.getLocalizedMessage());
}
}
//commands
@ -102,68 +99,6 @@ public class DefaultWorkspaceValidator implements WorkspaceValidator {
}
}
private void validateEnv(Environment environment, String workspaceName) throws BadRequestException {
final String envName = environment.getName();
checkArgument(!isNullOrEmpty(envName), "Environment name should be neither null nor empty");
//machine configs
checkArgument(!environment.getMachineConfigs().isEmpty(), "Environment '%s' should contain at least 1 machine", envName);
final long devCount = environment.getMachineConfigs()
.stream()
.filter(MachineConfig::isDev)
.count();
checkArgument(devCount == 1,
"Environment should contain exactly 1 dev machine, but '%s' contains '%d'",
envName,
devCount);
for (MachineConfig machineCfg : environment.getMachineConfigs()) {
validateMachine(machineCfg, envName);
}
}
private void validateMachine(MachineConfig machineCfg, String envName) throws BadRequestException {
checkArgument(!isNullOrEmpty(machineCfg.getName()), "Environment %s contains machine with null or empty name", envName);
checkNotNull(machineCfg.getSource(), "Environment " + envName + " contains machine without source");
checkArgument(!(machineCfg.getSource().getContent() == null && machineCfg.getSource().getLocation() == null),
"Environment " + envName + " contains machine with source but this source doesn't define a location or content");
checkArgument(machineInstanceProviders.hasProvider(machineCfg.getType()),
"Type %s of machine %s in environment %s is not supported. Supported values: %s.",
machineCfg.getType(),
machineCfg.getName(),
envName,
Joiner.on(", ").join(machineInstanceProviders.getProviderTypes()));
if (machineCfg.getSource().getType().equals("dockerfile") && machineCfg.getSource().getLocation() != null) {
try {
final String protocol = new URL(machineCfg.getSource().getLocation()).getProtocol();
checkArgument(protocol.equals("http") || protocol.equals("https"),
"Environment " + envName + " contains machine with invalid source location protocol: " +
machineCfg.getSource().getLocation());
} catch (MalformedURLException e) {
throw new BadRequestException("Environment " + envName + " contains machine with invalid source location: " +
machineCfg.getSource().getLocation());
}
}
for (ServerConf serverConf : machineCfg.getServers()) {
checkArgument(serverConf.getPort() != null && SERVER_PORT.matcher(serverConf.getPort()).matches(),
"Machine %s contains server conf with invalid port %s",
machineCfg.getName(),
serverConf.getPort());
checkArgument(serverConf.getProtocol() == null || SERVER_PROTOCOL.matcher(serverConf.getProtocol()).matches(),
"Machine %s contains server conf with invalid protocol %s",
machineCfg.getName(),
serverConf.getProtocol());
}
for (Map.Entry<String, String> envVariable : machineCfg.getEnvVariables().entrySet()) {
checkArgument(!isNullOrEmpty(envVariable.getKey()), "Machine %s contains environment variable with null or empty name");
checkNotNull(envVariable.getValue(), "Machine %s contains environment variable with null value");
}
}
/**
* Checks that object reference is not null, throws {@link BadRequestException}
* in the case of null {@code object} with given {@code message}.

View File

@ -149,11 +149,7 @@ public final class DtoConverter {
envDto.withRecipe(newDto(RecipeDto.class).withType(env.getRecipe().getType())
.withScript(env.getRecipe().getScript()));
}
return newDto(EnvironmentDto.class).withName(env.getName())
.withMachineConfigs(env.getMachineConfigs()
.stream()
.map(org.eclipse.che.api.machine.server.DtoConverter::asDto)
.collect(toList()));
return envDto;
}
/** Converts {@link WorkspaceRuntime} to {@link WorkspaceRuntimeDto}. */
@ -183,7 +179,7 @@ public final class DtoConverter {
.withType(snapshot.getType())
.withWorkspaceId(snapshot.getWorkspaceId())
.withEnvName(snapshot.getEnvName())
.withMachineName(snapshot.getEnvName());
.withMachineName(snapshot.getMachineName());
}
private DtoConverter() {}

View File

@ -8,13 +8,12 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.ext.machine.server;
package org.eclipse.che.api.workspace.server;
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.machine.server.MachineManager;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.util.RecipeRetriever;
import javax.inject.Inject;
@ -27,24 +26,27 @@ import javax.ws.rs.core.MediaType;
/**
* Service for downloading recipe script for machine.
*
* @author Mihail Kuznyetsov.
* @author Mihail Kuznyetsov
* @author Alexander Garagatyi
*/
@Path("/recipe/script")
public class RecipeScriptDownloadService extends Service {
private final MachineManager machineManager;
private final RecipeRetriever recipeRetriever;
private final WorkspaceManager workspaceManager;
private final RecipeRetriever recipeRetriever;
@Inject
public RecipeScriptDownloadService(MachineManager machineManager, RecipeRetriever recipeRetriever) {
this.machineManager = machineManager;
public RecipeScriptDownloadService(WorkspaceManager workspaceManager, RecipeRetriever recipeRetriever) {
this.workspaceManager = workspaceManager;
this.recipeRetriever = recipeRetriever;
}
@GET
@Path("/{machineId}")
@Path("/{workspaceId}/{machineId}")
@Produces(MediaType.TEXT_PLAIN)
public String getRecipeScript(@PathParam("machineId") String machineId) throws ServerException, NotFoundException {
return recipeRetriever.getRecipe(machineManager.getMachine(machineId).getConfig()).getScript();
public String getRecipeScript(@PathParam("workspaceId") String workspaceId,
@PathParam("machineId") String machineId) throws ServerException,
NotFoundException {
Instance machineInstance = workspaceManager.getMachineInstance(workspaceId, machineId);
return recipeRetriever.getRecipe(machineInstance.getConfig()).getScript();
}
}

View File

@ -0,0 +1,125 @@
/*******************************************************************************
* 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.workspace.server;
import com.google.common.util.concurrent.Striped;
import java.io.Closeable;
import java.util.concurrent.locks.ReadWriteLock;
/**
* Helper class to use striped locks in try-with-resources construction.
* </p>
* Examples of usage:
* <pre class="code"><code class="java">
* StripedLocks stripedLocks = new StripedLocks(16);
* try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(myKey)) {
* syncedObject.write();
* }
*
* try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(myKey)) {
* syncedObject.read();
* }
*
* try (StripedLocks.WriteAllLock lock = stripedLocks.acquireWriteAllLock(myKey)) {
* for (ObjectToSync objectToSync : allObjectsToSync) {
* objectToSync.write();
* }
* }
* </pre>
*
* @author Alexander Garagatyi
*/
// TODO consider usage of plain map with locks instead of Guava's Striped
// TODO consider moving to the util module of Che core
public class StripedLocks {
private final Striped<ReadWriteLock> striped;
public StripedLocks(int stripesCount) {
striped = Striped.readWriteLock(stripesCount);
}
/**
* Acquire read lock for provided key.
*/
public ReadLock acquireReadLock(String key) {
return new ReadLock(key);
}
/**
* Acquire write lock for provided key.
*/
public WriteLock acquireWriteLock(String key) {
return new WriteLock(key);
}
/**
* Acquire write lock for all possible keys.
*/
public WriteAllLock acquireWriteAllLock() {
return new WriteAllLock();
}
/**
* Represents read lock for the provided key.
* Can be used as {@link AutoCloseable} to release lock.
*/
public class ReadLock implements Closeable {
private String key;
private ReadLock(String key) {
this.key = key;
striped.get(key).readLock().lock();
}
@Override
public void close() {
striped.get(key).readLock().unlock();
}
}
/**
* Represents write lock for the provided key.
* Can be used as {@link AutoCloseable} to release lock.
*/
public class WriteLock implements Closeable {
private String key;
private WriteLock(String key) {
this.key = key;
striped.get(key).readLock().lock();
}
@Override
public void close() {
striped.get(key).readLock().unlock();
}
}
/**
* Represents write lock for all possible keys.
* Can be used as {@link AutoCloseable} to release locks.
*/
public class WriteAllLock implements Closeable {
private WriteAllLock() {
for (int i = 0; i < striped.size(); i++) {
striped.getAt(i).writeLock().lock();
}
}
@Override
public void close() {
for (int i = 0; i < striped.size(); i++) {
striped.getAt(i).writeLock().unlock();
}
}
}
}

View File

@ -17,7 +17,6 @@ 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;
@ -25,9 +24,10 @@ 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;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.machine.server.MachineManager;
import org.eclipse.che.api.machine.server.dao.SnapshotDao;
import org.eclipse.che.api.machine.server.model.impl.MachineImpl;
import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.workspace.server.WorkspaceRuntimes.RuntimeDescriptor;
import org.eclipse.che.api.workspace.server.event.WorkspaceCreatedEvent;
import org.eclipse.che.api.workspace.server.event.WorkspaceRemovedEvent;
@ -90,9 +90,9 @@ public class WorkspaceManager {
private final WorkspaceRuntimes runtimes;
private final EventService eventService;
private final ExecutorService executor;
private final MachineManager machineManager;
private final boolean defaultAutoSnapshot;
private final boolean defaultAutoRestore;
private final SnapshotDao snapshotDao;
private WorkspaceHooks hooks = new NoopWorkspaceHooks();
@ -100,15 +100,15 @@ public class WorkspaceManager {
public WorkspaceManager(WorkspaceDao workspaceDao,
WorkspaceRuntimes workspaceRegistry,
EventService eventService,
MachineManager machineManager,
@Named("workspace.runtime.auto_snapshot") boolean defaultAutoSnapshot,
@Named("workspace.runtime.auto_restore") boolean defaultAutoRestore) {
@Named("workspace.runtime.auto_restore") boolean defaultAutoRestore,
SnapshotDao snapshotDao) {
this.workspaceDao = workspaceDao;
this.runtimes = workspaceRegistry;
this.eventService = eventService;
this.machineManager = machineManager;
this.defaultAutoSnapshot = defaultAutoSnapshot;
this.defaultAutoRestore = defaultAutoRestore;
this.snapshotDao = snapshotDao;
executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("WorkspaceManager-%d")
.setDaemon(true)
@ -262,7 +262,9 @@ public class WorkspaceManager {
public List<WorkspaceImpl> getWorkspaces(String user) throws ServerException {
requireNonNull(user, "Required non-null user id");
final List<WorkspaceImpl> workspaces = workspaceDao.getWorkspaces(user);
workspaces.forEach(this::normalizeState);
for (WorkspaceImpl workspace : workspaces) {
normalizeState(workspace);
}
return workspaces;
}
@ -283,7 +285,9 @@ public class WorkspaceManager {
public List<WorkspaceImpl> getByNamespace(String namespace) throws ServerException {
requireNonNull(namespace, "Required non-null namespace");
final List<WorkspaceImpl> workspaces = workspaceDao.getByNamespace(namespace);
workspaces.forEach(this::normalizeState);
for (WorkspaceImpl workspace : workspaces) {
normalizeState(workspace);
}
return workspaces;
}
@ -431,7 +435,6 @@ public class WorkspaceManager {
*
* @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
@ -445,21 +448,18 @@ public class WorkspaceManager {
* @throws ServerException
* if any other exception occurs during starting
*/
public MachineImpl startMachine(MachineConfig machineConfig, String workspaceId)
throws ServerException,
ConflictException,
BadRequestException,
NotFoundException {
public void 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()));
performAsyncStart(machineConfig, workspaceId);
}
/**
@ -474,9 +474,13 @@ public class WorkspaceManager {
* @throws NotFoundException
* when workspace {@code workspaceId} doesn't have runtime
*/
public void stopWorkspace(String workspaceId) throws ServerException, NotFoundException, ConflictException {
public void stopWorkspace(String workspaceId) throws ServerException,
NotFoundException,
ConflictException {
requireNonNull(workspaceId, "Required non-null workspace id");
performAsyncStop(normalizeState(workspaceDao.get(workspaceId)));
final WorkspaceImpl workspace = normalizeState(workspaceDao.get(workspaceId));
checkWorkspaceIsRunning(workspace, "stop");
performAsyncStop(workspace);
}
/**
@ -504,12 +508,16 @@ public class WorkspaceManager {
* @throws ConflictException
* when workspace is not running
*/
public void createSnapshot(String workspaceId) throws NotFoundException, ServerException, ConflictException {
public void createSnapshot(String workspaceId) throws NotFoundException,
ServerException,
ConflictException {
requireNonNull(workspaceId, "Required non-null workspace id");
final WorkspaceImpl workspace = normalizeState(workspaceDao.get(workspaceId));
checkWorkspaceIsRunning(workspace, "create a snapshot of");
executor.execute(ThreadLocalPropagateContext.wrap(() -> {
createSnapshotSync(workspace.getRuntime(), workspace.getNamespace(), workspaceId);
createSnapshotSync(workspace.getRuntime(),
workspace.getNamespace(),
workspaceId);
}));
}
@ -530,11 +538,12 @@ public class WorkspaceManager {
requireNonNull(workspaceId, "Required non-null workspace id");
// check if workspace exists
final WorkspaceImpl workspace = workspaceDao.get(workspaceId);
return machineManager.getSnapshots(workspace.getNamespace(), workspaceId);
return snapshotDao.findSnapshots(workspace.getNamespace(), workspaceId);
}
/**
* Removes all snapshots of workspace machines
* Removes all snapshots of workspace machines.
* Continues to remove snapshots even when removal of some of them fails.
*
* @param workspaceId workspace id to remove machine snapshots
* @throws NotFoundException
@ -543,7 +552,64 @@ public class WorkspaceManager {
* when any other error occurs
*/
public void removeSnapshots(String workspaceId) throws NotFoundException, ServerException {
machineManager.removeSnapshots(getWorkspace(workspaceId).getNamespace(), workspaceId);
List<SnapshotImpl> snapshots = getSnapshot(workspaceId);
for (SnapshotImpl snapshot : snapshots) {
try {
runtimes.removeSnapshot(snapshot);
snapshotDao.removeSnapshot(snapshot.getId());
} catch (Exception e) {
LOG.error(e.getLocalizedMessage(), e);
}
}
}
/**
* Stops machine in running workspace.
*
* @param workspaceId
* ID of workspace that owns machine
* @param machineId
* ID of machine that should be stopped
* @throws NotFoundException
* if machine is not found in running workspace
* @throws ConflictException
* if workspace is not running
* @throws ConflictException
* if machine stop is forbidden (e.g. machine is dev-machine)
* @throws ServerException
* if other error occurs
*/
public void stopMachine(String workspaceId,
String machineId) throws NotFoundException,
ServerException,
ConflictException {
requireNonNull(workspaceId, "Required non-null workspace id");
requireNonNull(machineId, "Required non-null machine id");
final WorkspaceImpl workspace = normalizeState(workspaceDao.get(workspaceId));
checkWorkspaceIsRunning(workspace, format("stop machine with ID '%s' of", machineId));
runtimes.stopMachine(workspaceId, machineId);
}
/**
* Retrieves machine instance that allows to execute commands in a machine.
*
* @param workspaceId
* ID of workspace that owns machine
* @param machineId
* ID of requested machine
* @return instance of requested machine
* @throws NotFoundException
* if workspace is not running
* @throws NotFoundException
* if machine is not found in the workspace
*/
public Instance getMachineInstance(String workspaceId,
String machineId) throws NotFoundException,
ServerException {
requireNonNull(workspaceId, "Required non-null workspace id");
requireNonNull(machineId, "Required non-null machine id");
normalizeState(workspaceDao.get(workspaceId));
return runtimes.getMachine(workspaceId, machineId);
}
/** Asynchronously starts given workspace. */
@ -563,7 +629,7 @@ public class WorkspaceManager {
}
// WorkspaceRuntimes performs this check as well
// but this check needed here because permanent workspace start performed asynchronously
// which means that even if registry won't start workspace client receives workspace object
// which means that even if workspace runtimes won't start workspace client receives workspace object
// with starting status, this check prevents it and throws appropriate exception
try {
final RuntimeDescriptor descriptor = runtimes.get(workspace.getId());
@ -571,7 +637,7 @@ public class WorkspaceManager {
workspace.getConfig().getName(),
descriptor.getRuntimeStatus()));
} catch (NotFoundException ignored) {
// it is okay if workspace does not exist
// it is okay if workspace does not exist in runtimes
}
workspace.getAttributes().put(UPDATED_ATTRIBUTE_NAME, Long.toString(currentTimeMillis()));
@ -587,7 +653,7 @@ public class WorkspaceManager {
workspace.getConfig().getName(),
workspace.getId(),
sessionUserNameOr("undefined"));
} catch (RuntimeException | ServerException | NotFoundException | ConflictException | ForbiddenException ex) {
} catch (RuntimeException | ApiException ex) {
if (workspace.isTemporary()) {
try {
removeWorkspace(workspace.getId());
@ -644,6 +710,16 @@ public class WorkspaceManager {
}));
}
private void performAsyncStart(MachineConfig machineConfig, String workspaceId) {
executor.execute(ThreadLocalPropagateContext.wrap(() -> {
try {
runtimes.startMachine(workspaceId, machineConfig);
} catch (ApiException e) {
LOG.error(e.getLocalizedMessage(), e);
}
}));
}
/**
* Synchronously creates snapshot of the workspace.
*
@ -658,7 +734,10 @@ public class WorkspaceManager {
String devMachineSnapshotFailMessage = null;
for (MachineImpl machine : runtime.getMachines()) {
try {
machineManager.saveSync(machine.getId(), namespace, runtime.getActiveEnv());
SnapshotImpl snapshot = runtimes.saveMachine(namespace,
workspaceId,
machine.getId());
snapshotDao.saveSnapshot(snapshot);
} catch (ApiException apiEx) {
if (machine.getConfig().isDev()) {
devMachineSnapshotFailMessage = apiEx.getLocalizedMessage();
@ -679,7 +758,8 @@ public class WorkspaceManager {
return devMachineSnapshotFailMessage == null;
}
private void checkWorkspaceIsRunning(WorkspaceImpl workspace, String operation) throws ConflictException {
@VisibleForTesting
void checkWorkspaceIsRunning(WorkspaceImpl workspace, String operation) throws ConflictException {
if (workspace.getStatus() != RUNNING) {
throw new ConflictException(format("Could not %s the workspace '%s:%s' because its status is '%s'.",
operation,
@ -701,7 +781,7 @@ public class WorkspaceManager {
return nameIfNoUser;
}
private WorkspaceImpl normalizeState(WorkspaceImpl workspace) {
private WorkspaceImpl normalizeState(WorkspaceImpl workspace) throws ServerException {
try {
return normalizeState(workspace, runtimes.get(workspace.getId()));
} catch (NotFoundException e) {
@ -712,7 +792,7 @@ public class WorkspaceManager {
private WorkspaceImpl normalizeState(WorkspaceImpl workspace, RuntimeDescriptor descriptor) {
if (descriptor != null) {
workspace.setStatus(descriptor.getRuntimeStatus());
workspace.setRuntime(descriptor.getRuntime());
workspace.setRuntime(new WorkspaceRuntimeImpl(descriptor.getRuntime()));
} else {
workspace.setStatus(STOPPED);
}

View File

@ -28,9 +28,7 @@ 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.model.impl.CommandImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineImpl;
import org.eclipse.che.api.machine.shared.dto.CommandDto;
import org.eclipse.che.api.machine.shared.dto.MachineConfigDto;
import org.eclipse.che.api.machine.shared.dto.SnapshotDto;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl;
@ -53,7 +51,6 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import java.util.List;
@ -235,9 +232,7 @@ public class WorkspaceService extends Service {
NotFoundException,
ConflictException,
ForbiddenException {
if (!workspaceManager.getSnapshot(id).isEmpty()) {
workspaceManager.removeSnapshots(id);
}
workspaceManager.removeSnapshots(id);
workspaceManager.removeWorkspace(id);
}
@ -355,10 +350,12 @@ public class WorkspaceService extends Service {
"The snapshot doesn't exist for the workspace"),
@ApiResponse(code = 403, message = "The user is not workspace owner"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public List<SnapshotDto> getSnapshot(@ApiParam("The id of the workspace") @PathParam("id") String workspaceId) throws ServerException,
BadRequestException,
NotFoundException,
ForbiddenException {
public List<SnapshotDto> getSnapshot(@ApiParam("The id of the workspace") @PathParam("id") String workspaceId)
throws ServerException,
BadRequestException,
NotFoundException,
ForbiddenException {
return workspaceManager.getSnapshot(workspaceId)
.stream()
.map(DtoConverter::asDto)
@ -450,9 +447,9 @@ public class WorkspaceService extends Service {
if (workspace.getConfig().getCommands().removeIf(command -> command.getName().equals(commandName))) {
workspaceManager.updateWorkspace(id, workspace);
} else {
throw new NotFoundException(
String.format("Command with name '%s' was not found in workspace '%s'", commandName,
workspace.getConfig().getName()));
throw new NotFoundException(format("Command with name '%s' was not found in workspace '%s'",
commandName,
workspace.getConfig().getName()));
}
}
@ -632,44 +629,6 @@ public class WorkspaceService extends Service {
}
}
@POST
@Path("/{id}/machine")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Create a new machine based on the configuration",
notes = "This operation can be performed only by authorized user")
@ApiResponses({@ApiResponse(code = 201, message = "The machine successfully created"),
@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)." +
"Workspace is not in RUNNING state"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public Response createMachine(@ApiParam("The workspace id")
@PathParam("id")
String workspaceId,
@ApiParam(value = "The new machine configuration", required = true)
MachineConfigDto machineConfig) throws ForbiddenException,
NotFoundException,
ServerException,
ConflictException,
BadRequestException {
requiredNotNull(machineConfig, "Machine configuration");
requiredNotNull(machineConfig.getType(), "Machine type");
requiredNotNull(machineConfig.getSource(), "Machine source");
requiredNotNull(machineConfig.getSource().getType(), "Machine source type");
// definition of source should come either with a content or with location
requiredOnlyOneNotNull(machineConfig.getSource().getLocation(), machineConfig.getSource().getContent(),
"Machine source should provide either location or content");
final MachineImpl machine = workspaceManager.startMachine(machineConfig, workspaceId);
return Response.status(201)
.entity(linksInjector.injectMachineLinks(org.eclipse.che.api.machine.server.DtoConverter.asDto(machine),
getServiceContext()))
.build();
}
private static Map<String, String> parseAttrs(List<String> attributes) throws BadRequestException {
if (attributes == null) {
return emptyMap();
@ -703,27 +662,6 @@ public class WorkspaceService extends Service {
}
}
/**
* Checks only one of the given object reference is {@code null}
*
* @param object1
* object reference to check
* @param object2
* object reference to check
* @param subject
* used as subject of exception message "{subject} required"
* @throws BadRequestException
* when objects are both null or have both a value reference is {@code null}
*/
private void requiredOnlyOneNotNull(Object object1, Object object2, String subject) throws BadRequestException {
if (object1 == null && object2 == null) {
throw new BadRequestException(subject + " required");
}
if (object1 != null && object2 != null) {
throw new BadRequestException(subject + " required");
}
}
/*
* Validate composite key.
*

View File

@ -13,7 +13,7 @@ package org.eclipse.che.api.workspace.server;
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.environment.server.MachineServiceLinksInjector;
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;
@ -21,7 +21,6 @@ import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceRuntimeDto;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.ws.rs.core.UriBuilder;
import java.net.URI;

View File

@ -15,7 +15,6 @@ import org.eclipse.che.api.core.model.machine.Recipe;
import org.eclipse.che.api.core.model.workspace.Environment;
import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl;
import org.eclipse.che.api.machine.server.recipe.RecipeImpl;
import org.eclipse.che.commons.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
@ -55,12 +54,19 @@ public class EnvironmentImpl implements Environment {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
@Nullable
public Recipe getRecipe() {
return recipe;
}
public void setRecipe(RecipeImpl recipe) {
this.recipe = recipe;
}
@Override
public List<MachineConfigImpl> getMachineConfigs() {
if (machineConfigs == null) {
@ -69,23 +75,23 @@ public class EnvironmentImpl implements Environment {
return machineConfigs;
}
public void setMachineConfigs(List<MachineConfigImpl> machineConfigs) {
this.machineConfigs = machineConfigs;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof EnvironmentImpl)) return false;
final EnvironmentImpl other = (EnvironmentImpl)obj;
return Objects.equals(name, other.name) &&
Objects.equals(recipe, other.recipe) &&
getMachineConfigs().equals(other.getMachineConfigs());
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof EnvironmentImpl)) return false;
EnvironmentImpl that = (EnvironmentImpl)o;
return Objects.equals(name, that.name) &&
Objects.equals(recipe, that.recipe) &&
Objects.equals(machineConfigs, that.machineConfigs);
}
@Override
public int hashCode() {
int hash = 7;
hash = hash * 31 + Objects.hashCode(name);
hash = hash * 31 + Objects.hashCode(recipe);
hash = hash * 31 + getMachineConfigs().hashCode();
return hash;
return Objects.hash(name, recipe, machineConfigs);
}
@Override

View File

@ -95,28 +95,22 @@ public class WorkspaceRuntimeImpl implements WorkspaceRuntime {
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof WorkspaceRuntimeImpl)) {
return false;
}
final WorkspaceRuntimeImpl other = (WorkspaceRuntimeImpl)obj;
return Objects.equals(activeEnv, other.activeEnv)
&& Objects.equals(rootFolder, other.rootFolder)
&& Objects.equals(devMachine, other.devMachine)
&& getMachines().equals(getMachines());
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof WorkspaceRuntimeImpl)) return false;
WorkspaceRuntimeImpl that = (WorkspaceRuntimeImpl)o;
return Objects.equals(activeEnv, that.activeEnv) &&
Objects.equals(rootFolder, that.rootFolder) &&
Objects.equals(devMachine, that.devMachine) &&
Objects.equals(machines, that.machines);
}
@Override
public int hashCode() {
int hash = 7;
hash = hash * 31 + Objects.hashCode(activeEnv);
hash = 31 * hash + Objects.hashCode(rootFolder);
hash = 31 * hash + Objects.hashCode(devMachine);
hash = 31 * hash + getMachines().hashCode();
return hash;
return Objects.hash(activeEnv,
rootFolder,
devMachine,
machines);
}
@Override

View File

@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.machine.server.wsagent;
package org.eclipse.che.api.agent.server.wsagent;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.NotFoundException;
@ -18,15 +18,16 @@ import org.eclipse.che.api.core.model.machine.Server;
import org.eclipse.che.api.core.rest.HttpJsonRequest;
import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;
import org.eclipse.che.api.core.rest.HttpJsonResponse;
import org.eclipse.che.api.machine.server.MachineManager;
import org.eclipse.che.api.environment.server.MachineProcessManager;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.model.impl.CommandImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineRuntimeInfoImpl;
import org.eclipse.che.api.machine.server.model.impl.ServerImpl;
import org.eclipse.che.api.machine.shared.Constants;
import org.eclipse.che.commons.test.SelfReturningAnswer;
import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
@ -42,31 +43,30 @@ import static org.mockito.Matchers.any;
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.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@Listeners(MockitoTestNGListener.class)
public class WsAgentLauncherImplTest {
private static final String WS_ID = "wsId";
private static final String MACHINE_ID = "machineId";
private static final String WS_AGENT_START_CMD_LINE = "cmdLine";
private static final String WS_AGENT_PORT = Constants.WS_AGENT_PORT;
private static final long WS_AGENT_MAX_START_TIME_MS = 1000;
private static final long WS_AGENT_PING_DELAY_MS = 1;
private static final int WS_AGENT_PING_CONN_TIMEOUT_MS = 1;
private static final String WS_AGENT_SERVER_LOCATION = "ws-agent.com:456789/";
private static final String WS_AGENT_SERVER_URL = "http://" + WS_AGENT_SERVER_LOCATION;
private static final String MACHINE_ID = "machineId";
private static final String WORKSPACE_ID = "testWorkspaceId";
private static final String WS_AGENT_START_CMD_LINE = "cmdLine";
private static final String WS_AGENT_PORT = Constants.WS_AGENT_PORT;
private static final long WS_AGENT_MAX_START_TIME_MS = 1000;
private static final long WS_AGENT_PING_DELAY_MS = 1;
private static final int WS_AGENT_PING_CONN_TIMEOUT_MS = 1;
private static final String WS_AGENT_SERVER_LOCATION = "ws-agent.com:456789/";
private static final String WS_AGENT_SERVER_URL = "http://" + WS_AGENT_SERVER_LOCATION;
private static final ServerImpl SERVER = new ServerImpl("ref",
"http",
WS_AGENT_SERVER_LOCATION,
null,
WS_AGENT_SERVER_URL);
private static final String WS_AGENT_TIMED_OUT_MESSAGE = "timeout error message";
private static final String WS_AGENT_TIMED_OUT_MESSAGE = "timeout error message";
@Mock
private MachineManager machineManager;
private MachineProcessManager machineProcessManager;
@Mock
private HttpJsonRequestFactory requestFactory;
@Mock
@ -81,16 +81,16 @@ public class WsAgentLauncherImplTest {
@BeforeMethod
public void setUp() throws Exception {
wsAgentLauncher = new WsAgentLauncherImpl(() -> machineManager,
wsAgentLauncher = new WsAgentLauncherImpl(() -> machineProcessManager,
requestFactory,
WS_AGENT_START_CMD_LINE,
WS_AGENT_MAX_START_TIME_MS,
WS_AGENT_PING_DELAY_MS,
WS_AGENT_PING_CONN_TIMEOUT_MS,
WS_AGENT_TIMED_OUT_MESSAGE);
pingRequest = mock(HttpJsonRequest.class, new SelfReturningAnswer());
when(machineManager.getDevMachine(WS_ID)).thenReturn(machine);
pingRequest = Mockito.mock(HttpJsonRequest.class, new SelfReturningAnswer());
when(machine.getId()).thenReturn(MACHINE_ID);
when(machine.getWorkspaceId()).thenReturn(WORKSPACE_ID);
when(machine.getRuntime()).thenReturn(machineRuntime);
doReturn(Collections.<String, Server>singletonMap(WS_AGENT_PORT, SERVER)).when(machineRuntime).getServers();
when(requestFactory.fromUrl(anyString())).thenReturn(pingRequest);
@ -100,19 +100,20 @@ public class WsAgentLauncherImplTest {
@Test
public void shouldStartWsAgentUsingMachineExec() throws Exception {
wsAgentLauncher.startWsAgent(WS_ID);
wsAgentLauncher.startWsAgent(machine);
verify(machineManager).exec(eq(MACHINE_ID),
eq(new CommandImpl(WsAgentLauncherImpl.WS_AGENT_PROCESS_NAME,
verify(machineProcessManager).exec(eq(WORKSPACE_ID),
eq(MACHINE_ID),
eq(new CommandImpl(WsAgentLauncherImpl.WS_AGENT_PROCESS_NAME,
WS_AGENT_START_CMD_LINE,
"Arbitrary")),
eq(WsAgentLauncherImpl.getWsAgentProcessOutputChannel(WS_ID)));
eq(WsAgentLauncherImpl.getWsAgentProcessOutputChannel(WORKSPACE_ID)));
}
@Test
public void shouldPingWsAgentAfterStart() throws Exception {
wsAgentLauncher.startWsAgent(WS_ID);
wsAgentLauncher.startWsAgent(machine);
verify(requestFactory).fromUrl(UriBuilder.fromUri(WS_AGENT_SERVER_URL)
.build()
@ -130,7 +131,7 @@ public class WsAgentLauncherImplTest {
new IOException())
.thenReturn(pingResponse);
wsAgentLauncher.startWsAgent(WS_ID);
wsAgentLauncher.startWsAgent(machine);
verify(requestFactory).fromUrl(WS_AGENT_SERVER_URL);
verify(pingRequest).setMethod(HttpMethod.GET);
@ -145,7 +146,7 @@ public class WsAgentLauncherImplTest {
HttpURLConnection.HTTP_NO_CONTENT,
HttpURLConnection.HTTP_OK);
wsAgentLauncher.startWsAgent(WS_ID);
wsAgentLauncher.startWsAgent(machine);
verify(requestFactory).fromUrl(WS_AGENT_SERVER_URL);
verify(pingRequest).setMethod(HttpMethod.GET);
@ -159,57 +160,58 @@ public class WsAgentLauncherImplTest {
when(pingRequest.request()).thenThrow(new ServerException(""))
.thenReturn(pingResponse);
wsAgentLauncher.startWsAgent(WS_ID);
wsAgentLauncher.startWsAgent(machine);
verify(pingRequest, times(2)).request();
verify(pingResponse).getResponseCode();
}
@Test(expectedExceptions = NotFoundException.class, expectedExceptionsMessageRegExp = "Test exception")
public void shouldThrowNotFoundExceptionIfMachineManagerGetDevMachineForWsThrowsNotFoundException() throws Exception {
final String notExistingWsId = "notExistingWsId";
when(machineManager.getDevMachine(notExistingWsId)).thenThrow(new NotFoundException("Test exception"));
wsAgentLauncher.startWsAgent(notExistingWsId);
verify(machineManager).getDevMachine(eq(notExistingWsId));
}
@Test(expectedExceptions = MachineException.class, expectedExceptionsMessageRegExp = "Test exception")
public void shouldThrowMachineExceptionIfMachineManagerGetDevMachineForWsThrowsMachineException() throws Exception {
final String notExistingWsId = "notExistingWsId";
when(machineManager.getDevMachine(notExistingWsId)).thenThrow(new MachineException("Test exception"));
wsAgentLauncher.startWsAgent(notExistingWsId);
verify(machineManager).getDevMachine(eq(notExistingWsId));
}
@Test(expectedExceptions = NotFoundException.class, expectedExceptionsMessageRegExp = "Test exception")
public void shouldThrowNotFoundExceptionIfMachineManagerExecInDevMachineThrowsNotFoundException() throws Exception {
when(machineManager.exec(anyString(), any(Command.class), anyString())).thenThrow(new NotFoundException("Test exception"));
when(machineProcessManager.exec(anyString(),
anyString(),
any(Command.class),
anyString()))
.thenThrow(new NotFoundException("Test exception"));
wsAgentLauncher.startWsAgent(WS_ID);
wsAgentLauncher.startWsAgent(machine);
verify(machineManager).exec(anyString(), any(Command.class), anyString());
verify(machineProcessManager).exec(anyString(),
anyString(),
any(Command.class),
anyString());
}
@Test(expectedExceptions = MachineException.class, expectedExceptionsMessageRegExp = "Test exception")
public void shouldThrowMachineExceptionIfMachineManagerExecInDevMachineThrowsMachineException() throws Exception {
when(machineManager.exec(anyString(), any(Command.class), anyString())).thenThrow(new MachineException("Test exception"));
when(machineProcessManager.exec(anyString(),
anyString(),
any(Command.class),
anyString()))
.thenThrow(new MachineException("Test exception"));
wsAgentLauncher.startWsAgent(WS_ID);
wsAgentLauncher.startWsAgent(machine);
verify(machineManager).exec(anyString(), any(Command.class), anyString());
verify(machineProcessManager).exec(anyString(),
anyString(),
any(Command.class),
anyString());
}
@Test(expectedExceptions = MachineException.class, expectedExceptionsMessageRegExp = "Test exception")
public void shouldThrowMachineExceptionIfMachineManagerExecInDevMachineThrowsBadRequestException() throws Exception {
when(machineManager.exec(anyString(), any(Command.class), anyString())).thenThrow(new BadRequestException("Test exception"));
@Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = "Test exception")
public void shouldThrowExceptionIfMachineManagerExecInDevMachineThrowsBadRequestException() throws Exception {
when(machineProcessManager.exec(anyString(),
anyString(),
any(Command.class),
anyString()))
.thenThrow(new BadRequestException("Test exception"));
wsAgentLauncher.startWsAgent(WS_ID);
wsAgentLauncher.startWsAgent(machine);
verify(machineManager).exec(anyString(), any(Command.class), anyString());
verify(machineProcessManager).exec(anyString(),
anyString(),
any(Command.class),
anyString());
}
@Test(expectedExceptions = ServerException.class,
@ -217,14 +219,14 @@ public class WsAgentLauncherImplTest {
public void shouldThrowMachineExceptionIfPingsWereUnsuccessfulTooLong() throws Exception {
when(pingRequest.request()).thenThrow(new ServerException(""));
wsAgentLauncher.startWsAgent(WS_ID);
wsAgentLauncher.startWsAgent(machine);
}
@Test(expectedExceptions = MachineException.class,
@Test(expectedExceptions = ServerException.class,
expectedExceptionsMessageRegExp = "Workspace agent server not found in dev machine.")
public void shouldThrowMachineExceptionIfwsAgentNotFound() throws Exception {
public void shouldThrowExceptionIfWsAgentNotFound() throws Exception {
doReturn(Collections.emptyMap()).when(machineRuntime).getServers();
wsAgentLauncher.startWsAgent(WS_ID);
wsAgentLauncher.startWsAgent(machine);
}
}

View File

@ -0,0 +1,484 @@
/*******************************************************************************
* 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.environment.server;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.model.machine.Machine;
import org.eclipse.che.api.core.model.machine.MachineLogMessage;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.util.LineConsumer;
import org.eclipse.che.api.core.util.MessageConsumer;
import org.eclipse.che.api.environment.server.exception.EnvironmentNotRunningException;
import org.eclipse.che.api.machine.server.MachineInstanceProviders;
import org.eclipse.che.api.machine.server.dao.SnapshotDao;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.model.impl.LimitsImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl;
import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.spi.InstanceProvider;
import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.SubjectImpl;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
/**
* @author Alexander Garagatyi
*/
@Listeners(MockitoTestNGListener.class)
public class CheEnvironmentEngineTest {
@Mock
MessageConsumer<MachineLogMessage> messageConsumer;
@Mock
InstanceProvider instanceProvider;
@Mock
SnapshotDao snapshotDao;
@Mock
MachineInstanceProviders machineInstanceProviders;
@Mock
EventService eventService;
CheEnvironmentEngine engine;
@BeforeMethod
public void setUp() throws Exception {
engine = spy(new CheEnvironmentEngine(snapshotDao,
machineInstanceProviders,
"/tmp",
256,
eventService));
when(machineInstanceProviders.getProvider("docker")).thenReturn(instanceProvider);
when(instanceProvider.getRecipeTypes()).thenReturn(Collections.singleton("dockerfile"));
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("name", "id", "token", false));
}
@AfterMethod
public void tearDown() throws Exception {
EnvironmentContext.reset();
}
@Test
public void shouldBeAbleToGetMachinesOfEnv() throws Exception {
// given
List<Instance> instances = startEnv();
String workspaceId = instances.get(0).getWorkspaceId();
// when
List<Instance> actualMachines = engine.getMachines(workspaceId);
// then
assertEquals(actualMachines, instances);
}
@Test(expectedExceptions = EnvironmentNotRunningException.class,
expectedExceptionsMessageRegExp = "Environment with ID '.*' is not found")
public void shouldThrowExceptionOnGetMachinesIfEnvironmentIsNotFound() throws Exception {
engine.getMachines("wsIdOfNotRunningEnv");
}
@Test
public void shouldBeAbleToGetMachineOfEnv() throws Exception {
// given
List<Instance> instances = startEnv();
Instance instance = instances.get(0);
String workspaceId = instance.getWorkspaceId();
// when
Instance actualInstance = engine.getMachine(workspaceId, instance.getId());
// then
assertEquals(actualInstance, instance);
}
@Test(expectedExceptions = EnvironmentNotRunningException.class,
expectedExceptionsMessageRegExp = "Environment with ID '.*' is not found")
public void shouldThrowExceptionOnGetMachineIfEnvironmentIsNotFound() throws Exception {
// when
engine.getMachine("wsIdOfNotRunningEnv", "nonExistingInstanceId");
}
@Test(expectedExceptions = NotFoundException.class,
expectedExceptionsMessageRegExp = "Machine with ID .* is not found in the environment of workspace .*")
public void shouldThrowExceptionOnGetMachineIfMachineIsNotFound() throws Exception {
// given
List<Instance> instances = startEnv();
Instance instance = instances.get(0);
String workspaceId = instance.getWorkspaceId();
// when
engine.getMachine(workspaceId, "nonExistingInstanceId");
}
@Test
public void shouldBeAbleToStartEnvironment() throws Exception {
// given
EnvironmentImpl env = createEnv();
String workspaceId = "wsId";
List<Instance> expectedMachines = new ArrayList<>();
when(instanceProvider.createInstance(any(Machine.class),
any(LineConsumer.class)))
.thenAnswer(invocationOnMock -> {
Object[] arguments = invocationOnMock.getArguments();
Machine machine = (Machine)arguments[0];
Instance instance = spy(new NoOpMachineInstance(machine));
expectedMachines.add(instance);
return instance;
});
// when
List<Instance> machines = engine.start(workspaceId, env, false, messageConsumer);
// then
assertEquals(machines, expectedMachines);
verify(instanceProvider, times(env.getMachineConfigs().size()))
.createInstance(any(Machine.class), any(LineConsumer.class));
}
@Test
public void envStartShouldFireEvents() throws Exception {
// when
List<Instance> instances = startEnv();
assertTrue(instances.size() > 1, "This test requires at least 2 instances in environment");
// then
for (Instance instance : instances) {
verify(eventService).publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.CREATING)
.withDev(instance.getConfig().isDev())
.withMachineName(instance.getConfig().getName())
.withMachineId(instance.getId())
.withWorkspaceId(instance.getWorkspaceId()));
verify(eventService).publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.RUNNING)
.withDev(instance.getConfig().isDev())
.withMachineName(instance.getConfig().getName())
.withMachineId(instance.getId())
.withWorkspaceId(instance.getWorkspaceId()));
}
}
@Test(expectedExceptions = ConflictException.class,
expectedExceptionsMessageRegExp = "Environment of workspace '.*' already exists")
public void envStartShouldThrowsExceptionIfSameEnvironmentExists() throws Exception {
// given
List<Instance> instances = startEnv();
Instance instance = instances.get(0);
EnvironmentImpl env = createEnv();
// when
engine.start(instance.getWorkspaceId(),
env,
false,
messageConsumer);
}
@Test
public void shouldDestroyMachinesOnEnvStop() throws Exception {
// given
List<Instance> instances = startEnv();
Instance instance = instances.get(0);
// when
engine.stop(instance.getWorkspaceId());
// then
for (Instance instance1 : instances) {
verify(instance1).destroy();
}
}
@Test(expectedExceptions = EnvironmentNotRunningException.class,
expectedExceptionsMessageRegExp = "Environment with ID '.*' is not found")
public void shouldThrowExceptionOnEnvStopIfItIsNotRunning() throws Exception {
engine.stop("wsIdOFNonExistingEnv");
}
@Test
public void destroyOfMachineOnEnvStopShouldNotPreventStopOfOthers() throws Exception {
// given
List<Instance> instances = startEnv();
Instance instance = instances.get(0);
doThrow(new MachineException("test exception")).when(instance).destroy();
assertTrue(instances.size() > 1, "This test requires at least 2 instances in environment");
// when
engine.stop(instance.getWorkspaceId());
// then
InOrder inOrder = inOrder(instances.toArray());
for (Instance instance1 : instances) {
inOrder.verify(instance1).destroy();
}
}
@Test
public void shouldBeAbleToStartMachine() throws Exception {
// given
List<Instance> instances = startEnv();
String workspaceId = instances.get(0).getWorkspaceId();
when(engine.generateMachineId()).thenReturn("newMachineId");
Instance newMachine = mock(Instance.class);
when(newMachine.getId()).thenReturn("newMachineId");
when(newMachine.getWorkspaceId()).thenReturn(workspaceId);
doReturn(newMachine).when(instanceProvider).createInstance(any(Machine.class), any(LineConsumer.class));
MachineConfigImpl config = createConfig(false);
// when
Instance actualInstance = engine.startMachine(workspaceId, config);
// then
assertEquals(actualInstance, newMachine);
ArgumentCaptor<Machine> argumentCaptor = ArgumentCaptor.forClass(Machine.class);
verify(instanceProvider, times(3)).createInstance(argumentCaptor.capture(), any(LineConsumer.class));
assertEquals(argumentCaptor.getValue().getConfig(), config);
}
@Test(expectedExceptions = EnvironmentNotRunningException.class,
expectedExceptionsMessageRegExp = "Environment '.*' is not running")
public void shouldThrowExceptionOnMachineStartIfEnvironmentIsNotRunning() throws Exception {
MachineConfigImpl config = createConfig(false);
// when
engine.startMachine("wsIdOfNotRunningEnv", config);
}
@Test(expectedExceptions = ConflictException.class,
expectedExceptionsMessageRegExp = "Machine with name '.*' already exists in environment of workspace '.*'")
public void machineStartShouldThrowExceptionIfMachineWithTheSameNameAlreadyExistsInEnvironment() throws Exception {
// given
List<Instance> instances = startEnv();
Instance instance = instances.get(0);
MachineConfigImpl config = createConfig(false);
config.setName(instance.getConfig().getName());
// when
engine.startMachine(instance.getWorkspaceId(), config);
}
@Test
public void machineStartShouldPublishEvents() throws Exception {
// given
List<Instance> instances = startEnv();
Instance instance = instances.get(0);
MachineConfigImpl config = createConfig(false);
when(engine.generateMachineId()).thenReturn("newMachineId");
// when
engine.startMachine(instance.getWorkspaceId(), config);
// then
verify(eventService).publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.CREATING)
.withDev(config.isDev())
.withMachineName(config.getName())
.withMachineId("newMachineId")
.withWorkspaceId(instance.getWorkspaceId()));
verify(eventService).publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.RUNNING)
.withDev(config.isDev())
.withMachineName(config.getName())
.withMachineId("newMachineId")
.withWorkspaceId(instance.getWorkspaceId()));
}
@Test
public void shouldBeAbleToStopMachine() throws Exception {
// given
List<Instance> instances = startEnv();
Optional<Instance> instanceOpt = instances.stream()
.filter(machine -> !machine.getConfig().isDev())
.findAny();
assertTrue(instanceOpt.isPresent(), "Required for test non-dev machine is not found");
Instance instance = instanceOpt.get();
// when
engine.stopMachine(instance.getWorkspaceId(), instance.getId());
// then
verify(instance).destroy();
}
@Test(expectedExceptions = EnvironmentNotRunningException.class,
expectedExceptionsMessageRegExp = "Environment '.*' is not running")
public void machineStopShouldThrowExceptionIfEnvDoesNotExist() throws Exception {
engine.stopMachine("wsIdOfNotRunningEnv", "testMachineID");
}
@Test(expectedExceptions = ConflictException.class,
expectedExceptionsMessageRegExp = "Stop of dev machine is not allowed. Please, stop whole environment")
public void devMachineStopShouldThrowException() throws Exception {
// given
List<Instance> instances = startEnv();
Optional<Instance> instanceOpt = instances.stream()
.filter(machine -> machine.getConfig().isDev())
.findAny();
assertTrue(instanceOpt.isPresent(), "Required for test dev machine is not found");
Instance instance = instanceOpt.get();
// when
engine.stopMachine(instance.getWorkspaceId(), instance.getId());
}
@Test(expectedExceptions = NotFoundException.class,
expectedExceptionsMessageRegExp = "Machine with ID '.*' is not found in environment of workspace '.*'")
public void machineStopOfNonExistingMachineShouldThrowsException() throws Exception {
// given
List<Instance> instances = startEnv();
Instance instance = instances.get(0);
// when
engine.stopMachine(instance.getWorkspaceId(), "idOfNonExistingMachine");
}
@Test
public void machineStopShouldFireEvents() throws Exception {
// given
List<Instance> instances = startEnv();
Optional<Instance> instanceOpt = instances.stream()
.filter(machine -> !machine.getConfig().isDev())
.findAny();
assertTrue(instanceOpt.isPresent(), "Required for test non-dev machine is not found");
Instance instance = instanceOpt.get();
// when
engine.stopMachine(instance.getWorkspaceId(), instance.getId());
// then
verify(eventService).publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.CREATING)
.withDev(instance.getConfig().isDev())
.withMachineName(instance.getConfig().getName())
.withMachineId(instance.getId())
.withWorkspaceId(instance.getWorkspaceId()));
verify(eventService).publish(newDto(MachineStatusEvent.class)
.withEventType(MachineStatusEvent.EventType.RUNNING)
.withDev(instance.getConfig().isDev())
.withMachineName(instance.getConfig().getName())
.withMachineId(instance.getId())
.withWorkspaceId(instance.getWorkspaceId()));
}
@Test
public void shouldBeAbleToSaveMachineSnapshot() throws Exception {
// given
List<Instance> instances = startEnv();
Instance instance = instances.get(0);
doReturn(new MachineSourceImpl("someType").setContent("some content")).when(instance).saveToSnapshot();
// when
engine.saveSnapshot("someNamespace", instance.getWorkspaceId(), instance.getId());
// then
verify(instance).saveToSnapshot();
}
@Test(expectedExceptions = EnvironmentNotRunningException.class,
expectedExceptionsMessageRegExp = "Environment .*' is not running")
public void shouldThrowExceptionOnSaveSnapshotIfEnvIsNotRunning() throws Exception {
engine.saveSnapshot("someNamespace", "wsIdOfNotRunningEnv", "someId");
}
@Test(expectedExceptions = NotFoundException.class,
expectedExceptionsMessageRegExp = "Machine with id '.*' is not found in environment of workspace '.*'")
public void shouldThrowExceptionOnSaveSnapshotIfMachineIsNotFound() throws Exception {
// given
List<Instance> instances = startEnv();
Instance instance = instances.get(0);
// when
engine.saveSnapshot("someNamespace", instance.getWorkspaceId(), "idOfNonExistingMachine");
}
@Test
public void shouldBeAbleToRemoveSnapshot() throws Exception {
// given
SnapshotImpl snapshot = mock(SnapshotImpl.class);
MachineSourceImpl machineSource = mock(MachineSourceImpl.class);
when(snapshot.getType()).thenReturn("docker");
when(snapshot.getMachineSource()).thenReturn(machineSource);
// when
engine.removeSnapshot(snapshot);
// then
verify(instanceProvider).removeInstanceSnapshot(machineSource);
}
private List<Instance> startEnv() throws Exception {
EnvironmentImpl env = createEnv();
String workspaceId = "wsId";
when(instanceProvider.createInstance(any(Machine.class),
any(LineConsumer.class)))
.thenAnswer(invocationOnMock -> {
Object[] arguments = invocationOnMock.getArguments();
Machine machine = (Machine)arguments[0];
return spy(new NoOpMachineInstance(machine));
});
// when
return engine.start(workspaceId, env, false, messageConsumer);
}
private static MachineConfigImpl createConfig(boolean isDev) {
return MachineConfigImpl.builder()
.setDev(isDev)
.setType("docker")
.setLimits(new LimitsImpl(1024))
.setSource(new MachineSourceImpl("dockerfile").setLocation("location"))
.setName(UUID.randomUUID().toString())
.build();
}
private EnvironmentImpl createEnv() {
List<MachineConfigImpl> machines = new ArrayList<>();
machines.add(createConfig(true));
machines.add(createConfig(false));
return new EnvironmentImpl("envName",
null,
machines);
}
}

View File

@ -0,0 +1,345 @@
/*******************************************************************************
* 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.environment.server;
import org.eclipse.che.api.machine.server.MachineInstanceProviders;
import org.eclipse.che.api.machine.shared.dto.MachineConfigDto;
import org.eclipse.che.api.machine.shared.dto.MachineSourceDto;
import org.eclipse.che.api.machine.shared.dto.ServerConfDto;
import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto;
import org.eclipse.che.api.workspace.shared.dto.RecipeDto;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import static org.mockito.Mockito.when;
/**
* @author Alexander Garagatyi
*/
@Listeners(MockitoTestNGListener.class)
public class CheEnvironmentValidatorTest {
@Mock
MachineInstanceProviders machineInstanceProviders;
@InjectMocks
CheEnvironmentValidator environmentValidator;
@BeforeMethod
public void prepare() throws Exception {
when(machineInstanceProviders.hasProvider("docker")).thenReturn(true);
when(machineInstanceProviders.getProviderTypes()).thenReturn(Arrays.asList("docker", "ssh"));
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Environment name should not be neither null nor empty")
public void shouldFailValidationIfEnvNameIsNull() throws Exception {
EnvironmentDto environment = createConfig();
environment.setName(null);
environmentValidator.validate(environment);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Environment name should not be neither null nor empty")
public void shouldFailValidationIfEnvNameIsEmpty() throws Exception {
EnvironmentDto environment = createConfig();
environment.setName("");
environmentValidator.validate(environment);
}
@Test
public void shouldNotFailValidationIfEnvironmentRecipeTypeIsDocker() throws Exception {
EnvironmentDto config = createConfig();
config.withRecipe(newDto(RecipeDto.class).withType("docker"));
environmentValidator.validate(config);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Environment '.*' should contain at least 1 machine")
public void shouldFailValidationIfMachinesListIsEmpty() throws Exception {
EnvironmentDto config = createConfig();
config.withMachineConfigs(null);
environmentValidator.validate(config);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Environment '.*' should contain exactly 1 dev machine, but contains '0'")
public void shouldFailValidationIfNoDevMachineFound() throws Exception {
EnvironmentDto config = createConfig();
config.getMachineConfigs()
.stream()
.filter(MachineConfigDto::isDev)
.forEach(machine -> machine.withDev(false));
environmentValidator.validate(config);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Environment '.*' should contain exactly 1 dev machine, but contains '2'")
public void shouldFailValidationIf2DevMachinesFound() throws Exception {
EnvironmentDto config = createConfig();
final Optional<MachineConfigDto> devMachine = config.getMachineConfigs()
.stream()
.filter(MachineConfigDto::isDev)
.findAny();
config.getMachineConfigs()
.add(devMachine.get().withName("other-name"));
environmentValidator.validate(config);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Environment .* contains machine with null or empty name")
public void shouldFailValidationIfMachineNameIsNull() throws Exception {
EnvironmentDto config = createConfig();
config.getMachineConfigs()
.get(0)
.withName(null);
environmentValidator.validate(config);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Environment .* contains machine with null or empty name")
public void shouldFailValidationIfMachineNameIsEmpty() throws Exception {
EnvironmentDto config = createConfig();
config.getMachineConfigs()
.get(0)
.withName("");
environmentValidator.validate(config);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Machine '.*' in environment '.*' doesn't have source")
public void shouldFailValidationIfMachineSourceIsNull() throws Exception {
EnvironmentDto config = createConfig();
config.getMachineConfigs()
.get(0)
.withSource(null);
environmentValidator.validate(config);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Type 'null' of machine '.*' in environment '.*' is not supported. Supported values are: docker, ssh.")
public void shouldFailValidationIfMachineTypeIsNull() throws Exception {
EnvironmentDto config = createConfig();
config.getMachineConfigs()
.get(0)
.withType(null);
environmentValidator.validate(config);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Type 'compose' of machine '.*' in environment '.*' is not supported. Supported values are: docker, ssh.")
public void shouldFailValidationIfMachineTypeIsNotDocker() throws Exception {
EnvironmentDto config = createConfig();
config.getMachineConfigs()
.get(0)
.withType("compose");
environmentValidator.validate(config);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Machine .* contains server conf with invalid port .*",
dataProvider = "invalidPortProvider")
public void shouldFailValidationIfServerConfPortIsInvalid(String invalidPort) throws Exception {
EnvironmentDto config = createConfig();
config.getMachineConfigs()
.get(0)
.getServers()
.add(newDto(ServerConfDto.class).withPort(invalidPort));
environmentValidator.validate(config);
}
@DataProvider(name = "invalidPortProvider")
public static Object[][] invalidPortProvider() {
return new Object[][] {
{"0"},
{"0123"},
{"012/tcp"},
{"8080"},
{"8080/pct"},
{"8080/pdu"},
{"/tcp"},
{"tcp"},
{""},
{"8080/tcp1"},
{"8080/tcpp"},
{"8080tcp"},
{"8080/tc"},
{"8080/ud"},
{"8080/udpp"},
{"8080/udp/"},
{"8080/tcp/"},
{"8080/tcp/udp"},
{"8080/tcp/tcp"},
{"8080/tcp/8080"},
{null}
};
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Machine .* contains server conf with invalid protocol .*",
dataProvider = "invalidProtocolProvider")
public void shouldFailValidationIfServerConfProtocolIsInvalid(String invalidProtocol) throws Exception {
EnvironmentDto config = createConfig();
config.getMachineConfigs()
.get(0)
.getServers()
.add(newDto(ServerConfDto.class).withPort("8080/tcp")
.withProtocol(invalidProtocol));
environmentValidator.validate(config);
}
@DataProvider(name = "invalidProtocolProvider")
public static Object[][] invalidProtocolProvider() {
return new Object[][] {
{""},
{"http!"},
{"2http"},
{"http:"},
};
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Machine '.*' in environment '.*' contains environment variable with null or empty name")
public void shouldFailValidationIfEnvVarNameIsNull() throws Exception {
EnvironmentDto config = createConfig();
config.getMachineConfigs()
.get(0)
.getEnvVariables()
.put(null, "value");
environmentValidator.validate(config);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Machine '.*' in environment '.*' contains environment variable with null or empty name")
public void shouldFailValidationIfEnvVarNameIsEmpty() throws Exception {
EnvironmentDto config = createConfig();
config.getMachineConfigs()
.get(0)
.getEnvVariables()
.put("", "value");
environmentValidator.validate(config);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Machine '.*' in environment '.*' contains environment variable '.*' with null value")
public void shouldFailValidationIfEnvVarValueIsNull() throws Exception {
EnvironmentDto config = createConfig();
config.getMachineConfigs()
.get(0)
.getEnvVariables()
.put("key", null);
environmentValidator.validate(config);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Source of machine '.*' in environment '.*' must contain location or content")
public void shouldFailValidationIfMissingSourceLocationAndContent() throws Exception {
EnvironmentDto config = createConfig();
config.getMachineConfigs()
.get(0)
.withSource(newDto(MachineSourceDto.class).withType("dockerfile"));
environmentValidator.validate(config);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Environment '.*' contains machine '.*' with invalid source location: 'localhost'")
public void shouldFailValidationIfLocationIsInvalidUrl() throws Exception {
EnvironmentDto config = createConfig();
config.getMachineConfigs()
.get(0)
.withSource(newDto(MachineSourceDto.class).withType("dockerfile").withLocation("localhost"));
environmentValidator.validate(config);
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Environment '.*' contains machine '.*' with invalid source location protocol: ftp://localhost")
public void shouldFailValidationIfLocationHasInvalidProtocol() throws Exception {
EnvironmentDto config = createConfig();
config.getMachineConfigs()
.get(0)
.withSource(newDto(MachineSourceDto.class).withType("dockerfile").withLocation("ftp://localhost"));
environmentValidator.validate(config);
}
private EnvironmentDto createConfig() {
final List<ServerConfDto> serversConf =
new ArrayList<>(Arrays.asList(newDto(ServerConfDto.class).withRef("ref1")
.withPort("8080/tcp")
.withProtocol("https")
.withPath("some/path"),
newDto(ServerConfDto.class).withRef("ref2")
.withPort("9090/udp")
.withProtocol("protocol")
.withPath("/some/path")));
MachineConfigDto devMachine = newDto(MachineConfigDto.class).withDev(true)
.withName("dev-machine")
.withType("docker")
.withSource(newDto(MachineSourceDto.class)
.withLocation("http://location")
.withType("dockerfile"))
.withServers(serversConf)
.withEnvVariables(new HashMap<>(
singletonMap("key1", "value1")));
return newDto(EnvironmentDto.class).withName("dev-env")
.withMachineConfigs(
new ArrayList<>(singletonList(devMachine)))
.withRecipe(null);
}
}

View File

@ -0,0 +1,131 @@
/*******************************************************************************
* 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.environment.server;
import org.eclipse.che.api.core.model.machine.Command;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.util.LineConsumer;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.spi.InstanceProcess;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.lang.IoUtil;
import org.eclipse.che.commons.subject.SubjectImpl;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import java.io.File;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ThreadPoolExecutor;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertNotNull;
/**
* Unit tests for {@link MachineProcessManager}
*
* @author Anton Korneta
* @author Alexander Garagatyi
*/
@Listeners(MockitoTestNGListener.class)
public class MachineProcessManagerTest {
private static final String USER_ID = "userId";
private static final String MACHINE_ID = "machineId";
private static final String WORKSPACE_ID = "testWorkspaceId";
private static final SubjectImpl CREATOR = new SubjectImpl("name", USER_ID, "token", false);
@Mock
Instance instance;
@Mock
Command command;
@Mock
InstanceProcess instanceProcess;
@Mock
LineConsumer logConsumer;
@Mock
CheEnvironmentEngine environmentEngine;
private MachineProcessManager manager;
@BeforeMethod
public void setUp() throws Exception {
final EventService eventService = mock(EventService.class);
final String machineLogsDir = targetDir().resolve("logs-dir").toString();
IoUtil.deleteRecursive(new File(machineLogsDir));
manager = spy(new MachineProcessManager(machineLogsDir,
eventService,
environmentEngine));
EnvironmentContext envCont = new EnvironmentContext();
envCont.setSubject(CREATOR);
EnvironmentContext.setCurrent(envCont);
doReturn(logConsumer).when(manager).getProcessLogger(MACHINE_ID, 111, "outputChannel");
when(command.getCommandLine()).thenReturn("CommandLine");
when(command.getName()).thenReturn("CommandName");
when(command.getType()).thenReturn("CommandType");
when(instance.createProcess(command, "outputChannel")).thenReturn(instanceProcess);
when(instanceProcess.getPid()).thenReturn(111);
when(environmentEngine.getMachine(WORKSPACE_ID, MACHINE_ID)).thenReturn(instance);
}
@AfterMethod
public void tearDown() throws Exception {
EnvironmentContext.reset();
}
@Test
public void shouldCloseProcessLoggerIfExecIsSuccess() throws Exception {
//when
manager.exec(WORKSPACE_ID, MACHINE_ID, command, "outputChannel");
waitForExecutorIsCompletedTask();
//then
verify(logConsumer).close();
}
@Test
public void shouldCloseProcessLoggerIfExecFails() throws Exception {
//given
doThrow(Exception.class).when(instanceProcess).start();
//when
manager.exec(WORKSPACE_ID, MACHINE_ID, command, "outputChannel");
waitForExecutorIsCompletedTask();
//then
verify(logConsumer).close();
}
private void waitForExecutorIsCompletedTask() throws Exception {
for (int i = 0; ((ThreadPoolExecutor)manager.executor).getCompletedTaskCount() == 0 && i < 10; i++) {
Thread.sleep(300);
}
}
private static Path targetDir() throws Exception {
final URL url = Thread.currentThread().getContextClassLoader().getResource(".");
assertNotNull(url);
return Paths.get(url.toURI()).getParent();
}
}

View File

@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.machine.server;
package org.eclipse.che.api.environment.server;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@ -20,7 +20,6 @@ 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.MachineRuntimeInfoDto;
import org.eclipse.che.api.machine.shared.dto.ServerDto;
import org.eclipse.che.api.machine.shared.dto.SnapshotDto;
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.dto.server.DtoFactory;
import org.everrest.core.impl.uri.UriBuilderImpl;
@ -36,17 +35,13 @@ 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_ENVIRONMENT_OUTPUT_CHANNEL;
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;
import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_MACHINE_LOGS;
import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_PROCESSES;
import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_PROCESS_LOGS;
import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_SNAPSHOTS;
import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_REMOVE_SNAPSHOT;
import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_SAVE_SNAPSHOT;
import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_SELF;
import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_STOP_PROCESS;
import static org.eclipse.che.api.machine.shared.Constants.TERMINAL_REFERENCE;
@ -102,10 +97,7 @@ public class MachineServiceLinksInjectorTest {
Pair.of("GET", LINK_REL_GET_MACHINES),
Pair.of("POST", LINK_REL_EXECUTE_COMMAND),
Pair.of("DELETE", LINK_REL_DESTROY_MACHINE),
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_ENVIRONMENT_OUTPUT_CHANNEL),
Pair.of("GET", ENVIRONMENT_STATUS_CHANNEL_TEMPLATE)));
@ -115,7 +107,7 @@ public class MachineServiceLinksInjectorTest {
@Test
public void shouldInjectLinksIntoMachineProcessDto() {
final MachineProcessDto machineProcessDto = DtoFactory.newDto(MachineProcessDto.class);
machineLinksInjector.injectLinks(machineProcessDto, "machineId", serviceContextMock);
machineLinksInjector.injectLinks(machineProcessDto, "workspaceId", "machineId", serviceContextMock);
final Set<Pair<String, String>> links = machineProcessDto.getLinks()
.stream()
.map(link -> Pair.of(link.getMethod(), link.getRel()))
@ -126,18 +118,4 @@ public class MachineServiceLinksInjectorTest {
assertEquals(links, expectedLinks, "Difference " + Sets.symmetricDifference(links, expectedLinks) + "\n");
}
@Test
public void shouldInjectLinksIntoSnapshotDto() {
final SnapshotDto snapshotDto = DtoFactory.newDto(SnapshotDto.class)
.withId("id");
machineLinksInjector.injectLinks(snapshotDto, serviceContextMock);
final Set<Pair<String, String>> links = snapshotDto.getLinks()
.stream()
.map(link -> Pair.of(link.getMethod(), link.getRel()))
.collect(Collectors.toSet());
final Set<Pair<String, String>> expectedLinks = ImmutableSet.of(Pair.of("DELETE", LINK_REL_REMOVE_SNAPSHOT));
assertEquals(links, expectedLinks, "Difference " + Sets.symmetricDifference(links, expectedLinks) + "\n");
}
}

View File

@ -0,0 +1,142 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.environment.server;
import com.jayway.restassured.http.ContentType;
import com.jayway.restassured.response.Response;
import org.eclipse.che.api.core.model.machine.MachineConfig;
import org.eclipse.che.api.core.rest.ApiExceptionMapper;
import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl;
import org.eclipse.che.api.workspace.server.WorkspaceManager;
import org.eclipse.che.api.workspace.server.WorkspaceServiceTest;
import org.everrest.assured.EverrestJetty;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import static com.jayway.restassured.RestAssured.given;
import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME;
import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD;
import static org.everrest.assured.JettyHttpServer.SECURE_PATH;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.testng.Assert.assertEquals;
/**
* @author Alexander Garagatyi
*/
@Listeners(value = {EverrestJetty.class, MockitoTestNGListener.class})
public class MachineServiceTest {
@SuppressWarnings("unused")
private static final ApiExceptionMapper MAPPER = new ApiExceptionMapper();
@SuppressWarnings("unused")
private static final WorkspaceServiceTest.EnvironmentFilter FILTER = new WorkspaceServiceTest.EnvironmentFilter();
@Mock
private WorkspaceManager wsManager;
@Mock
private MachineProcessManager machineProcessManager;
private MachineService service;
@BeforeMethod
public void setup() {
service = new MachineService(machineProcessManager,
new MachineServiceLinksInjector(),
wsManager);
}
@Test(dataProvider = "illegalMachineConfigProvider")
public void shouldReturnErrorOnStartMachineIfBodyIsInvalid(MachineConfig machineConfig) throws Exception {
// given
String workspaceId = "wsId";
// when
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.body(machineConfig)
.contentType(ContentType.JSON)
.post(SECURE_PATH + "/workspace/" + workspaceId + "/machine");
// then
assertEquals(response.getStatusCode(), 400);
verify(wsManager, never()).startMachine(any(MachineConfig.class), anyString());
}
@DataProvider(name = "illegalMachineConfigProvider")
public static Object[][] illegalMachineConfigProvider() {
MachineConfigImpl.MachineConfigImplBuilder builder =
MachineConfigImpl.builder()
.setDev(false)
.setName("name")
.setType("type")
.setSource(new MachineSourceImpl("type1").setContent("content"));
return new Object[][] {
{builder.setType(null)
.build()},
{builder.setSource(null)
.build()},
{builder.setSource(new MachineSourceImpl((String)null).setContent("content"))
.build()},
{builder.setSource(new MachineSourceImpl("type").setContent("content")
.setLocation("location"))
.build()},
};
}
@Test
public void shouldStartMachine() throws Exception {
// given
String workspaceId = "wsId";
MachineConfig machineConfig = MachineConfigImpl.builder()
.setDev(false)
.setName("name")
.setType("type")
.setSource(new MachineSourceImpl("type1").setContent("content"))
.build();
// when
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.body(machineConfig)
.contentType(ContentType.JSON)
.post(SECURE_PATH + "/workspace/" + workspaceId + "/machine");
// then
assertEquals(response.getStatusCode(), 204);
verify(wsManager).startMachine(any(MachineConfig.class), eq(workspaceId));
}
@Test
public void shouldStopMachine() throws Exception {
// given
String workspaceId = "wsId";
String machineId = "mcId";
// when
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.delete(SECURE_PATH + "/workspace/" + workspaceId + "/machine/" + machineId);
// then
assertEquals(response.getStatusCode(), 204);
}
}

View File

@ -11,33 +11,26 @@
package org.eclipse.che.api.workspace.server;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.machine.server.MachineInstanceProviders;
import org.eclipse.che.api.environment.server.CheEnvironmentValidator;
import org.eclipse.che.api.machine.shared.dto.CommandDto;
import org.eclipse.che.api.machine.shared.dto.MachineConfigDto;
import org.eclipse.che.api.machine.shared.dto.MachineSourceDto;
import org.eclipse.che.api.machine.shared.dto.ServerConfDto;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto;
import org.eclipse.che.api.workspace.shared.dto.RecipeDto;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import static org.mockito.Mockito.when;
/**
* Tests for {@link WorkspaceValidator} and {@link DefaultWorkspaceValidator}
@ -48,15 +41,9 @@ import static org.mockito.Mockito.when;
public class DefaultWorkspaceValidatorTest {
@Mock
private MachineInstanceProviders machineInstanceProviders;
CheEnvironmentValidator environmentValidator;
@InjectMocks
private DefaultWorkspaceValidator wsValidator;
@BeforeMethod
public void prepare() throws Exception {
when(machineInstanceProviders.hasProvider("docker")).thenReturn(true);
when(machineInstanceProviders.getProviderTypes()).thenReturn(Arrays.asList(new String[] { "docker", "ssh" }));
}
DefaultWorkspaceValidator wsValidator;
@Test
public void shouldValidateCorrectWorkspace() throws Exception {
@ -184,161 +171,6 @@ public class DefaultWorkspaceValidatorTest {
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Environment name should be neither null nor empty")
public void shouldFailValidationIfEnvNameIsNull() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.add(newDto(EnvironmentDto.class).withName(null)
.withMachineConfigs(config.getEnvironments()
.get(0)
.getMachineConfigs()));
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Environment name should be neither null nor empty")
public void shouldFailValidationIfEnvNameIsEmpty() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.add(newDto(EnvironmentDto.class).withName("")
.withMachineConfigs(config.getEnvironments()
.get(0)
.getMachineConfigs()));
wsValidator.validateConfig(config);
}
@Test
public void shouldNotFailValidationIfEnvironmentRecipeTypeIsDocker() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.withRecipe(newDto(RecipeDto.class).withType("docker"));
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Environment '.*' should contain at least 1 machine")
public void shouldFailValidationIfMachinesListIsEmpty() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.withMachineConfigs(null);
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Environment should contain exactly 1 dev machine, but '.*' contains '0'")
public void shouldFailValidationIfNoDevMachineFound() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.stream()
.filter(MachineConfigDto::isDev)
.forEach(machine -> machine.withDev(false));
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Environment should contain exactly 1 dev machine, but '.*' contains '2'")
public void shouldFailValidationIf2DevMachinesFound() throws Exception {
final WorkspaceConfigDto config = createConfig();
final Optional<MachineConfigDto> devMachine = config.getEnvironments()
.get(0)
.getMachineConfigs()
.stream()
.filter(MachineConfigDto::isDev)
.findAny();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.add(devMachine.get().withName("other-name"));
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Environment .* contains machine with null or empty name")
public void shouldFailValidationIfMachineNameIsNull() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.get(0)
.withName(null);
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Environment .* contains machine with null or empty name")
public void shouldFailValidationIfMachineNameIsEmpty() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.get(0)
.withName("");
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Environment .* contains machine without source")
public void shouldFailValidationIfMachineSourceIsNull() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.get(0)
.withSource(null);
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Type .* of machine .* in environment .* is not supported. Supported values: docker, ssh.")
public void shouldFailValidationIfMachineTypeIsNull() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.get(0)
.withType(null);
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Type .* of machine .* in environment .* is not supported. Supported values: docker, ssh.")
public void shouldFailValidationIfMachineTypeIsNotDocker() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.get(0)
.withType("compose");
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Workspace ws-name contains command with null or empty name")
public void shouldFailValidationIfCommandNameIsNull() throws Exception {
@ -387,181 +219,12 @@ public class DefaultWorkspaceValidatorTest {
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Machine .* contains server conf with invalid port .*",
dataProvider = "invalidPortProvider")
public void shouldFailValidationIfServerConfPortIsInvalid(String invalidPort) throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.get(0)
.getServers()
.add(newDto(ServerConfDto.class).withPort(invalidPort));
wsValidator.validateConfig(config);
}
@DataProvider(name = "invalidPortProvider")
public static Object[][] invalidPortProvider() {
return new Object[][] {
{"0"},
{"0123"},
{"012/tcp"},
{"8080"},
{"8080/pct"},
{"8080/pdu"},
{"/tcp"},
{"tcp"},
{""},
{"8080/tcp1"},
{"8080/tcpp"},
{"8080tcp"},
{"8080/tc"},
{"8080/ud"},
{"8080/udpp"},
{"8080/udp/"},
{"8080/tcp/"},
{"8080/tcp/udp"},
{"8080/tcp/tcp"},
{"8080/tcp/8080"},
{null}
};
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Machine .* contains server conf with invalid protocol .*",
dataProvider = "invalidProtocolProvider")
public void shouldFailValidationIfServerConfProtocolIsInvalid(String invalidProtocol) throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.get(0)
.getServers()
.add(newDto(ServerConfDto.class).withPort("8080/tcp")
.withProtocol(invalidProtocol));
wsValidator.validateConfig(config);
}
@DataProvider(name = "invalidProtocolProvider")
public static Object[][] invalidProtocolProvider() {
return new Object[][] {
{""},
{"http!"},
{"2http"},
{"http:"},
};
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Machine %s contains environment variable with null or empty name")
public void shouldFailValidationIfEnvVarNameIsNull() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.get(0)
.getEnvVariables()
.put(null, "value");
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Machine %s contains environment variable with null or empty name")
public void shouldFailValidationIfEnvVarNameIsEmpty() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.get(0)
.getEnvVariables()
.put("", "value");
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Machine %s contains environment variable with null value")
public void shouldFailValidationIfEnvVarValueIsNull() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.get(0)
.getEnvVariables()
.put("key", null);
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Environment dev-env contains machine with source but this source doesn't define a location or content")
public void shouldFailValidationIfMissingLocationOrContent() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.get(0)
.withSource(newDto(MachineSourceDto.class).withType("dockerfile"));
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Environment dev-env contains machine with invalid source location: localhost")
public void shouldFailValidationIfLocationIsInvalidUrl() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.get(0)
.withSource(newDto(MachineSourceDto.class).withType("dockerfile").withLocation("localhost"));
wsValidator.validateConfig(config);
}
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Environment dev-env contains machine with invalid source location protocol: ftp://localhost")
public void shouldFailValidationIfLocationHasInvalidProtocol() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getEnvironments()
.get(0)
.getMachineConfigs()
.get(0)
.withSource(newDto(MachineSourceDto.class).withType("dockerfile").withLocation("ftp://localhost"));
wsValidator.validateConfig(config);
}
private static WorkspaceConfigDto createConfig() {
final WorkspaceConfigDto workspaceConfigDto = newDto(WorkspaceConfigDto.class).withName("ws-name")
.withDefaultEnv("dev-env");
final List<ServerConfDto> serversConf = new ArrayList<>(Arrays.asList(newDto(ServerConfDto.class).withRef("ref1")
.withPort("8080/tcp")
.withProtocol("https")
.withPath("some/path"),
newDto(ServerConfDto.class).withRef("ref2")
.withPort("9090/udp")
.withProtocol("protocol")
.withPath("/some/path")));
MachineConfigDto devMachine = newDto(MachineConfigDto.class).withDev(true)
.withName("dev-machine")
.withType("docker")
.withSource(newDto(MachineSourceDto.class).withLocation("http://location")
.withType("dockerfile"))
.withServers(serversConf)
.withEnvVariables(new HashMap<>(singletonMap("key1", "value1")));
EnvironmentDto devEnv = newDto(EnvironmentDto.class).withName("dev-env")
.withMachineConfigs(new ArrayList<>(singletonList(devMachine)))
.withMachineConfigs(emptyList())
.withRecipe(null);
workspaceConfigDto.setEnvironments(new ArrayList<>(singletonList(devEnv)));
@ -569,7 +232,8 @@ public class DefaultWorkspaceValidatorTest {
commandDtos.add(newDto(CommandDto.class).withName("command_name")
.withType("maven")
.withCommandLine("mvn clean install")
.withAttributes(new HashMap<>(singletonMap("cmd-attribute-name", "cmd-attribute-value"))));
.withAttributes(new HashMap<>(singletonMap("cmd-attribute-name",
"cmd-attribute-value"))));
workspaceConfigDto.setCommands(commandDtos);
return workspaceConfigDto;

View File

@ -15,9 +15,9 @@ 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.environment.server.MachineProcessManager;
import org.eclipse.che.api.machine.server.dao.SnapshotDao;
import org.eclipse.che.api.machine.server.exception.SnapshotException;
import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineRuntimeInfoImpl;
@ -37,6 +37,7 @@ import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.commons.subject.SubjectImpl;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
@ -44,6 +45,7 @@ import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import java.util.List;
import java.util.Optional;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.Arrays.asList;
@ -62,6 +64,8 @@ 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.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
@ -70,7 +74,7 @@ import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.AssertJUnit.assertTrue;
import static org.testng.Assert.assertTrue;
/**
* Covers main cases of {@link WorkspaceManager}.
@ -90,13 +94,15 @@ public class WorkspaceManagerTest {
@Mock
private WorkspaceValidator workspaceConfigValidator;
@Mock
private MachineManager client;
private MachineProcessManager client;
@Mock
private WorkspaceHooks workspaceHooks;
@Mock
private MachineManager machineManager;
private MachineProcessManager machineProcessManager;
@Mock
private WorkspaceRuntimes runtimes;
@Mock
private SnapshotDao snapshotDao;
@Captor
private ArgumentCaptor<WorkspaceImpl> workspaceCaptor;
@ -107,9 +113,9 @@ public class WorkspaceManagerTest {
workspaceManager = spy(new WorkspaceManager(workspaceDao,
runtimes,
eventService,
machineManager,
false,
false));
false,
snapshotDao));
workspaceManager.setHooks(workspaceHooks);
when(workspaceDao.create(any(WorkspaceImpl.class))).thenAnswer(invocation -> invocation.getArguments()[0]);
@ -349,26 +355,60 @@ public class WorkspaceManagerTest {
public void shouldRecoverWorkspaceWhenRecoverParameterIsNullAndAutoRestoreAttributeIsSetAndSnapshotExists() throws Exception {
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account");
workspace.getAttributes().put(AUTO_RESTORE_FROM_SNAPSHOT, "true");
when(machineManager.getSnapshots(any(), any())).thenReturn(singletonList(mock(SnapshotImpl.class)));
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
when(runtimes.get(any())).thenThrow(new NotFoundException(""));
SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder()
.generateId()
.setEnvName("env")
.setDev(true)
.setMachineName("machine1")
.setNamespace(workspace.getNamespace())
.setWorkspaceId(workspace.getId())
.setType("docker")
.setMachineSource(new MachineSourceImpl("image"));
SnapshotImpl snapshot1 = snapshotBuilder.build();
SnapshotImpl snapshot2 = snapshotBuilder.generateId()
.setDev(false)
.setMachineName("machine2")
.build();
when(snapshotDao.findSnapshots(workspace.getNamespace(), workspace.getId()))
.thenReturn(asList(snapshot1, snapshot2));
workspaceManager.startWorkspace(workspace.getId(),
workspace.getConfig().getDefaultEnv(),
"account",
null);
verify(runtimes, timeout(2000)).start(workspace, workspace.getConfig().getDefaultEnv(), true);
verify(workspaceHooks, timeout(2000)).beforeStart(workspace, workspace.getConfig().getDefaultEnv(), "account");
verify(runtimes, timeout(2000)).start(workspace,
workspace.getConfig().getDefaultEnv(),
true);
verify(workspaceHooks, timeout(2000)).beforeStart(workspace,
workspace.getConfig().getDefaultEnv(),
"account");
assertNotNull(workspace.getAttributes().get(UPDATED_ATTRIBUTE_NAME));
}
@Test
public void shouldRecoverWorkspaceWhenRecoverParameterIsTrueAndSnapshotExists() throws Exception {
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account");
when(machineManager.getSnapshots(any(), any())).thenReturn(singletonList(mock(SnapshotImpl.class)));
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
when(runtimes.get(any())).thenThrow(new NotFoundException(""));
SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder()
.generateId()
.setEnvName("env")
.setDev(true)
.setMachineName("machine1")
.setNamespace(workspace.getNamespace())
.setWorkspaceId(workspace.getId())
.setType("docker")
.setMachineSource(new MachineSourceImpl("image"));
SnapshotImpl snapshot1 = snapshotBuilder.build();
SnapshotImpl snapshot2 = snapshotBuilder.generateId()
.setDev(false)
.setMachineName("machine2")
.build();
when(snapshotDao.findSnapshots(workspace.getNamespace(), workspace.getId()))
.thenReturn(asList(snapshot1, snapshot2));
workspaceManager.startWorkspace(workspace.getId(),
workspace.getConfig().getDefaultEnv(),
@ -417,7 +457,6 @@ public class WorkspaceManagerTest {
public void shouldNotRecoverWorkspaceWhenRecoverParameterIsFalseAndAutoRestoreAttributeIsSetAndSnapshotExists() throws Exception {
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account");
workspace.getAttributes().put(AUTO_RESTORE_FROM_SNAPSHOT, "true");
when(machineManager.getSnapshots(any(), any())).thenReturn(singletonList(mock(SnapshotImpl.class)));
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
when(runtimes.get(any())).thenThrow(new NotFoundException(""));
@ -555,8 +594,6 @@ public class WorkspaceManagerTest {
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
// force createSnapshotSync to return true
when(machineManager.saveSync(anyString(), anyString(), anyString())).thenThrow(new MachineException("test"));
workspaceManager.stopWorkspace(workspace.getId());
@ -595,7 +632,7 @@ public class WorkspaceManagerTest {
final String wsId = "workspace123";
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE, "account");
when(workspaceDao.get(wsId)).thenReturn(workspace);
when(machineManager.getSnapshots(NAMESPACE, wsId)).thenReturn(singletonList(any()));
when(snapshotDao.findSnapshots(NAMESPACE, wsId)).thenReturn(singletonList(any()));
final List<SnapshotImpl> snapshots = workspaceManager.getSnapshot("workspace123");
@ -607,9 +644,9 @@ public class WorkspaceManagerTest {
workspaceManager = spy(new WorkspaceManager(workspaceDao,
runtimes,
eventService,
machineManager,
true,
false));
false,
snapshotDao));
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
@ -626,13 +663,28 @@ public class WorkspaceManagerTest {
workspaceManager = spy(new WorkspaceManager(workspaceDao,
runtimes,
eventService,
machineManager,
false,
true));
true,
snapshotDao));
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account");
when(machineManager.getSnapshots(any(), any())).thenReturn(singletonList(mock(SnapshotImpl.class)));
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
when(runtimes.get(any())).thenThrow(new NotFoundException(""));
SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder()
.generateId()
.setEnvName("env")
.setDev(true)
.setMachineName("machine1")
.setNamespace(workspace.getNamespace())
.setWorkspaceId(workspace.getId())
.setType("docker")
.setMachineSource(new MachineSourceImpl("image"));
SnapshotImpl snapshot1 = snapshotBuilder.build();
SnapshotImpl snapshot2 = snapshotBuilder.generateId()
.setDev(false)
.setMachineName("machine2")
.build();
when(snapshotDao.findSnapshots(workspace.getNamespace(), workspace.getId()))
.thenReturn(asList(snapshot1, snapshot2));
workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), "account", null);
@ -645,14 +697,69 @@ public class WorkspaceManagerTest {
String testWsId = "testWsId";
String testNamespace = "testNamespace";
WorkspaceImpl workspaceMock = mock(WorkspaceImpl.class);
doReturn(workspaceMock).when(workspaceManager).getWorkspace(testWsId);
when(workspaceDao.get(testWsId)).thenReturn(workspaceMock);
when(workspaceMock.getNamespace()).thenReturn(testNamespace);
SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder()
.generateId()
.setEnvName("env")
.setDev(true)
.setMachineName("machine1")
.setNamespace(testNamespace)
.setWorkspaceId(testWsId)
.setType("docker")
.setMachineSource(new MachineSourceImpl("image"));
SnapshotImpl snapshot1 = snapshotBuilder.build();
SnapshotImpl snapshot2 = snapshotBuilder.generateId()
.setDev(false)
.setMachineName("machine2")
.build();
when(snapshotDao.findSnapshots(testNamespace, testWsId)).thenReturn(asList(snapshot1, snapshot2));
// when
workspaceManager.removeSnapshots(testWsId);
// then
verify(machineManager).removeSnapshots(testNamespace, testWsId);
InOrder runtimesInOrder = inOrder(runtimes);
runtimesInOrder.verify(runtimes).removeSnapshot(snapshot1);
runtimesInOrder.verify(runtimes).removeSnapshot(snapshot2);
InOrder snapshotDaoInOrder = inOrder(snapshotDao);
snapshotDaoInOrder.verify(snapshotDao).removeSnapshot(snapshot1.getId());
snapshotDaoInOrder.verify(snapshotDao).removeSnapshot(snapshot2.getId());
}
@Test
public void shouldRemoveMachinesSnapshotsEvenSomeRemovalFails() throws Exception {
// given
String testWsId = "testWsId";
String testNamespace = "testNamespace";
WorkspaceImpl workspaceMock = mock(WorkspaceImpl.class);
when(workspaceDao.get(testWsId)).thenReturn(workspaceMock);
when(workspaceMock.getNamespace()).thenReturn(testNamespace);
SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder()
.generateId()
.setEnvName("env")
.setDev(true)
.setMachineName("machine1")
.setNamespace(testNamespace)
.setWorkspaceId(testWsId)
.setType("docker")
.setMachineSource(new MachineSourceImpl("image"));
SnapshotImpl snapshot1 = snapshotBuilder.build();
SnapshotImpl snapshot2 = snapshotBuilder.generateId()
.setDev(false)
.setMachineName("machine2")
.build();
when(snapshotDao.findSnapshots(testNamespace, testWsId)).thenReturn(asList(snapshot1, snapshot2));
doThrow(new SnapshotException("test")).when(snapshotDao).removeSnapshot(snapshot1.getId());
// when
workspaceManager.removeSnapshots(testWsId);
// then
verify(runtimes).removeSnapshot(snapshot1);
verify(runtimes).removeSnapshot(snapshot2);
verify(snapshotDao).removeSnapshot(snapshot1.getId());
verify(snapshotDao).removeSnapshot(snapshot2.getId());
}
@Test
@ -666,15 +773,13 @@ public class WorkspaceManagerTest {
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));
verify(runtimes, timeout(2000)).startMachine(testWsId, machineConfig);
}
@Test(expectedExceptions = ConflictException.class, expectedExceptionsMessageRegExp = "Workspace .* is not running, new machine can't be started")
@ -690,16 +795,107 @@ public class WorkspaceManagerTest {
workspaceManager.startMachine(machineConfig, testWsId);
}
@Test
public void shouldBeAbleToCreateSnapshot() throws Exception {
// then
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
// when
workspaceManager.createSnapshot(workspace.getId());
// then
verify(workspaceManager, timeout(1_000)).createSnapshotSync(any(WorkspaceRuntimeImpl.class),
eq(workspace.getNamespace()),
eq(workspace.getId()));
}
@Test(expectedExceptions = ConflictException.class,
expectedExceptionsMessageRegExp = "Could not .* the workspace '.*' because its status is '.*'.")
public void shouldNotCreateSnapshotIfWorkspaceIsNotRunning() throws Exception {
// then
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING);
when(runtimes.get(any())).thenReturn(descriptor);
// when
workspaceManager.createSnapshot(workspace.getId());
}
@Test
public void shouldBeAbleToStopMachine() throws Exception {
// given
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
MachineImpl machine = descriptor.getRuntime().getMachines().get(0);
// when
workspaceManager.stopMachine(workspace.getId(), machine.getId());
// then
verify(runtimes).stopMachine(workspace.getId(), machine.getId());
}
@Test(expectedExceptions = ConflictException.class,
expectedExceptionsMessageRegExp = "Could not .* the workspace '.*' because its status is '.*'.")
public void shouldNotStopMachineIfWorkspaceIsNotRunning() throws Exception {
// given
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING);
when(runtimes.get(any())).thenReturn(descriptor);
// when
workspaceManager.stopMachine(workspace.getId(), "someId");
}
@Test
public void shouldBeAbleToGetMachineInstanceIfWorkspaceIsRunning() throws Exception {
// given
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
MachineImpl machine = descriptor.getRuntime().getMachines().get(0);
// when
workspaceManager.getMachineInstance(workspace.getId(), machine.getId());
// then
verify(runtimes).getMachine(workspace.getId(), machine.getId());
}
@Test
public void shouldBeAbleToGetMachineInstanceIfWorkspaceIsStarting() throws Exception {
// given
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING);
when(runtimes.get(any())).thenReturn(descriptor);
MachineImpl machine = descriptor.getRuntime().getMachines().get(0);
// when
workspaceManager.getMachineInstance(workspace.getId(), machine.getId());
// then
verify(runtimes).getMachine(workspace.getId(), machine.getId());
}
private RuntimeDescriptor createDescriptor(WorkspaceImpl workspace, WorkspaceStatus status) {
final WorkspaceRuntimeImpl runtime = new WorkspaceRuntimeImpl(workspace.getConfig().getDefaultEnv());
final String env = workspace.getConfig().getDefaultEnv();
for (MachineConfigImpl machineConfig : workspace.getConfig()
.getEnvironment(workspace.getConfig().getDefaultEnv())
.get()
.getMachineConfigs()) {
Optional<EnvironmentImpl> environmentOpt = workspace.getConfig().getEnvironment(workspace.getConfig().getDefaultEnv());
assertTrue(environmentOpt.isPresent());
EnvironmentImpl environment = environmentOpt.get();
final WorkspaceRuntimeImpl runtime = new WorkspaceRuntimeImpl(environment.getName());
for (MachineConfigImpl machineConfig : environment.getMachineConfigs()) {
final MachineImpl machine = MachineImpl.builder()
.setConfig(machineConfig)
.setEnvName(env)
.setEnvName(environment.getName())
.setId(NameGenerator.generate("machine", 10))
.setOwner(workspace.getNamespace())
.setRuntime(new MachineRuntimeInfoImpl(emptyMap(), emptyMap(), emptyMap()))

Some files were not shown because too many files have changed in this diff Show More