Workspace start code cleanup
Moved async operations from WorkspaceManager to WorkspaceRuntimes to have async facility in one place instead of two. Moved workspace start/stop logging from WorkspaceManager to WorkspaceRuntimes since WorkspaceManager can not correctly log them. Improved logging of workspace start/stop including addition of new logs. Fixed logging of exception thrown by RuntimeInfrastructure on runtime start/stop. Fix docker image deletion bug on stop of a workspace. Signed-off-by: Oleksandr Garagatyi <ogaragat@redhat.com>6.19.x
parent
70ac13c008
commit
0c77efc2b3
|
|
@ -154,8 +154,7 @@ public class DockerMachine implements Machine {
|
|||
try {
|
||||
docker.removeImage(RemoveImageParams.create(image).withForce(false));
|
||||
} catch (IOException e) {
|
||||
// TODO make log level warning if we ignoring it or remove ignoring phrase
|
||||
LOG.error("IOException during destroy(). Ignoring.", e);
|
||||
LOG.warn("IOException during destroy(). Ignoring.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ public class DockerMachineCreator {
|
|||
|
||||
return new DockerMachine(
|
||||
container.getId(),
|
||||
container.getImage(),
|
||||
container.getConfig().getImage(),
|
||||
docker,
|
||||
new ServersMapper(hostname).map(networkSettings.getPorts(), configs),
|
||||
registry,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import org.eclipse.che.api.core.model.workspace.config.Environment;
|
|||
import org.eclipse.che.api.core.notification.EventService;
|
||||
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.WorkspaceValidator;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
|
||||
import org.eclipse.che.api.workspace.server.spi.WorkspaceDao;
|
||||
|
|
@ -76,13 +75,12 @@ public class LimitsCheckingWorkspaceManager extends WorkspaceManager {
|
|||
EventService eventService,
|
||||
AccountManager accountManager,
|
||||
WorkspaceValidator workspaceValidator,
|
||||
WorkspaceSharedPool sharedPool,
|
||||
//own injects
|
||||
@Named("che.limits.workspace.env.ram") String maxRamPerEnv,
|
||||
EnvironmentRamCalculator environmentRamCalculator,
|
||||
ResourceUsageManager resourceUsageManager,
|
||||
ResourcesLocks resourcesLocks) {
|
||||
super(workspaceDao, runtimes, eventService, accountManager, workspaceValidator, sharedPool);
|
||||
super(workspaceDao, runtimes, eventService, accountManager, workspaceValidator);
|
||||
this.environmentRamCalculator = environmentRamCalculator;
|
||||
this.maxRamPerEnvMB = "-1".equals(maxRamPerEnv) ? -1 : Size.parseSizeToMegabytes(maxRamPerEnv);
|
||||
this.resourceUsageManager = resourceUsageManager;
|
||||
|
|
|
|||
|
|
@ -274,7 +274,6 @@ public class LimitsCheckingWorkspaceManagerTest {
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
maxRamPerEnv,
|
||||
environmentRamCalculator,
|
||||
resourceUsageManager,
|
||||
|
|
|
|||
|
|
@ -11,20 +11,17 @@
|
|||
package org.eclipse.che.api.workspace.server;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.common.base.Throwables.getCausalChain;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.currentTimeMillis;
|
||||
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.STARTING;
|
||||
import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STOPPED;
|
||||
import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_STOPPED_BY;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import javax.inject.Singleton;
|
||||
import org.eclipse.che.account.api.AccountManager;
|
||||
import org.eclipse.che.account.shared.model.Account;
|
||||
|
|
@ -38,8 +35,6 @@ import org.eclipse.che.api.core.model.workspace.WorkspaceStatus;
|
|||
import org.eclipse.che.api.core.notification.EventService;
|
||||
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.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.spi.WorkspaceDao;
|
||||
import org.eclipse.che.api.workspace.shared.event.WorkspaceCreatedEvent;
|
||||
import org.eclipse.che.commons.annotation.Nullable;
|
||||
|
|
@ -69,7 +64,6 @@ public class WorkspaceManager {
|
|||
private final WorkspaceDao workspaceDao;
|
||||
private final WorkspaceRuntimes runtimes;
|
||||
private final AccountManager accountManager;
|
||||
private final WorkspaceSharedPool sharedPool;
|
||||
private final EventService eventService;
|
||||
private final WorkspaceValidator validator;
|
||||
|
||||
|
|
@ -79,13 +73,11 @@ public class WorkspaceManager {
|
|||
WorkspaceRuntimes runtimes,
|
||||
EventService eventService,
|
||||
AccountManager accountManager,
|
||||
WorkspaceValidator validator,
|
||||
WorkspaceSharedPool sharedPool) {
|
||||
WorkspaceValidator validator) {
|
||||
this.workspaceDao = workspaceDao;
|
||||
this.runtimes = runtimes;
|
||||
this.accountManager = accountManager;
|
||||
this.eventService = eventService;
|
||||
this.sharedPool = sharedPool;
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +265,7 @@ public class WorkspaceManager {
|
|||
}
|
||||
|
||||
workspaceDao.remove(workspaceId);
|
||||
LOG.info("Workspace '{}' removed by user '{}'", workspaceId, sessionUserNameOr("undefined"));
|
||||
LOG.info("Workspace '{}' removed by user '{}'", workspaceId, sessionUserNameOrUndefined());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -344,13 +336,24 @@ public class WorkspaceManager {
|
|||
|
||||
requireNonNull(workspaceId, "Required non-null workspace id");
|
||||
final WorkspaceImpl workspace = normalizeState(workspaceDao.get(workspaceId), true);
|
||||
checkWorkspaceIsRunningOrStarting(workspace, "stop");
|
||||
stopAsync(workspace, options);
|
||||
checkWorkspaceIsRunningOrStarting(workspace);
|
||||
if (!workspace.isTemporary()) {
|
||||
workspace.getAttributes().put(UPDATED_ATTRIBUTE_NAME, Long.toString(currentTimeMillis()));
|
||||
workspaceDao.update(workspace);
|
||||
}
|
||||
|
||||
runtimes
|
||||
.stopAsync(workspace, options)
|
||||
.whenComplete(
|
||||
(aVoid, throwable) -> {
|
||||
if (workspace.isTemporary()) {
|
||||
removeWorkspaceQuietly(workspace);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Asynchronously starts given workspace. */
|
||||
private CompletableFuture<Void> startAsync(
|
||||
WorkspaceImpl workspace, String envName, Map<String, String> options)
|
||||
private void startAsync(WorkspaceImpl workspace, String envName, Map<String, String> options)
|
||||
throws ConflictException, NotFoundException, ServerException {
|
||||
if (envName != null && !workspace.getConfig().getEnvironments().containsKey(envName)) {
|
||||
throw new NotFoundException(
|
||||
|
|
@ -362,81 +365,23 @@ public class WorkspaceManager {
|
|||
workspaceDao.update(workspace);
|
||||
final String env = firstNonNull(envName, workspace.getConfig().getDefaultEnv());
|
||||
|
||||
return runtimes
|
||||
runtimes
|
||||
.startAsync(workspace, env, firstNonNull(options, Collections.emptyMap()))
|
||||
.thenRun(
|
||||
() ->
|
||||
LOG.info(
|
||||
"Workspace '{}:{}' with id '{}' started by user '{}'",
|
||||
workspace.getNamespace(),
|
||||
workspace.getConfig().getName(),
|
||||
workspace.getId(),
|
||||
sessionUserNameOr("undefined")))
|
||||
.exceptionally(
|
||||
ex -> {
|
||||
if (workspace.isTemporary()) {
|
||||
removeWorkspaceQuietly(workspace);
|
||||
}
|
||||
for (Throwable cause : getCausalChain(ex)) {
|
||||
if (cause instanceof InfrastructureException
|
||||
&& !(cause instanceof InternalInfrastructureException)) {
|
||||
|
||||
// InfrastructureException is supposed to be an exception that can't be solved
|
||||
// by the admin, so should not be logged (but not InternalInfrastructureException).
|
||||
// It will prevent bothering the admin when user made a mistake in WS configuration.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
LOG.error(ex.getLocalizedMessage(), ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> stopAsync(WorkspaceImpl workspace, Map<String, String> options)
|
||||
throws ConflictException, NotFoundException, ServerException {
|
||||
if (!workspace.isTemporary()) {
|
||||
workspace.getAttributes().put(UPDATED_ATTRIBUTE_NAME, Long.toString(currentTimeMillis()));
|
||||
workspaceDao.update(workspace);
|
||||
}
|
||||
String stoppedBy = sessionUserNameOr(workspace.getAttributes().get(WORKSPACE_STOPPED_BY));
|
||||
LOG.info(
|
||||
"Workspace '{}/{}' with id '{}' is being stopped by user '{}'",
|
||||
workspace.getNamespace(),
|
||||
workspace.getConfig().getName(),
|
||||
workspace.getId(),
|
||||
firstNonNull(stoppedBy, "undefined"));
|
||||
|
||||
return sharedPool.runAsync(
|
||||
() -> {
|
||||
try {
|
||||
runtimes.stop(workspace.getId(), options);
|
||||
|
||||
LOG.info(
|
||||
"Workspace '{}/{}' with id '{}' stopped by user '{}'",
|
||||
workspace.getNamespace(),
|
||||
workspace.getConfig().getName(),
|
||||
workspace.getId(),
|
||||
firstNonNull(stoppedBy, "undefined"));
|
||||
} catch (Exception ex) {
|
||||
LOG.error(ex.getLocalizedMessage(), ex);
|
||||
} finally {
|
||||
if (workspace.isTemporary()) {
|
||||
removeWorkspaceQuietly(workspace);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void checkWorkspaceIsRunningOrStarting(WorkspaceImpl workspace, String operation)
|
||||
throws ConflictException {
|
||||
private void checkWorkspaceIsRunningOrStarting(WorkspaceImpl workspace) throws ConflictException {
|
||||
if (workspace.getStatus() != RUNNING && workspace.getStatus() != STARTING) {
|
||||
throw new ConflictException(
|
||||
format(
|
||||
"Could not %s the workspace '%s/%s' because its status is '%s'.",
|
||||
operation,
|
||||
workspace.getNamespace(),
|
||||
workspace.getConfig().getName(),
|
||||
workspace.getStatus()));
|
||||
"Could not stop the workspace '%s/%s' because its status is '%s'.",
|
||||
workspace.getNamespace(), workspace.getConfig().getName(), workspace.getStatus()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -448,12 +393,12 @@ public class WorkspaceManager {
|
|||
}
|
||||
}
|
||||
|
||||
private String sessionUserNameOr(String nameIfNoUser) {
|
||||
private String sessionUserNameOrUndefined() {
|
||||
final Subject subject = EnvironmentContext.getCurrent().getSubject();
|
||||
if (!subject.isAnonymous()) {
|
||||
return subject.getUserName();
|
||||
}
|
||||
return nameIfNoUser;
|
||||
return "undefined";
|
||||
}
|
||||
|
||||
private WorkspaceImpl normalizeState(WorkspaceImpl workspace, boolean includeRuntimes)
|
||||
|
|
@ -486,7 +431,7 @@ public class WorkspaceManager {
|
|||
account.getName(),
|
||||
workspace.getConfig().getName(),
|
||||
workspace.getId(),
|
||||
sessionUserNameOr("undefined"));
|
||||
sessionUserNameOrUndefined());
|
||||
eventService.publish(new WorkspaceCreatedEvent(workspace));
|
||||
return workspace;
|
||||
}
|
||||
|
|
@ -515,31 +460,4 @@ public class WorkspaceManager {
|
|||
}
|
||||
return workspaceDao.get(wsName, namespace);
|
||||
}
|
||||
|
||||
// FIXME: this code is from master version of runtimes, where
|
||||
// WorkspaceRuntimes is responsible for statuses management.
|
||||
//
|
||||
// /** Adds runtime data (whole or status only) and extra attributes to each of the given workspaces. */
|
||||
// private void injectRuntimeAndAttributes(List<WorkspaceImpl> workspaces, boolean statusOnly) throws SnapshotException {
|
||||
// if (statusOnly) {
|
||||
// for (WorkspaceImpl workspace : workspaces) {
|
||||
// workspace.setStatus(runtimes.getStatus(workspace.getId()));
|
||||
// addExtraAttributes(workspace);
|
||||
// }
|
||||
// } else {
|
||||
// for (WorkspaceImpl workspace : workspaces) {
|
||||
// runtimes.injectRuntime(workspace);
|
||||
// addExtraAttributes(workspace);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /** Adds attributes that are not originally stored in workspace but should be published. */
|
||||
// private void addExtraAttributes(WorkspaceImpl workspace) throws SnapshotException {
|
||||
// // snapshotted_at
|
||||
// List<SnapshotImpl> snapshots = snapshotDao.findSnapshots(workspace.getId());
|
||||
// if (!snapshots.isEmpty()) {
|
||||
// workspace.getAttributes().put(SNAPSHOTTED_AT_ATTRIBUTE_NAME, Long.toString(snapshots.get(0).getCreationDate()));
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,14 @@
|
|||
*/
|
||||
package org.eclipse.che.api.workspace.server;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static java.lang.String.format;
|
||||
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.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.shared.Constants.WORKSPACE_STOPPED_BY;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
|
@ -64,8 +66,6 @@ import org.eclipse.che.dto.server.DtoFactory;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
// TODO: spi: deal with exceptions
|
||||
|
||||
/**
|
||||
* Defines an internal API for managing {@link RuntimeImpl} instances.
|
||||
*
|
||||
|
|
@ -167,15 +167,13 @@ public class WorkspaceRuntimes {
|
|||
* Starts all machines from specified workspace environment, creates workspace runtime instance
|
||||
* based on that environment.
|
||||
*
|
||||
* <p>
|
||||
*
|
||||
* <p>During the start of the workspace its runtime is visible with {@link
|
||||
* WorkspaceStatus#STARTING} status.
|
||||
*
|
||||
* @param workspace workspace which environment should be started
|
||||
* @param envName the name of the environment to start
|
||||
* @param options whether machines should be recovered(true) or not(false)
|
||||
* @return the workspace runtime instance with machines set.
|
||||
* @return completable future of start execution.
|
||||
* @throws ConflictException when workspace is already running
|
||||
* @throws ConflictException when start is interrupted
|
||||
* @throws NotFoundException when any not found exception occurs during environment start
|
||||
|
|
@ -237,32 +235,18 @@ public class WorkspaceRuntimes {
|
|||
throw new ConflictException(
|
||||
"Could not start workspace '" + workspaceId + "' because it is not in 'STOPPED' state");
|
||||
}
|
||||
eventService.publish(
|
||||
DtoFactory.newDto(WorkspaceStatusEvent.class)
|
||||
.withWorkspaceId(workspaceId)
|
||||
.withStatus(STARTING)
|
||||
.withPrevStatus(STOPPED));
|
||||
LOG.info(
|
||||
"Starting workspace '{}/{}' with id '{}' by user '{}'",
|
||||
workspace.getNamespace(),
|
||||
workspace.getConfig().getName(),
|
||||
workspace.getId(),
|
||||
sessionUserNameOr("undefined"));
|
||||
|
||||
publishWorkspaceStatusEvent(workspaceId, STARTING, STOPPED, null);
|
||||
|
||||
return CompletableFuture.runAsync(
|
||||
ThreadLocalPropagateContext.wrap(
|
||||
() -> {
|
||||
try {
|
||||
runtime.start(options);
|
||||
runtimes.replace(workspaceId, new RuntimeState(runtime, RUNNING));
|
||||
eventService.publish(
|
||||
DtoFactory.newDto(WorkspaceStatusEvent.class)
|
||||
.withWorkspaceId(workspaceId)
|
||||
.withStatus(RUNNING)
|
||||
.withPrevStatus(STARTING));
|
||||
} catch (InfrastructureException e) {
|
||||
runtimes.remove(workspaceId);
|
||||
if (!(e instanceof RuntimeStartInterruptedException)) {
|
||||
publishWorkspaceStoppedEvent(workspaceId, STARTING, e.getMessage());
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}),
|
||||
ThreadLocalPropagateContext.wrap(new StartRuntimeTask(workspace, options, runtime)),
|
||||
sharedPool.getExecutor());
|
||||
//TODO made complete rework of exceptions.
|
||||
} catch (ValidationException e) {
|
||||
LOG.error(e.getLocalizedMessage(), e);
|
||||
throw new ConflictException(e.getLocalizedMessage());
|
||||
|
|
@ -272,23 +256,73 @@ public class WorkspaceRuntimes {
|
|||
}
|
||||
}
|
||||
|
||||
private class StartRuntimeTask implements Runnable {
|
||||
private final Workspace workspace;
|
||||
private final Map<String, String> options;
|
||||
private final InternalRuntime runtime;
|
||||
|
||||
public StartRuntimeTask(
|
||||
Workspace workspace, Map<String, String> options, InternalRuntime runtime) {
|
||||
this.workspace = workspace;
|
||||
this.options = options;
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String workspaceId = workspace.getId();
|
||||
try {
|
||||
runtime.start(options);
|
||||
runtimes.replace(workspaceId, new RuntimeState(runtime, RUNNING));
|
||||
|
||||
LOG.info(
|
||||
"Workspace '{}:{}' with id '{}' started by user '{}'",
|
||||
workspace.getNamespace(),
|
||||
workspace.getConfig().getName(),
|
||||
workspaceId,
|
||||
sessionUserNameOr("undefined"));
|
||||
publishWorkspaceStatusEvent(workspaceId, RUNNING, STARTING, null);
|
||||
} catch (InfrastructureException e) {
|
||||
runtimes.remove(workspaceId);
|
||||
String failureCause = "failed";
|
||||
if (e instanceof RuntimeStartInterruptedException) {
|
||||
failureCause = "interrupted";
|
||||
}
|
||||
LOG.info(
|
||||
"Workspace '{}:{}' with id '{}' start {}",
|
||||
workspace.getNamespace(),
|
||||
workspace.getConfig().getName(),
|
||||
workspaceId,
|
||||
failureCause);
|
||||
// InfrastructureException is supposed to be an exception that can't be solved
|
||||
// by Che admin, so should not be logged (but not InternalInfrastructureException).
|
||||
// It will prevent bothering the admin when user made a mistake in WS configuration.
|
||||
if (e instanceof InternalInfrastructureException) {
|
||||
LOG.error(e.getLocalizedMessage(), e);
|
||||
}
|
||||
publishWorkspaceStatusEvent(workspaceId, STOPPED, STARTING, e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops running workspace runtime.
|
||||
*
|
||||
* <p>
|
||||
* Stops running workspace runtime asynchronously.
|
||||
*
|
||||
* <p>Stops environment in an implementation specific way. During the stop of the workspace its
|
||||
* runtime is accessible with {@link WorkspaceStatus#STOPPING stopping} status. Workspace may be
|
||||
* stopped only if its status is {@link WorkspaceStatus#RUNNING}.
|
||||
* stopped only if its status is {@link WorkspaceStatus#RUNNING} or {@link
|
||||
* WorkspaceStatus#STARTING}.
|
||||
*
|
||||
* @param workspaceId identifier of workspace which should be stopped
|
||||
* @param workspace workspace which runtime should be stopped
|
||||
* @throws NotFoundException when workspace with specified identifier does not have runtime
|
||||
* @throws ConflictException when running workspace status is different from {@link
|
||||
* WorkspaceStatus#RUNNING}
|
||||
* WorkspaceStatus#RUNNING} or {@link WorkspaceStatus#STARTING}
|
||||
* @see WorkspaceStatus#STOPPING
|
||||
*/
|
||||
public void stop(String workspaceId, Map<String, String> options)
|
||||
public CompletableFuture<Void> stopAsync(Workspace workspace, Map<String, String> options)
|
||||
throws NotFoundException, ConflictException {
|
||||
String workspaceId = workspace.getId();
|
||||
RuntimeState state = runtimes.get(workspaceId);
|
||||
if (state == null) {
|
||||
throw new NotFoundException("Workspace with id '" + workspaceId + "' is not running.");
|
||||
|
|
@ -305,25 +339,74 @@ public class WorkspaceRuntimes {
|
|||
throw new ConflictException(
|
||||
format("Could not stop workspace '%s' because its state is '%s'", workspaceId, status));
|
||||
}
|
||||
eventService.publish(
|
||||
DtoFactory.newDto(WorkspaceStatusEvent.class)
|
||||
.withWorkspaceId(workspaceId)
|
||||
.withPrevStatus(state.status)
|
||||
.withStatus(STOPPING));
|
||||
String stoppedBy =
|
||||
firstNonNull(
|
||||
sessionUserNameOr(workspace.getAttributes().get(WORKSPACE_STOPPED_BY)), "undefined");
|
||||
LOG.info(
|
||||
"Workspace '{}/{}' with id '{}' is being stopped by user '{}'",
|
||||
workspace.getNamespace(),
|
||||
workspace.getConfig().getName(),
|
||||
workspace.getId(),
|
||||
stoppedBy);
|
||||
publishWorkspaceStatusEvent(workspaceId, STOPPING, state.status, null);
|
||||
|
||||
try {
|
||||
state.runtime.stop(options);
|
||||
return CompletableFuture.runAsync(
|
||||
ThreadLocalPropagateContext.wrap(new StopRuntimeTask(workspace, options, stoppedBy, state)),
|
||||
sharedPool.getExecutor());
|
||||
}
|
||||
|
||||
// remove before firing an event to have consistency between state and the event
|
||||
runtimes.remove(workspaceId);
|
||||
publishWorkspaceStoppedEvent(workspaceId, STOPPING, null);
|
||||
} catch (InfrastructureException e) {
|
||||
// remove before firing an event to have consistency between state and the event
|
||||
runtimes.remove(workspaceId);
|
||||
publishWorkspaceStoppedEvent(
|
||||
workspaceId,
|
||||
STOPPING,
|
||||
"Error occurs on workspace runtime stop. Error: " + e.getMessage());
|
||||
private class StopRuntimeTask implements Runnable {
|
||||
private final Workspace workspace;
|
||||
private final Map<String, String> options;
|
||||
private final String stoppedBy;
|
||||
private final RuntimeState state;
|
||||
|
||||
public StopRuntimeTask(
|
||||
Workspace workspace, Map<String, String> options, String stoppedBy, RuntimeState state) {
|
||||
this.workspace = workspace;
|
||||
this.options = options;
|
||||
this.stoppedBy = stoppedBy;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String workspaceId = workspace.getId();
|
||||
try {
|
||||
state.runtime.stop(options);
|
||||
|
||||
// remove before firing an event to have consistency between state and the event
|
||||
runtimes.remove(workspaceId);
|
||||
LOG.info(
|
||||
"Workspace '{}/{}' with id '{}' stopped by user '{}'",
|
||||
workspace.getNamespace(),
|
||||
workspace.getConfig().getName(),
|
||||
workspaceId,
|
||||
stoppedBy);
|
||||
publishWorkspaceStatusEvent(workspaceId, STOPPED, STOPPING, null);
|
||||
} catch (InfrastructureException e) {
|
||||
// remove before firing an event to have consistency between state and the event
|
||||
runtimes.remove(workspaceId);
|
||||
LOG.info(
|
||||
"Error occurs on workspace '{}/{}' with id '{}' stopped by user '{}'. Error: {}",
|
||||
workspace.getNamespace(),
|
||||
workspace.getConfig().getName(),
|
||||
workspaceId,
|
||||
stoppedBy,
|
||||
e);
|
||||
publishWorkspaceStatusEvent(
|
||||
workspaceId,
|
||||
STOPPED,
|
||||
STOPPING,
|
||||
"Error occurs on workspace runtime stop. Error: " + e.getMessage());
|
||||
// InfrastructureException is supposed to be an exception that can't be solved
|
||||
// by Che admin, so should not be logged (but not InternalInfrastructureException).
|
||||
// It will prevent bothering the admin when user made a mistake in WS configuration.
|
||||
if (e instanceof InternalInfrastructureException) {
|
||||
LOG.error(e.getLocalizedMessage(), e);
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -350,8 +433,9 @@ public class WorkspaceRuntimes {
|
|||
LOG.warn("Not recoverable infrastructure: '{}'", infra.getName());
|
||||
} catch (InternalInfrastructureException x) {
|
||||
LOG.error(
|
||||
"An error occurred while attempted to recover runtimes using infrastructure '{}'. Reason: '{}'",
|
||||
infra.getName(),
|
||||
format(
|
||||
"An error occurred while attempted to recover runtimes using infrastructure '%s'",
|
||||
infra.getName()),
|
||||
x);
|
||||
} catch (ServerException | InfrastructureException x) {
|
||||
LOG.error(
|
||||
|
|
@ -419,14 +503,14 @@ public class WorkspaceRuntimes {
|
|||
eventService.subscribe(new CleanupRuntimeOnAbnormalRuntimeStop());
|
||||
}
|
||||
|
||||
private void publishWorkspaceStoppedEvent(
|
||||
String workspaceId, WorkspaceStatus previous, String errorMsg) {
|
||||
private void publishWorkspaceStatusEvent(
|
||||
String workspaceId, WorkspaceStatus status, WorkspaceStatus previous, String errorMsg) {
|
||||
eventService.publish(
|
||||
DtoFactory.newDto(WorkspaceStatusEvent.class)
|
||||
.withWorkspaceId(workspaceId)
|
||||
.withPrevStatus(previous)
|
||||
.withError(errorMsg)
|
||||
.withStatus(STOPPED));
|
||||
.withStatus(status));
|
||||
}
|
||||
|
||||
private static EnvironmentImpl copyEnv(Workspace workspace, String envName) {
|
||||
|
|
@ -484,18 +568,25 @@ public class WorkspaceRuntimes {
|
|||
return Optional.of(state.runtime.getContext());
|
||||
}
|
||||
|
||||
private String sessionUserNameOr(String nameIfNoUser) {
|
||||
final Subject subject = EnvironmentContext.getCurrent().getSubject();
|
||||
if (!subject.isAnonymous()) {
|
||||
return subject.getUserName();
|
||||
}
|
||||
return nameIfNoUser;
|
||||
}
|
||||
|
||||
private class CleanupRuntimeOnAbnormalRuntimeStop implements EventSubscriber<RuntimeStatusEvent> {
|
||||
@Override
|
||||
public void onEvent(RuntimeStatusEvent event) {
|
||||
if (event.isFailed()) {
|
||||
RuntimeState state = runtimes.remove(event.getIdentity().getWorkspaceId());
|
||||
if (state != null) {
|
||||
eventService.publish(
|
||||
DtoFactory.newDto(WorkspaceStatusEvent.class)
|
||||
.withWorkspaceId(state.runtime.getContext().getIdentity().getWorkspaceId())
|
||||
.withPrevStatus(RUNNING)
|
||||
.withStatus(STOPPED)
|
||||
.withError("Error occurs on workspace runtime stop. Error: " + event.getError()));
|
||||
publishWorkspaceStatusEvent(
|
||||
state.runtime.getContext().getIdentity().getWorkspaceId(),
|
||||
STOPPED,
|
||||
RUNNING,
|
||||
"Error occurs on workspace runtime stop. Error: " + event.getError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public abstract class AbstractBootstrapper {
|
|||
BootstrapperStatus status = event.getStatus();
|
||||
//skip starting status event
|
||||
if (status.equals(BootstrapperStatus.DONE) || status.equals(BootstrapperStatus.FAILED)) {
|
||||
//check boostrapper belongs to current runtime and machine
|
||||
//check bootstrapper belongs to current runtime and machine
|
||||
RuntimeIdentityDto runtimeId = event.getRuntimeId();
|
||||
if (event.getMachineName().equals(machineName)
|
||||
&& runtimeIdentity.getEnvName().equals(runtimeId.getEnvName())
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
package org.eclipse.che.api.workspace.server.spi;
|
||||
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
|
@ -27,7 +26,6 @@ import org.eclipse.che.api.workspace.server.URLRewriter;
|
|||
import org.eclipse.che.api.workspace.server.model.impl.MachineImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.ServerImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WarningImpl;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* Implementation of concrete Runtime
|
||||
|
|
@ -36,8 +34,6 @@ import org.slf4j.Logger;
|
|||
*/
|
||||
public abstract class InternalRuntime<T extends RuntimeContext> implements Runtime {
|
||||
|
||||
private static final Logger LOG = getLogger(InternalRuntime.class);
|
||||
|
||||
private final T context;
|
||||
private final URLRewriter urlRewriter;
|
||||
private final List<Warning> warnings;
|
||||
|
|
@ -139,15 +135,9 @@ public abstract class InternalRuntime<T extends RuntimeContext> implements Runti
|
|||
|
||||
try {
|
||||
internalStop(stopOptions);
|
||||
} catch (InternalInfrastructureException e) {
|
||||
LOG.error(
|
||||
"Error occurs on stop of workspace {}. Error: {}",
|
||||
context.getIdentity().getWorkspaceId(),
|
||||
e.getMessage());
|
||||
} catch (InfrastructureException e) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
} finally {
|
||||
status = WorkspaceStatus.STOPPED;
|
||||
}
|
||||
status = WorkspaceStatus.STOPPED;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import static org.mockito.Matchers.any;
|
|||
import static org.mockito.Matchers.anyObject;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
|
@ -95,21 +94,18 @@ public class WorkspaceManagerTest {
|
|||
@Mock private WorkspaceDao workspaceDao;
|
||||
@Mock private WorkspaceRuntimes runtimes;
|
||||
@Mock private AccountManager accountManager;
|
||||
@Mock private WorkspaceSharedPool sharedPool;
|
||||
@Mock private EventService eventService;
|
||||
@Mock private WorkspaceValidator validator;
|
||||
@Mock private RuntimeInfrastructure infrastructure;
|
||||
|
||||
@Captor private ArgumentCaptor<WorkspaceImpl> workspaceCaptor;
|
||||
@Captor private ArgumentCaptor<Runnable> taskCaptor;
|
||||
|
||||
private WorkspaceManager workspaceManager;
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp() throws Exception {
|
||||
workspaceManager =
|
||||
new WorkspaceManager(
|
||||
workspaceDao, runtimes, eventService, accountManager, validator, sharedPool);
|
||||
new WorkspaceManager(workspaceDao, runtimes, eventService, accountManager, validator);
|
||||
when(accountManager.getByName(NAMESPACE_1))
|
||||
.thenReturn(new AccountImpl("accountId", NAMESPACE_1, "test"));
|
||||
when(accountManager.getByName(NAMESPACE_2))
|
||||
|
|
@ -429,36 +425,25 @@ public class WorkspaceManagerTest {
|
|||
public void stopsWorkspace() throws Exception {
|
||||
final WorkspaceImpl workspace = createAndMockWorkspace(createConfig(), NAMESPACE_1);
|
||||
mockRuntime(workspace, RUNNING);
|
||||
mockAnyWorkspaceStop();
|
||||
|
||||
workspaceManager.stopWorkspace(workspace.getId(), emptyMap());
|
||||
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
verify(runtimes).stop(workspace.getId(), emptyMap());
|
||||
verify(runtimes).stopAsync(workspace, emptyMap());
|
||||
verify(workspaceDao).update(workspaceCaptor.capture());
|
||||
assertNotNull(workspaceCaptor.getValue().getAttributes().get(UPDATED_ATTRIBUTE_NAME));
|
||||
}
|
||||
|
||||
@Test(
|
||||
expectedExceptions = ConflictException.class,
|
||||
expectedExceptionsMessageRegExp =
|
||||
"Could not stop the workspace " + "'.*' because its status is 'STOPPED'."
|
||||
)
|
||||
public void throwsConflictExceptionWhenStoppingWorkspaceWithoutRuntime() throws Exception {
|
||||
final WorkspaceImpl workspace = createAndMockWorkspace();
|
||||
|
||||
workspaceManager.stopWorkspace(workspace.getId(), emptyMap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removesTemporaryWorkspaceAfterStop() throws Exception {
|
||||
final WorkspaceImpl workspace = createAndMockWorkspace();
|
||||
workspace.setTemporary(true);
|
||||
mockRuntime(workspace, RUNNING);
|
||||
mockAnyWorkspaceStop();
|
||||
|
||||
workspaceManager.stopWorkspace(workspace.getId(), emptyMap());
|
||||
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
verify(runtimes).stop(workspace.getId(), emptyMap());
|
||||
verify(runtimes).stopAsync(workspace, emptyMap());
|
||||
verify(workspaceDao).remove(workspace.getId());
|
||||
}
|
||||
|
||||
|
|
@ -469,10 +454,10 @@ public class WorkspaceManagerTest {
|
|||
mockRuntime(workspace, RUNNING);
|
||||
doThrow(new ConflictException("runtime stop failed"))
|
||||
.when(runtimes)
|
||||
.stop(workspace.getId(), emptyMap());
|
||||
.stopAsync(workspace, emptyMap());
|
||||
mockAnyWorkspaceStop();
|
||||
|
||||
workspaceManager.stopWorkspace(workspace.getId(), emptyMap());
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
verify(workspaceDao).remove(workspace.getId());
|
||||
}
|
||||
|
||||
|
|
@ -488,13 +473,6 @@ public class WorkspaceManagerTest {
|
|||
verify(workspaceDao, times(1)).remove(anyString());
|
||||
}
|
||||
|
||||
private void captureRunAsyncCallsAndRunSynchronously() {
|
||||
verify(sharedPool, atLeastOnce()).runAsync(taskCaptor.capture());
|
||||
for (Runnable runnable : taskCaptor.getAllValues()) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
private void mockRuntimeStatus(WorkspaceImpl workspace, WorkspaceStatus status) {
|
||||
when(runtimes.getStatus(workspace.getId())).thenReturn(status);
|
||||
}
|
||||
|
|
@ -562,6 +540,11 @@ public class WorkspaceManagerTest {
|
|||
when(runtimes.startAsync(any(), anyString(), any())).thenReturn(cmpFuture);
|
||||
}
|
||||
|
||||
private void mockAnyWorkspaceStop() throws Exception {
|
||||
CompletableFuture<Void> cmpFuture = CompletableFuture.completedFuture(null);
|
||||
when(runtimes.stopAsync(any(), any())).thenReturn(cmpFuture);
|
||||
}
|
||||
|
||||
private void mockAnyWorkspaceStartFailed(Exception cause) throws Exception {
|
||||
final CompletableFuture<Void> cmpFuture = new CompletableFuture<>();
|
||||
cmpFuture.completeExceptionally(cause);
|
||||
|
|
|
|||
|
|
@ -225,7 +225,10 @@ public class InternalRuntimeTest {
|
|||
// given
|
||||
setRunningRuntime();
|
||||
doThrow(new InfrastructureException("")).when(internalRuntime).internalStop(any());
|
||||
internalRuntime.stop(emptyMap());
|
||||
try {
|
||||
internalRuntime.stop(emptyMap());
|
||||
} catch (InfrastructureException ignore) {
|
||||
}
|
||||
|
||||
// when
|
||||
internalRuntime.start(emptyMap());
|
||||
|
|
|
|||
Loading…
Reference in New Issue