diff --git a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java index e55f6205a97..84909f2f7a1 100644 --- a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java +++ b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java @@ -89,6 +89,9 @@ protected void configure() { new org.eclipse.che.api.machine.server.model.impl.ServerConfImpl(Constants.WSAGENT_DEBUG_REFERENCE, "4403/tcp", "http", null)); + bind(org.eclipse.che.api.agent.server.WsAgentHealthChecker.class) + .to(org.eclipse.che.api.agent.server.WsAgentHealthCheckerImpl.class); + bind(org.eclipse.che.api.machine.server.recipe.RecipeLoader.class); Multibinder.newSetBinder(binder(), String.class, Names.named("predefined.recipe.path")) .addBinding() diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/WsAgentStateController.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/WsAgentStateController.java index 639e8d9c007..dbe7a40a88f 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/WsAgentStateController.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/WsAgentStateController.java @@ -19,17 +19,21 @@ import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; -import org.eclipse.che.ide.api.machine.events.WsAgentStateEvent; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.promises.client.PromiseError; import org.eclipse.che.api.promises.client.callback.AsyncPromiseHelper; +import org.eclipse.che.api.workspace.shared.dto.WsAgentHealthStateDto; +import org.eclipse.che.ide.api.dialogs.DialogFactory; +import org.eclipse.che.ide.api.machine.events.WsAgentStateEvent; +import org.eclipse.che.ide.api.workspace.WorkspaceServiceClient; import org.eclipse.che.ide.rest.AsyncRequestFactory; import org.eclipse.che.ide.rest.RestServiceInfo; import org.eclipse.che.ide.rest.StringUnmarshaller; import org.eclipse.che.ide.ui.loaders.LoaderPresenter; import org.eclipse.che.ide.util.loging.Log; +import org.eclipse.che.ide.websocket.MachineMessageBus; import org.eclipse.che.ide.websocket.MessageBus; import org.eclipse.che.ide.websocket.MessageBusProvider; import org.eclipse.che.ide.websocket.events.ConnectionClosedHandler; @@ -41,42 +45,57 @@ import java.util.List; import static com.google.common.collect.Lists.newArrayList; +import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.RUNNING; import static org.eclipse.che.ide.api.machine.WsAgentState.STARTED; import static org.eclipse.che.ide.api.machine.WsAgentState.STOPPED; /** + * Controls workspace agent's state, defines actions to be perform on different events related to websocket + * connection (close, open, error, etc.), checks http/websocket connection to control it's state. Currently + * there are only two states that a workspace agent can be at: {@link WsAgentState#STARTED} or + * {@link WsAgentState#STOPPED}. + * * @author Roman Nikitenko * @author Valeriy Svydenko */ @Singleton public class WsAgentStateController implements ConnectionOpenedHandler, ConnectionClosedHandler, ConnectionErrorHandler { - - private final EventBus eventBus; - private final MessageBusProvider messageBusProvider; - private final AsyncRequestFactory asyncRequestFactory; - private DevMachine devMachine; - private final LoaderPresenter loader; - + private final EventBus eventBus; + private final MessageBusProvider messageBusProvider; + private final DialogFactory dialogFactory; + private final AsyncRequestFactory asyncRequestFactory; + private final WorkspaceServiceClient workspaceServiceClient; + private final LoaderPresenter loader; //not used now added it for future if it we will have possibility check that service available for client call - private final List availableServices; - - private MessageBus messageBus; - private WsAgentState state; + private final List availableServices; + private DevMachine devMachine; + private MessageBus messageBus; + private WsAgentState state; private List> messageBusCallbacks = newArrayList(); private List> devMachineCallbacks = newArrayList(); @Inject - public WsAgentStateController(EventBus eventBus, + public WsAgentStateController(WorkspaceServiceClient workspaceServiceClient, + EventBus eventBus, + LoaderPresenter loader, MessageBusProvider messageBusProvider, AsyncRequestFactory asyncRequestFactory, - LoaderPresenter loader) { + DialogFactory dialogFactory) { + this.workspaceServiceClient = workspaceServiceClient; + this.loader = loader; this.eventBus = eventBus; this.messageBusProvider = messageBusProvider; this.asyncRequestFactory = asyncRequestFactory; + this.dialogFactory = dialogFactory; this.availableServices = new ArrayList<>(); - this.loader = loader; } + /** + * Initialize state of the agent. + * + * @param devMachine + * development machine instance + */ public void initialize(DevMachine devMachine) { this.devMachine = devMachine; this.state = STOPPED; @@ -86,17 +105,14 @@ public void initialize(DevMachine devMachine) { @Override public void onClose(WebSocketClosedEvent event) { - Log.info(getClass(), "Test WS connection closed with code " + event.getCode() + " reason: " + event.getReason()); - if (state.equals(STARTED)) { - state = STOPPED; - eventBus.fireEvent(WsAgentStateEvent.createWsAgentStoppedEvent()); + if (STARTED.equals(state)) { + checkWsAgentHealth(); } } @Override public void onError() { - Log.info(getClass(), "Test WS connection error"); - if (state.equals(STARTED)) { + if (STARTED.equals(state)) { state = STOPPED; eventBus.fireEvent(WsAgentStateEvent.createWsAgentStoppedEvent()); } @@ -117,27 +133,12 @@ public void run() { }.scheduleRepeating(300); } - private void started() { - state = STARTED; - loader.setSuccess(LoaderPresenter.Phase.STARTING_WORKSPACE_AGENT); - - for (AsyncCallback callback : messageBusCallbacks) { - callback.onSuccess(messageBus); - } - messageBusCallbacks.clear(); - - for (AsyncCallback callback : devMachineCallbacks) { - callback.onSuccess(devMachine); - } - devMachineCallbacks.clear(); - - eventBus.fireEvent(WsAgentStateEvent.createWsAgentStartedEvent()); - } - + /** Returns state of the ws agent */ public WsAgentState getState() { return state; } + /** Returns instance of {@link MachineMessageBus}. */ public Promise getMessageBus() { return AsyncPromiseHelper.createFromAsyncRequest(new AsyncPromiseHelper.RequestCall() { @Override @@ -151,63 +152,74 @@ public void makeCall(AsyncCallback callback) { }); } - public Promise getDevMachine() { - return AsyncPromiseHelper.createFromAsyncRequest(new AsyncPromiseHelper.RequestCall() { + /** + * Goto checking HTTP connection via getting all registered REST Services + */ + private void checkHttpConnection() { + //here we add trailing slash because {@link org.eclipse.che.api.core.rest.ApiInfoService} mapped in this way + String url = devMachine.getWsAgentBaseUrl() + '/'; + asyncRequestFactory.createGetRequest(url).send(new StringUnmarshaller()).then(new Operation() { @Override - public void makeCall(AsyncCallback callback) { - if (messageBus != null) { - callback.onSuccess(devMachine); - } else { - WsAgentStateController.this.devMachineCallbacks.add(callback); + public void apply(String result) throws OperationException { + try { + JSONObject object = JSONParser.parseStrict(result).isObject(); + if (object.containsKey("rootResources")) { + JSONArray rootResources = object.get("rootResources").isArray(); + for (int i = 0; i < rootResources.size(); i++) { + JSONObject rootResource = rootResources.get(i).isObject(); + String regex = rootResource.get("regex").isString().stringValue(); + String fqn = rootResource.get("fqn").isString().stringValue(); + String path = rootResource.get("path").isString().stringValue(); + availableServices.add(new RestServiceInfo(fqn, regex, path)); + } + } + } catch (Exception exception) { + Log.warn(getClass(), "Parse root resources failed."); } + + checkWsConnection(); + } + }).catchError(new Operation() { + @Override + public void apply(PromiseError arg) throws OperationException { + checkWsAgentHealth(); } }); } - /** - * Goto checking HTTP connection via getting all registered REST Services - */ - private void checkHttpConnection() { - String url = devMachine.getWsAgentBaseUrl() + '/'; //here we add trailing slash because - // {@link org.eclipse.che.api.core.rest.ApiInfoService} mapped in this way - asyncRequestFactory.createGetRequest(url) - .send(new StringUnmarshaller()) - .then(new Operation() { - @Override - public void apply(String result) throws OperationException { - JSONObject object = null; - try { - object = JSONParser.parseStrict(result).isObject(); - } catch (Exception exception) { - Log.warn(getClass(), "Parse root resources failed."); - } - - if (object != null && object.containsKey("rootResources")) { - JSONArray rootResources = object.get("rootResources").isArray(); - for (int i = 0; i < rootResources.size(); i++) { - JSONObject rootResource = rootResources.get(i).isObject(); - String regex = rootResource.get("regex").isString().stringValue(); - String fqn = rootResource.get("fqn").isString().stringValue(); - String path = rootResource.get("path").isString().stringValue(); - availableServices.add(new RestServiceInfo(fqn, regex, path)); - } - } - - checkWsConnection(); - } - }) - .catchError(new Operation() { - @Override - public void apply(PromiseError arg) throws OperationException { - Log.error(getClass(), arg.getMessage()); - new Timer() { - @Override - public void run() { - checkHttpConnection(); - } - }.schedule(1000); - } - }); + private void started() { + state = STARTED; + loader.setSuccess(LoaderPresenter.Phase.STARTING_WORKSPACE_AGENT); + + for (AsyncCallback callback : messageBusCallbacks) { + callback.onSuccess(messageBus); + } + messageBusCallbacks.clear(); + + for (AsyncCallback callback : devMachineCallbacks) { + callback.onSuccess(devMachine); + } + devMachineCallbacks.clear(); + + eventBus.fireEvent(WsAgentStateEvent.createWsAgentStartedEvent()); + } + + private void checkStateOfWsAgent(WsAgentHealthStateDto agentHealthStateDto) { + final int statusCode = agentHealthStateDto.getCode(); + + String infoWindowTitle = "Workspace Agent"; + + if (statusCode == 200) { + dialogFactory.createMessageDialog(infoWindowTitle, + "Your workspace is not responding. To fix the problem, verify you have a good " + + "network connection and restart the workspace.", + null).show(); + } else { + dialogFactory.createMessageDialog(infoWindowTitle, + "Your workspace has stopped responding. To fix the problem, " + + "restart the workspace in the dashboard.", + null).show(); + } } /** @@ -223,4 +235,20 @@ private void checkWsConnection() { messageBus.addOnOpenHandler(this); } + private void checkWsAgentHealth() { + workspaceServiceClient.getWsAgentState(devMachine.getWorkspace()).then(new Operation() { + @Override + public void apply(WsAgentHealthStateDto arg) throws OperationException { + if (RUNNING.equals(arg.getWorkspaceStatus())) { + checkStateOfWsAgent(arg); + } + } + }).catchError(new Operation() { + @Override + public void apply(PromiseError arg) throws OperationException { + Log.error(getClass(), arg.getMessage()); + } + }); + } + } diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/workspace/WorkspaceServiceClient.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/workspace/WorkspaceServiceClient.java index 16edd7e48a5..72d343d6696 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/workspace/WorkspaceServiceClient.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/workspace/WorkspaceServiceClient.java @@ -17,8 +17,9 @@ import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; -import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; +import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; +import org.eclipse.che.api.workspace.shared.dto.WsAgentHealthStateDto; import java.util.List; @@ -283,4 +284,14 @@ public interface WorkspaceServiceClient { */ Promise createSnapshot(String workspaceId); + /** + * Gets state of the workspace agent. + * + * @param workspaceId + * workspace ID + * @return a promise that will resolve when the snapshot has been created, or rejects with an error + * @see WorkspaceService#checkAgentHealth(String) + */ + Promise getWsAgentState(String workspaceId); + } diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/workspace/WorkspaceServiceClientImpl.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/workspace/WorkspaceServiceClientImpl.java index 8254ca3eb27..aa36bff00e1 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/workspace/WorkspaceServiceClientImpl.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/workspace/WorkspaceServiceClientImpl.java @@ -25,6 +25,7 @@ import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; +import org.eclipse.che.api.workspace.shared.dto.WsAgentHealthStateDto; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.rest.AsyncRequestFactory; import org.eclipse.che.ide.rest.DtoUnmarshallerFactory; @@ -381,4 +382,11 @@ public void makeCall(AsyncCallback callback) { } }); } + + @Override + public Promise getWsAgentState(String workspaceId) { + return asyncRequestFactory.createGetRequest(baseHttpUrl + '/' + workspaceId + "/check") + .header(ACCEPT, APPLICATION_JSON) + .send(dtoUnmarshallerFactory.newUnmarshaller(WsAgentHealthStateDto.class)); + } } diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/ProjectTypeComponent.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/ProjectTypeComponent.java index c81a9755305..5861487b3cc 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/ProjectTypeComponent.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/ProjectTypeComponent.java @@ -14,13 +14,13 @@ import com.google.inject.Inject; import com.google.inject.Singleton; -import org.eclipse.che.ide.api.project.ProjectTypeServiceClient; import org.eclipse.che.api.project.shared.dto.ProjectTypeDto; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.promises.client.PromiseError; import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.component.WsAgentComponent; +import org.eclipse.che.ide.api.project.ProjectTypeServiceClient; import org.eclipse.che.ide.api.project.type.ProjectTypeRegistry; import java.util.List; diff --git a/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/WsAgentHealthStateDto.java b/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/WsAgentHealthStateDto.java new file mode 100644 index 00000000000..e58f4c48b66 --- /dev/null +++ b/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/WsAgentHealthStateDto.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.workspace.shared.dto; + +import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; +import org.eclipse.che.dto.shared.DTO; + +/** + * Describes status of the ws agent. + * + * @author Vitalii Parfonov + * @author Valeriy Svydenko + */ +@DTO +public interface WsAgentHealthStateDto { + + void setWorkspaceStatus(WorkspaceStatus status); + + WsAgentHealthStateDto withWorkspaceStatus(WorkspaceStatus status); + + /** + * Returns the status of the current workspace instance. + *

+ *

All the workspaces which are stopped have runtime + * are considered {@link WorkspaceStatus#STOPPED}. + */ + WorkspaceStatus getWorkspaceStatus(); + + void setCode(int code); + + /** Returns HTTP status code, see {@code javax.ws.rs.core.Response.Status} */ + int getCode(); + + WsAgentHealthStateDto withCode(int code); + + void setReason(String reason); + + /** Returns reason of the state. */ + String getReason(); + + WsAgentHealthStateDto withReason(String reason); +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/WsAgentHealthChecker.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/WsAgentHealthChecker.java new file mode 100644 index 00000000000..c047a4416f1 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/WsAgentHealthChecker.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.agent.server; + +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.workspace.shared.dto.WsAgentHealthStateDto; + +/** + * Describes a mechanism for checking ws agent's state. + * It needs when Workspace Agent (WS Agent) stops to respond and projects disappear from the project tree, + * and the page shows 'Cannot get project types' error. + * It may happens for example, when OOM happens in a WS Agent and kernel kills WS Agent process. + * Problem here that we can't detect properly OOM error but we can check if WS Agent is alive for user. + *

+ * If client (IDE) lost WebSocket connection to the WS Agent - in this case IDE will request some other service in our infrastructure to + * check WS Agent state, here we have two ways: + *

+ * 1/ WS Agent was shutdown by OS. If it not available for this service too, a user should be notified that the workspace is broken + * probably because of OOM (it will be just suggest because we not sure about reason). + *

+ * 2/ WS Agent is working well and is accessible for our infrastructure, in this case user has networking problem. It can be not + * well configured proxy server or other problems which are not related to our responsibility. + * + * @author Vitalii Parfonov + */ +public interface WsAgentHealthChecker { + + /** + * Verifies if ws agent is alive. + * + * @param machine + * machine instance + * @return state of the ws agent + * @throws ServerException + * if internal server error occurred + */ + WsAgentHealthStateDto check(Machine machine) throws ServerException; +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/WsAgentHealthCheckerImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/WsAgentHealthCheckerImpl.java new file mode 100644 index 00000000000..8561141ee67 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/WsAgentHealthCheckerImpl.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.agent.server; + +import org.eclipse.che.api.core.ApiException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.core.model.machine.Server; +import org.eclipse.che.api.core.rest.HttpJsonRequest; +import org.eclipse.che.api.core.rest.HttpJsonResponse; +import org.eclipse.che.api.workspace.shared.dto.WsAgentHealthStateDto; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.util.Map; + +import static javax.ws.rs.core.Response.Status.NOT_FOUND; +import static javax.ws.rs.core.Response.Status.SERVICE_UNAVAILABLE; +import static org.eclipse.che.api.machine.shared.Constants.WSAGENT_REFERENCE; +import static org.eclipse.che.dto.server.DtoFactory.newDto; + +/** + * Mechanism for checking workspace agent's state. + * + * @author Vitalii Parfonov + * @author Valeriy Svydenko + */ +@Singleton +public class WsAgentHealthCheckerImpl implements WsAgentHealthChecker { + protected static final Logger LOG = LoggerFactory.getLogger(WsAgentHealthCheckerImpl.class); + + private final WsAgentPingRequestFactory wsAgentPingRequestFactory; + + @Inject + public WsAgentHealthCheckerImpl(WsAgentPingRequestFactory wsAgentPingRequestFactory) { + this.wsAgentPingRequestFactory = wsAgentPingRequestFactory; + } + + @Override + public WsAgentHealthStateDto check(Machine machine) throws ServerException { + Server wsAgent = getWsAgent(machine); + final WsAgentHealthStateDto agentHealthStateDto = newDto(WsAgentHealthStateDto.class); + if (wsAgent == null) { + return agentHealthStateDto.withCode(NOT_FOUND.getStatusCode()) + .withReason("Workspace Agent not available"); + } + try { + final HttpJsonRequest pingRequest = createPingRequest(machine); + final HttpJsonResponse response = pingRequest.request(); + return agentHealthStateDto.withCode(response.getResponseCode()); + } catch (ApiException | IOException e) { + return agentHealthStateDto.withCode(SERVICE_UNAVAILABLE.getStatusCode()) + .withReason(e.getMessage()); + } + } + + protected HttpJsonRequest createPingRequest(Machine machine) throws ServerException { + return wsAgentPingRequestFactory.createRequest(machine); + } + + private Server getWsAgent(Machine machine) { + final Map servers = machine.getRuntime().getServers(); + for (Server server : servers.values()) { + if (WSAGENT_REFERENCE.equals(server.getRef())) { + return server; + } + } + return null; + } + +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/WsAgentPingRequestFactory.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/WsAgentPingRequestFactory.java new file mode 100644 index 00000000000..6feef522e83 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/agent/server/WsAgentPingRequestFactory.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.agent.server; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.core.model.machine.Server; +import org.eclipse.che.api.core.rest.HttpJsonRequest; +import org.eclipse.che.api.core.rest.HttpJsonRequestFactory; +import org.eclipse.che.api.machine.shared.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Named; +import javax.ws.rs.HttpMethod; +import java.util.Map; + +import static com.google.common.base.Strings.isNullOrEmpty; + +/** + * Creates a request for pinging Workspace Agent. + * + * @author Valeriy Svydenko + */ +@Singleton +public class WsAgentPingRequestFactory { + protected static final Logger LOG = LoggerFactory.getLogger(WsAgentPingRequestFactory.class); + + private static final String WS_AGENT_SERVER_NOT_FOUND_ERROR = "Workspace agent server not found in dev machine."; + private static final String WS_AGENT_URL_IS_NULL_OR_EMPTY_ERROR = "URL of Workspace Agent is null or empty."; + + private final HttpJsonRequestFactory httpJsonRequestFactory; + private final int wsAgentPingConnectionTimeoutMs; + + @Inject + public WsAgentPingRequestFactory(HttpJsonRequestFactory httpJsonRequestFactory, + @Named("machine.ws_agent.ping_conn_timeout_ms") int wsAgentPingConnectionTimeoutMs) { + this.httpJsonRequestFactory = httpJsonRequestFactory; + this.wsAgentPingConnectionTimeoutMs = wsAgentPingConnectionTimeoutMs; + } + + /** + * Creates request which can check if workspace agent is pinging. + * + * @param machine + * machine instance + * @return instance of {@link HttpJsonRequest} + * @throws ServerException + * if internal server error occurred + */ + public HttpJsonRequest createRequest(Machine machine) throws ServerException { + Map servers = machine.getRuntime().getServers(); + Server wsAgentServer = servers.get(Constants.WS_AGENT_PORT); + if (wsAgentServer == null) { + LOG.error("{} WorkspaceId: {}, DevMachine Id: {}, found servers: {}", + WS_AGENT_SERVER_NOT_FOUND_ERROR, machine.getWorkspaceId(), machine.getId(), servers); + throw new ServerException(WS_AGENT_SERVER_NOT_FOUND_ERROR); + } + String wsAgentPingUrl = wsAgentServer.getProperties().getInternalUrl(); + if (isNullOrEmpty(wsAgentPingUrl)) { + LOG.error(WS_AGENT_URL_IS_NULL_OR_EMPTY_ERROR); + throw new ServerException(WS_AGENT_URL_IS_NULL_OR_EMPTY_ERROR); + } + // since everrest mapped on the slash in case of it absence + // we will always obtain not found response + if (!wsAgentPingUrl.endsWith("/")) { + wsAgentPingUrl = wsAgentPingUrl.concat("/"); + } + return httpJsonRequestFactory.fromUrl(wsAgentPingUrl) + .setMethod(HttpMethod.GET) + .setTimeout(wsAgentPingConnectionTimeoutMs); + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java index 8cd4dc94a35..bfa25feaa25 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java @@ -20,14 +20,17 @@ import com.google.common.collect.Maps; +import org.eclipse.che.api.agent.server.WsAgentHealthChecker; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.rest.Service; import org.eclipse.che.api.core.rest.annotations.GenerateLink; import org.eclipse.che.api.machine.server.model.impl.CommandImpl; +import org.eclipse.che.api.machine.server.model.impl.MachineImpl; import org.eclipse.che.api.machine.shared.dto.CommandDto; import org.eclipse.che.api.machine.shared.dto.SnapshotDto; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; @@ -37,6 +40,7 @@ import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; +import org.eclipse.che.api.workspace.shared.dto.WsAgentHealthStateDto; import org.eclipse.che.commons.env.EnvironmentContext; import javax.inject.Inject; @@ -61,10 +65,12 @@ import static java.util.Collections.emptyMap; import static java.util.stream.Collectors.toList; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static javax.ws.rs.core.Response.Status.NOT_FOUND; import static org.eclipse.che.api.workspace.server.DtoConverter.asDto; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_CREATE_WORKSPACE; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_GET_BY_NAMESPACE; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_GET_WORKSPACES; +import static org.eclipse.che.dto.server.DtoFactory.newDto; /** * Defines Workspace REST API. @@ -78,6 +84,7 @@ public class WorkspaceService extends Service { private final WorkspaceManager workspaceManager; private final WorkspaceValidator validator; + private final WsAgentHealthChecker agentHealthChecker; private final WorkspaceServiceLinksInjector linksInjector; @Context @@ -86,9 +93,11 @@ public class WorkspaceService extends Service { @Inject public WorkspaceService(WorkspaceManager workspaceManager, WorkspaceValidator validator, + WsAgentHealthChecker agentHealthChecker, WorkspaceServiceLinksInjector workspaceServiceLinksInjector) { this.workspaceManager = workspaceManager; this.validator = validator; + this.agentHealthChecker = agentHealthChecker; this.linksInjector = workspaceServiceLinksInjector; } @@ -656,6 +665,33 @@ public void deleteProject(@ApiParam("The workspace id") } } + @GET + @Path("/{id}/check") + @Produces(APPLICATION_JSON) + @ApiOperation(value = "Get state of the workspace agent by the workspace id") + @ApiResponses({@ApiResponse(code = 200, message = "The response contains requested workspace entity"), + @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{ + final WorkspaceImpl workspace = workspaceManager.getWorkspace(key); + if (WorkspaceStatus.RUNNING != workspace.getStatus()) { + return newDto(WsAgentHealthStateDto.class).withWorkspaceStatus(workspace.getStatus()); + } + + final MachineImpl devMachine = workspace.getRuntime().getDevMachine(); + if (devMachine == null) { + return newDto(WsAgentHealthStateDto.class) + .withWorkspaceStatus(workspace.getStatus()) + .withCode(NOT_FOUND.getStatusCode()) + .withReason("Workspace Agent isn't available if Dev machine isn't RUNNING"); + } + + final WsAgentHealthStateDto check = agentHealthChecker.check(devMachine); + check.setWorkspaceStatus(workspace.getStatus()); + return check; + } + private static Map parseAttrs(List attributes) throws BadRequestException { if (attributes == null) { return emptyMap(); diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/launcher/WsAgentLauncherImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/launcher/WsAgentLauncherImpl.java index 6ce7cdee867..2cf3eaba1dc 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/launcher/WsAgentLauncherImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/launcher/WsAgentLauncherImpl.java @@ -10,21 +10,19 @@ *******************************************************************************/ package org.eclipse.che.api.workspace.server.launcher; +import org.eclipse.che.api.agent.server.WsAgentPingRequestFactory; import org.eclipse.che.api.agent.server.launcher.AgentLauncher; import org.eclipse.che.api.agent.shared.model.Agent; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.core.model.machine.Server; import org.eclipse.che.api.core.rest.HttpJsonRequest; -import org.eclipse.che.api.core.rest.HttpJsonRequestFactory; import org.eclipse.che.api.core.rest.HttpJsonResponse; import org.eclipse.che.api.environment.server.MachineProcessManager; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.model.impl.CommandImpl; import org.eclipse.che.api.machine.server.spi.Instance; -import org.eclipse.che.api.machine.shared.Constants; import org.eclipse.che.commons.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,10 +31,8 @@ import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; -import javax.ws.rs.HttpMethod; import java.io.IOException; import java.net.HttpURLConnection; -import java.util.Map; import static com.google.common.base.MoreObjects.firstNonNull; import static org.eclipse.che.api.workspace.shared.Constants.WS_AGENT_PROCESS_NAME; @@ -51,32 +47,27 @@ public class WsAgentLauncherImpl implements AgentLauncher { protected static final Logger LOG = LoggerFactory.getLogger(WsAgentLauncherImpl.class); - private static final String WS_AGENT_PROCESS_OUTPUT_CHANNEL = "workspace:%s:ext-server:output"; - private static final String WS_AGENT_SERVER_NOT_FOUND_ERROR = "Workspace agent server not found in dev machine."; - - protected static final String DEFAULT_WS_AGENT_RUN_COMMAND = "~/che/ws-agent/bin/catalina.sh run"; + private static final String WS_AGENT_PROCESS_OUTPUT_CHANNEL = "workspace:%s:ext-server:output"; + protected static final String DEFAULT_WS_AGENT_RUN_COMMAND = "~/che/ws-agent/bin/catalina.sh run"; private final Provider machineProcessManagerProvider; - private final HttpJsonRequestFactory httpJsonRequestFactory; + private final WsAgentPingRequestFactory wsAgentPingRequestFactory; private final long wsAgentMaxStartTimeMs; private final long wsAgentPingDelayMs; - private final int wsAgentPingConnectionTimeoutMs; private final String pingTimedOutErrorMessage; - private final String wsAgentRunCommand; + private final String wsAgentRunCommand; @Inject public WsAgentLauncherImpl(Provider machineProcessManagerProvider, - HttpJsonRequestFactory httpJsonRequestFactory, + WsAgentPingRequestFactory wsAgentPingRequestFactory, @Nullable @Named("machine.ws_agent.run_command") String wsAgentRunCommand, @Named("machine.ws_agent.max_start_time_ms") long wsAgentMaxStartTimeMs, @Named("machine.ws_agent.ping_delay_ms") long wsAgentPingDelayMs, - @Named("machine.ws_agent.ping_conn_timeout_ms") int wsAgentPingConnectionTimeoutMs, @Named("machine.ws_agent.ping_timed_out_error_msg") String pingTimedOutErrorMessage) { this.machineProcessManagerProvider = machineProcessManagerProvider; - this.httpJsonRequestFactory = httpJsonRequestFactory; + this.wsAgentPingRequestFactory = wsAgentPingRequestFactory; this.wsAgentMaxStartTimeMs = wsAgentMaxStartTimeMs; this.wsAgentPingDelayMs = wsAgentPingDelayMs; - this.wsAgentPingConnectionTimeoutMs = wsAgentPingConnectionTimeoutMs; this.pingTimedOutErrorMessage = pingTimedOutErrorMessage; this.wsAgentRunCommand = wsAgentRunCommand; } @@ -143,22 +134,7 @@ public static String getWsAgentProcessOutputChannel(String workspaceId) { // forms the ping request based on information about the machine. protected HttpJsonRequest createPingRequest(Instance machine) throws ServerException { - Map servers = machine.getRuntime().getServers(); - Server wsAgentServer = servers.get(Constants.WS_AGENT_PORT); - if (wsAgentServer == null) { - LOG.error("{} WorkspaceId: {}, DevMachine Id: {}, found servers: {}", - WS_AGENT_SERVER_NOT_FOUND_ERROR, machine.getWorkspaceId(), machine.getId(), servers); - throw new ServerException(WS_AGENT_SERVER_NOT_FOUND_ERROR); - } - String wsAgentPingUrl = wsAgentServer.getProperties().getInternalUrl(); - // since everrest mapped on the slash in case of it absence - // we will always obtain not found response - if (!wsAgentPingUrl.endsWith("/")) { - wsAgentPingUrl = wsAgentPingUrl.concat("/"); - } - return httpJsonRequestFactory.fromUrl(wsAgentPingUrl) - .setMethod(HttpMethod.GET) - .setTimeout(wsAgentPingConnectionTimeoutMs); + return wsAgentPingRequestFactory.createRequest(machine); } private boolean pingWsAgent(HttpJsonRequest wsAgentPingRequest) throws ServerException { diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/agent/server/WsAgentHealthCheckerTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/agent/server/WsAgentHealthCheckerTest.java new file mode 100644 index 00000000000..81fb5ea5e86 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/agent/server/WsAgentHealthCheckerTest.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.agent.server; + +import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.core.model.machine.MachineRuntimeInfo; +import org.eclipse.che.api.core.model.machine.Server; +import org.eclipse.che.api.core.rest.HttpJsonRequest; +import org.eclipse.che.api.core.rest.HttpJsonResponse; +import org.eclipse.che.api.workspace.shared.dto.WsAgentHealthStateDto; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Collections.emptyMap; +import static javax.ws.rs.core.Response.Status.NOT_FOUND; +import static javax.ws.rs.core.Response.Status.SERVICE_UNAVAILABLE; +import static org.eclipse.che.api.machine.shared.Constants.WSAGENT_REFERENCE; +import static org.eclipse.che.api.machine.shared.Constants.WS_AGENT_PORT; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; + +/** + * @author Valeriy Svydenko + */ +@Listeners(value = {MockitoTestNGListener.class}) +public class WsAgentHealthCheckerTest { + private final static String WS_AGENT_SERVER_URL = "ws_agent"; + + @Mock + private WsAgentPingRequestFactory wsAgentPingRequestFactory; + @Mock + private Machine devMachine; + @Mock + private MachineRuntimeInfo machineRuntimeInfo; + @Mock + private Server server; + @Mock + private HttpJsonRequest httpJsonRequest; + @Mock + private HttpJsonResponse httpJsonResponse; + + private Map servers = new HashMap<>(1); + + private WsAgentHealthCheckerImpl checker; + + @BeforeMethod + public void setUp() throws Exception { + servers.put(WSAGENT_REFERENCE, server); + servers.put(WS_AGENT_PORT, server); + + when(server.getRef()).thenReturn(WSAGENT_REFERENCE); + when(server.getUrl()).thenReturn(WS_AGENT_SERVER_URL); + when(wsAgentPingRequestFactory.createRequest(devMachine)).thenReturn(httpJsonRequest); + + checker = new WsAgentHealthCheckerImpl(wsAgentPingRequestFactory); + + when(httpJsonRequest.setMethod(any())).thenReturn(httpJsonRequest); + when(httpJsonRequest.setTimeout(anyInt())).thenReturn(httpJsonRequest); + when(httpJsonRequest.request()).thenReturn(httpJsonResponse); + + when(httpJsonResponse.getResponseCode()).thenReturn(200); + when(httpJsonResponse.asString()).thenReturn("response"); + + when(devMachine.getRuntime()).thenReturn(machineRuntimeInfo); + doReturn(servers).when(machineRuntimeInfo).getServers(); + } + + @Test + public void stateShouldBeReturnedWithStatusNotFoundIfWorkspaceAgentIsNotExist() throws Exception { + when(machineRuntimeInfo.getServers()).thenReturn(emptyMap()); + + WsAgentHealthStateDto result = checker.check(devMachine); + + assertEquals(NOT_FOUND.getStatusCode(), result.getCode()); + } + + @Test + public void returnStateWithNotFoundCode() throws Exception { + doReturn(emptyMap()).when(machineRuntimeInfo).getServers(); + + final WsAgentHealthStateDto check = checker.check(devMachine); + assertEquals(NOT_FOUND.getStatusCode(), check.getCode()); + assertEquals("Workspace Agent not available", check.getReason()); + } + + @Test + public void pingRequestToWsAgentShouldBeSent() throws Exception { + final WsAgentHealthStateDto result = checker.check(devMachine); + + verify(httpJsonRequest).request(); + + assertEquals(200, result.getCode()); + } + + @Test + public void returnResultWithUnavailableStateIfDoNotGetResponseFromWsAgent() throws Exception { + doThrow(IOException.class).when(httpJsonRequest).request(); + final WsAgentHealthStateDto result = checker.check(devMachine); + + verify(httpJsonRequest).request(); + + assertEquals(SERVICE_UNAVAILABLE.getStatusCode(), result.getCode()); + } + +} diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/agent/server/WsAgentPingRequestFactoryTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/agent/server/WsAgentPingRequestFactoryTest.java new file mode 100644 index 00000000000..c77d3a14e25 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/agent/server/WsAgentPingRequestFactoryTest.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.agent.server; + +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.core.model.machine.MachineRuntimeInfo; +import org.eclipse.che.api.core.model.machine.Server; +import org.eclipse.che.api.core.model.machine.ServerProperties; +import org.eclipse.che.api.core.rest.HttpJsonRequest; +import org.eclipse.che.api.core.rest.HttpJsonRequestFactory; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +import javax.ws.rs.HttpMethod; +import java.util.HashMap; +import java.util.Map; + +import static org.eclipse.che.api.machine.shared.Constants.WS_AGENT_PORT; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@Listeners(value = {MockitoTestNGListener.class}) +public class WsAgentPingRequestFactoryTest { + private final static int WS_AGENT_PING_CONNECTION_TIMEOUT_MS = 20; + private final static String WS_AGENT_URL_IS_NOT_VALID = "URL of Workspace Agent is null or empty."; + private static final String WS_AGENT_SERVER_NOT_FOUND_ERROR = "Workspace agent server not found in dev machine."; + private final static String WS_AGENT_SERVER_URL = "ws_agent"; + + @Mock + private HttpJsonRequestFactory httpJsonRequestFactory; + @Mock + private HttpJsonRequest httpJsonRequest; + @Mock + private Machine devMachine; + @Mock + private Server server; + @Mock + private MachineRuntimeInfo machineRuntimeInfo; + @Mock + private ServerProperties serverProperties; + + private Map servers = new HashMap<>(1); + + private WsAgentPingRequestFactory factory; + + @BeforeMethod + public void setUp() throws Exception { + factory = new WsAgentPingRequestFactory(httpJsonRequestFactory, WS_AGENT_PING_CONNECTION_TIMEOUT_MS); + + servers.put(WS_AGENT_SERVER_URL, server); + servers.put(WS_AGENT_PORT, server); + + when(httpJsonRequestFactory.fromUrl(anyString())).thenReturn(httpJsonRequest); + when(httpJsonRequest.setMethod(HttpMethod.GET)).thenReturn(httpJsonRequest); + when(server.getProperties()).thenReturn(serverProperties); + when(serverProperties.getInternalUrl()).thenReturn(WS_AGENT_SERVER_URL); + when(devMachine.getRuntime()).thenReturn(machineRuntimeInfo); + doReturn(servers).when(machineRuntimeInfo).getServers(); + } + + + @Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = WS_AGENT_SERVER_NOT_FOUND_ERROR) + public void throwsServerExceptionWhenWsAgentIsNull() throws Exception { + servers.clear(); + + factory.createRequest(devMachine); + } + + @Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = WS_AGENT_URL_IS_NOT_VALID) + public void throwsServerExceptionWhenWsServerUrlIsNull() throws Exception { + when(serverProperties.getInternalUrl()).thenReturn(null); + + factory.createRequest(devMachine); + } + + @Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = WS_AGENT_URL_IS_NOT_VALID) + public void throwsServerExceptionWhenWsServerUrlIsEmpty() throws Exception { + when(serverProperties.getInternalUrl()).thenReturn(""); + + factory.createRequest(devMachine); + } + + @Test + public void pingRequestShouldBeCreated() throws Exception { + factory.createRequest(devMachine); + + verify(httpJsonRequestFactory).fromUrl(WS_AGENT_SERVER_URL + '/'); + verify(httpJsonRequest).setMethod(javax.ws.rs.HttpMethod.GET); + verify(httpJsonRequest).setTimeout(WS_AGENT_PING_CONNECTION_TIMEOUT_MS); + } +} diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java index 4c726a89e18..0d8c27ae319 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java @@ -17,6 +17,7 @@ import org.eclipse.che.account.shared.model.Account; import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.agent.server.WsAgentHealthChecker; import org.eclipse.che.api.core.model.machine.MachineStatus; import org.eclipse.che.api.core.model.project.ProjectConfig; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; @@ -48,6 +49,7 @@ import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; +import org.eclipse.che.api.workspace.shared.dto.WsAgentHealthStateDto; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.dto.server.DtoFactory; @@ -134,13 +136,16 @@ public class WorkspaceServiceTest { private MachineProcessManager machineProcessManager; @Mock private WorkspaceValidator validator; + @Mock + private WsAgentHealthChecker wsAgentHealthChecker; - private WorkspaceService service; + private WorkspaceService service; @BeforeMethod public void setup() { service = new WorkspaceService(wsManager, validator, + wsAgentHealthChecker, new WorkspaceServiceLinksInjector(new MachineServiceLinksInjector())); } @@ -358,7 +363,7 @@ public void shouldDeleteWorkspace() throws Exception { @Test public void shouldStartWorkspace() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); - when(wsManager.startWorkspace(any(), any(), any())).thenReturn(workspace); + when(wsManager.startWorkspace(any(), any(), any())).thenReturn(workspace); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() @@ -833,6 +838,46 @@ public void shouldReturnSnapshotsOnGetSnapshot() throws Exception { verify(wsManager).getSnapshot(workspaceId); } + @Test + public void stateOfWsAgentShouldBeChecked() throws Exception { + final WorkspaceImpl workspace = createWorkspace(createConfigDto()); + workspace.setStatus(RUNNING); + + WsAgentHealthStateDto wsAgentState = newDto(WsAgentHealthStateDto.class); + WorkspaceRuntimeImpl runtime = mock(WorkspaceRuntimeImpl.class); + MachineImpl machine = mock(MachineImpl.class); + when(runtime.getDevMachine()).thenReturn(machine); + when(wsAgentHealthChecker.check(machine)).thenReturn(wsAgentState); + + workspace.setRuntime(runtime); + + when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); + + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .get(SECURE_PATH + "/workspace/" + workspace.getId() + "/check"); + + verify(wsAgentHealthChecker).check(machine); + assertEquals(RUNNING, wsAgentState.getWorkspaceStatus()); + assertEquals(200, response.getStatusCode()); + } + + @Test + public void stateOfWsAgentShouldNotBeCheckedIfWsIsNotRunning() throws Exception { + final WorkspaceImpl workspace = createWorkspace(createConfigDto()); + workspace.setStatus(STARTING); + when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); + + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .get(SECURE_PATH + "/workspace/" + workspace.getId() + "/check"); + + verify(wsAgentHealthChecker, never()).check(any()); + assertEquals(200, response.getStatusCode()); + } + @Test public void shouldReturnEmptyListIfNotSnapshotsFound() throws Exception { // given @@ -908,7 +953,7 @@ private static WorkspaceConfigDto createConfigDto() { final WorkspaceConfigImpl config = WorkspaceConfigImpl.builder() .setName("dev-workspace") .setDefaultEnv("dev-env") - .setEnvironments(singletonMap("dev-env",new EnvironmentImpl(createEnvDto()))) + .setEnvironments(singletonMap("dev-env", new EnvironmentImpl(createEnvDto()))) .setCommands(singletonList(createCommandDto())) .setProjects(singletonList(createProjectDto())) .build(); diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/launcher/WsAgentLauncherImplTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/launcher/WsAgentLauncherImplTest.java index 56802376782..33cc3d35d78 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/launcher/WsAgentLauncherImplTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/launcher/WsAgentLauncherImplTest.java @@ -10,13 +10,13 @@ *******************************************************************************/ package org.eclipse.che.api.workspace.server.launcher; +import org.eclipse.che.api.agent.server.WsAgentPingRequestFactory; import org.eclipse.che.api.agent.shared.model.Agent; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.machine.Command; import org.eclipse.che.api.core.model.machine.Server; -import org.eclipse.che.api.core.model.machine.ServerProperties; import org.eclipse.che.api.core.rest.HttpJsonRequest; import org.eclipse.che.api.core.rest.HttpJsonRequestFactory; import org.eclipse.che.api.core.rest.HttpJsonResponse; @@ -36,8 +36,6 @@ import org.testng.annotations.Listeners; import org.testng.annotations.Test; -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.UriBuilder; import java.io.IOException; import java.net.HttpURLConnection; import java.util.Collections; @@ -58,33 +56,34 @@ public class WsAgentLauncherImplTest { private static final String WS_AGENT_PORT = Constants.WS_AGENT_PORT; private static final long WS_AGENT_MAX_START_TIME_MS = 1000; private static final long WS_AGENT_PING_DELAY_MS = 1; - private static final int WS_AGENT_PING_CONN_TIMEOUT_MS = 1; private static final String WS_AGENT_SERVER_LOCATION = "ws-agent.com:456789/"; private static final String WS_AGENT_SERVER_URL = "http://" + WS_AGENT_SERVER_LOCATION; private static final String WS_AGENT_SERVER_LOCATION_EXT = "ws-agent-ext.com:456789/"; private static final String WS_AGENT_SERVER_URL_EXT = "http://" + WS_AGENT_SERVER_LOCATION; private static final ServerPropertiesImpl SERVER_PROPERTIES = new ServerPropertiesImpl(null, - WS_AGENT_SERVER_LOCATION, - WS_AGENT_SERVER_URL); + WS_AGENT_SERVER_LOCATION, + WS_AGENT_SERVER_URL); private static final ServerImpl SERVER = new ServerImpl("ref", - "http", - WS_AGENT_SERVER_LOCATION_EXT, - WS_AGENT_SERVER_URL_EXT, - SERVER_PROPERTIES); + "http", + WS_AGENT_SERVER_LOCATION_EXT, + WS_AGENT_SERVER_URL_EXT, + SERVER_PROPERTIES); private static final String WS_AGENT_TIMED_OUT_MESSAGE = "timeout error message"; @Mock - private MachineProcessManager machineProcessManager; + private MachineProcessManager machineProcessManager; @Mock - private HttpJsonRequestFactory requestFactory; + private HttpJsonRequestFactory requestFactory; @Mock - private Instance machine; + private Instance machine; @Mock - private HttpJsonResponse pingResponse; + private HttpJsonResponse pingResponse; @Mock - private MachineRuntimeInfoImpl machineRuntime; + private MachineRuntimeInfoImpl machineRuntime; @Mock - private Agent agent; + private WsAgentPingRequestFactory wsAgentPingRequestFactory; + @Mock + private Agent agent; private HttpJsonRequest pingRequest; private WsAgentLauncherImpl wsAgentLauncher; @@ -92,10 +91,9 @@ public class WsAgentLauncherImplTest { @BeforeMethod public void setUp() throws Exception { wsAgentLauncher = new WsAgentLauncherImpl(() -> machineProcessManager, - requestFactory, null, + wsAgentPingRequestFactory, null, WS_AGENT_MAX_START_TIME_MS, WS_AGENT_PING_DELAY_MS, - WS_AGENT_PING_CONN_TIMEOUT_MS, WS_AGENT_TIMED_OUT_MESSAGE ); pingRequest = Mockito.mock(HttpJsonRequest.class, new SelfReturningAnswer()); @@ -105,6 +103,7 @@ public void setUp() throws Exception { when(machine.getRuntime()).thenReturn(machineRuntime); doReturn(Collections.singletonMap(WS_AGENT_PORT, SERVER)).when(machineRuntime).getServers(); when(requestFactory.fromUrl(anyString())).thenReturn(pingRequest); + when(wsAgentPingRequestFactory.createRequest(machine)).thenReturn(pingRequest); when(pingRequest.request()).thenReturn(pingResponse); when(pingResponse.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); } @@ -126,11 +125,6 @@ public void shouldStartWsAgentUsingMachineExec() throws Exception { public void shouldPingWsAgentAfterStart() throws Exception { wsAgentLauncher.launch(machine, agent); - verify(requestFactory).fromUrl(UriBuilder.fromUri(WS_AGENT_SERVER_URL) - .build() - .toString()); - verify(pingRequest).setMethod(HttpMethod.GET); - verify(pingRequest).setTimeout(WS_AGENT_PING_CONN_TIMEOUT_MS); verify(pingRequest).request(); verify(pingResponse).getResponseCode(); } @@ -144,9 +138,6 @@ public void shouldPingWsAgentMultipleTimesAfterStartIfPingFailsWithException() t wsAgentLauncher.launch(machine, agent); - verify(requestFactory).fromUrl(WS_AGENT_SERVER_URL); - verify(pingRequest).setMethod(HttpMethod.GET); - verify(pingRequest).setTimeout(WS_AGENT_PING_CONN_TIMEOUT_MS); verify(pingRequest, times(4)).request(); verify(pingResponse).getResponseCode(); } @@ -159,9 +150,6 @@ public void shouldPingWsAgentMultipleTimesAfterStartIfPingReturnsNotOKResponseCo wsAgentLauncher.launch(machine, agent); - verify(requestFactory).fromUrl(WS_AGENT_SERVER_URL); - verify(pingRequest).setMethod(HttpMethod.GET); - verify(pingRequest).setTimeout(WS_AGENT_PING_CONN_TIMEOUT_MS); verify(pingRequest, times(3)).request(); verify(pingResponse, times(3)).getResponseCode(); } @@ -226,18 +214,11 @@ public void shouldThrowExceptionIfMachineManagerExecInDevMachineThrowsBadRequest } @Test(expectedExceptions = ServerException.class, - expectedExceptionsMessageRegExp = WS_AGENT_TIMED_OUT_MESSAGE) + expectedExceptionsMessageRegExp = WS_AGENT_TIMED_OUT_MESSAGE) public void shouldThrowMachineExceptionIfPingsWereUnsuccessfulTooLong() throws Exception { when(pingRequest.request()).thenThrow(new ServerException("")); wsAgentLauncher.launch(machine, agent); } - @Test(expectedExceptions = ServerException.class, - expectedExceptionsMessageRegExp = "Workspace agent server not found in dev machine.") - public void shouldThrowExceptionIfWsAgentNotFound() throws Exception { - doReturn(Collections.emptyMap()).when(machineRuntime).getServers(); - - wsAgentLauncher.launch(machine, agent); - } }