getMachines(String workspaceId) throws EnvironmentNotRunni
*/
public Instance getMachine(String workspaceId, String machineId) throws NotFoundException {
EnvironmentHolder environment;
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireReadLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.readLock(workspaceId)) {
environment = environments.get(workspaceId);
}
if (environment == null) {
@@ -207,8 +209,8 @@ public Instance getMachine(String workspaceId, String machineId) throws NotFound
/**
* Starts provided environment.
- *
- * Environment starts if and only all machines in environment definition start successfully.
+ *
+ * Environment starts if and only all machines in environment definition start successfully.
* Otherwise exception is thrown by this method.
* It is not defined whether environment start fails right after first failure or in the end of the process.
* Starting order of machines is not guarantied. Machines can start sequentially or in parallel.
@@ -223,6 +225,8 @@ public Instance getMachine(String workspaceId, String machineId) throws NotFound
* whether machines from environment should be recovered or not
* @param messageConsumer
* consumer of log messages from machines in the environment
+ * @param startedHandler
+ * handler for started machines
* @return list of running machines of this environment
* @throws ServerException
* if other error occurs
@@ -231,9 +235,10 @@ public List start(String workspaceId,
String envName,
Environment env,
boolean recover,
- MessageConsumer messageConsumer) throws ServerException,
- ConflictException,
- EnvironmentException {
+ MessageConsumer messageConsumer,
+ MachineStartedHandler startedHandler) throws ServerException,
+ EnvironmentException,
+ ConflictException {
// TODO move to machines provider
// add random chars to ensure that old environments that weren't removed by some reason won't prevent start
String networkId = NameGenerator.generate(workspaceId + "_", 16);
@@ -256,9 +261,10 @@ public List start(String workspaceId,
workspaceId,
devMachineName,
networkId,
- recover);
+ recover,
+ startedHandler);
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.writeLock(workspaceId)) {
EnvironmentHolder environmentHolder = environments.get(workspaceId);
// possible only if environment was stopped during its start
if (environmentHolder == null) {
@@ -270,6 +276,21 @@ public List start(String workspaceId,
}
}
+ /**
+ * Starts workspace environment.
+ *
+ * @see #start(String, String, Environment, boolean, MessageConsumer, MachineStartedHandler)
+ */
+ public List start(String workspaceId,
+ String envName,
+ Environment env,
+ boolean recover,
+ MessageConsumer messageConsumer) throws ServerException,
+ ConflictException,
+ EnvironmentException {
+ return start(workspaceId, envName, env, recover, messageConsumer, NO_OP_HANDLER);
+ }
+
/**
* Stops running environment of specified workspace.
*
@@ -284,7 +305,7 @@ public void stop(String workspaceId) throws EnvironmentNotRunningException,
ServerException {
List machinesCopy = null;
EnvironmentHolder environmentHolder;
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireReadLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.readLock(workspaceId)) {
environmentHolder = environments.get(workspaceId);
if (environmentHolder == null || environmentHolder.status != EnvStatus.RUNNING) {
throw new EnvironmentNotRunningException(
@@ -302,7 +323,7 @@ public void stop(String workspaceId) throws EnvironmentNotRunningException,
destroyEnvironment(environmentHolder.networkId, machinesCopy);
}
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.writeLock(workspaceId)) {
environments.remove(workspaceId);
}
}
@@ -333,7 +354,7 @@ public Instance startMachine(String workspaceId,
MachineConfig machineConfigCopy = new MachineConfigImpl(machineConfig);
EnvironmentHolder environmentHolder;
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireReadLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.readLock(workspaceId)) {
environmentHolder = environments.get(workspaceId);
if (environmentHolder == null || environmentHolder.status != EnvStatus.RUNNING) {
throw new EnvironmentNotRunningException(format("Environment '%s' is not running", workspaceId));
@@ -428,7 +449,7 @@ public void stopMachine(String workspaceId, String machineId) throws NotFoundExc
ServerException,
ConflictException {
Instance targetMachine = null;
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.writeLock(workspaceId)) {
EnvironmentHolder environmentHolder = environments.get(workspaceId);
if (environmentHolder == null || environmentHolder.status != EnvStatus.RUNNING) {
throw new EnvironmentNotRunningException(format("Environment '%s' is not running", workspaceId));
@@ -475,7 +496,7 @@ public SnapshotImpl saveSnapshot(String workspaceId,
EnvironmentHolder environmentHolder;
SnapshotImpl snapshot = null;
Instance instance = null;
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireReadLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.readLock(workspaceId)) {
environmentHolder = environments.get(workspaceId);
if (environmentHolder == null || environmentHolder.status != EnvStatus.RUNNING) {
throw new EnvironmentNotRunningException(format("Environment '%s' is not running", workspaceId));
@@ -560,7 +581,7 @@ private void initializeEnvironment(String namespace,
envName,
networkId);
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.writeLock(workspaceId)) {
if (environments.putIfAbsent(workspaceId, environmentHolder) != null) {
throw new ConflictException(format("Environment of workspace '%s' already exists", workspaceId));
}
@@ -698,7 +719,8 @@ private void startEnvironmentQueue(String namespace,
String workspaceId,
String devMachineName,
String networkId,
- boolean recover)
+ boolean recover,
+ MachineStartedHandler startedHandler)
throws ServerException,
EnvironmentException {
// Starting all machines in environment one by one by getting configs
@@ -706,7 +728,7 @@ private void startEnvironmentQueue(String namespace,
// Config will be null only if there are no machines left in the queue
String envName;
MessageConsumer envLogger;
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireReadLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.readLock(workspaceId)) {
EnvironmentHolder environmentHolder = environments.get(workspaceId);
if (environmentHolder == null) {
throw new ServerException("Environment start is interrupted.");
@@ -727,7 +749,7 @@ private void startEnvironmentQueue(String namespace,
String creator = EnvironmentContext.getCurrent().getSubject().getUserId();
CheServiceImpl service;
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireReadLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.readLock(workspaceId)) {
EnvironmentHolder environmentHolder = environments.get(workspaceId);
if (environmentHolder == null) {
throw new ServerException("Environment start is interrupted.");
@@ -774,10 +796,15 @@ private void startEnvironmentQueue(String namespace,
.setOwner(creator)
.build();
+ checkInterruption(workspaceId, envName);
Instance instance = startInstance(recover,
envLogger,
machine,
machineStarter);
+ checkInterruption(workspaceId, envName);
+
+ startedHandler.started(instance);
+ checkInterruption(workspaceId, envName);
// Machine destroying is an expensive operation which must be
// performed outside of the lock, this section checks if
@@ -785,7 +812,7 @@ private void startEnvironmentQueue(String namespace,
// polled flag to true if the environment wasn't stopped.
// Also polls the proceeded machine configuration from the queue
boolean queuePolled = false;
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.writeLock(workspaceId)) {
ensurePreDestroyIsNotExecuted();
EnvironmentHolder environmentHolder = environments.get(workspaceId);
if (environmentHolder != null) {
@@ -828,9 +855,10 @@ private void startEnvironmentQueue(String namespace,
machineName = queuePeekOrFail(workspaceId);
}
- } catch (RuntimeException | ServerException e) {
+ } catch (RuntimeException | ServerException | EnvironmentStartInterruptedException e) {
+ boolean interrupted = Thread.interrupted();
EnvironmentHolder env;
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.writeLock(workspaceId)) {
env = environments.remove(workspaceId);
}
@@ -839,7 +867,23 @@ private void startEnvironmentQueue(String namespace,
} catch (Exception remEx) {
LOG.error(remEx.getLocalizedMessage(), remEx);
}
- throw new ServerException(e.getLocalizedMessage(), e);
+
+ if (interrupted) {
+ throw new EnvironmentStartInterruptedException(workspaceId, envName);
+ }
+ try {
+ throw e;
+ } catch (ServerException | EnvironmentStartInterruptedException rethrow) {
+ throw rethrow;
+ } catch (Exception wrap) {
+ throw new ServerException(wrap.getMessage(), wrap);
+ }
+ }
+ }
+
+ private void checkInterruption(String workspaceId, String envName) throws EnvironmentStartInterruptedException {
+ if (Thread.interrupted()) {
+ throw new EnvironmentStartInterruptedException(workspaceId, envName);
}
}
@@ -912,6 +956,8 @@ private Instance startInstance(boolean recover,
return instance;
} catch (ApiException | RuntimeException e) {
+ boolean interrupted = Thread.interrupted();
+
removeMachine(machine.getWorkspaceId(), machine.getId());
if (instance != null) {
@@ -942,6 +988,9 @@ private Instance startInstance(boolean recover,
.withMachineId(machine.getId())
.withWorkspaceId(machine.getWorkspaceId()));
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
throw new ServerException(e.getLocalizedMessage(), e);
}
}
@@ -998,7 +1047,7 @@ private Machine normalizeMachineSource(MachineImpl machine, MachineSource machin
private void addMachine(MachineImpl machine) throws ServerException {
Instance instance = new NoOpMachineInstance(machine);
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireWriteLock(machine.getWorkspaceId())) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.writeLock(machine.getWorkspaceId())) {
ensurePreDestroyIsNotExecuted();
EnvironmentHolder environmentHolder = environments.get(machine.getWorkspaceId());
if (environmentHolder != null && environmentHolder.status != EnvStatus.STOPPING) {
@@ -1017,7 +1066,7 @@ private int bytesToMB(long bytes) {
private void removeMachine(String workspaceId,
String machineId) {
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.writeLock(workspaceId)) {
EnvironmentHolder environmentHolder = environments.get(workspaceId);
if (environmentHolder != null) {
for (Instance machine : environmentHolder.machines) {
@@ -1031,7 +1080,7 @@ private void removeMachine(String workspaceId,
}
private void replaceMachine(Instance machine) throws ServerException {
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireWriteLock(machine.getWorkspaceId())) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.writeLock(machine.getWorkspaceId())) {
ensurePreDestroyIsNotExecuted();
EnvironmentHolder environmentHolder = environments.get(machine.getWorkspaceId());
if (environmentHolder != null) {
@@ -1071,7 +1120,7 @@ private void replaceMachine(Instance machine) throws ServerException {
* if pre destroy has been invoked before peek config retrieved
*/
private String queuePeekOrFail(String workspaceId) throws ServerException {
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireReadLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.readLock(workspaceId)) {
ensurePreDestroyIsNotExecuted();
EnvironmentHolder environmentHolder = environments.get(workspaceId);
if (environmentHolder == null || environmentHolder.startQueue == null) {
@@ -1277,7 +1326,7 @@ private class MachineCleaner implements EventSubscriber {
public void onEvent(InstanceStateEvent event) {
if ((event.getType() == OOM) || (event.getType() == DIE)) {
EnvironmentHolder environmentHolder;
- try (@SuppressWarnings("unused") CloseableLock lock = stripedLocks.acquireReadLock("workspaceId")) {
+ try (@SuppressWarnings("unused") Unlocker u = stripedLocks.readLock("workspaceId")) {
environmentHolder = environments.get(event.getWorkspaceId());
}
if (environmentHolder != null) {
@@ -1311,4 +1360,9 @@ public void onEvent(InstanceStateEvent event) {
}
}
}
+
+ private static class NoOpStartedHandler implements MachineStartedHandler {
+ @Override
+ public void started(Instance machine) throws ServerException {}
+ }
}
diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/MachineStartedHandler.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/MachineStartedHandler.java
new file mode 100644
index 00000000000..d1094ea0040
--- /dev/null
+++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/MachineStartedHandler.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.api.environment.server;
+
+import org.eclipse.che.api.core.ServerException;
+import org.eclipse.che.api.environment.server.exception.EnvironmentException;
+import org.eclipse.che.api.machine.server.spi.Instance;
+
+/**
+ * Used in couple with {@link CheEnvironmentEngine#start} method to
+ * allow sequential handling and interruption of the start process.
+ *
+ * This interface is a part of a contract for {@link CheEnvironmentEngine}.
+ *
+ * @author Yevhenii Voevodin
+ */
+public interface MachineStartedHandler {
+ void started(Instance machine) throws EnvironmentException, ServerException;
+}
diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/exception/EnvironmentStartInterruptedException.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/exception/EnvironmentStartInterruptedException.java
new file mode 100644
index 00000000000..21629b71e6d
--- /dev/null
+++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/exception/EnvironmentStartInterruptedException.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.api.environment.server.exception;
+
+/**
+ * Thrown when environment start is interrupted.
+ *
+ * @author Yevhenii Voevodin
+ */
+public class EnvironmentStartInterruptedException extends EnvironmentException {
+ public EnvironmentStartInterruptedException(String workspaceId, String envName) {
+ super(String.format("Start of environment '%s' in workspace '%s' is interrupted",
+ envName,
+ workspaceId));
+ }
+}
diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java
index 56bcecc576d..585416bdb49 100644
--- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java
+++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java
@@ -29,7 +29,6 @@
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.SnapshotDao;
-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.model.impl.WorkspaceConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
@@ -46,8 +45,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Throwables.getCausalChain;
@@ -130,10 +127,12 @@ public WorkspaceImpl createWorkspace(WorkspaceConfig config,
NotFoundException {
requireNonNull(config, "Required non-null config");
requireNonNull(namespace, "Required non-null namespace");
- return normalizeState(doCreateWorkspace(config,
- accountManager.getByName(namespace),
- emptyMap(),
- false));
+ WorkspaceImpl workspace = doCreateWorkspace(config,
+ accountManager.getByName(namespace),
+ emptyMap(),
+ false);
+ workspace.setStatus(WorkspaceStatus.STOPPED);
+ return workspace;
}
/**
@@ -164,10 +163,12 @@ public WorkspaceImpl createWorkspace(WorkspaceConfig config,
requireNonNull(config, "Required non-null config");
requireNonNull(namespace, "Required non-null namespace");
requireNonNull(attributes, "Required non-null attributes");
- return normalizeState(doCreateWorkspace(config,
- accountManager.getByName(namespace),
- attributes,
- false));
+ WorkspaceImpl workspace = doCreateWorkspace(config,
+ accountManager.getByName(namespace),
+ emptyMap(),
+ false);
+ workspace.setStatus(WorkspaceStatus.STOPPED);
+ return workspace;
}
/**
@@ -193,7 +194,9 @@ public WorkspaceImpl createWorkspace(WorkspaceConfig config,
*/
public WorkspaceImpl getWorkspace(String key) throws NotFoundException, ServerException {
requireNonNull(key, "Required non-null workspace key");
- return normalizeState(getByKey(key));
+ WorkspaceImpl workspace = getByKey(key);
+ runtimes.injectRuntime(workspace);
+ return workspace;
}
/**
@@ -215,7 +218,9 @@ public WorkspaceImpl getWorkspace(String key) throws NotFoundException, ServerEx
public WorkspaceImpl getWorkspace(String name, String namespace) throws NotFoundException, ServerException {
requireNonNull(name, "Required non-null workspace name");
requireNonNull(namespace, "Required non-null workspace owner");
- return normalizeState(workspaceDao.get(name, namespace));
+ WorkspaceImpl workspace = workspaceDao.get(name, namespace);
+ runtimes.injectRuntime(workspace);
+ return workspace;
}
/**
@@ -256,8 +261,10 @@ public List getWorkspaces(String user) throws ServerException {
public List getWorkspaces(String user, boolean includeRuntimes) throws ServerException {
requireNonNull(user, "Required non-null user id");
final List workspaces = workspaceDao.getWorkspaces(user);
- for (WorkspaceImpl workspace : workspaces) {
- normalizeState(workspace, includeRuntimes);
+ if (includeRuntimes) {
+ injectRuntimes(workspaces);
+ } else {
+ injectStatuses(workspaces);
}
return workspaces;
}
@@ -300,8 +307,10 @@ public List getByNamespace(String namespace) throws ServerExcepti
public List getByNamespace(String namespace, boolean includeRuntimes) throws ServerException {
requireNonNull(namespace, "Required non-null namespace");
final List workspaces = workspaceDao.getByNamespace(namespace);
- for (WorkspaceImpl workspace : workspaces) {
- normalizeState(workspace, includeRuntimes);
+ if (includeRuntimes) {
+ injectRuntimes(workspaces);
+ } else {
+ injectStatuses(workspaces);
}
return workspaces;
}
@@ -329,12 +338,14 @@ public WorkspaceImpl updateWorkspace(String id, Workspace update) throws Conflic
NotFoundException {
requireNonNull(id, "Required non-null workspace id");
requireNonNull(update, "Required non-null workspace update");
- final WorkspaceImpl workspace = workspaceDao.get(id);
+ WorkspaceImpl workspace = workspaceDao.get(id);
workspace.setConfig(new WorkspaceConfigImpl(update.getConfig()));
update.getAttributes().put(UPDATED_ATTRIBUTE_NAME, Long.toString(currentTimeMillis()));
workspace.setAttributes(update.getAttributes());
workspace.setTemporary(update.isTemporary());
- return normalizeState(workspaceDao.update(workspace));
+ WorkspaceImpl updated = workspaceDao.update(workspace);
+ runtimes.injectRuntime(updated);
+ return updated;
}
/**
@@ -401,7 +412,8 @@ public WorkspaceImpl startWorkspace(String workspaceId,
final String restoreAttr = workspace.getAttributes().get(AUTO_RESTORE_FROM_SNAPSHOT);
final boolean autoRestore = restoreAttr == null ? defaultAutoRestore : parseBoolean(restoreAttr);
startAsync(workspace, envName, firstNonNull(restore, autoRestore) && !getSnapshot(workspaceId).isEmpty());
- return normalizeState(workspace);
+ runtimes.injectRuntime(workspace);
+ return workspace;
}
/**
@@ -431,7 +443,8 @@ public WorkspaceImpl startWorkspace(WorkspaceConfig config,
emptyMap(),
isTemporary);
startAsync(workspace, workspace.getConfig().getDefaultEnv(), false);
- return normalizeState(workspace);
+ runtimes.injectRuntime(workspace);
+ return workspace;
}
/**
@@ -506,8 +519,8 @@ public void stopWorkspace(String workspaceId, @Nullable Boolean createSnapshot)
NotFoundException,
ServerException {
requireNonNull(workspaceId, "Required non-null workspace id");
- final WorkspaceImpl workspace = normalizeState(workspaceDao.get(workspaceId));
- checkWorkspaceIsRunning(workspace, "stop");
+ final WorkspaceImpl workspace = workspaceDao.get(workspaceId);
+ workspace.setStatus(runtimes.getStatus(workspaceId));
stopAsync(workspace, createSnapshot);
}
@@ -615,7 +628,8 @@ public void stopMachine(String workspaceId,
ConflictException {
requireNonNull(workspaceId, "Required non-null workspace id");
requireNonNull(machineId, "Required non-null machine id");
- final WorkspaceImpl workspace = normalizeState(workspaceDao.get(workspaceId));
+ final WorkspaceImpl workspace = workspaceDao.get(workspaceId);
+ workspace.setStatus(runtimes.getStatus(workspaceId));
checkWorkspaceIsRunning(workspace, format("stop machine with ID '%s' of", machineId));
runtimes.stopMachine(workspaceId, machineId);
}
@@ -638,7 +652,7 @@ public Instance getMachineInstance(String workspaceId,
ServerException {
requireNonNull(workspaceId, "Required non-null workspace id");
requireNonNull(machineId, "Required non-null machine id");
- normalizeState(workspaceDao.get(workspaceId));
+ workspaceDao.get(workspaceId);
return runtimes.getMachine(workspaceId, machineId);
}
@@ -658,33 +672,46 @@ private void startAsync(WorkspaceImpl workspace,
workspaceDao.update(workspace);
final String env = firstNonNull(envName, workspace.getConfig().getDefaultEnv());
- // barrier, safely doesn't allow to start the workspace twice
- final Future descriptor = runtimes.startAsync(workspace, env, recover);
-
- sharedPool.execute(() -> {
- try {
- descriptor.get();
- LOG.info("Workspace '{}:{}' with id '{}' started by user '{}'",
- workspace.getNamespace(),
- workspace.getConfig().getName(),
- workspace.getId(),
- sessionUserNameOr("undefined"));
- } catch (Exception ex) {
- if (workspace.isTemporary()) {
- removeWorkspaceQuietly(workspace);
- }
- for (Throwable cause : getCausalChain(ex)) {
- if (cause instanceof SourceNotFoundException) {
- return;
+ runtimes.startAsync(workspace, env, recover)
+ .whenComplete((runtime, ex) -> {
+ if (ex == null) {
+ LOG.info("Workspace '{}:{}' with id '{}' started by user '{}'",
+ workspace.getNamespace(),
+ workspace.getConfig().getName(),
+ workspace.getId(),
+ sessionUserNameOr("undefined"));
+ } else {
+ if (workspace.isTemporary()) {
+ removeWorkspaceQuietly(workspace);
+ }
+ for (Throwable cause : getCausalChain(ex)) {
+ if (cause instanceof SourceNotFoundException) {
+ return;
+ }
+ }
+ try {
+ throw ex;
+ } catch (EnvironmentException envEx) {
+ // it's okay, e.g. recipe is invalid | start interrupted
+ LOG.info("Workspace '{}:{}' can't be started because: {}",
+ workspace.getNamespace(),
+ workspace.getConfig().getName(),
+ envEx.getMessage());
+ } catch (Throwable thr) {
+ LOG.error(thr.getMessage(), thr);
+ }
}
- }
- LOG.error(ex.getLocalizedMessage(), ex);
- }
- });
+ });
}
private void stopAsync(WorkspaceImpl workspace, @Nullable Boolean createSnapshot) throws ConflictException {
- checkWorkspaceIsRunning(workspace, "stop");
+ if (workspace.getStatus() != WorkspaceStatus.RUNNING && workspace.getStatus() != WorkspaceStatus.STARTING) {
+ throw new ConflictException(format("Could not stop the workspace '%s:%s' because its status is '%s'. " +
+ "Workspace must be either 'STARTING' or 'RUNNING'",
+ workspace.getNamespace(),
+ workspace.getConfig().getName(),
+ workspace.getStatus()));
+ }
sharedPool.execute(() -> {
final String stoppedBy = sessionUserNameOr(workspace.getAttributes().get(WORKSPACE_STOPPED_BY));
@@ -695,7 +722,7 @@ private void stopAsync(WorkspaceImpl workspace, @Nullable Boolean createSnapshot
firstNonNull(stoppedBy, "undefined"));
final boolean snapshotBeforeStop;
- if (workspace.isTemporary()) {
+ if (workspace.isTemporary() || workspace.getStatus() == WorkspaceStatus.STARTING) {
snapshotBeforeStop = false;
} else if (createSnapshot != null) {
snapshotBeforeStop = createSnapshot;
@@ -728,7 +755,7 @@ private void stopAsync(WorkspaceImpl workspace, @Nullable Boolean createSnapshot
workspace.getConfig().getName(),
workspace.getId(),
firstNonNull(stoppedBy, "undefined"));
- } catch (RuntimeException | ConflictException | NotFoundException | ServerException ex) {
+ } catch (Exception ex) {
LOG.error(ex.getLocalizedMessage(), ex);
} finally {
if (workspace.isTemporary()) {
@@ -778,33 +805,6 @@ private String sessionUserNameOr(String nameIfNoUser) {
return nameIfNoUser;
}
- private WorkspaceImpl normalizeState(WorkspaceImpl workspace) throws ServerException {
- return normalizeState(workspace, true);
- }
-
- private WorkspaceImpl normalizeState(WorkspaceImpl workspace, boolean includeRuntimes) throws ServerException {
- if (includeRuntimes) {
- try {
- return normalizeState(workspace, runtimes.get(workspace.getId()));
- } catch (NotFoundException e) {
- return normalizeState(workspace, null);
- }
- } else {
- workspace.setStatus(runtimes.getStatus(workspace.getId()));
- return workspace;
- }
- }
-
- private WorkspaceImpl normalizeState(WorkspaceImpl workspace, RuntimeDescriptor descriptor) {
- if (descriptor != null) {
- workspace.setStatus(descriptor.getRuntimeStatus());
- workspace.setRuntime(descriptor.getRuntime());
- } else {
- workspace.setStatus(WorkspaceStatus.STOPPED);
- }
- return workspace;
- }
-
private WorkspaceImpl doCreateWorkspace(WorkspaceConfig config,
Account account,
Map attributes,
@@ -843,4 +843,17 @@ private WorkspaceImpl getByKey(String key) throws NotFoundException, ServerExcep
final String namespace = nsPart.isEmpty() ? sessionUser().getUserName() : nsPart;
return workspaceDao.get(wsName, namespace);
}
+
+
+ private void injectRuntimes(List extends WorkspaceImpl> workspaces) {
+ for (WorkspaceImpl workspace : workspaces) {
+ runtimes.injectRuntime(workspace);
+ }
+ }
+
+ private void injectStatuses(List extends WorkspaceImpl> workspaces) {
+ for (WorkspaceImpl workspace : workspaces) {
+ workspace.setStatus(runtimes.getStatus(workspace.getId()));
+ }
+ }
}
diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimes.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimes.java
index 9df69f7d712..339ce5cdb24 100644
--- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimes.java
+++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimes.java
@@ -20,25 +20,21 @@
import org.eclipse.che.api.agent.server.launcher.AgentLauncherFactory;
import org.eclipse.che.api.agent.shared.model.Agent;
import org.eclipse.che.api.agent.shared.model.AgentKey;
-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.MachineStatus;
import org.eclipse.che.api.core.model.workspace.Environment;
import org.eclipse.che.api.core.model.workspace.ExtendedMachine;
import org.eclipse.che.api.core.model.workspace.Workspace;
-import org.eclipse.che.api.core.model.workspace.WorkspaceRuntime;
import org.eclipse.che.api.core.model.workspace.WorkspaceStatus;
import org.eclipse.che.api.core.notification.EventService;
-import org.eclipse.che.api.core.util.AbstractMessageConsumer;
-import org.eclipse.che.api.core.util.MessageConsumer;
import org.eclipse.che.api.core.util.WebsocketMessageConsumer;
import org.eclipse.che.api.environment.server.CheEnvironmentEngine;
+import org.eclipse.che.api.environment.server.MachineStartedHandler;
import org.eclipse.che.api.environment.server.exception.EnvironmentException;
import org.eclipse.che.api.environment.server.exception.EnvironmentNotRunningException;
+import org.eclipse.che.api.environment.server.exception.EnvironmentStartInterruptedException;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.exception.SnapshotException;
import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl;
@@ -47,39 +43,44 @@
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.spi.SnapshotDao;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
-import org.eclipse.che.api.workspace.server.model.impl.ExtendedMachineImpl;
+import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceRuntimeImpl;
import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent;
import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent.EventType;
-import org.eclipse.che.commons.lang.concurrent.CloseableLock;
import org.eclipse.che.commons.lang.concurrent.StripedLocks;
+import org.eclipse.che.commons.lang.concurrent.Unlocker;
import org.eclipse.che.dto.server.DtoFactory;
import org.slf4j.Logger;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Singleton;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import static java.lang.String.format;
import static java.util.Comparator.comparing;
+import static java.util.Objects.requireNonNull;
import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.RUNNING;
import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.SNAPSHOTTING;
+import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STARTING;
import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STOPPING;
import static org.eclipse.che.api.machine.shared.Constants.ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE;
import static org.slf4j.LoggerFactory.getLogger;
@@ -89,16 +90,14 @@
*
* This component implements {@link WorkspaceStatus} contract.
*
- *
All the operations performed by this component are synchronous.
- *
*
The implementation is thread-safe and guarded by
* eagerly initialized readwrite locks produced by {@link StripedLocks}.
* The component doesn't expose any api for client-side locking.
* All the instances produced by this component are copies of the real data.
*
*
The component doesn't check if the incoming objects are in application-valid state.
- * Which means that it is expected that if {@link #start(Workspace, String, boolean)} method is called
- * then {@code WorkspaceImpl} argument is a application-valid object which contains
+ * Which means that it is expected that if {@link #startAsync(Workspace, String, boolean)} method is called
+ * then {@code Workspace} argument is a application-valid object which contains
* all the required data for performing start.
*
* @author Yevhenii Voevodin
@@ -109,15 +108,15 @@ public class WorkspaceRuntimes {
private static final Logger LOG = getLogger(WorkspaceRuntimes.class);
- private final ConcurrentMap workspaces;
- private final EventService eventsService;
- private final StripedLocks locks;
- private final CheEnvironmentEngine envEngine;
- private final AgentSorter agentSorter;
- private final AgentLauncherFactory launcherFactory;
- private final AgentRegistry agentRegistry;
- private final SnapshotDao snapshotDao;
- private final WorkspaceSharedPool sharedPool;
+ private final ConcurrentMap states;
+ private final EventService eventsService;
+ private final StripedLocks locks;
+ private final CheEnvironmentEngine envEngine;
+ private final AgentSorter agentSorter;
+ private final AgentLauncherFactory launcherFactory;
+ private final AgentRegistry agentRegistry;
+ private final SnapshotDao snapshotDao;
+ private final WorkspaceSharedPool sharedPool;
private volatile boolean isPreDestroyInvoked;
@@ -129,27 +128,119 @@ public WorkspaceRuntimes(EventService eventsService,
AgentRegistry agentRegistry,
SnapshotDao snapshotDao,
WorkspaceSharedPool sharedPool) {
+ this(eventsService,
+ envEngine,
+ agentSorter,
+ launcherFactory,
+ agentRegistry,
+ snapshotDao,
+ sharedPool,
+ new ConcurrentHashMap<>());
+ }
+
+ public WorkspaceRuntimes(EventService eventsService,
+ CheEnvironmentEngine envEngine,
+ AgentSorter agentSorter,
+ AgentLauncherFactory launcherFactory,
+ AgentRegistry agentRegistry,
+ SnapshotDao snapshotDao,
+ WorkspaceSharedPool sharedPool,
+ ConcurrentMap states) {
this.eventsService = eventsService;
this.envEngine = envEngine;
this.agentSorter = agentSorter;
this.launcherFactory = launcherFactory;
this.agentRegistry = agentRegistry;
this.snapshotDao = snapshotDao;
- this.workspaces = new ConcurrentHashMap<>();
// 16 - experimental value for stripes count, it comes from default hash map size
this.locks = new StripedLocks(16);
this.sharedPool = sharedPool;
+ this.states = states;
}
/**
- * Returns the runtime descriptor describing currently starting/running/stopping
- * workspace runtime.
+ * Asynchronously starts the environment of the workspace.
+ * Before executing start task checks whether all conditions
+ * are met and throws appropriate exceptions if not, so
+ * there is no way to start the same workspace twice.
+ *
+ * Note that cancellation of resulting future won't
+ * interrupt workspace start, call {@link #stop(String)} directly instead.
+ *
+ *
If starting process is interrupted let's say within call
+ * to {@link #stop(String)} method, resulting future will
+ * be exceptionally completed(eventually) with an instance of
+ * {@link EnvironmentStartInterruptedException}. Note that clients
+ * don't have to cleanup runtime resources, the component
+ * will do necessary cleanup when interrupted.
+ *
+ *
Implementation notes:
+ * if thread which executes the task is interrupted, then the
+ * task is also eventually(depends on the environment engine implementation)
+ * interrupted as if {@link #stop(String)} is called directly.
+ * That helps to shutdown gracefully when thread pool is asked
+ * to {@link ExecutorService#shutdownNow()} and also reduces
+ * shutdown time when there are starting workspaces.
*
- *
Note that the {@link RuntimeDescriptor#getRuntime()} method
- * returns a copy of a real {@code WorkspaceRuntime} object,
- * which means that any runtime copy modifications won't affect the
- * real object and also it means that copy won't be affected with modifications applied
- * to the real runtime workspace object state.
+ * @param workspace
+ * workspace containing target environment
+ * @param envName
+ * the name of the environment to start
+ * @param recover
+ * whether to recover from the snapshot
+ * @return completable future describing the instance of running environment
+ * @throws ConflictException
+ * when the workspace is already started
+ * @throws IllegalArgumentException
+ * when the workspace doesn't contain the environment
+ * @throws NullPointerException
+ * when either {@code workspace} or {@code envName} is null
+ */
+ public CompletableFuture startAsync(Workspace workspace,
+ String envName,
+ boolean recover) throws ConflictException {
+ requireNonNull(workspace, "Non-null workspace required");
+ requireNonNull(envName, "Non-null environment name required");
+ EnvironmentImpl environment = copyEnv(workspace, envName);
+ String workspaceId = workspace.getId();
+ CompletableFuture cmpFuture;
+ StartTask startTask;
+ try (@SuppressWarnings("unused") Unlocker u = locks.writeLock(workspaceId)) {
+ ensurePreDestroyIsNotExecuted();
+ RuntimeState state = states.get(workspaceId);
+ if (state != null) {
+ throw new ConflictException(format("Could not start workspace '%s' because its status is '%s'",
+ workspace.getConfig().getName(),
+ state.status));
+ }
+ startTask = new StartTask(workspaceId,
+ envName,
+ environment,
+ recover,
+ cmpFuture = new CompletableFuture<>());
+ states.put(workspaceId, new RuntimeState(WorkspaceStatus.STARTING,
+ envName,
+ startTask,
+ sharedPool.submit(startTask)));
+ }
+
+ // publish event synchronously as the task may not be executed by
+ // executors service(due to legal cancellation), clients still have
+ // to receive STOPPED -> STARTING event
+ eventsService.publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
+ .withWorkspaceId(workspaceId)
+ .withStatus(WorkspaceStatus.STARTING)
+ .withEventType(EventType.STARTING)
+ .withPrevStatus(WorkspaceStatus.STOPPED));
+
+ // so the start thread is free to go and start the environment
+ startTask.unlockStart();
+
+ return cmpFuture;
+ }
+
+ /**
+ * Gets workspace runtime descriptor.
*
* @param workspaceId
* the id of the workspace to get its runtime
@@ -157,100 +248,74 @@ public WorkspaceRuntimes(EventService eventsService,
* @throws NotFoundException
* when workspace with given {@code workspaceId} is not found
* @throws ServerException
- * if environment is in illegal state
+ * if any error occurs while getting machines runtime information
*/
- public RuntimeDescriptor get(String workspaceId) throws NotFoundException,
- ServerException {
- WorkspaceState workspaceState;
- try (@SuppressWarnings("unused") CloseableLock lock = locks.acquireReadLock(workspaceId)) {
- workspaceState = workspaces.get(workspaceId);
- }
- if (workspaceState == null) {
- throw new NotFoundException("Workspace with id '" + workspaceId + "' is not running.");
- }
-
- RuntimeDescriptor runtimeDescriptor = new RuntimeDescriptor(workspaceState.status,
- new WorkspaceRuntimeImpl(workspaceState.activeEnv,
- null,
- Collections.emptyList(),
- null));
- List machines = envEngine.getMachines(workspaceId);
- Optional devMachineOptional = machines.stream()
- .filter(machine -> machine.getConfig().isDev())
- .findAny();
- if (devMachineOptional.isPresent()) {
- String projectsRoot = devMachineOptional.get().getStatus() == MachineStatus.RUNNING ?
- devMachineOptional.get().getRuntime().projectsRoot() :
- null;
- runtimeDescriptor.setRuntime(new WorkspaceRuntimeImpl(workspaceState.activeEnv,
- projectsRoot,
- machines,
- devMachineOptional.get()));
- } else if (workspaceState.status == WorkspaceStatus.RUNNING) {
- // invalid state of environment is detected
- String error = format("Dev machine is not found in active environment of workspace '%s'",
- workspaceId);
- throw new ServerException(error);
- }
-
- return runtimeDescriptor;
+ public WorkspaceRuntimeImpl getRuntime(String workspaceId) throws NotFoundException, ServerException {
+ requireNonNull(workspaceId, "Required non-null workspace id");
+ RuntimeState state;
+ try (@SuppressWarnings("unused") Unlocker u = locks.readLock(workspaceId)) {
+ state = new RuntimeState(getExistingState(workspaceId));
+ }
+ return new WorkspaceRuntimeImpl(state.envName, envEngine.getMachines(workspaceId));
}
/**
- * Starts all machines from specified workspace environment,
- * creates workspace runtime instance based on that environment.
+ * Return status of the workspace.
*
- * During the start of the workspace its
- * runtime is visible with {@link WorkspaceStatus#STARTING} status.
+ * @param workspaceId
+ * ID of requested workspace
+ * @return {@link WorkspaceStatus#STOPPED} if workspace is not running or,
+ * the status of workspace runtime otherwise
+ */
+ public WorkspaceStatus getStatus(String workspaceId) {
+ requireNonNull(workspaceId, "Required non-null workspace id");
+ try (@SuppressWarnings("unused") Unlocker u = locks.readLock(workspaceId)) {
+ RuntimeState state = states.get(workspaceId);
+ if (state == null) {
+ return WorkspaceStatus.STOPPED;
+ }
+ return state.status;
+ }
+ }
+
+ /**
+ * Injects runtime information such as status and {@link WorkspaceRuntimeImpl}
+ * into the workspace object, if the workspace doesn't have runtime sets the
+ * status to {@link WorkspaceStatus#STOPPED}.
*
* @param workspace
- * workspace which environment should be started
- * @param envName
- * the name of the environment to start
- * @param recover
- * whether machines should be recovered(true) or not(false)
- * @return the workspace runtime instance with machines set.
- * @throws ConflictException
- * when workspace is already running
- * @throws ConflictException
- * when start is interrupted
- * @throws NotFoundException
- * when any not found exception occurs during environment start
- * @throws ServerException
- * when component {@link #isPreDestroyInvoked is stopped}
- * @throws ServerException
- * other error occurs during environment start
- * @see CheEnvironmentEngine#start(String, String, Environment, boolean, MessageConsumer)
- * @see WorkspaceStatus#STARTING
- * @see WorkspaceStatus#RUNNING
+ * the workspace to inject runtime into
*/
- public RuntimeDescriptor start(Workspace workspace,
- String envName,
- boolean recover) throws ServerException,
- ConflictException,
- NotFoundException {
- final EnvironmentImpl environment = copyEnv(workspace, envName);
- final String workspaceId = workspace.getId();
- initState(workspaceId, workspace.getConfig().getName(), envName);
- doStart(environment, workspaceId, envName, recover);
- return get(workspaceId);
+ public void injectRuntime(WorkspaceImpl workspace) {
+ requireNonNull(workspace, "Required non-null workspace");
+ RuntimeState state = null;
+ try (@SuppressWarnings("unused") Unlocker u = locks.readLock(workspace.getId())) {
+ if (states.containsKey(workspace.getId())) {
+ state = new RuntimeState(states.get(workspace.getId()));
+ }
+ }
+ if (state == null) {
+ workspace.setStatus(WorkspaceStatus.STOPPED);
+ } else {
+ workspace.setStatus(state.status);
+ try {
+ workspace.setRuntime(new WorkspaceRuntimeImpl(state.envName, envEngine.getMachines(workspace.getId())));
+ } catch (Exception x) {
+ workspace.setRuntime(new WorkspaceRuntimeImpl(state.envName, Collections.emptyList()));
+ }
+ }
}
/**
- * Starts the workspace like {@link #start(Workspace, String, boolean)}
- * method does, but asynchronously. Nonetheless synchronously checks that workspace
- * doesn't have runtime and makes it {@link WorkspaceStatus#STARTING}.
+ * Returns true if the status of the workspace is different
+ * from {@link WorkspaceStatus#STOPPED}.
+ *
+ * @param workspaceId
+ * workspace identifier to perform check
+ * @return true if workspace status is different from {@link WorkspaceStatus#STOPPED}
*/
- public Future startAsync(Workspace workspace,
- String envName,
- boolean recover) throws ConflictException, ServerException {
- final EnvironmentImpl environment = copyEnv(workspace, envName);
- final String workspaceId = workspace.getId();
- initState(workspaceId, workspace.getConfig().getName(), envName);
- return sharedPool.submit(() -> {
- doStart(environment, workspaceId, envName, recover);
- return get(workspaceId);
- });
+ public boolean hasRuntime(String workspaceId) {
+ return states.containsKey(workspaceId);
}
/**
@@ -271,86 +336,52 @@ public Future startAsync(Workspace workspace,
* @see CheEnvironmentEngine#stop(String)
* @see WorkspaceStatus#STOPPING
*/
- public void stop(String workspaceId) throws NotFoundException, ServerException, ConflictException {
- // This check allows to exit with an appropriate exception before blocking on lock.
- // The double check is required as it is still possible to get unlucky timing
- // between locking and stopping workspace.
- ensurePreDestroyIsNotExecuted();
- try (@SuppressWarnings("unused") CloseableLock lock = locks.acquireWriteLock(workspaceId)) {
+ public void stop(String workspaceId) throws NotFoundException,
+ ServerException,
+ ConflictException,
+ EnvironmentException {
+ requireNonNull(workspaceId, "Required not-null workspace id");
+ RuntimeState prevState;
+ try (@SuppressWarnings("unused") Unlocker u = locks.writeLock(workspaceId)) {
ensurePreDestroyIsNotExecuted();
- WorkspaceState workspaceState = workspaces.get(workspaceId);
- if (workspaceState == null) {
- throw new NotFoundException("Workspace with id '" + workspaceId + "' is not running.");
- }
- if (workspaceState.status != WorkspaceStatus.RUNNING) {
- throw new ConflictException(format("Couldn't stop '%s' workspace because its status is '%s'. " +
- "Workspace can be stopped only if it is 'RUNNING'",
+ RuntimeState state = getExistingState(workspaceId);
+ if (state.status != WorkspaceStatus.RUNNING && state.status != WorkspaceStatus.STARTING) {
+ throw new ConflictException(format("Couldn't stop the workspace '%s' because its status is '%s'. " +
+ "Workspace can be stopped only if it is 'RUNNING' or 'STARTING'",
workspaceId,
- workspaceState.status));
+ state.status));
}
-
- workspaceState.status = WorkspaceStatus.STOPPING;
+ prevState = new RuntimeState(state);
+ state.status = WorkspaceStatus.STOPPING;
}
- eventsService.publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withWorkspaceId(workspaceId)
- .withPrevStatus(WorkspaceStatus.RUNNING)
- .withStatus(WorkspaceStatus.STOPPING)
- .withEventType(EventType.STOPPING));
- String error = null;
- try {
- envEngine.stop(workspaceId);
- } catch (ServerException | RuntimeException e) {
- error = e.getLocalizedMessage();
- } finally {
- try (@SuppressWarnings("unused") CloseableLock lock = locks.acquireWriteLock(workspaceId)) {
- workspaces.remove(workspaceId);
- }
+ // workspace is running, stop normally
+ if (prevState.status == WorkspaceStatus.RUNNING) {
+ stopEnvironmentAndPublishEvents(workspaceId, WorkspaceStatus.RUNNING);
+ return;
}
- final WorkspaceStatusEvent event = DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withWorkspaceId(workspaceId)
- .withPrevStatus(WorkspaceStatus.STOPPING);
- if (error == null) {
- event.setStatus(WorkspaceStatus.STOPPED);
- event.setEventType(EventType.STOPPED);
- } else {
- event.setStatus(WorkspaceStatus.STOPPED);
- event.setEventType(EventType.ERROR);
- event.setError(error);
+ // interrupt workspace start thread
+ prevState.startFuture.cancel(true);
+
+ // if task wasn't called by executor service, then
+ // no real machines were started but, the clients still
+ // have to be notified about the workspace shut down
+ StartTask startTask = prevState.startTask;
+ if (startTask.markAsUsed()) {
+ removeStateAndPublishStopEvents(workspaceId);
+ prevState.startTask.earlyComplete();
+ return;
}
- eventsService.publish(event);
- }
- /**
- * Returns true if workspace was started and its status is
- * {@link WorkspaceStatus#RUNNING running}, {@link WorkspaceStatus#STARTING starting}
- * or {@link WorkspaceStatus#STOPPING stopping} - otherwise returns false.
- *
- * This method is less expensive alternative to {@link #get(String)} + {@code try catch}, see example:
- *
{@code
- *
- * if (!runtimes.hasRuntime("workspace123")) {
- * doStuff("workspace123");
- * }
- *
- * //vs
- *
- * try {
- * runtimes.get("workspace123");
- * } catch (NotFoundException ex) {
- * doStuff("workspace123");
- * }
- *
- * }
- *
- * @param workspaceId
- * workspace identifier to perform check
- * @return true if workspace is running, otherwise false
- */
- public boolean hasRuntime(String workspaceId) {
- try (@SuppressWarnings("unused") CloseableLock lock = locks.acquireReadLock(workspaceId)) {
- return workspaces.containsKey(workspaceId);
+ // otherwise stop will be triggered by the start task, wait for it to finish
+ try {
+ startTask.await();
+ } catch (EnvironmentStartInterruptedException ignored) {
+ // environment start successfully interrupted
+ } catch (InterruptedException x) {
+ Thread.currentThread().interrupt();
+ throw new ServerException("Interrupted while waiting for start task cancellation", x);
}
}
@@ -375,7 +406,7 @@ public Instance startMachine(String workspaceId,
NotFoundException,
EnvironmentException {
- try (@SuppressWarnings("unused") CloseableLock lock = locks.acquireReadLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = locks.readLock(workspaceId)) {
getRunningState(workspaceId);
}
@@ -388,9 +419,9 @@ public Instance startMachine(String workspaceId,
Instance instance = envEngine.startMachine(workspaceId, machineConfigCopy, agents);
launchAgents(instance, agents);
- try (@SuppressWarnings("unused") CloseableLock lock = locks.acquireWriteLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = locks.writeLock(workspaceId)) {
ensurePreDestroyIsNotExecuted();
- WorkspaceState workspaceState = workspaces.get(workspaceId);
+ RuntimeState workspaceState = states.get(workspaceId);
if (workspaceState == null || workspaceState.status != RUNNING) {
try {
envEngine.stopMachine(workspaceId, instance.getId());
@@ -423,7 +454,7 @@ public Instance startMachine(String workspaceId,
public void snapshot(String workspaceId) throws NotFoundException,
ConflictException,
ServerException {
- try (@SuppressWarnings("unused") CloseableLock l = locks.acquireWriteLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = locks.writeLock(workspaceId)) {
getRunningState(workspaceId).status = SNAPSHOTTING;
}
snapshotAndUpdateStatus(workspaceId);
@@ -437,7 +468,7 @@ public void snapshot(String workspaceId) throws NotFoundException,
* @see #snapshot(String)
*/
public Future snapshotAsync(String workspaceId) throws NotFoundException, ConflictException {
- try (@SuppressWarnings("unused") CloseableLock l = locks.acquireWriteLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = locks.writeLock(workspaceId)) {
getRunningState(workspaceId).status = SNAPSHOTTING;
}
return sharedPool.submit(() -> {
@@ -510,7 +541,7 @@ public void removeBinaries(Collection extends SnapshotImpl> snapshots) {
public void stopMachine(String workspaceId, String machineId) throws NotFoundException,
ServerException,
ConflictException {
- try (@SuppressWarnings("unused") CloseableLock lock = locks.acquireReadLock(workspaceId)) {
+ try (@SuppressWarnings("unused") Unlocker u = locks.readLock(workspaceId)) {
getRunningState(workspaceId);
}
envEngine.stopMachine(workspaceId, machineId);
@@ -532,42 +563,19 @@ public Instance getMachine(String workspaceId, String machineId) throws NotFound
}
/**
- * Returns all workspaces with statuses of its active environment.
- */
- public Map getWorkspaces() {
- return new HashMap<>(workspaces);
- }
-
- /**
- * Return status of the workspace.
+ * Gets the workspaces identifiers managed by this component.
+ * If an identifier is present in set then that workspace wasn't
+ * stopped at the moment of method execution.
*
- * @param workspaceId
- * ID of requested workspace
- * @return workspace status
+ * @return workspaces identifiers for those workspaces that are running(not stopped),
+ * or an empty set if there is no a single running workspace
*/
- public WorkspaceStatus getStatus(String workspaceId) {
- try (@SuppressWarnings("unused") CloseableLock l = locks.acquireReadLock(workspaceId)) {
- final WorkspaceState state = workspaces.get(workspaceId);
- if (state == null) {
- return WorkspaceStatus.STOPPED;
- }
- return state.status;
- }
- }
-
- private MessageConsumer getEnvironmentLogger(String workspaceId) throws ServerException {
- WebsocketMessageConsumer envMessageConsumer =
- new WebsocketMessageConsumer<>(format(ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE, workspaceId));
- return new AbstractMessageConsumer() {
- @Override
- public void consume(MachineLogMessage message) throws IOException {
- envMessageConsumer.consume(message);
- }
- };
+ public Set getRuntimesIds() {
+ return new HashSet<>(states.keySet());
}
/**
- * Removes all workspaces from the in-memory storage, while
+ * Removes all states from the in-memory storage, while
* {@link CheEnvironmentEngine} is responsible for environment destroying.
*/
@PreDestroy
@@ -579,13 +587,13 @@ void cleanup() {
sharedPool.terminateAndWait();
List idsToStop;
- try (@SuppressWarnings("unused") CloseableLock l = locks.acquireWriteAllLock()) {
- idsToStop = workspaces.entrySet()
- .stream()
- .filter(e -> e.getValue().status != STOPPING)
- .map(Map.Entry::getKey)
- .collect(Collectors.toList());
- workspaces.clear();
+ try (@SuppressWarnings("unused") Unlocker u = locks.writeAllLock()) {
+ idsToStop = states.entrySet()
+ .stream()
+ .filter(e -> e.getValue().status != STOPPING)
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toList());
+ states.clear();
}
// nothing to stop
@@ -593,7 +601,7 @@ void cleanup() {
return;
}
- LOG.info("Shutdown running workspaces, workspaces to shutdown '{}'", idsToStop.size());
+ LOG.info("Shutdown running states, states to shutdown '{}'", idsToStop.size());
ExecutorService executor =
Executors.newFixedThreadPool(2 * Runtime.getRuntime().availableProcessors(),
new ThreadFactoryBuilder().setNameFormat("StopEnvironmentsPool-%d")
@@ -603,6 +611,8 @@ void cleanup() {
executor.execute(() -> {
try {
envEngine.stop(id);
+ } catch (EnvironmentNotRunningException ignored) {
+ // could be stopped during workspace pool shutdown
} catch (Exception x) {
LOG.error(x.getMessage(), x);
}
@@ -623,21 +633,26 @@ void cleanup() {
}
}
- private void ensurePreDestroyIsNotExecuted() throws ServerException {
+ private void ensurePreDestroyIsNotExecuted() {
if (isPreDestroyInvoked) {
- throw new ServerException("Could not perform operation because application server is stopping");
+ throw new IllegalStateException("Could not perform operation because application server is stopping");
}
}
- private WorkspaceState getRunningState(String workspaceId) throws NotFoundException, ConflictException {
- final WorkspaceState state = workspaces.get(workspaceId);
+ private RuntimeState getExistingState(String workspaceId) throws NotFoundException {
+ RuntimeState state = states.get(workspaceId);
if (state == null) {
throw new NotFoundException("Workspace with id '" + workspaceId + "' is not running");
}
- if (state.getStatus() != RUNNING) {
+ return state;
+ }
+
+ private RuntimeState getRunningState(String workspaceId) throws NotFoundException, ConflictException {
+ RuntimeState state = getExistingState(workspaceId);
+ if (state.status != RUNNING) {
throw new ConflictException(format("Workspace with id '%s' is not 'RUNNING', it's status is '%s'",
workspaceId,
- state.getStatus()));
+ state.status));
}
return state;
}
@@ -645,11 +660,12 @@ private WorkspaceState getRunningState(String workspaceId) throws NotFoundExcept
protected void launchAgents(Instance instance, List agents) throws ServerException {
try {
for (AgentKey agentKey : agentSorter.sort(agents)) {
- LOG.info("Launching '{}' agent at workspace {}", agentKey.getId(), instance.getWorkspaceId());
-
- Agent agent = agentRegistry.getAgent(agentKey);
- AgentLauncher launcher = launcherFactory.find(agentKey.getId(), instance.getConfig().getType());
- launcher.launch(instance, agent);
+ if (!Thread.currentThread().isInterrupted()) {
+ LOG.info("Launching '{}' agent at workspace {}", agentKey.getId(), instance.getWorkspaceId());
+ Agent agent = agentRegistry.getAgent(agentKey);
+ AgentLauncher launcher = launcherFactory.find(agentKey.getId(), instance.getConfig().getType());
+ launcher.launch(instance, agent);
+ }
}
} catch (AgentException e) {
throw new MachineException(e.getMessage(), e);
@@ -657,95 +673,125 @@ protected void launchAgents(Instance instance, List agents) throws Serve
}
/**
- * Initializes workspace in {@link WorkspaceStatus#STARTING} status,
- * saves the state or throws an appropriate exception if the workspace is already initialized.
+ * Starts the environment publishing all the necessary events.
+ * Respects task interruption & stops the workspace if starting task is cancelled.
*/
- private void initState(String workspaceId, String workspaceName, String envName) throws ConflictException, ServerException {
- try (CloseableLock ignored = locks.acquireWriteLock(workspaceId)) {
+ private void startEnvironmentAndPublishEvents(EnvironmentImpl environment,
+ String workspaceId,
+ String envName,
+ boolean recover) throws ServerException,
+ EnvironmentException,
+ ConflictException {
+ try {
+ envEngine.start(workspaceId,
+ envName,
+ environment,
+ recover,
+ new WebsocketMessageConsumer<>(format(ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE, workspaceId)),
+ new MachineAgentsLauncher(environment.getMachines()));
+ } catch (EnvironmentStartInterruptedException x) {
+ // environment start was interrupted, it's either shutdown or direct stop
+ // in the case of shutdown make sure the status is correct,
+ // otherwise workspace is already stopping
+ compareAndSetStatus(workspaceId, WorkspaceStatus.STARTING, WorkspaceStatus.STOPPING);
+ removeStateAndPublishStopEvents(workspaceId);
+ throw x;
+ } catch (EnvironmentException | ServerException | ConflictException x) {
+ // environment can't be started for some reason, STARTING -> STOPPED
+ removeState(workspaceId);
+ eventsService.publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
+ .withWorkspaceId(workspaceId)
+ .withEventType(EventType.ERROR)
+ .withPrevStatus(WorkspaceStatus.STARTING)
+ .withStatus(WorkspaceStatus.STOPPED)
+ .withError("Start of environment '" + envName + "' failed. Error: " + x.getMessage()));
+ throw x;
+ }
+
+ // disallow direct start cancellation, STARTING -> RUNNING
+ WorkspaceStatus prevStatus;
+ try (@SuppressWarnings("unused") Unlocker u = locks.writeLock(workspaceId)) {
ensurePreDestroyIsNotExecuted();
- final WorkspaceState state = workspaces.get(workspaceId);
- if (state != null) {
- throw new ConflictException(format("Could not start workspace '%s' because its status is '%s'",
- workspaceName,
- state.status));
+ RuntimeState state = states.get(workspaceId);
+ prevStatus = state.status;
+ if (state.status == WorkspaceStatus.STARTING) {
+ state.status = WorkspaceStatus.RUNNING;
+ state.startTask = null;
+ state.startFuture = null;
+ }
+ }
+
+ // either current thread is interrupted right after status update,
+ // or stop is called directly, anyway stop the environment
+ if (Thread.interrupted() || prevStatus != WorkspaceStatus.STARTING) {
+ try {
+ stopEnvironmentAndPublishEvents(workspaceId, WorkspaceStatus.STARTING);
+ } catch (Exception x) {
+ LOG.error("Couldn't stop the environment '{}' of the workspace '{}'. Error: {}",
+ envName,
+ workspaceId,
+ x.getMessage());
}
- workspaces.put(workspaceId, new WorkspaceState(WorkspaceStatus.STARTING, envName));
+ throw new EnvironmentStartInterruptedException(workspaceId, envName);
}
+
+ // normally started, notify clients
+ eventsService.publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
+ .withWorkspaceId(workspaceId)
+ .withStatus(WorkspaceStatus.RUNNING)
+ .withEventType(EventType.RUNNING)
+ .withPrevStatus(WorkspaceStatus.STARTING));
}
- /** Starts the machine instances. */
- private void doStart(EnvironmentImpl environment,
- String workspaceId,
- String envName,
- boolean recover) throws ServerException {
+ /** STOPPING -> remove runtime -> STOPPED. */
+ private void removeStateAndPublishStopEvents(String workspaceId) {
eventsService.publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
.withWorkspaceId(workspaceId)
- .withStatus(WorkspaceStatus.STARTING)
- .withEventType(EventType.STARTING)
- .withPrevStatus(WorkspaceStatus.STOPPED));
+ .withPrevStatus(STARTING)
+ .withStatus(WorkspaceStatus.STOPPING)
+ .withEventType(EventType.STOPPING));
+ removeState(workspaceId);
+ eventsService.publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
+ .withWorkspaceId(workspaceId)
+ .withPrevStatus(WorkspaceStatus.STOPPING)
+ .withEventType(EventType.STOPPED)
+ .withStatus(WorkspaceStatus.STOPPED));
+ }
+ /**
+ * Stops the workspace publishing all the necessary events.
+ */
+ private void stopEnvironmentAndPublishEvents(String workspaceId,
+ WorkspaceStatus prevStatus) throws ServerException,
+ EnvironmentException {
+ eventsService.publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
+ .withWorkspaceId(workspaceId)
+ .withPrevStatus(prevStatus)
+ .withStatus(WorkspaceStatus.STOPPING)
+ .withEventType(EventType.STOPPING));
+ removeState(workspaceId);
try {
- List machines = envEngine.start(workspaceId,
- envName,
- environment,
- recover,
- getEnvironmentLogger(workspaceId));
- launchAgents(environment, machines);
-
- try (@SuppressWarnings("unused") CloseableLock lock = locks.acquireWriteLock(workspaceId)) {
- ensurePreDestroyIsNotExecuted();
- WorkspaceState workspaceState = workspaces.get(workspaceId);
- workspaceState.status = WorkspaceStatus.RUNNING;
- }
-
- eventsService.publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withWorkspaceId(workspaceId)
- .withStatus(WorkspaceStatus.RUNNING)
- .withEventType(EventType.RUNNING)
- .withPrevStatus(WorkspaceStatus.STARTING));
- } catch (ApiException | EnvironmentException | RuntimeException e) {
- try {
- envEngine.stop(workspaceId);
- } catch (EnvironmentNotRunningException ignore) {
- } catch (Exception ex) {
- LOG.error(ex.getLocalizedMessage(), ex);
- }
- String environmentStartError = "Start of environment " + envName +
- " failed. Error: " + e.getLocalizedMessage();
- try (@SuppressWarnings("unused") CloseableLock lock = locks.acquireWriteLock(workspaceId)) {
- workspaces.remove(workspaceId);
- }
+ envEngine.stop(workspaceId);
+ } catch (Exception x) {
eventsService.publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
.withWorkspaceId(workspaceId)
+ .withPrevStatus(WorkspaceStatus.STOPPING)
.withEventType(EventType.ERROR)
- .withPrevStatus(WorkspaceStatus.STARTING)
- .withError(environmentStartError));
-
- throw new ServerException(environmentStartError, e);
- }
- }
-
- private void launchAgents(EnvironmentImpl environment, List machines) throws ServerException {
- for (Instance instance : machines) {
- Map envMachines = environment.getMachines();
- if (envMachines != null) {
- ExtendedMachine extendedMachine = envMachines.get(instance.getConfig().getName());
- if (extendedMachine != null) {
- List agents = extendedMachine.getAgents();
- launchAgents(instance, agents);
- }
+ .withError(x.getMessage())
+ .withStatus(WorkspaceStatus.STOPPED));
+ try {
+ throw x;
+ } catch (ServerException rethrow) {
+ throw rethrow;
+ } catch (Exception wrap) {
+ throw new ServerException(wrap.getMessage(), wrap);
}
}
- }
-
- private static EnvironmentImpl copyEnv(Workspace workspace, String envName) {
- final Environment environment = workspace.getConfig().getEnvironments().get(envName);
- if (environment == null) {
- throw new IllegalArgumentException(format("Workspace '%s' doesn't contain environment '%s'",
- workspace.getId(),
- envName));
- }
- return new EnvironmentImpl(environment);
+ eventsService.publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
+ .withWorkspaceId(workspaceId)
+ .withPrevStatus(WorkspaceStatus.STOPPING)
+ .withEventType(EventType.STOPPED)
+ .withStatus(WorkspaceStatus.STOPPED));
}
/**
@@ -754,9 +800,10 @@ private static EnvironmentImpl copyEnv(Workspace workspace, String envName) {
* Returns true if the status of workspace was updated with {@code to} value.
*/
private boolean compareAndSetStatus(String id, WorkspaceStatus from, WorkspaceStatus to) {
- try (@SuppressWarnings("unused") CloseableLock l = locks.acquireWriteLock(id)) {
- WorkspaceState state = workspaces.get(id);
- if (state != null && state.getStatus() == from) {
+ try (@SuppressWarnings("unused") Unlocker u = locks.writeLock(id)) {
+ ensurePreDestroyIsNotExecuted();
+ RuntimeState state = states.get(id);
+ if (state != null && state.status == from) {
state.status = to;
return true;
}
@@ -764,7 +811,15 @@ private boolean compareAndSetStatus(String id, WorkspaceStatus from, WorkspaceSt
return false;
}
- /** Creates a snapshot and changes status SNAPSHOTTING -> RUNNING . */
+ /** Removes state from in-memory storage in write lock. */
+ private void removeState(String workspaceId) {
+ try (@SuppressWarnings("unused") Unlocker u = locks.writeLock(workspaceId)) {
+ ensurePreDestroyIsNotExecuted();
+ states.remove(workspaceId);
+ }
+ }
+
+ /** Creates a snapshot and changes status SNAPSHOTTING -> RUNNING. */
private void snapshotAndUpdateStatus(String workspaceId) throws NotFoundException,
ConflictException,
ServerException {
@@ -774,7 +829,7 @@ private void snapshotAndUpdateStatus(String workspaceId) throws NotFoundExceptio
.withEventType(EventType.SNAPSHOT_CREATING)
.withPrevStatus(WorkspaceStatus.RUNNING));
- WorkspaceRuntimeImpl runtime = get(workspaceId).getRuntime();
+ WorkspaceRuntimeImpl runtime = getRuntime(workspaceId);
List machines = runtime.getMachines();
machines.sort(comparing(m -> !m.getConfig().isDev(), Boolean::compare));
@@ -831,97 +886,155 @@ private void snapshotAndUpdateStatus(String workspaceId) throws NotFoundExceptio
.withPrevStatus(WorkspaceStatus.SNAPSHOTTING));
}
- public static class WorkspaceState {
- private WorkspaceStatus status;
- private String activeEnv;
-
- public WorkspaceState(WorkspaceStatus status, String activeEnv) {
- this.status = status;
- this.activeEnv = activeEnv;
+ /** Holds runtime information while workspace is running. */
+ @VisibleForTesting
+ static class RuntimeState {
+
+ WorkspaceStatus status;
+ String envName;
+ StartTask startTask;
+ Future startFuture;
+
+ RuntimeState(RuntimeState state) {
+ this.status = state.status;
+ this.envName = state.envName;
+ this.startFuture = state.startFuture;
+ this.startTask = state.startTask;
}
- public String getActiveEnv() {
- return activeEnv;
+ RuntimeState(WorkspaceStatus status,
+ String envName,
+ StartTask startTask,
+ Future startFuture) {
+ this.status = status;
+ this.envName = envName;
+ this.startTask = startTask;
+ this.startFuture = startFuture;
}
+ }
- public WorkspaceStatus getStatus() {
- return status;
+ @VisibleForTesting
+ class StartTask implements Callable {
+
+ final String workspaceId;
+ final String envName;
+ final EnvironmentImpl environment;
+ final boolean recover;
+ final CompletableFuture cmpFuture;
+ final AtomicBoolean used;
+ final CountDownLatch allowStartLatch;
+ final CountDownLatch completionLatch;
+
+ volatile Exception exception;
+
+ StartTask(String workspaceId,
+ String envName,
+ EnvironmentImpl environment,
+ boolean recover,
+ CompletableFuture cmpFuture) {
+ this.workspaceId = workspaceId;
+ this.envName = envName;
+ this.environment = environment;
+ this.recover = recover;
+ this.cmpFuture = cmpFuture;
+ this.used = new AtomicBoolean(false);
+ this.completionLatch = new CountDownLatch(1);
+ this.allowStartLatch = new CountDownLatch(1);
}
@Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof WorkspaceState)) return false;
- WorkspaceState that = (WorkspaceState)o;
- return status == that.status &&
- Objects.equals(activeEnv, that.activeEnv);
+ public WorkspaceRuntimeImpl call() throws Exception {
+ if (!markAsUsed()) {
+ throw new CancellationException(format("Start of the workspace '%s' was cancelled", workspaceId));
+ }
+ allowStartLatch.await();
+ try {
+ startEnvironmentAndPublishEvents(environment, workspaceId, envName, recover);
+ WorkspaceRuntimeImpl runtime = getRuntime(workspaceId);
+ cmpFuture.complete(runtime);
+ return runtime;
+ } catch (IllegalStateException illegalStateEx) {
+ if (isPreDestroyInvoked) {
+ exception = new EnvironmentStartInterruptedException(workspaceId, envName);
+ } else {
+ exception = new ServerException(illegalStateEx.getMessage(), illegalStateEx);
+ }
+ cmpFuture.completeExceptionally(exception);
+ throw exception;
+ } catch (Exception occurred) {
+ cmpFuture.completeExceptionally(exception = occurred);
+ throw occurred;
+ } finally {
+ completionLatch.countDown();
+ }
}
- @Override
- public int hashCode() {
- return Objects.hash(status, activeEnv);
- }
- }
-
- /**
- * Wrapper for the {@link WorkspaceRuntime} instance.
- * Knows the state of the started workspace runtime,
- * helps to postpone {@code WorkspaceRuntime} instance creation to
- * the time when all the machines from the workspace are created.
- */
- public static class RuntimeDescriptor {
-
- private WorkspaceRuntimeImpl runtime;
- private WorkspaceStatus status;
-
- public RuntimeDescriptor(WorkspaceStatus workspaceStatus,
- WorkspaceRuntimeImpl runtime) {
- this.status = workspaceStatus;
- this.runtime = runtime;
+ /**
+ * Awaits this task to complete, rethrows exceptions occurred during the invocation.
+ */
+ void await() throws InterruptedException,
+ ServerException,
+ ConflictException,
+ EnvironmentException {
+ completionLatch.await();
+ if (exception != null) {
+ try {
+ throw exception;
+ } catch (ServerException | EnvironmentException | ConflictException rethrow) {
+ throw rethrow;
+ } catch (Exception x) {
+ throw new ServerException(x.getMessage(), x);
+ }
+ }
}
- /** Returns the instance of {@code WorkspaceRuntime} described by this descriptor. */
- public WorkspaceRuntimeImpl getRuntime() {
- return runtime;
+ /**
+ * Completes corresponding completable future exceptionally
+ * with {@link EnvironmentStartInterruptedException}.
+ */
+ void earlyComplete() {
+ exception = new EnvironmentStartInterruptedException(workspaceId, envName);
+ cmpFuture.completeExceptionally(exception);
+ completionLatch.countDown();
}
- public void setRuntime(WorkspaceRuntimeImpl runtime) {
- this.runtime = runtime;
+ /**
+ * Marks this task as used, returns true only if it was unused before.
+ */
+ boolean markAsUsed() {
+ return used.compareAndSet(false, true);
}
/**
- * Returns the status of the {@code WorkspaceRuntime} described by this descriptor.
- * Never returns {@link WorkspaceStatus#STOPPED} status, you'll rather get {@link NotFoundException}
- * from {@link #get(String)} method.
+ * Allows start of this task.
+ * The task caller will wait until this method is called.
*/
- public WorkspaceStatus getRuntimeStatus() {
- return status;
+ void unlockStart() {
+ allowStartLatch.countDown();
}
+ }
- private void setRuntimeStatus(WorkspaceStatus status) {
- this.status = status;
- }
+ private class MachineAgentsLauncher implements MachineStartedHandler {
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof RuntimeDescriptor)) return false;
- RuntimeDescriptor that = (RuntimeDescriptor)o;
- return Objects.equals(runtime, that.runtime) &&
- status == that.status;
+ private final Map nameToMachine;
+
+ private MachineAgentsLauncher(Map nameToMachine) {
+ this.nameToMachine = nameToMachine;
}
@Override
- public int hashCode() {
- return Objects.hash(runtime, status);
+ public void started(Instance machine) throws ServerException {
+ launchAgents(machine, nameToMachine.get(machine.getConfig().getName()).getAgents());
}
+ }
- @Override
- public String toString() {
- return "RuntimeDescriptor{" +
- "runtime=" + runtime +
- ", status=" + status +
- '}';
+ private static EnvironmentImpl copyEnv(Workspace workspace, String envName) {
+ Environment environment = workspace.getConfig().getEnvironments().get(envName);
+ if (environment == null) {
+ throw new IllegalArgumentException(format("Workspace '%s' doesn't contain environment '%s'",
+ workspace.getId(),
+ envName));
}
+ return new EnvironmentImpl(environment);
}
}
diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceSharedPool.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceSharedPool.java
index 0254978578d..fecee50a592 100644
--- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceSharedPool.java
+++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceSharedPool.java
@@ -10,19 +10,25 @@
*******************************************************************************/
package org.eclipse.che.api.workspace.server;
+import com.google.common.base.MoreObjects;
+import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.inject.Inject;
+import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler;
import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
+import javax.inject.Named;
import javax.inject.Singleton;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
@@ -35,12 +41,35 @@ public class WorkspaceSharedPool {
private final ExecutorService executor;
- public WorkspaceSharedPool() {
- executor = Executors.newFixedThreadPool(2 * Runtime.getRuntime().availableProcessors(),
- new ThreadFactoryBuilder().setNameFormat("WorkspaceSharedPool-%d")
- .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance())
- .setDaemon(false)
- .build());
+ @Inject
+ public WorkspaceSharedPool(@Named("che.workspace.pool.type") String poolType,
+ @Named("che.workspace.pool.exact_size") @Nullable String exactSizeProp,
+ @Named("che.workspace.pool.cores_multiplier") @Nullable String coresMultiplierProp) {
+ ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("WorkspaceSharedPool-%d")
+ .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance())
+ .setDaemon(false)
+ .build();
+ switch (poolType.toLowerCase()) {
+ case "cached":
+ executor = Executors.newCachedThreadPool(factory);
+ break;
+ case "fixed":
+ Integer exactSize = exactSizeProp == null ? null : Ints.tryParse(exactSizeProp);
+ int size;
+ if (exactSize != null && exactSize > 0) {
+ size = exactSize;
+ } else {
+ size = Runtime.getRuntime().availableProcessors();
+ Integer coresMultiplier = coresMultiplierProp == null ? null : Ints.tryParse(coresMultiplierProp);
+ if (coresMultiplier != null && coresMultiplier > 0) {
+ size *= coresMultiplier;
+ }
+ }
+ executor = Executors.newFixedThreadPool(size, factory);
+ break;
+ default:
+ throw new IllegalArgumentException("The type of the pool '" + poolType + "' is not supported");
+ }
}
/** Returns an {@link ExecutorService} managed by this pool instance. */
@@ -66,7 +95,11 @@ public Future submit(Callable callable) {
/**
* Terminates this pool, may be called multiple times,
- * waits until pool is terminated or timeout reached.
+ * waits until pool is terminated or timeout is reached.
+ *
+ * Note that the method is not designed to be used from
+ * different threads, but the other components may use it in their
+ * post construct methods to ensure that all the tasks finished their execution.
*
* @return true if executor successfully terminated and false if not
* terminated(either await termination timeout is reached or thread was interrupted)
diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceRuntimeImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceRuntimeImpl.java
index 649fe2412f6..1f45f8d719c 100644
--- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceRuntimeImpl.java
+++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceRuntimeImpl.java
@@ -34,8 +34,22 @@ public class WorkspaceRuntimeImpl implements WorkspaceRuntime {
private MachineImpl devMachine;
private List machines;
- public WorkspaceRuntimeImpl(String activeEnv) {
+ public WorkspaceRuntimeImpl(String activeEnv, Collection extends Machine> machines) {
this.activeEnv = activeEnv;
+ if (machines != null) {
+ this.machines = new ArrayList<>(machines.size());
+ for (Machine machine : machines) {
+ if (machine.getConfig().isDev()) {
+ if (machine.getRuntime() != null) {
+ rootFolder = machine.getRuntime().projectsRoot();
+ }
+ devMachine = new MachineImpl(machine);
+ this.machines.add(devMachine);
+ } else {
+ this.machines.add(new MachineImpl(machine));
+ }
+ }
+ }
}
public WorkspaceRuntimeImpl(String activeEnv,
@@ -47,9 +61,11 @@ public WorkspaceRuntimeImpl(String activeEnv,
if (devMachine != null) {
this.devMachine = new MachineImpl(devMachine);
}
- this.machines = machines.stream()
- .map(MachineImpl::new)
- .collect(toList());
+ if (machines != null) {
+ this.machines = machines.stream()
+ .map(MachineImpl::new)
+ .collect(toList());
+ }
}
public WorkspaceRuntimeImpl(WorkspaceRuntime runtime) {
diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/CheEnvironmentEngineTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/CheEnvironmentEngineTest.java
index bcfaf4317f4..9c1b4600922 100644
--- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/CheEnvironmentEngineTest.java
+++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/CheEnvironmentEngineTest.java
@@ -15,6 +15,7 @@
import org.eclipse.che.api.agent.shared.model.AgentKey;
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.Machine;
import org.eclipse.che.api.core.model.machine.MachineLogMessage;
import org.eclipse.che.api.core.model.machine.MachineStatus;
@@ -24,6 +25,7 @@
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.environment.server.exception.EnvironmentStartInterruptedException;
import org.eclipse.che.api.environment.server.model.CheServiceBuildContextImpl;
import org.eclipse.che.api.environment.server.model.CheServiceImpl;
import org.eclipse.che.api.environment.server.model.CheServicesEnvironmentImpl;
@@ -65,6 +67,7 @@
import java.util.Optional;
import java.util.UUID;
+import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
@@ -88,6 +91,7 @@
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
/**
* @author Alexander Garagatyi
@@ -105,23 +109,25 @@ public class CheEnvironmentEngineTest {
@Mock
private MachineInstanceProvider machineProvider;
@Mock
- private MachineInstanceProviders machineInstanceProviders;
+ private MachineInstanceProviders machineInstanceProviders;
@Mock
- private EventService eventService;
+ private EventService eventService;
@Mock
- private SnapshotDao snapshotDao;
+ private SnapshotDao snapshotDao;
@Mock
- private RecipeDownloader recipeDownloader;
+ private RecipeDownloader recipeDownloader;
@Mock
- InfrastructureProvisioner infrastructureProvisioner;
+ InfrastructureProvisioner infrastructureProvisioner;
@Mock
- private ContainerNameGenerator containerNameGenerator;
+ private ContainerNameGenerator containerNameGenerator;
@Mock
- private AgentRegistry agentRegistry;
+ private AgentRegistry agentRegistry;
@Mock
- private Agent agent;
+ private Agent agent;
@Mock
- private EnvironmentParser environmentParser;
+ private EnvironmentParser environmentParser;
+ @Mock
+ private MachineStartedHandler startedHandler;
private CheEnvironmentEngine engine;
@@ -241,10 +247,76 @@ public void shouldBeAbleToStartEnvironment() throws Exception {
envName,
env,
false,
- messageConsumer);
+ messageConsumer,
+ startedHandler);
// then
assertEquals(machines, expectedMachines);
+ for (Instance expectedMachine : expectedMachines) {
+ verify(startedHandler).started(expectedMachine);
+ }
+ }
+
+ @Test
+ public void stopsTheEnvironmentWhileStartOfMachineIsInterrupted() throws Exception {
+ // given
+ EnvironmentImpl env = createEnv();
+ String envName = "env-1";
+ String workspaceId = "wsId";
+
+ int[] counter = new int[] {env.getMachines().size()};
+ ArrayList created = new ArrayList<>();
+ when(machineProvider.startService(anyString(),
+ eq(workspaceId),
+ eq(envName),
+ anyString(),
+ anyBoolean(),
+ anyString(),
+ any(CheServiceImpl.class),
+ any(LineConsumer.class)))
+ .thenAnswer(invocationOnMock -> {
+ // interrupt when the last machine from environment is started
+ if (--counter[0] == 0) {
+ Thread.currentThread().interrupt();
+ throw new ServerException("interrupted!");
+ }
+ Object[] arguments = invocationOnMock.getArguments();
+ NoOpMachineInstance instance = spy(new NoOpMachineInstance(createMachine(workspaceId,
+ envName,
+ (CheServiceImpl)arguments[6],
+ (String)arguments[3],
+ (boolean)arguments[4])));
+ created.add(instance);
+ return instance;
+ });
+ when(environmentParser.parse(env)).thenReturn(createCheServicesEnv());
+
+ // when, then
+ try {
+ engine.start(workspaceId,
+ envName,
+ env,
+ false,
+ messageConsumer,
+ startedHandler);
+ fail("environment must not be running");
+ } catch (EnvironmentStartInterruptedException x) {
+ assertEquals(x.getMessage(), format("Start of environment '%s' in workspace '%s' is interrupted",
+ envName, workspaceId));
+ }
+
+ // environment must not be running
+ try {
+ engine.getMachines(workspaceId);
+ fail("environment must not be running");
+ } catch (EnvironmentNotRunningException x) {
+ assertEquals(x.getMessage(), format("Environment with ID '%s' is not found", workspaceId));
+ }
+
+ // all the machines expect of the last one must be destroyed
+ for (Instance instance : created) {
+ verify(instance).destroy();
+ }
}
@Test
@@ -471,10 +543,10 @@ public void shouldBeAbleToStartEnvironmentWithRecover() throws Exception {
// when
List machines = engine.start(workspaceId,
- envName,
- env,
- true,
- messageConsumer);
+ envName,
+ env,
+ true,
+ messageConsumer);
// then
assertEquals(machines, expectedMachines);
diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java
index a68d6e3d4a2..129cd4d4f28 100644
--- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java
+++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java
@@ -16,6 +16,7 @@
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.model.machine.MachineStatus;
+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;
@@ -27,7 +28,6 @@
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.SnapshotDao;
-import org.eclipse.che.api.workspace.server.WorkspaceRuntimes.RuntimeDescriptor;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentRecipeImpl;
import org.eclipse.che.api.workspace.server.model.impl.ExtendedMachineImpl;
@@ -52,23 +52,26 @@
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import static com.google.common.base.Strings.isNullOrEmpty;
-import static com.google.common.util.concurrent.Futures.immediateFuture;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.RUNNING;
import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STARTING;
import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STOPPED;
+import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STOPPING;
import static org.eclipse.che.api.workspace.server.WorkspaceManager.CREATED_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.server.WorkspaceManager.UPDATED_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.shared.Constants.AUTO_CREATE_SNAPSHOT;
import static org.eclipse.che.api.workspace.shared.Constants.AUTO_RESTORE_FROM_SNAPSHOT;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -79,7 +82,6 @@
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
/**
@@ -164,7 +166,7 @@ public void getsWorkspaceByIdWithoutRuntime() throws Exception {
@Test
public void getsWorkspaceByIdWithRuntime() throws Exception {
WorkspaceImpl workspace = createAndMockWorkspace();
- createAndMockDescriptor(workspace, STARTING);
+ mockRuntime(workspace, STARTING);
WorkspaceImpl result = workspaceManager.getWorkspace(workspace.getId());
@@ -206,7 +208,7 @@ public void shouldBeAbleToGetWorkspaceByKeyWithoutOwner() throws Exception {
}
@Test
- public void shouldBeAbleToGetWorkspacesAvailableForUserWithRuntimes() throws Exception {
+ public void shouldBeAbleToGetWorkspacesAvailableForUser() throws Exception {
// given
final WorkspaceConfig config = createConfig();
@@ -214,9 +216,8 @@ public void shouldBeAbleToGetWorkspacesAvailableForUserWithRuntimes() throws Exc
final WorkspaceImpl workspace2 = createAndMockWorkspace(config, NAMESPACE_2);
when(workspaceDao.getWorkspaces(NAMESPACE)).thenReturn(asList(workspace1, workspace2));
- final RuntimeDescriptor descriptor = createDescriptor(workspace2, RUNNING);
- when(runtimes.get(workspace2.getId())).thenReturn(descriptor);
- when(runtimes.get(workspace1.getId())).thenThrow(new NotFoundException("no runtime"));
+ mockRuntime(workspace1, STOPPED);
+ mockRuntime(workspace2, RUNNING);
// when
final List result = workspaceManager.getWorkspaces(NAMESPACE, true);
@@ -225,68 +226,19 @@ public void shouldBeAbleToGetWorkspacesAvailableForUserWithRuntimes() throws Exc
assertEquals(result.size(), 2);
final WorkspaceImpl res1 = result.get(0);
- assertEquals(res1.getStatus(), STOPPED, "Workspace status wasn't changed from STARTING to STOPPED");
- assertNull(res1.getRuntime(), "Workspace has unexpected runtime");
+ assertEquals(res1.getStatus(), STOPPED);
assertFalse(res1.isTemporary(), "Workspace must be permanent");
final WorkspaceImpl res2 = result.get(1);
assertEquals(res2.getStatus(), RUNNING, "Workspace status wasn't changed to the runtime instance status");
- assertEquals(res2.getRuntime(), descriptor.getRuntime(), "Workspace doesn't have expected runtime");
assertFalse(res2.isTemporary(), "Workspace must be permanent");
}
@Test
- public void shouldBeAbleToGetWorkspacesAvailableForUserWithoutRuntimes() throws Exception {
- // given
- final WorkspaceConfig config = createConfig();
-
- final WorkspaceImpl workspace1 = createAndMockWorkspace(config, NAMESPACE);
- final WorkspaceImpl workspace2 = createAndMockWorkspace(config, NAMESPACE_2);
-
- when(workspaceDao.getWorkspaces(NAMESPACE)).thenReturn(asList(workspace1, workspace2));
- when(runtimes.getStatus(workspace2.getId())).thenReturn(RUNNING);
- when(runtimes.getStatus(workspace1.getId())).thenReturn(STOPPED);
-
- // when
- final List result = workspaceManager.getWorkspaces(NAMESPACE, false);
-
- // then
- assertEquals(result.size(), 2);
-
- final WorkspaceImpl res1 = result.get(0);
- assertEquals(res1.getStatus(), STOPPED, "Workspace status wasn't changed from STARTING to STOPPED");
- assertNull(res1.getRuntime(), "Workspace has unexpected runtime");
- assertFalse(res1.isTemporary(), "Workspace must be permanent");
-
- final WorkspaceImpl res2 = result.get(1);
- assertEquals(res2.getStatus(), RUNNING, "Workspace status wasn't changed to the runtime instance status");
- assertNull(res1.getRuntime(), "Workspace has unexpected runtime");
- assertFalse(res2.isTemporary(), "Workspace must be permanent");
- }
-
- @Test
- public void shouldBeAbleToGetWorkspacesByNamespaceWithoutRuntimes() throws Exception {
+ public void shouldBeAbleToGetWorkspacesByNamespace() throws Exception {
// given
final WorkspaceImpl workspace = createAndMockWorkspace();
- createAndMockDescriptor(workspace, RUNNING);
-
- // when
- final List result = workspaceManager.getByNamespace(workspace.getNamespace(), false);
-
- // then
- assertEquals(result.size(), 1);
-
- final WorkspaceImpl res1 = result.get(0);
- assertEquals(res1.getStatus(), RUNNING, "Workspace status wasn't changed to the runtime instance status");
- assertNull(res1.getRuntime(), "workspace has unexpected runtime");
- assertFalse(res1.isTemporary(), "Workspace must be permanent");
- }
-
- @Test
- public void shouldBeAbleToGetWorkspacesByNamespaceWithRuntimes() throws Exception {
- // given
- final WorkspaceImpl workspace = createAndMockWorkspace();
- final RuntimeDescriptor descriptor = createAndMockDescriptor(workspace, RUNNING);
+ mockRuntime(workspace, RUNNING);
// when
final List result = workspaceManager.getByNamespace(workspace.getNamespace(), true);
@@ -296,14 +248,13 @@ public void shouldBeAbleToGetWorkspacesByNamespaceWithRuntimes() throws Exceptio
final WorkspaceImpl res1 = result.get(0);
assertEquals(res1.getStatus(), RUNNING, "Workspace status wasn't changed to the runtime instance status");
- assertEquals(res1.getRuntime(), descriptor.getRuntime(), "Workspace doesn't have expected runtime");
assertFalse(res1.isTemporary(), "Workspace must be permanent");
}
@Test
public void getWorkspaceByNameShouldReturnWorkspaceWithStatusEqualToItsRuntimeStatus() throws Exception {
final WorkspaceImpl workspace = createAndMockWorkspace();
- createAndMockDescriptor(workspace, STARTING);
+ mockRuntime(workspace, STARTING);
final WorkspaceImpl result = workspaceManager.getWorkspace(workspace.getConfig().getName(), workspace.getNamespace());
@@ -325,7 +276,7 @@ public void shouldBeAbleToUpdateWorkspace() throws Exception {
@Test
public void workspaceUpdateShouldReturnWorkspaceWithStatusEqualToItsRuntimeStatus() throws Exception {
final WorkspaceImpl workspace = createAndMockWorkspace();
- createAndMockDescriptor(workspace, STARTING);
+ mockRuntime(workspace, STARTING);
final WorkspaceImpl updated = workspaceManager.updateWorkspace(workspace.getId(), workspace);
@@ -352,6 +303,7 @@ public void shouldNotRemoveWorkspaceIfItIsNotStopped() throws Exception {
@Test
public void shouldBeAbleToStartWorkspaceById() throws Exception {
final WorkspaceImpl workspace = createAndMockWorkspace();
+ mockStart(workspace);
workspaceManager.startWorkspace(workspace.getId(),
workspace.getConfig().getDefaultEnv(),
@@ -365,6 +317,7 @@ public void shouldBeAbleToStartWorkspaceById() throws Exception {
public void shouldRecoverWorkspaceWhenRecoverParameterIsNullAndAutoRestoreAttributeIsSetAndSnapshotExists() throws Exception {
final WorkspaceImpl workspace = createAndMockWorkspace();
workspace.getAttributes().put(AUTO_RESTORE_FROM_SNAPSHOT, "true");
+ mockStart(workspace);
SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder()
.generateId()
.setEnvName("env")
@@ -392,6 +345,7 @@ public void shouldRecoverWorkspaceWhenRecoverParameterIsNullAndAutoRestoreAttrib
@Test
public void shouldRecoverWorkspaceWhenRecoverParameterIsTrueAndSnapshotExists() throws Exception {
final WorkspaceImpl workspace = createAndMockWorkspace();
+ mockStart(workspace);
SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder()
.generateId()
.setEnvName("env")
@@ -421,6 +375,7 @@ public void shouldNotRecoverWorkspaceWhenRecoverParameterIsNullAndAutoRestoreAtt
final WorkspaceImpl workspace = createAndMockWorkspace();
workspace.getAttributes().put(AUTO_RESTORE_FROM_SNAPSHOT, "true");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
+ mockStart(workspace);
workspaceManager.startWorkspace(workspace.getId(),
workspace.getConfig().getDefaultEnv(),
@@ -433,6 +388,7 @@ public void shouldNotRecoverWorkspaceWhenRecoverParameterIsNullAndAutoRestoreAtt
@Test
public void shouldNotRecoverWorkspaceWhenRecoverParameterIsTrueButSnapshotDoesNotExist() throws Exception {
final WorkspaceImpl workspace = createAndMockWorkspace();
+ mockStart(workspace);
workspaceManager.startWorkspace(workspace.getId(),
workspace.getConfig().getDefaultEnv(),
@@ -446,6 +402,7 @@ public void shouldNotRecoverWorkspaceWhenRecoverParameterIsTrueButSnapshotDoesNo
public void shouldNotRecoverWorkspaceWhenRecoverParameterIsFalseAndAutoRestoreAttributeIsSetAndSnapshotExists() throws Exception {
WorkspaceImpl workspace = createAndMockWorkspace();
workspace.getAttributes().put(AUTO_RESTORE_FROM_SNAPSHOT, "true");
+ mockStart(workspace);
workspaceManager.startWorkspace(workspace.getId(),
workspace.getConfig().getDefaultEnv(),
@@ -458,8 +415,7 @@ public void shouldNotRecoverWorkspaceWhenRecoverParameterIsFalseAndAutoRestoreAt
@Test
public void workspaceStartShouldUseDefaultEnvIfNullEnvNameProvided() throws Exception {
final WorkspaceImpl workspace = createAndMockWorkspace();
- final RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING);
- when(runtimes.startAsync(any(), anyString(), anyBoolean())).thenReturn(immediateFuture(descriptor));
+ mockStart(workspace);
workspaceManager.startWorkspace(workspace.getId(), null, null);
@@ -471,9 +427,7 @@ public void usesProvidedEnvironmentInsteadOfDefault() throws Exception {
WorkspaceConfigImpl config = createConfig();
config.getEnvironments().put("non-default-env", new EnvironmentImpl(null, null));
WorkspaceImpl workspace = createAndMockWorkspace(config, NAMESPACE);
-
- RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING);
- when(runtimes.startAsync(any(), anyString(), anyBoolean())).thenReturn(immediateFuture(descriptor));
+ mockStart(workspace);
workspaceManager.startWorkspace(workspace.getId(), "non-default-env", false);
@@ -491,8 +445,7 @@ public void startShouldThrowNotFoundExceptionWhenProvidedEnvDoesNotExist() throw
@Test
public void shouldBeAbleToStartTemporaryWorkspace() throws Exception {
- when(runtimes.start(any(), anyString(), anyBoolean())).thenReturn(mock(RuntimeDescriptor.class));
- when(runtimes.get(any())).thenThrow(new NotFoundException(""));
+ mockAnyWorkspaceStart();
workspaceManager.startWorkspace(createConfig(), NAMESPACE, true);
@@ -504,7 +457,7 @@ public void shouldBeAbleToStartTemporaryWorkspace() throws Exception {
@Test
public void shouldBeAbleToStopWorkspace() throws Exception {
WorkspaceImpl workspace = createAndMockWorkspace(createConfig(), NAMESPACE);
- createAndMockDescriptor(workspace, RUNNING);
+ mockRuntime(workspace, RUNNING);
// when
workspaceManager.stopWorkspace(workspace.getId());
@@ -522,7 +475,7 @@ public void shouldBeAbleToStopWorkspace() throws Exception {
@Test
public void createsSnapshotBeforeStoppingWorkspace() throws Exception {
WorkspaceImpl workspace = createAndMockWorkspace();
- createAndMockDescriptor(workspace, RUNNING);
+ mockRuntime(workspace, RUNNING);
workspaceManager.stopWorkspace(workspace.getId(), true);
@@ -531,11 +484,11 @@ public void createsSnapshotBeforeStoppingWorkspace() throws Exception {
}
@Test(expectedExceptions = ConflictException.class,
- expectedExceptionsMessageRegExp = "Could not stop the workspace " +
- "'.*' because its status is 'STARTING'.")
+ expectedExceptionsMessageRegExp = "Could not stop the workspace 'test-namespace:dev-workspace' because its " +
+ "status is 'STOPPING'. Workspace must be either 'STARTING' or 'RUNNING'")
public void failsToStopNotRunningWorkspace() throws Exception {
WorkspaceImpl workspace = createAndMockWorkspace();
- createAndMockDescriptor(workspace, STARTING);
+ mockRuntime(workspace, STOPPING);
workspaceManager.stopWorkspace(workspace.getId());
}
@@ -543,7 +496,7 @@ public void failsToStopNotRunningWorkspace() throws Exception {
@Test
public void shouldStopWorkspaceEventIfSnapshotCreationFailed() throws Exception {
WorkspaceImpl workspace = createAndMockWorkspace();
- createAndMockDescriptor(workspace, RUNNING);
+ mockRuntime(workspace, RUNNING);
doThrow(new ServerException("Test")).when(runtimes).snapshot(workspace.getId());
workspaceManager.stopWorkspace(workspace.getId(), true);
@@ -556,7 +509,7 @@ public void shouldStopWorkspaceEventIfSnapshotCreationFailed() throws Exception
public void shouldRemoveTemporaryWorkspaceAfterStop() throws Exception {
WorkspaceImpl workspace = createAndMockWorkspace();
workspace.setTemporary(true);
- createAndMockDescriptor(workspace, RUNNING);
+ mockRuntime(workspace, RUNNING);
workspaceManager.stopWorkspace(workspace.getId());
@@ -568,7 +521,7 @@ public void shouldRemoveTemporaryWorkspaceAfterStop() throws Exception {
public void shouldRemoveTemporaryWorkspaceAfterStartFailed() throws Exception {
WorkspaceImpl workspace = createAndMockWorkspace();
workspace.setTemporary(true);
- createAndMockDescriptor(workspace, RUNNING);
+ mockRuntime(workspace, RUNNING);
doThrow(new ServerException("")).when(runtimes).stop(workspace.getId());
workspaceManager.stopWorkspace(workspace.getId());
@@ -598,7 +551,7 @@ public void shouldBeAbleToGetSnapshots() throws Exception {
public void shouldNotCreateSnapshotIfWorkspaceIsTemporaryAndAutoCreateSnapshotActivated() throws Exception {
final WorkspaceImpl workspace = createAndMockWorkspace();
workspace.getAttributes().put(Constants.AUTO_CREATE_SNAPSHOT, "true");
- createAndMockDescriptor(workspace, RUNNING);
+ mockRuntime(workspace, RUNNING);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
@@ -618,7 +571,7 @@ public void shouldNotCreateSnapshotIfWorkspaceIsTemporaryAndAutoCreateSnapshotAc
public void shouldNotCreateSnapshotIfWorkspaceIsTemporaryAndAutoCreateSnapshotDisactivated() throws Exception {
final WorkspaceImpl workspace = createAndMockWorkspace();
workspace.getAttributes().put(Constants.AUTO_CREATE_SNAPSHOT, "false");
- createAndMockDescriptor(workspace, RUNNING);
+ mockRuntime(workspace, RUNNING);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
@@ -647,7 +600,7 @@ public void shouldCreateWorkspaceSnapshotUsingDefaultValueForAutoRestore() throw
sharedPool);
final WorkspaceImpl workspace = createAndMockWorkspace();
- createAndMockDescriptor(workspace, RUNNING);
+ mockRuntime(workspace, RUNNING);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
@@ -675,6 +628,7 @@ public void shouldStartWorkspaceFromSnapshotUsingDefaultValueForAutoRestore() th
snapshotDao,
sharedPool);
WorkspaceImpl workspace = createAndMockWorkspace();
+ mockStart(workspace);
SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder()
.generateId()
@@ -769,9 +723,9 @@ public void shouldRemoveMachinesSnapshotsEvenSomeRemovalFails() throws Exception
public void shouldBeAbleToStartMachineInRunningWs() throws Exception {
// given
WorkspaceImpl workspace = createAndMockWorkspace();
- RuntimeDescriptor descriptor = createAndMockDescriptor(workspace, RUNNING);
+ WorkspaceRuntimeImpl runtime = mockRuntime(workspace, RUNNING);
MachineConfigImpl machineConfig = createMachine(workspace.getId(),
- descriptor.getRuntime().getActiveEnv(),
+ runtime.getActiveEnv(),
false).getConfig();
// when
@@ -796,7 +750,7 @@ public void shouldThrowExceptionOnStartMachineInNonRunningWs() throws Exception
public void shouldBeAbleToCreateSnapshot() throws Exception {
// then
WorkspaceImpl workspace = createAndMockWorkspace();
- createAndMockDescriptor(workspace, RUNNING);
+ mockRuntime(workspace, RUNNING);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
@@ -814,8 +768,8 @@ public void shouldBeAbleToCreateSnapshot() throws Exception {
public void shouldBeAbleToStopMachine() throws Exception {
// given
final WorkspaceImpl workspace = createAndMockWorkspace();
- RuntimeDescriptor descriptor = createAndMockDescriptor(workspace, RUNNING);
- MachineImpl machine = descriptor.getRuntime().getMachines().get(0);
+ WorkspaceRuntimeImpl runtime = mockRuntime(workspace, RUNNING);
+ MachineImpl machine = runtime.getMachines().get(0);
// when
workspaceManager.stopMachine(workspace.getId(), machine.getId());
@@ -838,8 +792,8 @@ public void shouldNotStopMachineIfWorkspaceIsNotRunning() throws Exception {
public void shouldBeAbleToGetMachineInstanceIfWorkspaceIsRunning() throws Exception {
// given
final WorkspaceImpl workspace = createAndMockWorkspace();
- RuntimeDescriptor descriptor = createAndMockDescriptor(workspace, RUNNING);
- MachineImpl machine = descriptor.getRuntime().getMachines().get(0);
+ WorkspaceRuntimeImpl runtime = mockRuntime(workspace, RUNNING);
+ MachineImpl machine = runtime.getMachines().get(0);
// when
workspaceManager.getMachineInstance(workspace.getId(), machine.getId());
@@ -852,8 +806,8 @@ public void shouldBeAbleToGetMachineInstanceIfWorkspaceIsRunning() throws Except
public void shouldBeAbleToGetMachineInstanceIfWorkspaceIsStarting() throws Exception {
// given
final WorkspaceImpl workspace = createAndMockWorkspace();
- RuntimeDescriptor descriptor = createAndMockDescriptor(workspace, STARTING);
- MachineImpl machine = descriptor.getRuntime().getMachines().get(0);
+ WorkspaceRuntimeImpl runtime = mockRuntime(workspace, STARTING);
+ MachineImpl machine = runtime.getMachines().get(0);
// when
workspaceManager.getMachineInstance(workspace.getId(), machine.getId());
@@ -866,7 +820,7 @@ public void shouldBeAbleToGetMachineInstanceIfWorkspaceIsStarting() throws Excep
public void passedCreateSnapshotParameterIsUsedInPreferenceToAttribute() throws Exception {
final WorkspaceImpl workspace = createAndMockWorkspace();
workspace.getAttributes().put(AUTO_CREATE_SNAPSHOT, "true");
- createAndMockDescriptor(workspace, RUNNING);
+ mockRuntime(workspace, RUNNING);
workspaceManager.stopWorkspace(workspace.getId(), false);
@@ -878,7 +832,7 @@ public void passedCreateSnapshotParameterIsUsedInPreferenceToAttribute() throws
public void passedNullCreateSnapshotParameterIsIgnored() throws Exception {
final WorkspaceImpl workspace = createAndMockWorkspace();
workspace.getAttributes().put(AUTO_CREATE_SNAPSHOT, "true");
- createAndMockDescriptor(workspace, RUNNING);
+ mockRuntime(workspace, RUNNING);
workspaceManager.stopWorkspace(workspace.getId(), null);
@@ -890,7 +844,7 @@ public void passedNullCreateSnapshotParameterIsIgnored() throws Exception {
public void passedFalseCreateSnapshotParameterIsUsedInPreferenceToAttribute() throws Exception {
final WorkspaceImpl workspace = createAndMockWorkspace();
workspace.getAttributes().put(AUTO_CREATE_SNAPSHOT, "true");
- createAndMockDescriptor(workspace, RUNNING);
+ mockRuntime(workspace, RUNNING);
workspaceManager.stopWorkspace(workspace.getId(), false);
@@ -903,32 +857,20 @@ private void captureAsyncTaskAndExecuteSynchronously() {
taskCaptor.getValue().run();
}
- private RuntimeDescriptor createAndMockDescriptor(WorkspaceImpl workspace, WorkspaceStatus status)
- throws ServerException, NotFoundException, ConflictException {
- RuntimeDescriptor descriptor = createDescriptor(workspace, status);
- when(runtimes.get(workspace.getId())).thenReturn(descriptor);
+ private WorkspaceRuntimeImpl mockRuntime(WorkspaceImpl workspace, WorkspaceStatus status) {
when(runtimes.getStatus(workspace.getId())).thenReturn(status);
- return descriptor;
- }
-
- private RuntimeDescriptor createDescriptor(WorkspaceImpl workspace, WorkspaceStatus status)
- throws ServerException, NotFoundException, ConflictException {
- EnvironmentImpl environment = workspace.getConfig().getEnvironments().get(workspace.getConfig().getDefaultEnv());
- assertNotNull(environment);
-
- final WorkspaceRuntimeImpl runtime = new WorkspaceRuntimeImpl(workspace.getConfig().getDefaultEnv());
- final MachineImpl machine1 = spy(createMachine(workspace.getId(), workspace.getConfig().getDefaultEnv(), true));
- final MachineImpl machine2 = spy(createMachine(workspace.getId(), workspace.getConfig().getDefaultEnv(), false));
- final Map machines = new HashMap<>();
+ MachineImpl machine1 = spy(createMachine(workspace.getId(), workspace.getConfig().getDefaultEnv(), true));
+ MachineImpl machine2 = spy(createMachine(workspace.getId(), workspace.getConfig().getDefaultEnv(), false));
+ Map machines = new HashMap<>();
machines.put(machine1.getId(), machine1);
machines.put(machine2.getId(), machine2);
- runtime.getMachines().addAll(machines.values());
- runtime.setDevMachine(machine1);
-
- final RuntimeDescriptor descriptor = mock(RuntimeDescriptor.class);
- when(descriptor.getRuntimeStatus()).thenReturn(status);
- when(descriptor.getRuntime()).thenReturn(runtime);
- return descriptor;
+ WorkspaceRuntimeImpl runtime = new WorkspaceRuntimeImpl(workspace.getConfig().getDefaultEnv(), machines.values());
+ doAnswer(inv -> {
+ workspace.setStatus(status);
+ workspace.setRuntime(runtime);
+ return null;
+ }).when(runtimes).injectRuntime(workspace);
+ return runtime;
}
private WorkspaceImpl createAndMockWorkspace() throws NotFoundException, ServerException {
@@ -950,6 +892,16 @@ private WorkspaceImpl createAndMockWorkspace(WorkspaceConfig cfg, String namespa
return workspace;
}
+ private void mockStart(Workspace workspace) throws Exception {
+ CompletableFuture cmpFuture = CompletableFuture.completedFuture(mock(WorkspaceRuntimeImpl.class));
+ when(runtimes.startAsync(eq(workspace), anyString(), anyBoolean())).thenReturn(cmpFuture);
+ }
+
+ private void mockAnyWorkspaceStart() throws Exception {
+ CompletableFuture cmpFuture = CompletableFuture.completedFuture(mock(WorkspaceRuntimeImpl.class));
+ when(runtimes.startAsync(anyObject(), anyString(), anyBoolean())).thenReturn(cmpFuture);
+ }
+
private static WorkspaceConfigImpl createConfig() {
EnvironmentImpl environment = new EnvironmentImpl(new EnvironmentRecipeImpl("type",
"contentType",
diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimeIntegrationTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimeIntegrationTest.java
index 67465bb61d7..ae02d54d039 100644
--- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimeIntegrationTest.java
+++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimeIntegrationTest.java
@@ -15,7 +15,6 @@
import org.eclipse.che.api.agent.server.AgentRegistry;
import org.eclipse.che.api.agent.server.impl.AgentSorter;
import org.eclipse.che.api.agent.server.launcher.AgentLauncherFactory;
-import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.model.workspace.Environment;
import org.eclipse.che.api.core.notification.EventService;
@@ -33,6 +32,7 @@
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.spi.SnapshotDao;
import org.eclipse.che.api.machine.server.util.RecipeDownloader;
+import org.eclipse.che.api.workspace.server.model.impl.WorkspaceRuntimeImpl;
import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto;
import org.eclipse.che.api.workspace.shared.dto.ExtendedMachineDto;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto;
@@ -40,6 +40,8 @@
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.SubjectImpl;
import org.eclipse.che.commons.test.mockito.answer.WaitingAnswer;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.slf4j.Logger;
@@ -48,6 +50,7 @@
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
+import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -75,29 +78,31 @@ public class WorkspaceRuntimeIntegrationTest {
private static final String ENV_NAME = "default-env";
@Mock
- private EventService eventService;
+ private EventService eventService;
@Mock
- private MachineInstanceProviders machineInstanceProviders;
+ private MachineInstanceProviders machineInstanceProviders;
@Mock
- private EnvironmentParser environmentParser;
+ private EnvironmentParser environmentParser;
@Mock
- private MachineInstanceProvider instanceProvider;
+ private MachineInstanceProvider instanceProvider;
@Mock
- private InfrastructureProvisioner infrastructureProvisioner;
+ private InfrastructureProvisioner infrastructureProvisioner;
@Mock
- private RecipeDownloader recipeDownloader;
+ private RecipeDownloader recipeDownloader;
@Mock
- private ContainerNameGenerator containerNameGenerator;
+ private ContainerNameGenerator containerNameGenerator;
@Mock
- private AgentRegistry agentRegistry;
+ private AgentRegistry agentRegistry;
@Mock
- private AgentSorter agentSorter;
+ private AgentSorter agentSorter;
@Mock
- private AgentLauncherFactory launcherFactory;
+ private AgentLauncherFactory launcherFactory;
@Mock
- private WorkspaceSharedPool sharedPool;
+ private WorkspaceSharedPool sharedPool;
@Mock
- private SnapshotDao snapshotDao;
+ private SnapshotDao snapshotDao;
+ @Captor
+ private ArgumentCaptor> taskCaptor;
private ExecutorService executor;
private WorkspaceRuntimes runtimes;
@@ -141,7 +146,7 @@ public void tearDown() throws Exception {
// Check for https://github.com/codenvy/codenvy/issues/593
@Test(expectedExceptions = NotFoundException.class,
- expectedExceptionsMessageRegExp = "Workspace with id '" + WORKSPACE_ID + "' is not running.")
+ expectedExceptionsMessageRegExp = "Workspace with id '" + WORKSPACE_ID + "' is not running")
public void environmentEngineShouldDestroyAllMachinesBeforeRemovalOfEnvironmentRecord() throws Exception {
// given
EnvironmentDto environment = newDto(EnvironmentDto.class);
@@ -176,7 +181,9 @@ public void environmentEngineShouldDestroyAllMachinesBeforeRemovalOfEnvironmentR
any(LineConsumer.class)))
.thenReturn(instance);
- runtimes.start(workspace, ENV_NAME, false);
+ runtimes.startAsync(workspace, ENV_NAME, false);
+ verify(sharedPool).submit(taskCaptor.capture());
+ taskCaptor.getValue().call();
WaitingAnswer waitingAnswer = new WaitingAnswer<>();
doAnswer(waitingAnswer).when(instance).destroy();
@@ -185,7 +192,7 @@ public void environmentEngineShouldDestroyAllMachinesBeforeRemovalOfEnvironmentR
executor.execute(() -> {
try {
runtimes.stop(WORKSPACE_ID);
- } catch (ApiException e) {
+ } catch (Exception e) {
LOG.error(e.getLocalizedMessage(), e);
}
});
@@ -194,7 +201,7 @@ public void environmentEngineShouldDestroyAllMachinesBeforeRemovalOfEnvironmentR
// then
// no exception - environment and workspace are still running
- runtimes.get(WORKSPACE_ID);
+ runtimes.getRuntime(WORKSPACE_ID);
// let instance removal proceed
waitingAnswer.completeAnswer();
// verify destroying was called
@@ -203,6 +210,6 @@ public void environmentEngineShouldDestroyAllMachinesBeforeRemovalOfEnvironmentR
// wait to ensure that removal of runtime is finished
Thread.sleep(500);
// runtime is removed - now getting of it should throw an exception
- runtimes.get(WORKSPACE_ID);
+ runtimes.getRuntime(WORKSPACE_ID);
}
}
diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java
index 60b9a73bb26..9a922eb155d 100644
--- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java
+++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java
@@ -10,20 +10,28 @@
*******************************************************************************/
package org.eclipse.che.api.workspace.server;
-import org.eclipse.che.account.spi.AccountImpl;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+
import org.eclipse.che.api.agent.server.AgentRegistry;
import org.eclipse.che.api.agent.server.impl.AgentSorter;
import org.eclipse.che.api.agent.server.launcher.AgentLauncherFactory;
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.Machine;
import org.eclipse.che.api.core.model.machine.MachineConfig;
+import org.eclipse.che.api.core.model.machine.MachineStatus;
import org.eclipse.che.api.core.model.workspace.Environment;
+import org.eclipse.che.api.core.model.workspace.ExtendedMachine;
+import org.eclipse.che.api.core.model.workspace.Workspace;
import org.eclipse.che.api.core.model.workspace.WorkspaceStatus;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.environment.server.CheEnvironmentEngine;
import org.eclipse.che.api.environment.server.NoOpMachineInstance;
+import org.eclipse.che.api.environment.server.exception.EnvironmentException;
+import org.eclipse.che.api.environment.server.exception.EnvironmentNotRunningException;
+import org.eclipse.che.api.environment.server.exception.EnvironmentStartInterruptedException;
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;
@@ -33,52 +41,64 @@
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.SnapshotDao;
-import org.eclipse.che.api.workspace.server.WorkspaceRuntimes.RuntimeDescriptor;
+import org.eclipse.che.api.workspace.server.WorkspaceRuntimes.RuntimeState;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
+import org.eclipse.che.api.workspace.server.model.impl.ExtendedMachineImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceRuntimeImpl;
import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent;
import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent.EventType;
-import org.eclipse.che.commons.lang.NameGenerator;
import org.eclipse.che.dto.server.DtoFactory;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
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.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.UUID;
import java.util.concurrent.Callable;
-
-import static java.util.Arrays.asList;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static java.lang.String.format;
import static java.util.Collections.singletonList;
-import static java.util.Collections.singletonMap;
-import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.RUNNING;
-import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STOPPED;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
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.times;
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.assertNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
/**
* @author Yevhenii Voevodin
@@ -87,395 +107,514 @@
@Listeners(MockitoTestNGListener.class)
public class WorkspaceRuntimesTest {
- private static final String WORKSPACE_ID = "workspace123";
- private static final String ENV_NAME = "default-env";
-
@Mock
- private EventService eventService;
+ private EventService eventService;
@Mock
- private CheEnvironmentEngine envEngine;
+ private CheEnvironmentEngine envEngine;
@Mock
- private AgentSorter agentSorter;
+ private AgentSorter agentSorter;
@Mock
- private AgentLauncherFactory launcherFactory;
+ private AgentLauncherFactory launcherFactory;
@Mock
- private AgentRegistry agentRegistry;
+ private AgentRegistry agentRegistry;
@Mock
- private WorkspaceSharedPool sharedPool;
+ private WorkspaceSharedPool sharedPool;
@Mock
- private SnapshotDao snapshotDao;
+ private SnapshotDao snapshotDao;
+ @Mock
+ private Future runtimeFuture;
+ @Mock
+ private WorkspaceRuntimes.StartTask startTask;
@Captor
private ArgumentCaptor eventCaptor;
@Captor
- private ArgumentCaptor taskCaptor;
+ private ArgumentCaptor> taskCaptor;
@Captor
private ArgumentCaptor> snapshotsCaptor;
- private WorkspaceRuntimes runtimes;
+ private WorkspaceRuntimes runtimes;
+ private ConcurrentMap runtimeStates;
@BeforeMethod
- public void setUp(Method method) throws Exception {
- runtimes = spy(new WorkspaceRuntimes(eventService,
- envEngine,
- agentSorter,
- launcherFactory,
- agentRegistry,
- snapshotDao,
- sharedPool));
-
- List machines = asList(createMachine(true), createMachine(false));
- when(envEngine.start(anyString(),
- anyString(),
- any(Environment.class),
- anyBoolean(),
- any()))
- .thenReturn(machines);
- when(envEngine.getMachines(WORKSPACE_ID)).thenReturn(machines);
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ runtimes = new WorkspaceRuntimes(eventService,
+ envEngine,
+ agentSorter,
+ launcherFactory,
+ agentRegistry,
+ snapshotDao,
+ sharedPool,
+ runtimeStates = new ConcurrentHashMap<>());
+ }
+
+ @Test(dataProvider = "allStatuses")
+ public void getsStatus(WorkspaceStatus status) throws Exception {
+ setRuntime("workspace", status);
+
+ assertEquals(runtimes.getStatus("workspace"), status);
}
@Test(expectedExceptions = NotFoundException.class,
- expectedExceptionsMessageRegExp = "Workspace with id '.*' is not running.")
- public void shouldThrowNotFoundExceptionIfWorkspaceRuntimeDoesNotExist() throws Exception {
- runtimes.get(WORKSPACE_ID);
+ expectedExceptionsMessageRegExp = "Workspace with id 'non_running' is not running")
+ public void throwsNotFoundExceptionWhenGettingNonExistingRuntime() throws Exception {
+ runtimes.getRuntime("non_running");
}
- @Test(expectedExceptions = ServerException.class,
- expectedExceptionsMessageRegExp = "Dev machine is not found in active environment of workspace 'workspace123'")
- public void shouldThrowExceptionOnGetRuntimesIfDevMachineIsMissingInTheEnvironment() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
+ @Test
+ public void returnsStoppedStatusWhenWorkspaceIsNotRunning() throws Exception {
+ assertEquals(runtimes.getStatus("not_running"), WorkspaceStatus.STOPPED);
+ }
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
- when(envEngine.getMachines(workspace.getId()))
- .thenReturn(asList(createMachine(false), createMachine(false)));
+ @Test
+ public void getsRuntime() throws Exception {
+ setRuntime("workspace", WorkspaceStatus.RUNNING, "env-name");
+ List machines = prepareMachines("workspace", "env-name");
- // when
- runtimes.get(workspace.getId());
+ assertEquals(runtimes.getRuntime("workspace"), new WorkspaceRuntimeImpl("env-name", machines));
+ verify(envEngine).getMachines("workspace");
}
@Test
- public void shouldFetchMachinesFromEnvEngineOnGetRuntime() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
- Instance devMachine = createMachine(true);
- List machines = asList(devMachine, createMachine(false));
- when(envEngine.start(anyString(),
- anyString(),
- any(Environment.class),
- anyBoolean(),
- any()))
- .thenReturn(machines);
- when(envEngine.getMachines(WORKSPACE_ID)).thenReturn(machines);
+ public void hasRuntime() {
+ setRuntime("workspace", WorkspaceStatus.STARTING);
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
+ assertTrue(runtimes.hasRuntime("workspace"));
+ }
- // when
- RuntimeDescriptor runtimeDescriptor = runtimes.get(workspace.getId());
+ @Test
+ public void doesNotHaveRuntime() {
+ assertFalse(runtimes.hasRuntime("not_running"));
+ }
- // then
- RuntimeDescriptor expected = new RuntimeDescriptor(WorkspaceStatus.RUNNING,
- new WorkspaceRuntimeImpl(workspace.getConfig()
- .getDefaultEnv(),
- devMachine.getRuntime()
- .projectsRoot(),
- machines,
- devMachine));
- verify(envEngine, times(2)).getMachines(workspace.getId());
- assertEquals(runtimeDescriptor, expected);
- }
-
- @Test(expectedExceptions = ServerException.class,
- expectedExceptionsMessageRegExp = "Could not perform operation because application server is stopping")
- public void shouldNotStartTheWorkspaceIfPostConstructWasIsInvoked() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
- runtimes.cleanup();
+ @Test
+ public void injectsRuntime() throws Exception {
+ setRuntime("workspace", WorkspaceStatus.RUNNING, "env-name");
+ List machines = prepareMachines("workspace", "env-name");
+ WorkspaceImpl workspace = WorkspaceImpl.builder()
+ .setId("workspace")
+ .build();
- // when
- runtimes.start(createWorkspace(), workspace.getConfig().getDefaultEnv(), false);
+ runtimes.injectRuntime(workspace);
+
+ assertEquals(workspace.getStatus(), WorkspaceStatus.RUNNING);
+ assertEquals(workspace.getRuntime(), new WorkspaceRuntimeImpl("env-name", machines));
}
@Test
- public void workspaceShouldNotHaveRuntimeIfEnvStartFails() throws Exception {
- // given
- when(envEngine.start(anyString(),
- anyString(),
- any(Environment.class),
- anyBoolean(),
- any()))
- .thenThrow(new ServerException("Test env start error"));
- WorkspaceImpl workspaceMock = createWorkspace();
+ public void injectsStoppedStatusWhenWorkspaceDoesNotHaveRuntime() throws Exception {
+ WorkspaceImpl workspace = WorkspaceImpl.builder()
+ .setId("workspace")
+ .build();
- try {
- // when
- runtimes.start(workspaceMock,
- workspaceMock.getConfig().getDefaultEnv(),
- false);
- } catch (Exception ex) {
- // then
- assertFalse(runtimes.hasRuntime(workspaceMock.getId()));
- }
+ runtimes.injectRuntime(workspace);
+
+ assertEquals(workspace.getStatus(), WorkspaceStatus.STOPPED);
+ assertNull(workspace.getRuntime());
}
@Test
- public void workspaceShouldContainAllMachinesAndBeInRunningStatusAfterSuccessfulStart() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
+ public void injectsStatusAndEmptyMachinesWhenCanNotGetEnvironmentMachines() throws Exception {
+ setRuntime("workspace", WorkspaceStatus.RUNNING, "env-name");
+ setNoMachinesForWorkspace("workspace");
+ WorkspaceImpl workspace = WorkspaceImpl.builder()
+ .setId("workspace")
+ .build();
- // when
- RuntimeDescriptor runningWorkspace = runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
+ runtimes.injectRuntime(workspace);
- // then
- assertEquals(runningWorkspace.getRuntimeStatus(), RUNNING);
- assertNotNull(runningWorkspace.getRuntime().getDevMachine());
- assertEquals(runningWorkspace.getRuntime().getMachines().size(), 2);
+ assertEquals(workspace.getStatus(), WorkspaceStatus.RUNNING);
+ assertEquals(workspace.getRuntime().getActiveEnv(), "env-name");
+ assertTrue(workspace.getRuntime().getMachines().isEmpty());
+ }
+
+ @Test
+ public void startsWorkspace() throws Exception {
+ WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
+ List machines = allowEnvironmentStart(workspace, "env-name");
+ prepareMachines(workspace.getId(), machines);
+
+ CompletableFuture cmpFuture = runtimes.startAsync(workspace, "env-name", false);
+ captureAsyncTaskAndExecuteSynchronously();
+ WorkspaceRuntimeImpl runtime = cmpFuture.get();
+
+ assertEquals(runtimes.getStatus(workspace.getId()), WorkspaceStatus.RUNNING);
+ assertEquals(runtime.getActiveEnv(), "env-name");
+ assertEquals(runtime.getMachines().size(), machines.size());
+ verifyEventsSequence(event("workspace",
+ WorkspaceStatus.STOPPED,
+ WorkspaceStatus.STARTING,
+ EventType.STARTING,
+ null),
+ event("workspace",
+ WorkspaceStatus.STARTING,
+ WorkspaceStatus.RUNNING,
+ EventType.RUNNING,
+ null));
}
@Test(expectedExceptions = ConflictException.class,
- expectedExceptionsMessageRegExp = "Could not start workspace '.*' because its status is 'RUNNING'")
- public void shouldNotStartWorkspaceIfItIsAlreadyRunning() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
+ expectedExceptionsMessageRegExp = "Could not start workspace 'test-workspace' because its status is 'RUNNING'")
+ public void throwsConflictExceptionWhenWorkspaceIsRunning() throws Exception {
+ WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
+ setRuntime(workspace.getId(), WorkspaceStatus.RUNNING);
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
- // when
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
+ runtimes.startAsync(workspace, "env-name", false);
}
@Test
- public void testCleanup() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
+ public void cancelsWorkspaceStartIfEnvironmentStartIsInterrupted() throws Exception {
+ WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
+ rejectEnvironmentStart(workspace, "env-name", new EnvironmentStartInterruptedException(workspace.getId(), "env-name"));
- runtimes.cleanup();
+ CompletableFuture cmpFuture = runtimes.startAsync(workspace, "env-name", false);
- // when, then
- assertFalse(runtimes.hasRuntime(workspace.getId()));
+ captureAndVerifyRuntimeStateAfterInterruption(workspace, cmpFuture);
}
@Test
- public void shouldStopRunningWorkspace() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
+ public void failsWorkspaceStartWhenEnvironmentStartIsFailed() throws Exception {
+ WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
+ rejectEnvironmentStart(workspace, "env-name", new EnvironmentException("no no no!"));
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
- // when
- runtimes.stop(workspace.getId());
+ CompletableFuture cmpFuture = runtimes.startAsync(workspace, "env-name", false);
- // then
+ try {
+ captureAsyncTaskAndExecuteSynchronously();
+ } catch (EnvironmentException x) {
+ assertEquals(x.getMessage(), "no no no!");
+ verifyCompletionException(cmpFuture, EnvironmentException.class, "no no no!");
+ }
assertFalse(runtimes.hasRuntime(workspace.getId()));
+ verifyEventsSequence(event("workspace",
+ WorkspaceStatus.STOPPED,
+ WorkspaceStatus.STARTING,
+ EventType.STARTING,
+ null),
+ event("workspace",
+ WorkspaceStatus.STARTING,
+ WorkspaceStatus.STOPPED,
+ EventType.ERROR,
+ "Start of environment 'env-name' failed. Error: no no no!"));
}
- @Test(expectedExceptions = NotFoundException.class,
- expectedExceptionsMessageRegExp = "Workspace with id 'workspace123' is not running.")
- public void shouldThrowNotFoundExceptionWhenStoppingWorkspaceWhichDoesNotHaveRuntime() throws Exception {
- runtimes.stop(WORKSPACE_ID);
+ @Test
+ public void interruptsStartAfterEnvironmentIsStartedButRuntimeStatusIsNotRunning() throws Exception {
+ WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
+ // let's say status is changed to STOPPING by stop method,
+ // but starting thread hasn't been interrupted yet
+ allowEnvironmentStart(workspace, "env-name", () -> setRuntime("workspace", WorkspaceStatus.STOPPING));
+
+ CompletableFuture cmpFuture = runtimes.startAsync(workspace, "env-name", false);
+
+ captureAndVerifyRuntimeStateAfterInterruption(workspace, cmpFuture);
+ verifyEventsSequence(event("workspace",
+ WorkspaceStatus.STOPPED,
+ WorkspaceStatus.STARTING,
+ EventType.STARTING,
+ null),
+ event("workspace",
+ WorkspaceStatus.STARTING,
+ WorkspaceStatus.STOPPING,
+ EventType.STOPPING,
+ null),
+ event("workspace",
+ WorkspaceStatus.STOPPING,
+ WorkspaceStatus.STOPPED,
+ EventType.STOPPED,
+ null));
+ verify(envEngine).stop(workspace.getId());
}
@Test
- public void startedRuntimeShouldBeTheSameToRuntimeTakenFromGetMethod() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
+ public void interruptsStartAfterEnvironmentIsStartedButThreadIsInterrupted() throws Exception {
+ WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
+ // the status is successfully updated from STARTING -> RUNNING but after
+ // that thread is interrupted so #stop is waiting for starting thread to stop the environment
+ allowEnvironmentStart(workspace, "env-name", () -> Thread.currentThread().interrupt());
+
+ CompletableFuture cmpFuture = runtimes.startAsync(workspace, "env-name", false);
+
+ captureAndVerifyRuntimeStateAfterInterruption(workspace, cmpFuture);
+ verifyEventsSequence(event("workspace",
+ WorkspaceStatus.STOPPED,
+ WorkspaceStatus.STARTING,
+ EventType.STARTING,
+ null),
+ event("workspace",
+ WorkspaceStatus.STARTING,
+ WorkspaceStatus.STOPPING,
+ EventType.STOPPING,
+ null),
+ event("workspace",
+ WorkspaceStatus.STOPPING,
+ WorkspaceStatus.STOPPED,
+ EventType.STOPPED,
+ null));
+ verify(envEngine).stop(workspace.getId());
+ }
- // when
- RuntimeDescriptor descriptorFromStartMethod = runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
- RuntimeDescriptor descriptorFromGetMethod = runtimes.get(workspace.getId());
+ @Test
+ public void throwsStartInterruptedExceptionWhenStartIsInterruptedAndEnvironmentStopIsFailed() throws Exception {
+ WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
+ // let's say status is changed to STOPPING by stop method,
+ // but starting thread hasn't been interrupted yet
+ allowEnvironmentStart(workspace, "env-name", () -> Thread.currentThread().interrupt());
+ rejectEnvironmentStop(workspace, new ServerException("no!"));
+
+ CompletableFuture cmpFuture = runtimes.startAsync(workspace, "env-name", false);
+
+ captureAndVerifyRuntimeStateAfterInterruption(workspace, cmpFuture);
+ verifyEventsSequence(event("workspace",
+ WorkspaceStatus.STOPPED,
+ WorkspaceStatus.STARTING,
+ EventType.STARTING,
+ null),
+ event("workspace",
+ WorkspaceStatus.STARTING,
+ WorkspaceStatus.STOPPING,
+ EventType.STOPPING,
+ null),
+ event("workspace",
+ WorkspaceStatus.STOPPING,
+ WorkspaceStatus.STOPPED,
+ EventType.ERROR,
+ "no!"));
+ verify(envEngine).stop(workspace.getId());
+ }
- // then
- assertEquals(descriptorFromStartMethod,
- descriptorFromGetMethod);
+ @Test
+ public void releasesClientsWhoWaitForStartTaskResultAndTaskIsCompleted() throws Exception {
+ ExecutorService pool = Executors.newCachedThreadPool();
+ CountDownLatch releasedLatch = new CountDownLatch(5);
+ // this thread + 5 awaiting threads
+ CyclicBarrier callTaskBarrier = new CyclicBarrier(6);
+
+ WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
+ allowEnvironmentStart(workspace, "env-name");
+
+ // the action
+ runtimes.startAsync(workspace, "env-name", false);
+
+ // register waiters
+ for (int i = 0; i < 5; i++) {
+ WorkspaceRuntimes.StartTask startTask = runtimeStates.get(workspace.getId()).startTask;
+ pool.submit(() -> {
+ // wait all the task to meet this barrier
+ callTaskBarrier.await();
+
+ // wait for start task to finish
+ startTask.await();
+
+ // good, release a part
+ releasedLatch.countDown();
+ return null;
+ });
+ }
+
+ callTaskBarrier.await();
+ captureAsyncTaskAndExecuteSynchronously();
+ try {
+ assertTrue(releasedLatch.await(2, TimeUnit.SECONDS), "start task wait clients are not released");
+ } finally {
+ shutdownAndWaitPool(pool);
+ }
}
@Test
- public void startingEventShouldBePublishedBeforeStart() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
+ public void stopsRunningWorkspace() throws Exception {
+ setRuntime("workspace", WorkspaceStatus.RUNNING);
- // when
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
+ runtimes.stop("workspace");
- // then
- verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withWorkspaceId(workspace.getId())
- .withStatus(WorkspaceStatus.STARTING)
- .withEventType(EventType.STARTING)
- .withPrevStatus(WorkspaceStatus.STOPPED));
+ verify(envEngine).stop("workspace");
+ verifyEventsSequence(event("workspace",
+ WorkspaceStatus.RUNNING,
+ WorkspaceStatus.STOPPING,
+ EventType.STOPPING,
+ null),
+ event("workspace",
+ WorkspaceStatus.STOPPING,
+ WorkspaceStatus.STOPPED,
+ EventType.STOPPED,
+ null));
+ assertFalse(runtimeStates.containsKey("workspace"));
}
@Test
- public void runningEventShouldBePublishedAfterEnvStart() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
+ public void stopsTheRunningWorkspaceWhileServerExceptionOccurs() throws Exception {
+ setRuntime("workspace", WorkspaceStatus.RUNNING);
+ doThrow(new ServerException("no!")).when(envEngine).stop("workspace");
- // when
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
+ try {
+ runtimes.stop("workspace");
+ } catch (ServerException x) {
+ assertEquals(x.getMessage(), "no!");
+ }
- // then
- verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withStatus(WorkspaceStatus.RUNNING)
- .withWorkspaceId(workspace.getId())
- .withEventType(EventType.RUNNING)
- .withPrevStatus(WorkspaceStatus.STARTING));
+ verify(envEngine).stop("workspace");
+ assertFalse(runtimeStates.containsKey("workspace"));
+ verifyEventsSequence(event("workspace",
+ WorkspaceStatus.RUNNING,
+ WorkspaceStatus.STOPPING,
+ EventType.STOPPING,
+ null),
+ event("workspace",
+ WorkspaceStatus.STOPPING,
+ WorkspaceStatus.STOPPED,
+ EventType.ERROR,
+ "no!"));
}
@Test
- public void errorEventShouldBePublishedIfDevMachineFailedToStart() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
- when(envEngine.start(anyString(),
- anyString(),
- any(Environment.class),
- anyBoolean(),
- any()))
- .thenReturn(singletonList(createMachine(false)));
+ public void stopsTheRunningWorkspaceAndRethrowsTheErrorDifferentFromServerException() throws Exception {
+ setRuntime("workspace", WorkspaceStatus.RUNNING);
+ doThrow(new EnvironmentNotRunningException("no!")).when(envEngine).stop("workspace");
try {
- // when
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
-
- } catch (Exception e) {
- // then
- verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withWorkspaceId(workspace.getId())
- .withEventType(EventType.ERROR)
- .withPrevStatus(WorkspaceStatus.STARTING));
+ runtimes.stop("workspace");
+ } catch (ServerException x) {
+ assertEquals(x.getMessage(), "no!");
+ assertTrue(x.getCause() instanceof EnvironmentNotRunningException);
}
+
+ verify(envEngine).stop("workspace");
+ assertFalse(runtimeStates.containsKey("workspace"));
+ verifyEventsSequence(event("workspace",
+ WorkspaceStatus.RUNNING,
+ WorkspaceStatus.STOPPING,
+ EventType.STOPPING,
+ null),
+ event("workspace",
+ WorkspaceStatus.STOPPING,
+ WorkspaceStatus.STOPPED,
+ EventType.ERROR,
+ "no!"));
}
@Test
- public void stoppingEventShouldBePublishedBeforeStop() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
+ public void cancellationOfPendingStartTask() throws Throwable {
+ WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
+ when(sharedPool.submit(any())).thenReturn(Futures.immediateFuture(null));
- // when
+ CompletableFuture cmpFuture = runtimes.startAsync(workspace, "env-name", false);
+
+ // the real start is not being executed, fake sharedPool suppressed it
+ // so the situation is the same to the one if the task is cancelled before
+ // executor service started executing it
runtimes.stop(workspace.getId());
- // then
- verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withStatus(WorkspaceStatus.STOPPING)
- .withWorkspaceId(workspace.getId())
- .withEventType(EventType.STOPPING)
- .withPrevStatus(WorkspaceStatus.RUNNING));
+ // start awaiting clients MUST receive interruption
+ try {
+ cmpFuture.get();
+ } catch (ExecutionException x) {
+ verifyCompletionException(cmpFuture,
+ EnvironmentStartInterruptedException.class,
+ "Start of environment 'env-name' in workspace 'workspace' is interrupted");
+ }
+
+ // if there is a state when the future is being cancelled,
+ // and start task is marked as used, executor must not execute the
+ // task but throw cancellation exception instead, once start task is
+ // completed clients receive interrupted exception and cancellation doesn't bother them
+ try {
+ captureAsyncTaskAndExecuteSynchronously();
+ } catch (CancellationException cancelled) {
+ assertEquals(cancelled.getMessage(), "Start of the workspace 'workspace' was cancelled");
+ }
+
+ verifyEventsSequence(event("workspace",
+ WorkspaceStatus.STOPPED,
+ WorkspaceStatus.STARTING,
+ EventType.STARTING,
+ null),
+ event("workspace",
+ WorkspaceStatus.STARTING,
+ WorkspaceStatus.STOPPING,
+ EventType.STOPPING,
+ null),
+ event("workspace",
+ WorkspaceStatus.STOPPING,
+ WorkspaceStatus.STOPPED,
+ EventType.STOPPED,
+ null));
}
@Test
- public void stoppedEventShouldBePublishedAfterEnvStop() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
+ public void cancellationOfRunningStartTask() throws Exception {
+ setRuntime("workspace",
+ WorkspaceStatus.STARTING,
+ "env-name",
+ runtimeFuture,
+ startTask);
+ doThrow(new EnvironmentStartInterruptedException("workspace", "env-name")).when(startTask).await();
- // when
- runtimes.stop(workspace.getId());
+ runtimes.stop("workspace");
- // then
- verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withStatus(WorkspaceStatus.STOPPED)
- .withWorkspaceId(workspace.getId())
- .withEventType(EventType.STOPPED)
- .withPrevStatus(WorkspaceStatus.STOPPING));
+ verify(runtimeFuture).cancel(true);
+ verify(startTask).await();
+ }
+
+ @Test(expectedExceptions = NotFoundException.class,
+ expectedExceptionsMessageRegExp = "Workspace with id 'workspace' is not running")
+ public void throwsNotFoundExceptionWhenStoppingNotRunningWorkspace() throws Exception {
+ runtimes.stop("workspace");
+ }
+
+ @Test(expectedExceptions = ConflictException.class,
+ expectedExceptionsMessageRegExp = "Couldn't stop the workspace 'workspace' because its status is '.*'.*",
+ dataProvider = "notAllowedToStopStatuses")
+ public void doesNotStopTheWorkspaceWhenStatusIsWrong(WorkspaceStatus status) throws Exception {
+ setRuntime("workspace", status);
+
+ runtimes.stop("workspace");
}
@Test
- public void errorEventShouldBePublishedIfEnvFailedToStop() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
+ public void cleanup() throws Exception {
+ setRuntime("workspace", WorkspaceStatus.RUNNING, "env-name");
- try {
- // when
- runtimes.stop(workspace.getId());
- } catch (Exception e) {
- // then
- verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withWorkspaceId(workspace.getId())
- .withEventType(EventType.ERROR)
- .withPrevStatus(WorkspaceStatus.STOPPING)
- .withError("Test error"));
- }
+ runtimes.cleanup();
+
+ assertFalse(runtimes.hasRuntime("workspace"));
+ verify(envEngine).stop("workspace");
}
@Test
- public void shouldBeAbleToStartMachine() throws Exception {
- // when
- WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
- MachineConfigImpl config = createConfig(false);
- Instance instance = mock(Instance.class);
- when(envEngine.startMachine(anyString(), any(MachineConfig.class), any())).thenReturn(instance);
- when(instance.getConfig()).thenReturn(config);
+ public void startedRuntimeAndReturnedFromGetMethodAreTheSame() throws Exception {
+ WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
+ allowEnvironmentStart(workspace, "env-name");
+ prepareMachines(workspace.getId(), "env-name");
- // when
- Instance actual = runtimes.startMachine(workspace.getId(), config);
+ CompletableFuture cmpFuture = runtimes.startAsync(workspace, "env-name", false);
+ captureAsyncTaskAndExecuteSynchronously();
- // then
- assertEquals(actual, instance);
- verify(envEngine).startMachine(eq(workspace.getId()), eq(config), any());
+ assertEquals(cmpFuture.get(), runtimes.getRuntime(workspace.getId()));
}
@Test
- public void shouldAddTerminalAgentOnMachineStart() throws Exception {
+ public void shouldBeAbleToStartMachine() throws Exception {
// when
- WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
- MachineConfigImpl config = createConfig(false);
+ setRuntime("workspace", WorkspaceStatus.RUNNING, "env-name");
+ MachineConfig config = newMachine("workspace", "env-name", "new", false).getConfig();
Instance instance = mock(Instance.class);
when(envEngine.startMachine(anyString(), any(MachineConfig.class), any())).thenReturn(instance);
when(instance.getConfig()).thenReturn(config);
// when
- Instance actual = runtimes.startMachine(workspace.getId(), config);
+ Instance actual = runtimes.startMachine("workspace", config);
// then
assertEquals(actual, instance);
- verify(envEngine).startMachine(eq(workspace.getId()),
- eq(config),
- eq(singletonList("org.eclipse.che.terminal")));
- verify(runtimes).launchAgents(instance, singletonList("org.eclipse.che.terminal"));
+ verify(envEngine).startMachine(eq("workspace"), eq(config), any());
}
@Test(expectedExceptions = NotFoundException.class,
expectedExceptionsMessageRegExp = "Workspace with id '.*' is not running")
public void shouldNotStartMachineIfEnvironmentIsNotRunning() throws Exception {
// when
- MachineConfigImpl config = createConfig(false);
-
- // when
- runtimes.startMachine("someWsID", config);
+ runtimes.startMachine("someWsID", mock(MachineConfig.class));
// then
verify(envEngine, never()).startMachine(anyString(), any(MachineConfig.class), any());
@@ -484,16 +623,13 @@ public void shouldNotStartMachineIfEnvironmentIsNotRunning() throws Exception {
@Test
public void shouldBeAbleToStopMachine() throws Exception {
// when
- WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
+ setRuntime("workspace", WorkspaceStatus.RUNNING);
// when
- runtimes.stopMachine(workspace.getId(), "testMachineId");
+ runtimes.stopMachine("workspace", "testMachineId");
// then
- verify(envEngine).stopMachine(workspace.getId(), "testMachineId");
+ verify(envEngine).stopMachine("workspace", "testMachineId");
}
@Test(expectedExceptions = NotFoundException.class,
@@ -509,112 +645,50 @@ public void shouldNotStopMachineIfEnvironmentIsNotRunning() throws Exception {
@Test
public void shouldBeAbleToGetMachine() throws Exception {
// given
- Instance expected = createMachine(false);
- when(envEngine.getMachine(WORKSPACE_ID, expected.getId())).thenReturn(expected);
+ Instance expected = newMachine("workspace", "env-name", "existing", false);
+ when(envEngine.getMachine("workspace", expected.getId())).thenReturn(expected);
// when
- Instance actualMachine = runtimes.getMachine(WORKSPACE_ID, expected.getId());
+ Instance actualMachine = runtimes.getMachine("workspace", expected.getId());
// then
assertEquals(actualMachine, expected);
- verify(envEngine).getMachine(WORKSPACE_ID, expected.getId());
- }
-
- @Test
- public void shouldBeAbleToGetStatusOfRunningWorkspace() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
-
- // when
- WorkspaceStatus status = runtimes.getStatus(workspace.getId());
-
- // then
- assertEquals(status, RUNNING);
- }
-
-
- @Test
- public void shouldBeAbleToGetStatusOfStoppedWorkspace() throws Exception {
- // given
- WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
- runtimes.stop(workspace.getId());
-
- // when
- WorkspaceStatus status = runtimes.getStatus(workspace.getId());
-
- // then
- assertEquals(status, STOPPED);
+ verify(envEngine).getMachine("workspace", expected.getId());
}
@Test(expectedExceptions = NotFoundException.class,
expectedExceptionsMessageRegExp = "test exception")
public void shouldThrowExceptionIfGetMachineFromEnvEngineThrowsException() throws Exception {
// given
- Instance expected = createMachine(false);
- when(envEngine.getMachine(WORKSPACE_ID, expected.getId()))
+ Instance expected = newMachine("workspace", "env-name", "existing", false);
+ when(envEngine.getMachine("workspace", expected.getId()))
.thenThrow(new NotFoundException("test exception"));
// when
- runtimes.getMachine(WORKSPACE_ID, expected.getId());
-
- // then
- verify(envEngine).getMachine(WORKSPACE_ID, expected.getId());
- }
-
- @Test
- public void shouldBeAbleToGetAllWorkspacesWithExistingRuntime() throws Exception {
- // then
- Map expectedWorkspaces = new HashMap<>();
- WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace,
- workspace.getConfig().getDefaultEnv(),
- false);
- expectedWorkspaces.put(workspace.getId(),
- new WorkspaceRuntimes.WorkspaceState(RUNNING,
- workspace.getConfig().getDefaultEnv()));
- WorkspaceImpl workspace2 = spy(createWorkspace());
- when(workspace2.getId()).thenReturn("testWsId");
- when(envEngine.getMachines(workspace2.getId()))
- .thenReturn(Collections.singletonList(createMachine(true)));
- runtimes.start(workspace2,
- workspace2.getConfig().getDefaultEnv(),
- false);
- expectedWorkspaces.put(workspace2.getId(),
- new WorkspaceRuntimes.WorkspaceState(RUNNING,
- workspace2.getConfig().getDefaultEnv()));
-
- // when
- Map actualWorkspaces = runtimes.getWorkspaces();
+ runtimes.getMachine("workspace", expected.getId());
// then
- assertEquals(actualWorkspaces, expectedWorkspaces);
+ verify(envEngine).getMachine("workspace", expected.getId());
}
@Test
public void changesStatusFromRunningToSnapshotting() throws Exception {
- final WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace, workspace.getConfig().getDefaultEnv(), false);
+ setRuntime("workspace", WorkspaceStatus.RUNNING);
- runtimes.snapshotAsync(workspace.getId());
+ runtimes.snapshotAsync("workspace");
- assertEquals(runtimes.get(workspace.getId()).getRuntimeStatus(), WorkspaceStatus.SNAPSHOTTING);
+ assertEquals(runtimes.getStatus("workspace"), WorkspaceStatus.SNAPSHOTTING);
}
@Test
public void changesStatusFromSnapshottingToRunning() throws Exception {
- final WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace, workspace.getConfig().getDefaultEnv(), false);
+ WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
+ setRuntime(workspace.getId(), WorkspaceStatus.RUNNING, "env-name");
runtimes.snapshotAsync(workspace.getId());
captureAsyncTaskAndExecuteSynchronously();
- assertEquals(runtimes.get(workspace.getId()).getRuntimeStatus(), WorkspaceStatus.RUNNING);
+ assertEquals(runtimes.getStatus(workspace.getId()), WorkspaceStatus.RUNNING);
}
@Test(expectedExceptions = NotFoundException.class,
@@ -626,41 +700,40 @@ public void throwsNotFoundExceptionWhenBeginningSnapshottingForNonExistingWorksp
@Test(expectedExceptions = ConflictException.class,
expectedExceptionsMessageRegExp = "Workspace with id '.*' is not 'RUNNING', it's status is 'SNAPSHOTTING'")
public void throwsConflictExceptionWhenBeginningSnapshottingForNotRunningWorkspace() throws Exception {
- final WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace, workspace.getConfig().getDefaultEnv(), false);
+ setRuntime("workspace", WorkspaceStatus.RUNNING);
- runtimes.snapshotAsync(workspace.getId());
- runtimes.snapshotAsync(workspace.getId());
+ runtimes.snapshotAsync("workspace");
+ runtimes.snapshotAsync("workspace");
}
@Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = "can't save")
public void failsToCreateSnapshotWhenDevMachineSnapshottingFailed() throws Exception {
- final WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace, workspace.getConfig().getDefaultEnv(), false);
+ WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
+ setRuntime(workspace.getId(), WorkspaceStatus.RUNNING);
+ prepareMachines(workspace.getId(), "env-name");
when(envEngine.saveSnapshot(any(), any())).thenThrow(new ServerException("can't save"));
try {
runtimes.snapshot(workspace.getId());
} catch (Exception x) {
- verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withWorkspaceId(workspace.getId())
- .withStatus(WorkspaceStatus.SNAPSHOTTING)
- .withPrevStatus(WorkspaceStatus.RUNNING)
- .withEventType(EventType.SNAPSHOT_CREATING));
- verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withWorkspaceId(workspace.getId())
- .withError("can't save")
- .withStatus(WorkspaceStatus.RUNNING)
- .withPrevStatus(WorkspaceStatus.SNAPSHOTTING)
- .withEventType(EventType.SNAPSHOT_CREATION_ERROR));
+ verifyEventsSequence(event(workspace.getId(),
+ WorkspaceStatus.RUNNING,
+ WorkspaceStatus.SNAPSHOTTING,
+ EventType.SNAPSHOT_CREATING,
+ null),
+ event(workspace.getId(),
+ WorkspaceStatus.SNAPSHOTTING,
+ WorkspaceStatus.RUNNING,
+ EventType.SNAPSHOT_CREATION_ERROR,
+ "can't save"));
throw x;
}
}
- @Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = "test")
+ @Test
public void removesNewlyCreatedSnapshotsWhenFailedToSaveTheirsMetadata() throws Exception {
- WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace, workspace.getConfig().getDefaultEnv(), false);
+ WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
+ setRuntime(workspace.getId(), WorkspaceStatus.RUNNING, "env-name");
doThrow(new SnapshotException("test")).when(snapshotDao)
.replaceSnapshots(any(), any(), any());
SnapshotImpl snapshot = mock(SnapshotImpl.class);
@@ -668,101 +741,283 @@ public void removesNewlyCreatedSnapshotsWhenFailedToSaveTheirsMetadata() throws
try {
runtimes.snapshot(workspace.getId());
- } catch (Exception x) {
- verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withStatus(WorkspaceStatus.SNAPSHOTTING)
- .withEventType(EventType.SNAPSHOT_CREATING)
- .withPrevStatus(WorkspaceStatus.RUNNING)
- .withWorkspaceId(workspace.getId()));
- verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withStatus(WorkspaceStatus.RUNNING)
- .withEventType(EventType.SNAPSHOT_CREATION_ERROR)
- .withWorkspaceId(workspace.getId())
- .withPrevStatus(WorkspaceStatus.SNAPSHOTTING)
- .withError("test"));
- verify(snapshotDao).replaceSnapshots(any(),
- any(),
- snapshotsCaptor.capture());
- verify(envEngine, times(snapshotsCaptor.getValue().size())).removeSnapshot(snapshot);
- throw x;
+ } catch (ServerException x) {
+ assertEquals(x.getMessage(), "test");
}
+
+ verify(snapshotDao).replaceSnapshots(any(), any(), snapshotsCaptor.capture());
+ verify(envEngine, times(snapshotsCaptor.getValue().size())).removeSnapshot(snapshot);
+ verifyEventsSequence(event(workspace.getId(),
+ WorkspaceStatus.RUNNING,
+ WorkspaceStatus.SNAPSHOTTING,
+ EventType.SNAPSHOT_CREATING,
+ null),
+ event(workspace.getId(),
+ WorkspaceStatus.SNAPSHOTTING,
+ WorkspaceStatus.RUNNING,
+ EventType.SNAPSHOT_CREATION_ERROR,
+ "test"));
}
@Test
public void removesOldSnapshotsWhenNewSnapshotsMetadataSuccessfullySaved() throws Exception {
- WorkspaceImpl workspace = createWorkspace();
- runtimes.start(workspace, workspace.getConfig().getDefaultEnv(), false);
+ WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
+ setRuntime(workspace.getId(), WorkspaceStatus.RUNNING);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
doReturn((singletonList(oldSnapshot))).when(snapshotDao)
.replaceSnapshots(any(), any(), any());
runtimes.snapshot(workspace.getId());
- verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withStatus(WorkspaceStatus.SNAPSHOTTING)
- .withEventType(EventType.SNAPSHOT_CREATING)
- .withPrevStatus(WorkspaceStatus.RUNNING)
- .withWorkspaceId(workspace.getId()));
- verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
- .withStatus(WorkspaceStatus.RUNNING)
- .withEventType(EventType.SNAPSHOT_CREATED)
- .withPrevStatus(WorkspaceStatus.SNAPSHOTTING)
- .withWorkspaceId(workspace.getId()));
verify(envEngine).removeSnapshot(oldSnapshot);
+ verifyEventsSequence(event(workspace.getId(),
+ WorkspaceStatus.RUNNING,
+ WorkspaceStatus.SNAPSHOTTING,
+ EventType.SNAPSHOT_CREATING,
+ null),
+ event(workspace.getId(),
+ WorkspaceStatus.SNAPSHOTTING,
+ WorkspaceStatus.RUNNING,
+ EventType.SNAPSHOT_CREATED,
+ null));
}
- private static Instance createMachine(boolean isDev) {
- return createMachine(createConfig(isDev));
+ @Test
+ public void getsRuntimesIds() {
+ setRuntime("workspace1", WorkspaceStatus.STARTING);
+ setRuntime("workspace2", WorkspaceStatus.RUNNING);
+ setRuntime("workspace3", WorkspaceStatus.STOPPING);
+ setRuntime("workspace4", WorkspaceStatus.SNAPSHOTTING);
+
+ assertEquals(runtimes.getRuntimesIds(), Sets.newHashSet("workspace1",
+ "workspace2",
+ "workspace3",
+ "workspace4"));
}
- private static Instance createMachine(MachineConfig cfg) {
- return new TestMachineInstance(MachineImpl.builder()
- .setId(NameGenerator.generate("machine", 10))
- .setWorkspaceId(WORKSPACE_ID)
- .setEnvName(ENV_NAME)
- .setConfig(new MachineConfigImpl(cfg))
- .build());
+ private void captureAsyncTaskAndExecuteSynchronously() throws Exception {
+ verify(sharedPool).submit(taskCaptor.capture());
+ taskCaptor.getValue().call();
}
- private static MachineConfigImpl createConfig(boolean isDev) {
- return MachineConfigImpl.builder()
- .setDev(isDev)
- .setType("docker")
- .setLimits(new MachineLimitsImpl(1024))
- .setSource(new MachineSourceImpl("git").setLocation("location"))
- .setName(UUID.randomUUID().toString())
- .build();
+
+ private void captureAndVerifyRuntimeStateAfterInterruption(Workspace workspace,
+ CompletableFuture cmpFuture) throws Exception {
+ try {
+ captureAsyncTaskAndExecuteSynchronously();
+ } catch (EnvironmentStartInterruptedException x) {
+ String expectedMessage = "Start of environment 'env-name' in workspace 'workspace' is interrupted";
+ assertEquals(x.getMessage(), expectedMessage);
+ verifyCompletionException(cmpFuture, EnvironmentStartInterruptedException.class, expectedMessage);
+ }
+ assertFalse(runtimes.hasRuntime(workspace.getId()));
}
- private static WorkspaceImpl createWorkspace() {
- EnvironmentImpl environment = new EnvironmentImpl(null,
- null);
- WorkspaceConfigImpl wsConfig = WorkspaceConfigImpl.builder()
- .setName("test workspace")
- .setEnvironments(singletonMap(ENV_NAME, environment))
- .setDefaultEnv(ENV_NAME)
- .build();
- return new WorkspaceImpl(WORKSPACE_ID, new AccountImpl("accountId", "user123", "test"), wsConfig);
+ private void verifyCompletionException(Future> f, Class extends Exception> expectedEx, String expectedMessage) {
+ assertTrue(f.isDone());
+ try {
+ f.get();
+ } catch (ExecutionException execEx) {
+ if (expectedEx.isInstance(execEx.getCause())) {
+ assertEquals(execEx.getCause().getMessage(), expectedMessage);
+ } else {
+ fail(execEx.getMessage(), execEx);
+ }
+ } catch (InterruptedException interruptedEx) {
+ fail(interruptedEx.getMessage(), interruptedEx);
+ }
}
- @SuppressWarnings("unchecked")
- private void captureAsyncTaskAndExecuteSynchronously() throws Exception {
- verify(sharedPool).submit(taskCaptor.capture());
- taskCaptor.getValue().call();
+ private void verifyEventsSequence(WorkspaceStatusEvent... expected) {
+ Iterator it = captureEvents().iterator();
+ for (WorkspaceStatusEvent expEvent : expected) {
+ if (!it.hasNext()) {
+ fail(format("It is expected to receive the status changed event '%s' -> '%s' " +
+ "but there are no more events published",
+ expEvent.getPrevStatus(),
+ expEvent.getStatus()));
+ }
+ WorkspaceStatusEvent cur = it.next();
+ if (cur.getPrevStatus() != expEvent.getPrevStatus() || cur.getStatus() != expEvent.getStatus()) {
+ fail(format("Expected to receive status change '%s' -> '%s', while received '%s' -> '%s'",
+ expEvent.getPrevStatus(),
+ expEvent.getStatus(),
+ cur.getPrevStatus(),
+ cur.getStatus()));
+ }
+ assertEquals(cur, expEvent);
+ }
+ if (it.hasNext()) {
+ WorkspaceStatusEvent next = it.next();
+ fail(format("No more events expected, but received '%s' -> '%s'",
+ next.getPrevStatus(),
+ next.getStatus()));
+ }
+ }
+
+ private static WorkspaceStatusEvent event(String workspaceId,
+ WorkspaceStatus prevStatus,
+ WorkspaceStatus status,
+ EventType eventType,
+ String error) {
+ return DtoFactory.newDto(WorkspaceStatusEvent.class)
+ .withWorkspaceId(workspaceId)
+ .withStatus(status)
+ .withPrevStatus(prevStatus)
+ .withEventType(eventType)
+ .withError(error);
+ }
+
+ private List captureEvents() {
+ verify(eventService, atLeastOnce()).publish(eventCaptor.capture());
+ return eventCaptor.getAllValues();
+ }
+
+ private void setRuntime(String workspaceId, WorkspaceStatus status) {
+ runtimeStates.put(workspaceId, new RuntimeState(status, null, null, null));
+ }
+
+ private void setRuntime(String workspaceId, WorkspaceStatus status, String envName) {
+ runtimeStates.put(workspaceId, new RuntimeState(status, envName, null, null));
}
- private static class TestMachineInstance extends NoOpMachineInstance {
+ private void setRuntime(String workspaceId,
+ WorkspaceStatus status,
+ String envName,
+ Future startFuture,
+ WorkspaceRuntimes.StartTask startTask) {
+ runtimeStates.put(workspaceId, new RuntimeState(status, envName, startTask, startFuture));
+ }
- MachineRuntimeInfoImpl runtime;
+ private void setNoMachinesForWorkspace(String workspaceId) throws EnvironmentNotRunningException {
+ when(envEngine.getMachines(workspaceId)).thenThrow(new EnvironmentNotRunningException(""));
+ }
- public TestMachineInstance(Machine machine) {
- super(machine);
- runtime = mock(MachineRuntimeInfoImpl.class);
+ private List allowEnvironmentStart(Workspace workspace,
+ String envName,
+ TestAction beforeReturn) throws Exception {
+ Environment environment = workspace.getConfig().getEnvironments().get(envName);
+ ArrayList machines = new ArrayList<>(environment.getMachines().size());
+ for (Map.Entry entry : environment.getMachines().entrySet()) {
+ machines.add(newMachine(workspace.getId(),
+ envName,
+ entry.getKey(),
+ entry.getValue().getAgents().contains("org.eclipse.che.ws-agent")));
}
+ when(envEngine.start(eq(workspace.getId()),
+ eq(envName),
+ eq(workspace.getConfig().getEnvironments().get(envName)),
+ anyBoolean(),
+ any(),
+ any())).thenAnswer(invocation -> {
+ if (beforeReturn != null) {
+ beforeReturn.call();
+ }
+ return machines;
+ });
+ return machines;
+ }
+
+ private List allowEnvironmentStart(Workspace workspace, String envName) throws Exception {
+ return allowEnvironmentStart(workspace, envName, null);
+ }
- @Override
- public MachineRuntimeInfoImpl getRuntime() {
- return runtime;
+ private void rejectEnvironmentStart(Workspace workspace, String envName, Exception x) throws Exception {
+ when(envEngine.start(eq(workspace.getId()),
+ eq(envName),
+ eq(workspace.getConfig().getEnvironments().get(envName)),
+ anyBoolean(),
+ any(),
+ any())).thenThrow(x);
+ }
+
+ private void rejectEnvironmentStop(Workspace workspace, Exception x) throws Exception {
+ doThrow(x).when(envEngine).stop(workspace.getId());
+ }
+
+ private List prepareMachines(String workspaceId, String envName) throws EnvironmentNotRunningException {
+ List machines = new ArrayList<>(3);
+ machines.add(newMachine(workspaceId, envName, "machine1", true));
+ machines.add(newMachine(workspaceId, envName, "machine2", false));
+ machines.add(newMachine(workspaceId, envName, "machine3", false));
+ prepareMachines(workspaceId, machines);
+ return machines;
+ }
+
+ private void prepareMachines(String workspaceId, List machines) throws EnvironmentNotRunningException {
+ when(envEngine.getMachines(workspaceId)).thenReturn(machines);
+ }
+
+ private Instance newMachine(String workspaceId, String envName, String name, boolean isDev) {
+ MachineImpl machine = MachineImpl.builder()
+ .setConfig(MachineConfigImpl.builder()
+ .setDev(isDev)
+ .setName(name)
+ .setType("docker")
+ .setSource(new MachineSourceImpl("type"))
+ .setLimits(new MachineLimitsImpl(1024))
+ .build())
+ .setWorkspaceId(workspaceId)
+ .setEnvName(envName)
+ .setOwner("owner")
+ .setRuntime(new MachineRuntimeInfoImpl(Collections.emptyMap(),
+ Collections.emptyMap(),
+ Collections.emptyMap()))
+ .setStatus(MachineStatus.RUNNING)
+ .build();
+ return new NoOpMachineInstance(machine);
+ }
+
+ private WorkspaceImpl newWorkspace(String workspaceId, String envName) {
+ EnvironmentImpl environment = new EnvironmentImpl();
+ Map machines = environment.getMachines();
+ machines.put("dev", new ExtendedMachineImpl(Arrays.asList("org.eclipse.che.terminal",
+ "org.eclipse.che.ws-agent"),
+ Collections.emptyMap(),
+ Collections.emptyMap()));
+ machines.put("db", new ExtendedMachineImpl(singletonList("org.eclipse.che.terminal"),
+ Collections.emptyMap(),
+ Collections.emptyMap()));
+ return WorkspaceImpl.builder()
+ .setId(workspaceId)
+ .setTemporary(false)
+ .setConfig(WorkspaceConfigImpl.builder()
+ .setName("test-workspace")
+ .setDescription("this is test workspace")
+ .setDefaultEnv(envName)
+ .setEnvironments(ImmutableMap.of(envName,
+ environment))
+ .build())
+ .build();
+ }
+
+ private void shutdownAndWaitPool(ExecutorService pool) throws InterruptedException {
+ pool.shutdownNow();
+ if (!pool.awaitTermination(10, TimeUnit.SECONDS)) {
+ fail("Can't shutdown test pool");
}
}
+
+ @FunctionalInterface
+ private interface TestAction {
+ void call() throws Exception;
+ }
+
+ @DataProvider
+ private static Object[][] allStatuses() {
+ WorkspaceStatus[] values = WorkspaceStatus.values();
+ WorkspaceStatus[][] result = new WorkspaceStatus[values.length][1];
+ for (int i = 0; i < values.length; i++) {
+ result[i][0] = values[i];
+ }
+ return result;
+ }
+
+ @DataProvider
+ private static Object[][] notAllowedToStopStatuses() {
+ return new WorkspaceStatus[][] {
+ {WorkspaceStatus.STOPPING},
+ {WorkspaceStatus.SNAPSHOTTING}
+ };
+ }
}
diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java
index b0332b338d9..84f27df150a 100644
--- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java
+++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java
@@ -798,7 +798,7 @@ public void testWorkspaceLinks() throws Exception {
.get(workspace.getConfig().getDefaultEnv());
assertNotNull(environment);
- final WorkspaceRuntimeImpl runtime = new WorkspaceRuntimeImpl(workspace.getConfig().getDefaultEnv());
+ final WorkspaceRuntimeImpl runtime = new WorkspaceRuntimeImpl(workspace.getConfig().getDefaultEnv(), null);
MachineConfigImpl devMachineConfig = MachineConfigImpl.builder()
.setDev(true)
.setEnvVariables(emptyMap())
diff --git a/wsmaster/integration-tests/cascade-removal/src/test/java/org/eclipse/che/core/db/jpa/CascadeRemovalTest.java b/wsmaster/integration-tests/cascade-removal/src/test/java/org/eclipse/che/core/db/jpa/CascadeRemovalTest.java
index 0f4252640fb..f6ac7865b69 100644
--- a/wsmaster/integration-tests/cascade-removal/src/test/java/org/eclipse/che/core/db/jpa/CascadeRemovalTest.java
+++ b/wsmaster/integration-tests/cascade-removal/src/test/java/org/eclipse/che/core/db/jpa/CascadeRemovalTest.java
@@ -42,6 +42,7 @@
import org.eclipse.che.api.user.server.spi.UserDao;
import org.eclipse.che.api.workspace.server.WorkspaceManager;
import org.eclipse.che.api.workspace.server.WorkspaceRuntimes;
+import org.eclipse.che.api.workspace.server.WorkspaceSharedPool;
import org.eclipse.che.api.workspace.server.event.BeforeWorkspaceRemovedEvent;
import org.eclipse.che.api.workspace.server.jpa.JpaWorkspaceDao.RemoveSnapshotsBeforeWorkspaceRemovedEventSubscriber;
import org.eclipse.che.api.workspace.server.jpa.JpaWorkspaceDao.RemoveWorkspaceBeforeAccountRemovedEventSubscriber;
@@ -142,6 +143,7 @@ protected void configure() {
bind(AccountManager.class);
bind(Boolean.class).annotatedWith(Names.named("che.workspace.auto_snapshot")).toInstance(false);
bind(Boolean.class).annotatedWith(Names.named("che.workspace.auto_restore")).toInstance(false);
+ bind(WorkspaceSharedPool.class).toInstance(new WorkspaceSharedPool("cached", null, null));
}
});