/******************************************************************************* * Copyright (c) 2015, 2016 Pivotal, Inc. * 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: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.dash.cloudfoundry; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.CFAppState; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.CFApplication; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.CFApplicationDetail; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.CFInstanceState; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.CFInstanceStats; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.ClientRequests; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.console.LogType; import org.springframework.ide.eclipse.boot.dash.model.RunState; import org.springframework.ide.eclipse.boot.dash.util.CancelationTokens.CancelationToken; import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil; public class ApplicationRunningStateTracker { // Give time for Diego-enabled apps with health check that may take a while to start // Users can always manually stop the app if it is taking too long to check the run state of the app public static final long APP_START_TIMEOUT = 1000 * 60 * 10; public static final long WAIT_TIME = 1000; private final ClientRequests requests; private final String appName; private final CloudFoundryBootDashModel model; private final long timeout; private final CancelationToken cancelationToken; private final CloudAppDashElement app; public ApplicationRunningStateTracker(CancelationToken cancelationToken, CloudAppDashElement app) { this.model = app.getCloudModel(); this.requests = model.getClient(); this.appName = app.getName(); this.timeout = APP_START_TIMEOUT; this.cancelationToken = cancelationToken; this.app = app; } protected void checkTerminate(IProgressMonitor monitor) throws OperationCanceledException { this.app.checkTerminationRequested(cancelationToken, monitor); } /** * Polls cloudfoundry until app has succeeded or failed to start. Sending updates to console * and return the final run state. */ public RunState startTracking(IProgressMonitor monitor) throws Exception, OperationCanceledException { // fetch an updated Cloud Application that reflects changes that // were // performed on it. Make sure the element app reference is updated // as // run state of the element depends on the app being up to date. // Wait for application to be started RunState runState = RunState.UNKNOWN; long currentTime = System.currentTimeMillis(); long roughEstimateFetchStatsms = 5000; long totalTime = currentTime + timeout; String checkingMessage = "Checking if the application is running"; int estimatedAttempts = (int) (timeout / (WAIT_TIME + roughEstimateFetchStatsms)); monitor.beginTask(checkingMessage, estimatedAttempts); model.getElementConsoleManager().writeToConsole(appName, checkingMessage + ". Please wait...", LogType.LOCALSTDOUT); CFApplicationDetail app = requests.getApplication(appName); if (app == null) { throw new OperationCanceledException(); } // Get the guid, as it is more efficient for lookup //UUID appGuid = app.getGuid(); while (runState != RunState.RUNNING && runState != RunState.FLAPPING && runState != RunState.CRASHED && currentTime < totalTime) { int timeLeft = (int) ((totalTime - currentTime) / 1000); // Don't log this. Only update the monitor monitor.setTaskName(checkingMessage + ". Time left before timeout: " + timeLeft + 's'); checkTerminate(monitor); monitor.worked(1); runState = getRunState(app.getInstanceDetails()); try { Thread.sleep(WAIT_TIME); } catch (InterruptedException e) { } app = requests.getApplication(app.getName()); // App no longer exists if (app == null) { throw new OperationCanceledException(); } currentTime = System.currentTimeMillis(); } if (runState != RunState.RUNNING) { String warning = "Timed out waiting for application - " + appName + " to start. Please wait and manually refresh the target, or check if the application logs show any errors."; model.getElementConsoleManager().writeToConsole(appName, warning, LogType.LOCALSTDERROR); throw ExceptionUtil.coreException(warning); } else { model.getElementConsoleManager().writeToConsole(appName, "Application appears to have started - " + appName, LogType.LOCALSTDOUT); } return runState; } public static RunState getRunState(CFInstanceState instanceState) { RunState runState = null; if (instanceState != null) { switch (instanceState) { case RUNNING: runState = RunState.RUNNING; break; case CRASHED: runState = RunState.CRASHED; break; case FLAPPING: runState = RunState.FLAPPING; break; case STARTING: runState = RunState.STARTING; break; case DOWN: runState = RunState.INACTIVE; break; default: runState = RunState.UNKNOWN; break; } } return runState; } public static RunState getRunState(CFApplication app, List<CFInstanceStats> instances) { RunState runState = RunState.UNKNOWN; // if app desired state is "Stopped", return inactive if ((instances == null || instances.isEmpty()) && app.getState() == CFAppState.STOPPED) { runState = RunState.INACTIVE; } else { runState = getRunState(instances); } return runState; } private static RunState getRunState(List<CFInstanceStats> stats) { RunState runState = RunState.UNKNOWN; if (stats!=null && !stats.isEmpty()) { for (CFInstanceStats stat : stats) { RunState instanceState = getRunState(stat.getState()); runState = instanceState != null ? runState.merge(instanceState) : null; } } return runState; } }