/******************************************************************************* * 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.api.agent; 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.rest.HttpJsonRequest; 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.commons.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import java.io.IOException; import java.net.HttpURLConnection; import static com.google.common.base.MoreObjects.firstNonNull; import static org.eclipse.che.api.workspace.shared.Constants.WS_AGENT_PROCESS_NAME; /** * Starts ws agent in the machine and waits until ws agent sends notification about its start. * * @author Alexander Garagatyi * @author Anatolii Bazko */ @Singleton public class WsAgentLauncher implements AgentLauncher { protected static final Logger LOG = LoggerFactory.getLogger(WsAgentLauncher.class); 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<MachineProcessManager> machineProcessManagerProvider; private final WsAgentPingRequestFactory wsAgentPingRequestFactory; private final long wsAgentMaxStartTimeMs; private final long wsAgentPingDelayMs; private final String pingTimedOutErrorMessage; private final String wsAgentRunCommand; @Inject public WsAgentLauncher(Provider<MachineProcessManager> machineProcessManagerProvider, WsAgentPingRequestFactory wsAgentPingRequestFactory, @Nullable @Named("machine.ws_agent.run_command") String wsAgentRunCommand, @Named("che.workspace.agent.dev.max_start_time_ms") long wsAgentMaxStartTimeMs, @Named("che.workspace.agent.dev.ping_delay_ms") long wsAgentPingDelayMs, @Named("che.workspace.agent.dev.ping_timeout_error_msg") String pingTimedOutErrorMessage) { this.machineProcessManagerProvider = machineProcessManagerProvider; this.wsAgentPingRequestFactory = wsAgentPingRequestFactory; this.wsAgentMaxStartTimeMs = wsAgentMaxStartTimeMs; this.wsAgentPingDelayMs = wsAgentPingDelayMs; this.pingTimedOutErrorMessage = pingTimedOutErrorMessage; this.wsAgentRunCommand = wsAgentRunCommand; } @Override public String getAgentId() { return "org.eclipse.che.ws-agent"; } @Override public String getMachineType() { return "docker"; } @Override public void launch(Instance machine, Agent agent) throws ServerException { final HttpJsonRequest wsAgentPingRequest; try { wsAgentPingRequest = createPingRequest(machine); } catch (ServerException e) { throw new MachineException(e.getServiceError()); } String script = agent.getScript() + "\n" + firstNonNull(wsAgentRunCommand, DEFAULT_WS_AGENT_RUN_COMMAND); final String wsAgentPingUrl = wsAgentPingRequest.getUrl(); try { // for server side type of command mean nothing // but we will use it as marker on // client side for track this command CommandImpl command = new CommandImpl(getAgentId(), script, WS_AGENT_PROCESS_NAME); machineProcessManagerProvider.get().exec(machine.getWorkspaceId(), machine.getId(), command, getWsAgentProcessOutputChannel(machine.getWorkspaceId())); final long pingStartTimestamp = System.currentTimeMillis(); LOG.debug("Starts pinging ws agent. Workspace ID:{}. Url:{}. Timestamp:{}", machine.getWorkspaceId(), wsAgentPingUrl, pingStartTimestamp); while (System.currentTimeMillis() - pingStartTimestamp < wsAgentMaxStartTimeMs) { if (pingWsAgent(wsAgentPingRequest)) { return; } else { Thread.sleep(wsAgentPingDelayMs); } } } catch (BadRequestException | ServerException | NotFoundException e) { throw new ServerException(e.getServiceError()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new ServerException("Ws agent pinging is interrupted"); } LOG.error("Fail pinging ws agent with {} url in {} workspace in {} machine on {} node.", wsAgentPingUrl, machine.getWorkspaceId(), machine.getId(), machine.getNode().getHost()); throw new ServerException(pingTimedOutErrorMessage); } public static String getWsAgentProcessOutputChannel(String workspaceId) { return String.format(WS_AGENT_PROCESS_OUTPUT_CHANNEL, workspaceId); } // forms the ping request based on information about the machine. protected HttpJsonRequest createPingRequest(Instance machine) throws ServerException { return wsAgentPingRequestFactory.createRequest(machine); } private boolean pingWsAgent(HttpJsonRequest wsAgentPingRequest) throws ServerException { try { final HttpJsonResponse pingResponse = wsAgentPingRequest.request(); if (pingResponse.getResponseCode() == HttpURLConnection.HTTP_OK) { return true; } } catch (ApiException | IOException ignored) { } return false; } }