/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.api.machine; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; 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.ConfirmCallback; 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.commons.exception.ServerDisconnectedException; import org.eclipse.che.ide.rest.AsyncRequestFactory; import org.eclipse.che.ide.ui.loaders.LoaderPresenter; import org.eclipse.che.ide.util.browser.BrowserUtils; import org.eclipse.che.ide.util.loging.Log; import org.eclipse.che.ide.websocket.MessageBus; import org.eclipse.che.ide.websocket.MessageBusProvider; import org.eclipse.che.ide.websocket.events.ConnectionClosedHandler; import org.eclipse.che.ide.websocket.events.ConnectionErrorHandler; import org.eclipse.che.ide.websocket.events.ConnectionOpenedHandler; import org.eclipse.che.ide.websocket.events.WebSocketClosedEvent; import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; 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 DialogFactory dialogFactory; private final AsyncRequestFactory asyncRequestFactory; private final WorkspaceServiceClient workspaceServiceClient; private final LoaderPresenter loader; private DevMachine devMachine; private MessageBus messageBus; private WsAgentState state; private List<AsyncCallback<MessageBus>> messageBusCallbacks = newArrayList(); private List<AsyncCallback<DevMachine>> devMachineCallbacks = newArrayList(); @Inject public WsAgentStateController(WorkspaceServiceClient workspaceServiceClient, EventBus eventBus, LoaderPresenter loader, MessageBusProvider messageBusProvider, AsyncRequestFactory asyncRequestFactory, DialogFactory dialogFactory) { this.workspaceServiceClient = workspaceServiceClient; this.loader = loader; this.eventBus = eventBus; this.messageBusProvider = messageBusProvider; this.asyncRequestFactory = asyncRequestFactory; this.dialogFactory = dialogFactory; } public void initialize(DevMachine devMachine) { checkNotNull(devMachine, "Developer machine should not be a null"); this.devMachine = devMachine; this.state = STOPPED; loader.show(LoaderPresenter.Phase.STARTING_WORKSPACE_AGENT); checkHttpConnection(); } @Override public void onClose(WebSocketClosedEvent event) { if (STARTED.equals(state)) { checkWsAgentHealth(); } } @Override public void onError() { if (STARTED.equals(state)) { state = STOPPED; eventBus.fireEvent(WsAgentStateEvent.createWsAgentStoppedEvent()); } } @Override public void onOpen() { messageBus.removeOnOpenHandler(this); //need to make sure ready state equals 1 (OPEN) in same situations after opening it still equals 0 (CONNECTING) new Timer() { @Override public void run() { if (messageBus.getReadyState().equals(MessageBus.ReadyState.OPEN)) { cancel(); started(); } } }.scheduleRepeating(300); } /** Returns state of the ws agent */ public WsAgentState getState() { return state; } public Promise<MessageBus> getMessageBus() { return AsyncPromiseHelper.createFromAsyncRequest(callback -> { if (messageBus != null) { callback.onSuccess(messageBus); } else { WsAgentStateController.this.messageBusCallbacks.add(callback); } }); } /** * 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().then(ignored -> { checkWsConnection(); }).catchError(ignored -> { checkWsAgentHealth(); }); } private void started() { state = STARTED; loader.setSuccess(LoaderPresenter.Phase.STARTING_WORKSPACE_AGENT); for (AsyncCallback<MessageBus> callback : messageBusCallbacks) { callback.onSuccess(messageBus); } messageBusCallbacks.clear(); for (AsyncCallback<DevMachine> callback : devMachineCallbacks) { callback.onSuccess(devMachine); } devMachineCallbacks.clear(); eventBus.fireEvent(WsAgentStateEvent.createWsAgentStartedEvent()); } private void checkStateOfWsAgent(WsAgentHealthStateDto agentHealthStateDto) { final int statusCode = agentHealthStateDto.getCode(); final String infoWindowTitle = "Workspace Agent Not Responding"; final boolean reloadPage = true; final boolean createSnapshot = true; final ConfirmCallback stopCallback = new StopCallback(!reloadPage, createSnapshot); final ConfirmCallback stopAndReloadCallback = new StopCallback(reloadPage, !createSnapshot); if (statusCode == 200) { dialogFactory.createChoiceDialog(infoWindowTitle, "Workspace agent is no longer responding. To fix the problem, verify you have a" + " good network connection and restart the workspace.", "Restart", "Close", stopAndReloadCallback, stopCallback).show(); } else { dialogFactory.createChoiceDialog(infoWindowTitle, "Workspace agent is no longer responding. To fix the problem, restart the workspace.", "Restart", "Close", stopAndReloadCallback, stopCallback).show(); } } private class StopCallback implements ConfirmCallback { private final boolean reloadPage; private final boolean createSnapshot; private StopCallback(boolean reloadPage, boolean createSnapshot) { this.reloadPage = reloadPage; this.createSnapshot = createSnapshot; } @Override public void accepted() { workspaceServiceClient.stop(devMachine.getWorkspaceId(), createSnapshot).then(ignored -> { if (reloadPage) { BrowserUtils.reloadPage(false); } }); } } /** * Try to connect via WebSocket connection */ private void checkWsConnection() { if (messageBus != null) { messageBus.cancelReconnection(); } messageBus = messageBusProvider.createMachineMessageBus(devMachine.getWsAgentWebSocketUrl()); messageBus.addOnCloseHandler(this); messageBus.addOnErrorHandler(this); messageBus.addOnOpenHandler(this); } private void checkWsAgentHealth() { workspaceServiceClient.getWsAgentState(devMachine.getWorkspace()).then(agentHealthState -> { if (RUNNING.equals(agentHealthState.getWorkspaceStatus())) { checkStateOfWsAgent(agentHealthState); } }).catchError(new Operation<PromiseError>() { @Override public void apply(PromiseError arg) throws OperationException { if (arg.getCause() instanceof ServerDisconnectedException) { dialogFactory.createMessageDialog("Server Unavailable", "Server is not responding. Your admin must restart it.", null).show(); } Log.error(getClass(), arg.getMessage()); } }); } }