Add snapshotting status (#2829)

6.19.x
Yevhenii Voevodin 2016-10-20 15:01:47 +03:00 committed by GitHub
parent f2661c97f4
commit e316ba7945
14 changed files with 561 additions and 510 deletions

View File

@ -46,6 +46,18 @@ public enum WorkspaceStatus {
*/
RUNNING,
/**
* Workspace is in SNAPSHOTTING status if and only if the workspace
* is currently creating snapshots of it's machines.
*
* <p>Workspace is in SNAPSHOTTING status after it was {@link #RUNNING}.
* The status map:
* <pre>
* RUNNING -> <b>SNAPSHOTTING</b> -> RUNNING (normal behaviour/error while snapshotting)
* </pre>
*/
SNAPSHOTTING,
/**
* Workspace considered as stopping if and only if its active environment is shutting down.
*

View File

@ -23,6 +23,7 @@ import javax.inject.Provider;
import javax.inject.Singleton;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import java.util.Collection;
import java.util.List;
import static java.lang.String.format;
@ -117,6 +118,20 @@ public class JpaSnapshotDao implements SnapshotDao {
}
}
@Override
public List<SnapshotImpl> replaceSnapshots(String workspaceId,
String envName,
Collection<? extends SnapshotImpl> newSnapshots) throws SnapshotException {
requireNonNull(workspaceId, "Required non-null workspace id");
requireNonNull(envName, "Required non-null environment name");
requireNonNull(newSnapshots, "Required non-null new snapshots");
try {
return doReplaceSnapshots(workspaceId, envName, newSnapshots);
} catch (RuntimeException x) {
throw new SnapshotException(x.getLocalizedMessage(), x);
}
}
@Transactional
protected void doSave(SnapshotImpl snapshot) {
managerProvider.get().persist(snapshot);
@ -131,4 +146,19 @@ public class JpaSnapshotDao implements SnapshotDao {
}
manager.remove(snapshot);
}
@Transactional
protected List<SnapshotImpl> doReplaceSnapshots(String workspaceId,
String envName,
Collection<? extends SnapshotImpl> newSnapshots) {
final EntityManager manager = managerProvider.get();
final List<SnapshotImpl> existing = manager.createNamedQuery("Snapshot.findByWorkspaceAndEnvironment", SnapshotImpl.class)
.setParameter("workspaceId", workspaceId)
.setParameter("envName", envName)
.getResultList();
existing.forEach(manager::remove);
manager.flush();
newSnapshots.forEach(manager::persist);
return existing;
}
}

View File

@ -44,7 +44,12 @@ import java.util.Objects;
@NamedQuery(name = "Snapshot.findSnapshots",
query = "SELECT snapshot " +
"FROM Snapshot snapshot " +
"WHERE snapshot.workspaceId = :workspaceId")
"WHERE snapshot.workspaceId = :workspaceId"),
@NamedQuery(name = "Snapshot.findByWorkspaceAndEnvironment",
query = "SELECT snapshot " +
"FROM Snapshot snapshot " +
"WHERE snapshot.workspaceId = :workspaceId " +
" AND snapshot.envName = :envName")
}
)
@Table(indexes = @Index(columnList = "workspaceId, envName, machineName", unique = true))

View File

@ -14,6 +14,7 @@ import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.machine.server.exception.SnapshotException;
import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl;
import java.util.Collection;
import java.util.List;
/**
@ -86,4 +87,24 @@ public interface SnapshotDao {
* if other error occur
*/
void removeSnapshot(String snapshotId) throws NotFoundException, SnapshotException;
/**
* Replaces all the existing snapshots related to the given workspace
* with a new list of snapshots.
*
* @param workspaceId
* the id of the workspace to replace snapshots
* @param envName
* the name of the environment in workspace with given id
* which is used to search those snapshots that should be replaced
* @param newSnapshots
* the list of the snapshots which will be stored instead of existing ones
* @return the list of replaced(removed/old) snapshots for given workspace and environment,
* or an empty list when there is no a single snapshot for the given workspace
* @throws SnapshotException
* when any error occurs
*/
List<SnapshotImpl> replaceSnapshots(String workspaceId,
String envName,
Collection<? extends SnapshotImpl> newSnapshots) throws SnapshotException;
}

View File

@ -10,6 +10,7 @@
*******************************************************************************/
package org.eclipse.che.api.machine.server.spi.tck;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import org.eclipse.che.api.core.NotFoundException;
@ -30,6 +31,7 @@ import java.util.HashSet;
import java.util.List;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
@ -180,6 +182,22 @@ public class SnapshotDaoTest {
snapshotDao.removeSnapshot(null);
}
@Test(dependsOnMethods = "shouldFindSnapshotsByWorkspaceAndNamespace")
public void replacesSnapshots() throws Exception {
final SnapshotImpl newSnapshot = createSnapshot("new-snapshot",
snapshots[0].getWorkspaceId(),
snapshots[0].getEnvName(),
snapshots[0].getMachineName());
final List<SnapshotImpl> replaced = snapshotDao.replaceSnapshots(newSnapshot.getWorkspaceId(),
newSnapshot.getEnvName(),
singletonList(newSnapshot));
assertEquals(new HashSet<>(replaced), Sets.newHashSet(snapshots[0], snapshots[1]));
assertEquals(new HashSet<>(snapshotDao.findSnapshots(snapshots[0].getWorkspaceId())),
Sets.newHashSet(newSnapshot, snapshots[2]));
}
@DataProvider(name = "missingSnapshots")
public Object[][] missingSnapshots() {
final SnapshotImpl snapshot = snapshots[0];

View File

@ -26,10 +26,11 @@ import org.eclipse.che.api.core.model.workspace.Workspace;
import org.eclipse.che.api.core.model.workspace.WorkspaceConfig;
import org.eclipse.che.api.core.model.workspace.WorkspaceStatus;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.machine.server.spi.SnapshotDao;
import org.eclipse.che.api.machine.server.exception.SnapshotException;
import org.eclipse.che.api.machine.server.model.impl.MachineImpl;
import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.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;
@ -43,11 +44,15 @@ import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext;
import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.dto.server.DtoFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
@ -57,7 +62,9 @@ import static com.google.common.base.MoreObjects.firstNonNull;
import static java.lang.Boolean.parseBoolean;
import static java.lang.String.format;
import static java.lang.System.currentTimeMillis;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap;
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.STOPPED;
@ -67,7 +74,6 @@ import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_STOPPED_B
import static org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent.EventType.SNAPSHOT_CREATED;
import static org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent.EventType.SNAPSHOT_CREATING;
import static org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent.EventType.SNAPSHOT_CREATION_ERROR;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
/**
* Facade for Workspace related operations.
@ -449,10 +455,32 @@ public class WorkspaceManager {
public void stopWorkspace(String workspaceId) throws ServerException,
NotFoundException,
ConflictException {
stopWorkspace(workspaceId, null);
}
/**
* Asynchronously stops the workspace,
* creates a snapshot of it if {@code createSnapshot} is set to true.
*
* @param workspaceId
* the id of the workspace to stop
* @param createSnapshot
* true if create snapshot, false if don't,
* null if default behaviour should be used
* @throws ServerException
* when any server error occurs
* @throws NullPointerException
* when {@code workspaceId} is null
* @throws NotFoundException
* when workspace {@code workspaceId} doesn't have runtime
*/
public void stopWorkspace(String workspaceId, @Nullable Boolean createSnapshot) throws ConflictException,
NotFoundException,
ServerException {
requireNonNull(workspaceId, "Required non-null workspace id");
final WorkspaceImpl workspace = normalizeState(workspaceDao.get(workspaceId));
checkWorkspaceIsRunning(workspace, "stop");
performAsyncStop(workspace);
performAsyncStop(workspace, createSnapshot);
}
/**
@ -487,9 +515,9 @@ public class WorkspaceManager {
final WorkspaceImpl workspace = normalizeState(workspaceDao.get(workspaceId));
checkWorkspaceIsRunning(workspace, "create a snapshot of");
executor.execute(ThreadLocalPropagateContext.wrap(() -> {
createSnapshotSync(workspace.getRuntime(),
workspace.getNamespace(),
workspaceId);
createSnapshotSync(workspace.getNamespace(),
workspaceId,
workspace.getRuntime().getActiveEnv());
}));
}
@ -644,15 +672,20 @@ public class WorkspaceManager {
* attribute set to true) and then stops the workspace(even if snapshot creation failed).
*/
@VisibleForTesting
void performAsyncStop(WorkspaceImpl workspace) throws ConflictException {
void performAsyncStop(WorkspaceImpl workspace, @Nullable Boolean createSnapshot) throws ConflictException {
checkWorkspaceIsRunning(workspace, "stop");
final String autoSnapshotAttr = workspace.getAttributes().get(AUTO_CREATE_SNAPSHOT);
boolean createSnapshot;
final boolean snapshotBeforeStop;
if (workspace.isTemporary()) {
createSnapshot = false;
snapshotBeforeStop = false;
} else if (createSnapshot != null) {
snapshotBeforeStop = createSnapshot;
} else if (workspace.getAttributes().containsKey(AUTO_CREATE_SNAPSHOT)) {
snapshotBeforeStop = parseBoolean(workspace.getAttributes().get(AUTO_CREATE_SNAPSHOT));
} else {
createSnapshot = autoSnapshotAttr == null ? defaultAutoSnapshot : parseBoolean(autoSnapshotAttr);
snapshotBeforeStop = defaultAutoSnapshot;
}
executor.execute(ThreadLocalPropagateContext.wrap(() -> {
final String stoppedBy = sessionUserNameOr(workspace.getAttributes().get(WORKSPACE_STOPPED_BY));
LOG.info("Workspace '{}:{}' with id '{}' is being stopped by user '{}'",
@ -660,7 +693,9 @@ public class WorkspaceManager {
workspace.getConfig().getName(),
workspace.getId(),
firstNonNull(stoppedBy, "undefined"));
if (createSnapshot && !createSnapshotSync(workspace.getRuntime(), workspace.getNamespace(), workspace.getId())) {
if (snapshotBeforeStop && !createSnapshotSync(workspace.getNamespace(),
workspace.getId(),
workspace.getRuntime().getActiveEnv())) {
LOG.warn("Could not create a snapshot of the workspace '{}:{}' with workspace id '{}'. The workspace will be stopped",
workspace.getNamespace(),
workspace.getConfig().getName(),
@ -702,81 +737,81 @@ public class WorkspaceManager {
* otherwise returns false.
*/
@VisibleForTesting
boolean createSnapshotSync(WorkspaceRuntimeImpl runtime, String namespace, String workspaceId) {
eventService.publish(newDto(WorkspaceStatusEvent.class)
.withEventType(SNAPSHOT_CREATING)
.withWorkspaceId(workspaceId));
String devMachineSnapshotFailMessage = null;
for (MachineImpl machine : runtime.getMachines()) {
String error = replaceSnapshot(machine, namespace);
if (error != null && machine.getConfig().isDev()) {
devMachineSnapshotFailMessage = error;
}
}
if (devMachineSnapshotFailMessage != null) {
eventService.publish(newDto(WorkspaceStatusEvent.class)
.withEventType(SNAPSHOT_CREATION_ERROR)
.withWorkspaceId(workspaceId)
.withError(devMachineSnapshotFailMessage));
} else {
eventService.publish(newDto(WorkspaceStatusEvent.class)
.withEventType(SNAPSHOT_CREATED)
.withWorkspaceId(workspaceId));
}
return devMachineSnapshotFailMessage == null;
}
private String replaceSnapshot(MachineImpl machine, String namespace) {
boolean createSnapshotSync(String namespace, String workspaceId, String envName) {
try {
runtimes.beginSnapshotting(workspaceId);
} catch (NotFoundException | ConflictException x) {
LOG.warn("Couldn't start snapshot creation of workspace '{}' due to error: '{}'",
workspaceId,
x.getMessage());
return false;
}
publishEvent(SNAPSHOT_CREATING, workspaceId);
final List<MachineImpl> machines;
try {
machines = runtimes.get(workspaceId).getRuntime().getMachines();
} catch (Exception x) {
throw new IllegalStateException(x.getLocalizedMessage(), x);
}
LOG.info("Creating snapshot of workspace '{}', machines to snapshot: '{}'", workspaceId, machines.size());
final List<SnapshotImpl> newSnapshots = new ArrayList<>(machines.size());
Collections.sort(machines, comparing(m -> !m.getConfig().isDev(), Boolean::compare));
for (MachineImpl machine : machines) {
try {
SnapshotImpl oldSnapshot = snapshotDao.getSnapshot(machine.getWorkspaceId(),
machine.getEnvName(),
machine.getConfig().getName());
snapshotDao.removeSnapshot(oldSnapshot.getId());
runtimes.removeSnapshot(oldSnapshot);
} catch (NotFoundException ignored) {
// Do nothing if no snapshot found
newSnapshots.add(runtimes.saveMachine(namespace, workspaceId, machine.getId()));
} catch (Exception x) {
if (machine.getConfig().isDev()) {
runtimes.finishSnapshotting(workspaceId);
publishEvent(SNAPSHOT_CREATION_ERROR, workspaceId, x.getMessage());
return false;
}
LOG.warn(format("Couldn't create snapshot of machine '%s:%s:%s' in workspace '%s'",
namespace,
machine.getEnvName(),
machine.getConfig().getName(),
workspaceId));
}
}
SnapshotImpl snapshot = null;
try {
LOG.info("Saving new snapshots metadata, workspace id '{}'", workspaceId);
final List<SnapshotImpl> removed = snapshotDao.replaceSnapshots(workspaceId, envName, newSnapshots);
if (!removed.isEmpty()) {
LOG.info("Removing old snapshots, workspace id '{}', snapshots to remove '{}'", workspaceId, removed.size());
removeSnapshotsBinaries(removed);
}
} catch (SnapshotException x) {
LOG.error(format("Couldn't remove existing snapshots metadata for workspace '%s'", workspaceId), x);
LOG.info("Removing newly created snapshots, workspace id '{}', snapshots to remove '{}'", workspaceId, newSnapshots.size());
removeSnapshotsBinaries(newSnapshots);
runtimes.finishSnapshotting(workspaceId);
publishEvent(SNAPSHOT_CREATION_ERROR, workspaceId, x.getMessage());
return false;
}
runtimes.finishSnapshotting(workspaceId);
publishEvent(SNAPSHOT_CREATED, workspaceId);
return true;
}
private void removeSnapshotsBinaries(Collection<? extends SnapshotImpl> snapshots) {
for (SnapshotImpl snapshot : snapshots) {
try {
snapshot = runtimes.saveMachine(namespace,
machine.getWorkspaceId(),
machine.getId());
// check if the workspace exists before creating a snapshot,
// if it is not an integrity constraint violation exception will occur,
// this may happen when workspace stop called simultaneously.
// The issue https://github.com/eclipse/che/issues/2683 should fix it
// in a way that it won't be possible to snapshot workspace simultaneously.
if (exists(machine.getWorkspaceId())) {
snapshotDao.saveSnapshot(snapshot);
} else {
LOG.warn("Snapshot for a workspace '{}' won't be saved, as the workspace doesn't exist anymore",
machine.getWorkspaceId());
runtimes.removeSnapshot(snapshot);
}
} catch (ApiException e) {
if (snapshot != null) {
try {
runtimes.removeSnapshot(snapshot);
} catch (ApiException e1) {
LOG.error(format("Snapshot removal failed. Snapshot: %s. Error: %s",
snapshot,
e1.getLocalizedMessage()),
e1);
}
}
throw e;
runtimes.removeSnapshot(snapshot);
} catch (ServerException | NotFoundException x) {
LOG.error(format("Couldn't remove snapshot '%s', workspace id '%s'",
snapshot.getId(),
snapshot.getWorkspaceId()),
x);
}
return null;
} catch (ApiException apiEx) {
LOG.error("Snapshot creation failed. Error: " + apiEx.getLocalizedMessage(), apiEx);
return apiEx.getLocalizedMessage();
}
}
@VisibleForTesting
void checkWorkspaceIsRunning(WorkspaceImpl workspace, String operation) throws ConflictException {
if (workspace.getStatus() != RUNNING) {
@ -808,6 +843,17 @@ public class WorkspaceManager {
}
}
private void publishEvent(EventType type, String workspaceId, String error) {
eventService.publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
.withEventType(type)
.withWorkspaceId(workspaceId)
.withError(error));
}
private void publishEvent(EventType type, String workspaceId) {
publishEvent(type, workspaceId, null);
}
private WorkspaceImpl normalizeState(WorkspaceImpl workspace, RuntimeDescriptor descriptor) {
if (descriptor != null) {
workspace.setStatus(descriptor.getRuntimeStatus());
@ -856,14 +902,4 @@ public class WorkspaceManager {
final String namespace = nsPart.isEmpty() ? sessionUser().getUserName() : nsPart;
return workspaceDao.get(wsName, namespace);
}
/** Returns true if workspace exists and false otherwise. */
private boolean exists(String workspaceId) throws ServerException {
try {
workspaceDao.get(workspaceId);
} catch (NotFoundException x) {
return false;
}
return true;
}
}

View File

@ -63,6 +63,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static java.lang.String.format;
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.machine.shared.Constants.ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import static org.slf4j.LoggerFactory.getLogger;
@ -401,10 +403,7 @@ public class WorkspaceRuntimes {
NotFoundException {
try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) {
WorkspaceState workspaceState = workspaces.get(workspaceId);
if (workspaceState == null || workspaceState.status != WorkspaceStatus.RUNNING) {
throw new ConflictException(format("Environment of workspace '%s' is not running", workspaceId));
}
getRunningState(workspaceId);
}
List<String> agents = Collections.singletonList("org.eclipse.che.terminal");
@ -413,7 +412,7 @@ public class WorkspaceRuntimes {
try (StripedLocks.WriteLock lock = stripedLocks.acquireWriteLock(workspaceId)) {
WorkspaceState workspaceState = workspaces.get(workspaceId);
if (workspaceState == null || workspaceState.status != WorkspaceStatus.RUNNING) {
if (workspaceState == null || workspaceState.status != RUNNING) {
try {
environmentEngine.stopMachine(workspaceId, instance.getId());
} catch (NotFoundException | ServerException | ConflictException e) {
@ -426,6 +425,41 @@ public class WorkspaceRuntimes {
return instance;
}
/**
* Changes workspace runtimes status from RUNNING to SNAPSHOTTING.
*
* @param workspaceId
* the id of the workspace to begin snapshotting for
* @throws NotFoundException
* when workspace with such id doesn't have runtime
* @throws ConflictException
* when workspace status is different from SNAPSHOTTING
* @see WorkspaceStatus#SNAPSHOTTING
*/
public void beginSnapshotting(String workspaceId) throws NotFoundException, ConflictException {
try (StripedLocks.WriteLock ignored = stripedLocks.acquireWriteLock(workspaceId)) {
getRunningState(workspaceId).status = SNAPSHOTTING;
}
}
/**
* Changes workspace runtimes status from SNAPSHOTTING back to RUNNING.
* This method won't affect workspace runtime or throw any exception
* if workspace is not in running status or doesn't have runtime.
*
* @param workspaceId
* the id of the workspace to finish snapshotting for
* @see WorkspaceStatus#SNAPSHOTTING
*/
public void finishSnapshotting(String workspaceId) {
try (StripedLocks.WriteLock ignored = stripedLocks.acquireWriteLock(workspaceId)) {
final WorkspaceState state = workspaces.get(workspaceId);
if (state != null && state.status == SNAPSHOTTING) {
state.status = RUNNING;
}
}
}
/**
* Stops machine in a running environment.
*
@ -447,7 +481,7 @@ public class WorkspaceRuntimes {
ConflictException {
try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) {
WorkspaceState workspaceState = workspaces.get(workspaceId);
if (workspaceState == null || workspaceState.status != WorkspaceStatus.RUNNING) {
if (workspaceState == null || workspaceState.status != RUNNING) {
throw new ConflictException(format("Environment of workspace '%s' is not running", workspaceId));
}
}
@ -479,8 +513,8 @@ public class WorkspaceRuntimes {
try (StripedLocks.ReadLock lock = stripedLocks.acquireReadLock(workspaceId)) {
WorkspaceState workspaceState = workspaces.get(workspaceId);
if (workspaceState == null || workspaceState.status != WorkspaceStatus.RUNNING) {
throw new ConflictException(format("Environment of workspace '%s' is not running", workspaceId));
if (workspaceState == null || !(workspaceState.status == SNAPSHOTTING || workspaceState.status == RUNNING)) {
throw new ConflictException(format("Environment of workspace '%s' is not running or snapshotting", workspaceId));
}
}
return environmentEngine.saveSnapshot(namespace, workspaceId, machineId);
@ -549,7 +583,7 @@ public class WorkspaceRuntimes {
.build());
try (StripedLocks.WriteAllLock lock = stripedLocks.acquireWriteAllLock()) {
for (Map.Entry<String, WorkspaceState> workspace : workspaces.entrySet()) {
if (workspace.getValue().status.equals(WorkspaceStatus.RUNNING) ||
if (workspace.getValue().status.equals(RUNNING) ||
workspace.getValue().status.equals(WorkspaceStatus.STARTING)) {
stopEnvExecutor.execute(() -> {
try {
@ -586,29 +620,30 @@ public class WorkspaceRuntimes {
.withError(error));
}
private Instance getDevMachine(List<Instance> machines) throws ServerException {
Optional<Instance> devMachineOpt = machines.stream()
.filter(machine -> machine.getConfig().isDev())
.findAny();
if (devMachineOpt.isPresent()) {
return devMachineOpt.get();
}
throw new ServerException(
"Environment has booted but it doesn't contain dev machine. Environment has been stopped.");
}
private void ensurePreDestroyIsNotExecuted() throws ServerException {
if (isPreDestroyInvoked) {
throw new ServerException("Could not perform operation because application server is stopping");
}
}
private WorkspaceState getRunningState(String workspaceId) throws NotFoundException, ConflictException {
final WorkspaceState state = workspaces.get(workspaceId);
if (state == null) {
throw new NotFoundException("Workspace with id '" + workspaceId + "' is not running");
}
if (state.getStatus() != RUNNING) {
throw new ConflictException(format("Workspace with id '%s' is not in 'RUNNING', it's status is '%s'",
workspaceId,
state.getStatus()));
}
return state;
}
protected void launchAgents(Instance instance, List<String> agents) throws ServerException {
try {
for (AgentKey agentKey : agentSorter.sort(agents)) {
LOG.info("Launching '{}' agent", agentKey.getName());
Agent agent = agentRegistry.getAgent(agentKey);
AgentLauncher launcher = launcherFactory.find(agentKey.getName(), instance.getConfig().getType());
launcher.launch(instance, agent);

View File

@ -346,11 +346,13 @@ public class WorkspaceService extends Service {
@ApiResponse(code = 404, message = "The workspace with specified id doesn't exist"),
@ApiResponse(code = 403, message = "The user is not workspace owner"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public void stop(@ApiParam("The workspace id") @PathParam("id") String id) throws ForbiddenException,
NotFoundException,
ServerException,
ConflictException {
workspaceManager.stopWorkspace(id);
public void stop(@ApiParam("The workspace id") @PathParam("id") String id,
@ApiParam("Whether to snapshot workspace before stopping it")
@QueryParam("create-snapshot") Boolean createSnapshot) throws ForbiddenException,
NotFoundException,
ServerException,
ConflictException {
workspaceManager.stopWorkspace(id, createSnapshot);
}
@POST
@ -506,10 +508,10 @@ public class WorkspaceService extends Service {
@ApiParam(value = "The name of the environment", required = true)
@QueryParam("name")
String envName) throws ServerException,
BadRequestException,
NotFoundException,
ConflictException,
ForbiddenException {
BadRequestException,
NotFoundException,
ConflictException,
ForbiddenException {
requiredNotNull(newEnvironment, "New environment");
requiredNotNull(envName, "New environment name");
final WorkspaceImpl workspace = workspaceManager.getWorkspace(id);
@ -673,7 +675,7 @@ public class WorkspaceService extends Service {
@ApiResponse(code = 404, message = "The workspace with specified id does not exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public WsAgentHealthStateDto checkAgentHealth(@ApiParam(value = "Workspace id")
@PathParam("id") String key) throws NotFoundException, ServerException{
@PathParam("id") String key) throws NotFoundException, ServerException {
final WorkspaceImpl workspace = workspaceManager.getWorkspace(key);
if (WorkspaceStatus.RUNNING != workspace.getStatus()) {
return newDto(WsAgentHealthStateDto.class).withWorkspaceStatus(workspace.getStatus());

View File

@ -204,13 +204,14 @@ public class WorkspaceConfigImpl implements WorkspaceConfig {
@Override
public String toString() {
return "UsersWorkspaceImpl{" +
return "WorkspaceConfigImpl{" +
"id=" + id +
", name='" + name + '\'' +
", description='" + description + '\'' +
", defaultEnv='" + defaultEnv + '\'' +
", commands=" + commands +
", projects=" + projects +
", environments=" + environments +
", description='" + description + '\'' +
'}';
}

View File

@ -41,6 +41,7 @@ import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.commons.subject.SubjectImpl;
import org.eclipse.che.dto.server.DtoFactory;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
@ -51,11 +52,14 @@ import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.RUNNING;
@ -77,6 +81,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
@ -99,23 +104,23 @@ public class WorkspaceManagerTest {
private static final String NAMESPACE_2 = "userNS2";
@Mock
private EventService eventService;
private EventService eventService;
@Mock
private WorkspaceDao workspaceDao;
private WorkspaceDao workspaceDao;
@Mock
private WorkspaceValidator workspaceConfigValidator;
private WorkspaceValidator workspaceConfigValidator;
@Mock
private MachineProcessManager client;
private MachineProcessManager client;
@Mock
private WorkspaceRuntimes runtimes;
private WorkspaceRuntimes runtimes;
@Mock
private AccountManager accountManager;
private AccountManager accountManager;
@Mock
private SnapshotDao snapshotDao;
@Mock
private WorkspaceFilesCleaner workspaceFilesCleaner;
private SnapshotDao snapshotDao;
@Captor
private ArgumentCaptor<WorkspaceImpl> workspaceCaptor;
private ArgumentCaptor<WorkspaceImpl> workspaceCaptor;
@Captor
private ArgumentCaptor<List<SnapshotImpl>> snapshotsCaptor;
private WorkspaceManager workspaceManager;
@ -530,62 +535,105 @@ public class WorkspaceManagerTest {
}
@Test
public void shouldNotCreateSnapshotIfWorkspaceIsTemporaryAndAutoCreateSnapshotActivated() throws Exception {
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
workspace.getAttributes().put(Constants.AUTO_CREATE_SNAPSHOT, "true");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenReturn(oldSnapshot);
workspace.setTemporary(true);
public void createsSnapshotBeforeStoppingWorkspace() throws Exception {
final WorkspaceImpl workspace = createRunningWorkspace();
workspaceManager.stopWorkspace(workspace.getId());
verify(workspaceManager, timeout(2000).never()).createSnapshotSync(anyObject(), anyString(), anyString());
verify(runtimes, timeout(2000)).stop(workspace.getId());
verify(runtimes, timeout(2000)).beginSnapshotting(workspace.getId());
verify(runtimes, timeout(2000)).finishSnapshotting(workspace.getId());
final Iterator<MachineImpl> machineIt = workspace.getRuntime().getMachines().iterator();
verify(runtimes).saveMachine(workspace.getNamespace(), workspace.getId(), machineIt.next().getId());
verify(runtimes).saveMachine(workspace.getNamespace(), workspace.getId(), machineIt.next().getId());
verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
.withEventType(SNAPSHOT_CREATING)
.withWorkspaceId(workspace.getId()));
verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
.withEventType(SNAPSHOT_CREATED)
.withWorkspaceId(workspace.getId()));
}
@Test
public void shouldNotCreateSnapshotIfWorkspaceIsTemporaryAndAutoCreateSnapshotDisactivated() throws Exception {
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
workspace.getAttributes().put(Constants.AUTO_CREATE_SNAPSHOT, "false");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenReturn(oldSnapshot);
workspace.setTemporary(true);
public void failsToCreateSnapshotWhenDevMachineSnapshottingFailed() throws Exception {
final WorkspaceImpl workspace = createRunningWorkspace();
when(runtimes.saveMachine(any(), any(), any())).thenThrow( new ServerException("test"));
workspaceManager.stopWorkspace(workspace.getId());
verify(workspaceManager, timeout(2000).never()).createSnapshotSync(anyObject(), anyString(), anyString());
verify(runtimes, timeout(2000)).stop(workspace.getId());
verify(runtimes, timeout(2000)).beginSnapshotting(workspace.getId());
verify(runtimes, timeout(2000)).finishSnapshotting(workspace.getId());
verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
.withEventType(SNAPSHOT_CREATING)
.withWorkspaceId(workspace.getId()));
verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
.withEventType(SNAPSHOT_CREATION_ERROR)
.withWorkspaceId(workspace.getId())
.withError("test"));
}
@Test
public void shouldCreateWorkspaceSnapshotBeforeStoppingWorkspace() throws Exception {
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
workspace.getAttributes().put(Constants.AUTO_CREATE_SNAPSHOT, "true");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenReturn(oldSnapshot);
public void removesNewlyCreatedSnapshotsWhenFailedToSaveTheirsMetadata() throws Exception {
final WorkspaceImpl workspace = createRunningWorkspace();
when(snapshotDao.replaceSnapshots(eq(workspace.getId()),
eq(workspace.getRuntime().getActiveEnv()),
anyObject())).thenThrow(new SnapshotException("test"));
workspaceManager.stopWorkspace(workspace.getId());
verify(workspaceManager, timeout(2000)).createSnapshotSync(anyObject(), anyString(), anyString());
verify(runtimes, timeout(2000)).stop(workspace.getId());
verify(runtimes, timeout(2000)).beginSnapshotting(workspace.getId());
verify(runtimes, timeout(2000)).finishSnapshotting(workspace.getId());
verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
.withEventType(SNAPSHOT_CREATING)
.withWorkspaceId(workspace.getId()));
verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
.withEventType(SNAPSHOT_CREATION_ERROR)
.withWorkspaceId(workspace.getId())
.withError("test"));
verify(snapshotDao).replaceSnapshots(eq(workspace.getId()),
eq(workspace.getRuntime().getActiveEnv()),
snapshotsCaptor.capture());
final Iterator<SnapshotImpl> snapshotsIt = snapshotsCaptor.getValue().iterator();
verify(runtimes).removeSnapshot(snapshotsIt.next());
verify(runtimes).removeSnapshot(snapshotsIt.next());
}
@Test
public void removesOldSnapshotsWhenNewSnapshotsMetadataSuccessfullySaved() throws Exception {
final WorkspaceImpl workspace = createRunningWorkspace();
final SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.replaceSnapshots(eq(workspace.getId()),
eq(workspace.getRuntime().getActiveEnv()),
anyObject())).thenReturn(singletonList(oldSnapshot));
workspaceManager.stopWorkspace(workspace.getId());
verify(runtimes, timeout(2000)).beginSnapshotting(workspace.getId());
verify(runtimes, timeout(2000)).finishSnapshotting(workspace.getId());
verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
.withEventType(SNAPSHOT_CREATING)
.withWorkspaceId(workspace.getId()));
verify(eventService).publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
.withEventType(SNAPSHOT_CREATED)
.withWorkspaceId(workspace.getId()));
verify(runtimes).removeSnapshot(oldSnapshot);
}
@Test
public void passedCreateSnapshotParameterIsUsedInPreferenceToAttribute() throws Exception {
final WorkspaceImpl workspace = createRunningWorkspace();
workspaceManager.stopWorkspace(workspace.getId(), false);
verify(runtimes, never()).beginSnapshotting(workspace.getId());
}
@Test
public void passedNullCreateSnapshotParameterIsIgnored() throws Exception {
final WorkspaceImpl workspace = createRunningWorkspace();
workspaceManager.stopWorkspace(workspace.getId(), null);
verify(runtimes, timeout(2000)).beginSnapshotting(workspace.getId());
}
@Test(expectedExceptions = ConflictException.class,
@ -658,6 +706,46 @@ public class WorkspaceManagerTest {
assertEquals(snapshots.get(0), wsSnapshot);
}
@Test
public void shouldNotCreateSnapshotIfWorkspaceIsTemporaryAndAutoCreateSnapshotActivated() throws Exception {
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
workspace.getAttributes().put(Constants.AUTO_CREATE_SNAPSHOT, "true");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenReturn(oldSnapshot);
workspace.setTemporary(true);
workspaceManager.stopWorkspace(workspace.getId());
verify(workspaceManager, timeout(2000).never()).createSnapshotSync(anyObject(), anyString(), anyString());
verify(runtimes, timeout(2000)).stop(workspace.getId());
}
@Test
public void shouldNotCreateSnapshotIfWorkspaceIsTemporaryAndAutoCreateSnapshotDisactivated() throws Exception {
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
workspace.getAttributes().put(Constants.AUTO_CREATE_SNAPSHOT, "false");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenReturn(oldSnapshot);
workspace.setTemporary(true);
workspaceManager.stopWorkspace(workspace.getId());
verify(workspaceManager, timeout(2000).never()).createSnapshotSync(anyObject(), anyString(), anyString());
verify(runtimes, timeout(2000)).stop(workspace.getId());
}
@Test
public void shouldCreateWorkspaceSnapshotUsingDefaultValueForAutoRestore() throws Exception {
// given
@ -682,7 +770,9 @@ public class WorkspaceManagerTest {
workspaceManager.stopWorkspace(workspace.getId());
// then
verify(workspaceManager, timeout(2000)).createSnapshotSync(workspace.getRuntime(), workspace.getNamespace(), workspace.getId());
verify(workspaceManager, timeout(2000)).createSnapshotSync(workspace.getNamespace(),
workspace.getId(),
workspace.getRuntime().getActiveEnv());
verify(runtimes, timeout(2000)).stop(any());
}
@ -838,9 +928,9 @@ public class WorkspaceManagerTest {
workspaceManager.createSnapshot(workspace.getId());
// then
verify(workspaceManager, timeout(1_000)).createSnapshotSync(any(WorkspaceRuntimeImpl.class),
eq(workspace.getNamespace()),
eq(workspace.getId()));
verify(workspaceManager, timeout(1_000)).createSnapshotSync(eq(workspace.getNamespace()),
eq(workspace.getId()),
anyString());
}
@Test(expectedExceptions = ConflictException.class,
@ -856,29 +946,6 @@ public class WorkspaceManagerTest {
workspaceManager.createSnapshot(workspace.getId());
}
@Test
public void shouldSnapshotAllMachinesInWs() throws Exception {
// given
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenReturn(oldSnapshot);
// when
workspaceManager.createSnapshot(workspace.getId());
// then
verify(runtimes, timeout(1_000).times(2)).saveMachine(eq(workspace.getNamespace()),
eq(workspace.getId()),
anyString());
verify(snapshotDao, timeout(1_000).times(2)).saveSnapshot(any(SnapshotImpl.class));
}
@Test
public void shouldSendEventOnStartSnapshotSaving() throws Exception {
// given
@ -923,228 +990,6 @@ public class WorkspaceManagerTest {
.withWorkspaceId(workspace.getId())));
}
@Test
public void shouldSendSnapshotSavingFailedEventIfDevMachineSnapshotSavingFailed() throws Exception {
// given
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenReturn(oldSnapshot);
for (MachineImpl machine : descriptor.getRuntime().getMachines()) {
if (machine.getConfig().isDev()) {
when(runtimes.saveMachine(workspace.getNamespace(), workspace.getId(), machine.getId()))
.thenThrow(new ServerException("test error"));
}
}
// when
workspaceManager.createSnapshot(workspace.getId());
// then
verify(eventService, timeout(1_000)).publish(eq(newDto(WorkspaceStatusEvent.class)
.withEventType(SNAPSHOT_CREATION_ERROR)
.withWorkspaceId(workspace.getId())
.withError("test error")));
}
@Test
public void shouldNotSendSnapshotSavingFailedEventIfNonDevMachineSnapshotSavingFailed() throws Exception {
// given
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenReturn(oldSnapshot);
for (MachineImpl machine : descriptor.getRuntime().getMachines()) {
if (!machine.getConfig().isDev()) {
when(runtimes.saveMachine(workspace.getNamespace(), workspace.getId(), machine.getId()))
.thenThrow(new ServerException("test error"));
}
}
// when
workspaceManager.createSnapshot(workspace.getId());
// then
verify(eventService, timeout(1_000)).publish(eq(newDto(WorkspaceStatusEvent.class)
.withEventType(SNAPSHOT_CREATED)
.withWorkspaceId(workspace.getId())));
}
@Test
public void shouldReturnFalseOnFailureSnapshotSavingIfDevMachineSavingFailed() throws Exception {
// given
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenReturn(oldSnapshot);
for (MachineImpl machine : descriptor.getRuntime().getMachines()) {
if (machine.getConfig().isDev()) {
when(runtimes.saveMachine(workspace.getNamespace(), workspace.getId(), machine.getId()))
.thenThrow(new ServerException("test error"));
}
}
// when
boolean snapshotSavingStatus = workspaceManager.createSnapshotSync(
new WorkspaceRuntimeImpl(descriptor.getRuntime()), workspace.getNamespace(), workspace.getId());
// then
assertFalse(snapshotSavingStatus);
}
@Test
public void shouldReturnTrueOnSuccessfulSavingSnapshotsForSeveralMachines() throws Exception {
// given
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenReturn(oldSnapshot);
// when
boolean snapshotSavingStatus = workspaceManager.createSnapshotSync(
new WorkspaceRuntimeImpl(descriptor.getRuntime()), workspace.getNamespace(), workspace.getId());
// then
assertTrue(snapshotSavingStatus);
// ensure that multiple machines were saved
verify(snapshotDao, timeout(1_000).atLeast(2)).saveSnapshot(any(SnapshotImpl.class));
}
@Test
public void shouldReturnTrueOnSavingSnapshotsForSeveralMachinesWhenNonDevMachineSavingFailed() throws Exception {
// given
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenReturn(oldSnapshot);
for (MachineImpl machine : descriptor.getRuntime().getMachines()) {
if (!machine.getConfig().isDev()) {
when(runtimes.saveMachine(workspace.getNamespace(), workspace.getId(), machine.getId()))
.thenThrow(new ServerException("test error"));
}
}
// when
boolean snapshotSavingStatus = workspaceManager.createSnapshotSync(
new WorkspaceRuntimeImpl(descriptor.getRuntime()), workspace.getNamespace(), workspace.getId());
// then
assertTrue(snapshotSavingStatus);
}
@Test
public void shouldRemoveRuntimeSnapshotIfSavingSnapshotInDaoFails() throws Exception {
// given
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenReturn(oldSnapshot);
doThrow(new SnapshotException("test error")).when(snapshotDao).saveSnapshot(any(SnapshotImpl.class));
// when
workspaceManager.createSnapshot(workspace.getId());
// then
verify(runtimes, timeout(1_000).times(2)).removeSnapshot(any(SnapshotImpl.class));
}
@Test
public void shouldIgnoreNotFoundExceptionOnOldSnapshotRemoval1() throws Exception {
// given
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenReturn(oldSnapshot);
doThrow(new NotFoundException("test error")).when(snapshotDao).removeSnapshot(anyString());
// when
workspaceManager.createSnapshot(workspace.getId());
// then
verify(workspaceManager, timeout(1_000)).createSnapshotSync(any(WorkspaceRuntimeImpl.class),
eq(workspace.getNamespace()),
eq(workspace.getId()));
}
@Test
public void shouldIgnoreNotFoundExceptionOnOldSnapshotRemoval2() throws Exception {
// given
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
SnapshotImpl oldSnapshot = mock(SnapshotImpl.class);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenReturn(oldSnapshot);
doThrow(new NotFoundException("test error")).when(runtimes).removeSnapshot(any(SnapshotImpl.class));
// when
workspaceManager.createSnapshot(workspace.getId());
// then
verify(workspaceManager, timeout(1_000)).createSnapshotSync(any(WorkspaceRuntimeImpl.class),
eq(workspace.getNamespace()),
eq(workspace.getId()));
}
@Test
public void shouldIgnoreNotFoundExceptionOnOldSnapshotRemoval3() throws Exception {
// given
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(any())).thenReturn(descriptor);
when(snapshotDao.getSnapshot(eq(workspace.getId()),
eq(workspace.getConfig().getDefaultEnv()),
anyString()))
.thenThrow(new NotFoundException("test error"));
// when
workspaceManager.createSnapshot(workspace.getId());
// then
verify(workspaceManager, timeout(1_000)).createSnapshotSync(any(WorkspaceRuntimeImpl.class),
eq(workspace.getNamespace()),
eq(workspace.getId()));
}
@Test
public void shouldBeAbleToStopMachine() throws Exception {
// given
@ -1206,22 +1051,64 @@ public class WorkspaceManagerTest {
verify(runtimes).getMachine(workspace.getId(), machine.getId());
}
private RuntimeDescriptor createDescriptor(WorkspaceImpl workspace, WorkspaceStatus status) {
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());
MachineImpl machine = spy(createMachine(workspace.getId(), workspace.getConfig().getDefaultEnv(), true));
runtime.getMachines().add(machine);
MachineImpl machine2 = spy(createMachine(workspace.getId(), workspace.getConfig().getDefaultEnv(), false));
runtime.getMachines().add(machine2);
final MachineImpl machine1 = spy(createMachine(workspace.getId(), workspace.getConfig().getDefaultEnv(), true));
final MachineImpl machine2 = spy(createMachine(workspace.getId(), workspace.getConfig().getDefaultEnv(), false));
final Map<String, MachineImpl> machines = new HashMap<>();
machines.put(machine1.getId(), machine1);
machines.put(machine2.getId(), machine2);
runtime.getMachines().addAll(machines.values());
runtime.setDevMachine(machine1);
when(runtimes.saveMachine(any(), any(), anyObject())).thenAnswer(inv -> {
final String machineId = (String)inv.getArguments()[2];
final MachineImpl machine = machines.get(machineId);
if (machine == null) {
return null;
}
return SnapshotImpl.builder()
.setWorkspaceId(machine.getWorkspaceId())
.useCurrentCreationDate()
.generateId()
.setDescription("test")
.setDev(machine.getConfig().isDev())
.setEnvName(machine.getEnvName())
.setMachineName(machine.getConfig().getName())
.build();
});
final RuntimeDescriptor descriptor = mock(RuntimeDescriptor.class);
when(descriptor.getRuntimeStatus()).thenReturn(status);
when(descriptor.getRuntime()).thenReturn(runtime);
workspace.setRuntime(runtime);
return descriptor;
}
private WorkspaceImpl createRunningWorkspace() throws ServerException, NotFoundException, ConflictException {
// should be snapshotted when stopped
final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE);
workspace.getAttributes().put(Constants.AUTO_CREATE_SNAPSHOT, "true");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
// has runtime
final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING);
when(runtimes.get(workspace.getId())).thenReturn(descriptor);
// doesn't have snapshots
when(snapshotDao.findSnapshots(workspace.getId())).thenReturn(emptyList());
when(snapshotDao.replaceSnapshots(eq(workspace.getId()),
eq(workspace.getRuntime().getActiveEnv()),
any())).thenReturn(emptyList());
return workspace;
}
private static WorkspaceConfigImpl createConfig() {
EnvironmentImpl environment = new EnvironmentImpl(new EnvironmentRecipeImpl("type",
"contentType",

View File

@ -36,8 +36,11 @@ import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
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.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
@ -51,23 +54,29 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import static java.lang.String.format;
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.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.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
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.fail;
/**
* @author Yevhenii Voevodin
@ -436,8 +445,8 @@ public class WorkspaceRuntimesTest {
verify(runtimes).launchAgents(instance, singletonList("org.eclipse.che.terminal"));
}
@Test(expectedExceptions = ConflictException.class,
expectedExceptionsMessageRegExp = "Environment of workspace '.*' is not running")
@Test(expectedExceptions = NotFoundException.class,
expectedExceptionsMessageRegExp = "Workspace with id '.*' is not running")
public void shouldNotStartMachineIfEnvironmentIsNotRunning() throws Exception {
// when
MachineConfigImpl config = createConfig(false);
@ -493,7 +502,7 @@ public class WorkspaceRuntimesTest {
}
@Test(expectedExceptions = ConflictException.class,
expectedExceptionsMessageRegExp = "Environment of workspace '.*' is not running")
expectedExceptionsMessageRegExp = "Environment of workspace 'workspaceId' is not running or snapshotting")
public void shouldNotSaveMachineIfEnvironmentIsNotRunning() throws Exception {
// when
runtimes.saveMachine("namespace", "workspaceId", "machineId");
@ -572,6 +581,72 @@ public class WorkspaceRuntimesTest {
assertEquals(actualWorkspaces, expectedWorkspaces);
}
@Test
public void changesStatusFromRunningToSnapshotting() throws Exception {
final WorkspaceImpl workspace = createWorkspace();
runtimes.start(workspace, workspace.getConfig().getDefaultEnv(), false);
runtimes.beginSnapshotting(workspace.getId());
assertEquals(runtimes.get(workspace.getId()).getRuntimeStatus(), WorkspaceStatus.SNAPSHOTTING);
}
@Test
public void changesStatusFromSnapshottingToRunning() throws Exception {
final WorkspaceImpl workspace = createWorkspace();
runtimes.start(workspace, workspace.getConfig().getDefaultEnv(), false);
runtimes.beginSnapshotting(workspace.getId());
runtimes.finishSnapshotting(workspace.getId());
assertEquals(runtimes.get(workspace.getId()).getRuntimeStatus(), WorkspaceStatus.RUNNING);
}
@Test
public void doesNothingWhenWorkspaceDoesNotHaveRuntimeAndFinishSnapshottingIsCalled() throws Exception {
runtimes.finishSnapshotting("fake");
}
@Test
public void doesNothingWhenWorkspaceStatusIsNotSnapshottingAndFinishSnapshottingIsCalled() throws Exception {
final WorkspaceImpl workspace = createWorkspace();
runtimes.start(workspace, workspace.getConfig().getDefaultEnv(), false);
runtimes.finishSnapshotting(workspace.getId());
assertEquals(runtimes.get(workspace.getId()).getRuntimeStatus(), WorkspaceStatus.RUNNING);
}
@Test(expectedExceptions = NotFoundException.class,
expectedExceptionsMessageRegExp = "Workspace with id 'non-existing' is not running")
public void throwsNotFoundExceptionWhenBeginningSnapshottingForNonExistingWorkspace() throws Exception {
runtimes.beginSnapshotting("non-existing");
}
@Test
public void throwsConflictExceptionWhenBeginningSnapshottingForNotRunningWorkspace() throws Exception {
final WorkspaceImpl workspace = createWorkspace();
doAnswer(inv -> {
// checking exception here
try {
runtimes.beginSnapshotting(workspace.getId());
fail("Expected to get an exception");
} catch (ConflictException x) {
assertEquals(x.getMessage(), format("Workspace with id '%s' is not in '%s', it's status is '%s'",
workspace.getId(),
WorkspaceStatus.RUNNING,
WorkspaceStatus.STARTING));
}
return null;
}).when(eventService)
.publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
.withEventType(EventType.STARTING)
.withWorkspaceId(workspace.getId()));
runtimes.start(workspace, workspace.getConfig().getDefaultEnv(), false);
}
private static Instance createMachine(boolean isDev) {
return createMachine(createConfig(isDev));
}

View File

@ -471,7 +471,7 @@ public class WorkspaceServiceTest {
.delete(SECURE_PATH + "/workspace/" + workspace.getId() + "/runtime");
assertEquals(response.getStatusCode(), 204);
verify(wsManager).stopWorkspace(workspace.getId());
verify(wsManager).stopWorkspace(workspace.getId(), null);
}
@Test

View File

@ -36,26 +36,10 @@
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-persist</artifactId>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
@ -64,14 +48,6 @@
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-account</artifactId>
@ -80,22 +56,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-dto</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-machine</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-machine-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-model</artifactId>
@ -104,18 +68,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-ssh</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-ssh-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-user</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-user-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace</artifactId>
@ -128,22 +84,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
</dependency>
<dependency>
<groupId>org.everrest</groupId>
<artifactId>everrest-core</artifactId>
</dependency>
<dependency>
<groupId>org.everrest</groupId>
<artifactId>everrest-websockets</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
@ -218,26 +162,4 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.testOutputDirectory}</outputDirectory>
<includeArtifactIds>che-core-api-user,
che-core-api-machine,
che-core-api-ssh</includeArtifactIds>
<includeScope>test</includeScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -27,6 +27,7 @@ import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -119,6 +120,12 @@ public class LocalSnapshotDaoImpl implements SnapshotDao {
snapshots.remove(snapshotId);
}
@Override
public List<SnapshotImpl> replaceSnapshots(String workspaceId, String envName, Collection<? extends SnapshotImpl> newSnapshots)
throws SnapshotException {
throw new RuntimeException("Not implemented");
}
@PostConstruct
public synchronized void loadSnapshots() {
snapshots.putAll(snapshotStorage.loadMap(new TypeToken<Map<String, SnapshotImpl>>() {}));