Skip to content

Commit

Permalink
Store last workspace startup or crash error status and message in wor…
Browse files Browse the repository at this point in the history
…kspace attributes. (#7988)
  • Loading branch information
mshaposhnik committed Dec 26, 2017
1 parent f983d15 commit e09667a
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
*/
package org.eclipse.che.ide.jsonrpc;

import static com.google.common.base.MoreObjects.firstNonNull;
import static java.util.Collections.singletonMap;
import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STOPPED;
import static org.eclipse.che.api.core.model.workspace.runtime.ServerStatus.RUNNING;
import static org.eclipse.che.api.workspace.shared.Constants.ERROR_MESSAGE_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.shared.Constants.INSTALLER_LOG_METHOD;
import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_ENVIRONMENT_STATUS_CHANNEL;
import static org.eclipse.che.api.workspace.shared.Constants.MACHINE_LOG_METHOD;
Expand Down Expand Up @@ -187,7 +189,12 @@ private void checkStatuses() {
((AppContextImpl) appContext).setWorkspace(workspace);

if (workspace.getStatus() != workspacePrev.getStatus()) {
if (workspace.getStatus() == WorkspaceStatus.RUNNING) {
if (workspace.getStatus() == WorkspaceStatus.STOPPED) {
String cause = workspace.getAttributes().get(ERROR_MESSAGE_ATTRIBUTE_NAME);
eventBus.fireEvent(
new WorkspaceStoppedEvent(true, firstNonNull(cause, "Reason is unknown.")));
return;
} else if (workspace.getStatus() == WorkspaceStatus.RUNNING) {
eventBus.fireEvent(new WorkspaceRunningEvent());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ public ServerAddressMacroRegistrar(
eventBus.addHandler(
WorkspaceStoppedEvent.TYPE,
e -> {
macros.forEach(macro -> macroRegistryProvider.get().unregister(macro));
macros.clear();
if (macros != null) {
macros.forEach(macro -> macroRegistryProvider.get().unregister(macro));
macros.clear();
}
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ public ContributionMixinProvider(
event -> {
lastSelected = null;
contributePart.showStub("");
selectionHandlerReg.removeHandler();
if (selectionHandlerReg != null) {
selectionHandlerReg.removeHandler();
}
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
*/
package org.eclipse.che.api.workspace.shared;

import org.eclipse.che.api.core.model.workspace.Workspace;

/**
* Constants for Workspace API
*
Expand Down Expand Up @@ -39,6 +41,32 @@ public final class Constants {

public static final String CHE_WORKSPACE_AUTO_START = "che.workspace.auto_start";

/**
* Describes time when workspace was created. Should be set/read from {@link
* Workspace#getAttributes}
*/
public static final String CREATED_ATTRIBUTE_NAME = "created";
/**
* Describes time when workspace was last updated or started. Should be set/read from {@link
* Workspace#getAttributes}
*/
public static final String UPDATED_ATTRIBUTE_NAME = "updated";
/**
* Describes time when workspace was last stopped. Should be set/read from {@link
* Workspace#getAttributes}
*/
public static final String STOPPED_ATTRIBUTE_NAME = "stopped";
/**
* Indicates that last workspace stop was abnormal. Should be set/read from {@link
* Workspace#getAttributes}
*/
public static final String STOPPED_ABNORMALLY_ATTRIBUTE_NAME = "stoppedAbnormally";
/**
* Describes latest workspace runtime error message. Should be set/read from {@link
* Workspace#getAttributes}
*/
public static final String ERROR_MESSAGE_ATTRIBUTE_NAME = "errorMessage";

public static final String COMMAND_PREVIEW_URL_ATTRIBUTE_NAME = "previewUrl";
public static final String COMMAND_GOAL_ATTRIBUTE_NAME = "goal";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
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.CREATED_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.shared.Constants.ERROR_MESSAGE_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.shared.Constants.STOPPED_ABNORMALLY_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.shared.Constants.STOPPED_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.shared.Constants.UPDATED_ATTRIBUTE_NAME;

import com.google.inject.Inject;
import java.util.Collections;
Expand Down Expand Up @@ -57,11 +62,6 @@ public class WorkspaceManager {

private static final Logger LOG = LoggerFactory.getLogger(WorkspaceManager.class);

/** This attribute describes time when workspace was created. */
public static final String CREATED_ATTRIBUTE_NAME = "created";
/** This attribute describes time when workspace was last updated or started/stopped/recovered. */
public static final String UPDATED_ATTRIBUTE_NAME = "updated";

private final WorkspaceDao workspaceDao;
private final WorkspaceRuntimes runtimes;
private final AccountManager accountManager;
Expand Down Expand Up @@ -330,7 +330,8 @@ public void stopWorkspace(String workspaceId, Map<String, String> options)
final WorkspaceImpl workspace = normalizeState(workspaceDao.get(workspaceId), true);
checkWorkspaceIsRunningOrStarting(workspace);
if (!workspace.isTemporary()) {
workspace.getAttributes().put(UPDATED_ATTRIBUTE_NAME, Long.toString(currentTimeMillis()));
workspace.getAttributes().put(STOPPED_ATTRIBUTE_NAME, Long.toString(currentTimeMillis()));
workspace.getAttributes().put(STOPPED_ABNORMALLY_ATTRIBUTE_NAME, Boolean.toString(false));
workspaceDao.update(workspace);
}

Expand Down Expand Up @@ -363,10 +364,13 @@ private void startAsync(WorkspaceImpl workspace, String envName, Map<String, Str

runtimes
.startAsync(workspace, env, firstNonNull(options, Collections.emptyMap()))
.thenAccept(aVoid -> handleStartupSuccess(workspace))
.exceptionally(
ex -> {
if (workspace.isTemporary()) {
removeWorkspaceQuietly(workspace);
} else {
handleStartupError(workspace, ex.getCause());
}
return null;
});
Expand Down Expand Up @@ -407,6 +411,38 @@ private WorkspaceImpl normalizeState(WorkspaceImpl workspace, boolean includeRun
return workspace;
}

private void handleStartupError(Workspace workspace, Throwable t) {
workspace
.getAttributes()
.put(
ERROR_MESSAGE_ATTRIBUTE_NAME,
t instanceof RuntimeException ? t.getCause().getMessage() : t.getMessage());
workspace.getAttributes().put(STOPPED_ATTRIBUTE_NAME, Long.toString(currentTimeMillis()));
workspace.getAttributes().put(STOPPED_ABNORMALLY_ATTRIBUTE_NAME, Boolean.toString(true));
try {
updateWorkspace(workspace.getId(), workspace);
} catch (NotFoundException | ServerException | ValidationException | ConflictException e) {
LOG.warn(
String.format(
"Cannot set error status of the workspace %s. Error is: %s",
workspace.getId(), e.getMessage()));
}
}

private void handleStartupSuccess(Workspace workspace) {
workspace.getAttributes().remove(STOPPED_ATTRIBUTE_NAME);
workspace.getAttributes().remove(STOPPED_ABNORMALLY_ATTRIBUTE_NAME);
workspace.getAttributes().remove(ERROR_MESSAGE_ATTRIBUTE_NAME);
try {
updateWorkspace(workspace.getId(), workspace);
} catch (NotFoundException | ServerException | ValidationException | ConflictException e) {
LOG.warn(
String.format(
"Cannot clear error status status of the workspace %s. Error is: %s",
workspace.getId(), e.getMessage()));
}
}

private WorkspaceImpl doCreateWorkspace(
WorkspaceConfig config, Account account, Map<String, String> attributes, boolean isTemporary)
throws NotFoundException, ServerException, ConflictException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@

import static com.google.common.base.MoreObjects.firstNonNull;
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.core.model.workspace.WorkspaceStatus.STOPPING;
import static org.eclipse.che.api.workspace.shared.Constants.ERROR_MESSAGE_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.shared.Constants.STOPPED_ABNORMALLY_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.shared.Constants.STOPPED_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_STOPPED_BY;

import com.google.common.annotations.VisibleForTesting;
Expand Down Expand Up @@ -114,8 +118,8 @@ public WorkspaceRuntimes(
}

@PostConstruct
private void init() {
subscribeCleanupOnAbnormalRuntimeStopEvent();
void init() {
subscribeAbnormalRuntimeStopListener();
recover();
}

Expand Down Expand Up @@ -484,8 +488,8 @@ void recoverOne(RuntimeInfrastructure infra, RuntimeIdentity identity) throws Se
}
}

private void subscribeCleanupOnAbnormalRuntimeStopEvent() {
eventService.subscribe(new CleanupRuntimeOnAbnormalRuntimeStop());
private void subscribeAbnormalRuntimeStopListener() {
eventService.subscribe(new AbnormalRuntimeStopListener());
}

private void publishWorkspaceStatusEvent(
Expand Down Expand Up @@ -576,20 +580,36 @@ private String sessionUserNameOr(String nameIfNoUser) {
return nameIfNoUser;
}

private class CleanupRuntimeOnAbnormalRuntimeStop implements EventSubscriber<RuntimeStatusEvent> {
private class AbnormalRuntimeStopListener implements EventSubscriber<RuntimeStatusEvent> {
@Override
public void onEvent(RuntimeStatusEvent event) {
if (event.isFailed()) {
RuntimeState state = runtimes.remove(event.getIdentity().getWorkspaceId());
String workspaceId = event.getIdentity().getWorkspaceId();
RuntimeState state = runtimes.remove(workspaceId);
if (state != null) {
publishWorkspaceStatusEvent(
state.runtime.getContext().getIdentity().getWorkspaceId(),
workspaceId,
STOPPED,
RUNNING,
"Error occurs on workspace runtime stop. Error: " + event.getError());
setAbnormalStopAttributes(workspaceId, event.getError());
}
}
}

private void setAbnormalStopAttributes(String workspaceId, String error) {
try {
WorkspaceImpl workspace = workspaceDao.get(workspaceId);
workspace.getAttributes().put(ERROR_MESSAGE_ATTRIBUTE_NAME, error);
workspace.getAttributes().put(STOPPED_ATTRIBUTE_NAME, Long.toString(currentTimeMillis()));
workspace.getAttributes().put(STOPPED_ABNORMALLY_ATTRIBUTE_NAME, Boolean.toString(true));
workspaceDao.update(workspace);
} catch (NotFoundException | ServerException | ConflictException e) {
LOG.warn(
String.format(
"Cannot set error status of the workspace %s. Error is: %s", workspaceId, error));
}
}
}

private static class RuntimeState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@
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.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE;
import static org.eclipse.che.api.workspace.server.WorkspaceManager.UPDATED_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.shared.Constants.CREATED_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.shared.Constants.ERROR_MESSAGE_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.shared.Constants.STOPPED_ABNORMALLY_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.shared.Constants.STOPPED_ATTRIBUTE_NAME;
import static org.eclipse.che.api.workspace.shared.Constants.UPDATED_ATTRIBUTE_NAME;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.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;
Expand Down Expand Up @@ -139,7 +144,7 @@ public void createsWorkspace() throws Exception {
assertEquals(workspace.getConfig(), cfg);
assertFalse(workspace.isTemporary());
assertEquals(workspace.getStatus(), STOPPED);
assertNotNull(workspace.getAttributes().get(WorkspaceManager.CREATED_ATTRIBUTE_NAME));
assertNotNull(workspace.getAttributes().get(CREATED_ATTRIBUTE_NAME));
verify(workspaceDao).create(workspace);
}

Expand Down Expand Up @@ -436,7 +441,10 @@ public void stopsWorkspace() throws Exception {

verify(runtimes).stopAsync(workspace, emptyMap());
verify(workspaceDao).update(workspaceCaptor.capture());
assertNotNull(workspaceCaptor.getValue().getAttributes().get(UPDATED_ATTRIBUTE_NAME));
assertNotNull(workspaceCaptor.getValue().getAttributes().get(STOPPED_ATTRIBUTE_NAME));
assertFalse(
Boolean.valueOf(
workspaceCaptor.getValue().getAttributes().get(STOPPED_ABNORMALLY_ATTRIBUTE_NAME)));
}

@Test
Expand Down Expand Up @@ -466,6 +474,40 @@ public void removesTemporaryWorkspaceAfterStopFailed() throws Exception {
verify(workspaceDao).remove(workspace.getId());
}

@Test
public void setsErrorAttributesAfterWorkspaceStartFailed() throws Exception {
final WorkspaceConfigImpl workspaceConfig = createConfig();
final WorkspaceImpl workspace = createAndMockWorkspace(workspaceConfig, NAMESPACE_1);
mockAnyWorkspaceStartFailed(new ServerException("start failed"));

workspaceManager.startWorkspace(workspaceConfig, workspace.getNamespace(), false, emptyMap());
verify(workspaceDao).update(workspaceCaptor.capture());
Workspace ws = workspaceCaptor.getAllValues().get(workspaceCaptor.getAllValues().size() - 1);
assertNotNull(ws.getAttributes().get(STOPPED_ATTRIBUTE_NAME));
assertTrue(Boolean.valueOf(ws.getAttributes().get(STOPPED_ABNORMALLY_ATTRIBUTE_NAME)));
assertEquals(ws.getAttributes().get(ERROR_MESSAGE_ATTRIBUTE_NAME), "start failed");
}

@Test
public void clearsErrorAttributesAfterWorkspaceStart() throws Exception {
final WorkspaceConfigImpl workspaceConfig = createConfig();
final WorkspaceImpl workspace = createAndMockWorkspace(workspaceConfig, NAMESPACE_1);
workspace
.getAttributes()
.put(STOPPED_ATTRIBUTE_NAME, Long.toString(System.currentTimeMillis()));
workspace.getAttributes().put(STOPPED_ABNORMALLY_ATTRIBUTE_NAME, Boolean.TRUE.toString());
workspace.getAttributes().put(ERROR_MESSAGE_ATTRIBUTE_NAME, "start failed");
when(workspaceDao.get(workspace.getId())).thenReturn(workspace);
mockStart(workspace);

workspaceManager.startWorkspace(workspace.getId(), null, emptyMap());
verify(workspaceDao, atLeastOnce()).update(workspaceCaptor.capture());
Workspace ws = workspaceCaptor.getAllValues().get(workspaceCaptor.getAllValues().size() - 1);
assertNull(ws.getAttributes().get(STOPPED_ATTRIBUTE_NAME));
assertNull(ws.getAttributes().get(STOPPED_ABNORMALLY_ATTRIBUTE_NAME));
assertNull(ws.getAttributes().get(ERROR_MESSAGE_ATTRIBUTE_NAME));
}

@Test
public void removesTemporaryWorkspaceAfterStartFailed() throws Exception {
final WorkspaceConfigImpl workspaceConfig = createConfig();
Expand Down

0 comments on commit e09667a

Please sign in to comment.