/*
* Copyright 2010-2012 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.eclipse.elasticbeanstalk;
import static com.amazonaws.eclipse.elasticbeanstalk.ElasticBeanstalkPlugin.trace;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.wst.server.core.IModule;
import org.eclipse.wst.server.core.IServer;
import org.eclipse.wst.server.core.model.ServerBehaviourDelegate;
import org.eclipse.wst.server.core.model.ServerDelegate;
import com.amazonaws.eclipse.elasticbeanstalk.deploy.WTPWarUtils;
import com.amazonaws.eclipse.elasticbeanstalk.jobs.RestartEnvironmentJob;
import com.amazonaws.eclipse.elasticbeanstalk.jobs.TerminateEnvironmentJob;
import com.amazonaws.eclipse.elasticbeanstalk.jobs.UpdateEnvironmentJob;
import com.amazonaws.services.elasticbeanstalk.model.ConfigurationOptionSetting;
import com.amazonaws.services.elasticbeanstalk.model.ConfigurationSettingsDescription;
import com.amazonaws.services.elasticbeanstalk.model.EnvironmentDescription;
import com.amazonaws.services.elasticbeanstalk.model.EnvironmentStatus;
import com.amazonaws.services.elasticbeanstalk.model.UpdateEnvironmentRequest;
public class EnvironmentBehavior extends ServerBehaviourDelegate {
/** The latest status of this environment, as reported from AWS Elastic Beanstalk. */
private EnvironmentStatus latestEnvironmentStatus;
@Override
public void setupLaunchConfiguration(ILaunchConfigurationWorkingCopy workingCopy, IProgressMonitor monitor)
throws CoreException {
trace("setupLaunchConfiguration(launchConfig: " + workingCopy+ ", environment: " + getEnvironment().getEnvironmentName() + ")");
super.setupLaunchConfiguration(workingCopy, monitor);
}
/**
* The current job to update an AWS Elastic Beanstalk environment. We set up the
* job as part of the WTP publishing process, then schedule it at the end of
* publishing.
*/
private UpdateEnvironmentJob currentUpdateEnvironmentJob;
/**
* Dialog to collect deployment information
*/
private DeploymentInformationDialog deploymentInformationDialog;
private static final IStatus ERROR_STATUS = new Status(IStatus.ERROR, ElasticBeanstalkPlugin.PLUGIN_ID, "Environment is not ready");
@Override
public void stop(boolean force) {
trace("Stopping (force:" + force + ")");
setServerState(IServer.STATE_STOPPING);
new TerminateEnvironmentJob(getEnvironment()).schedule();
}
@Override
public void restart(String launchMode) throws CoreException {
trace("Restarting(launchMode: " + launchMode + ", environment: " + getEnvironment().getEnvironmentName());
setServerState(IServer.STATE_STARTING);
if ( getServer().getMode().equals(launchMode) ) {
new RestartEnvironmentJob(getEnvironment()).schedule();
} else if ( launchMode.equals(ILaunchManager.DEBUG_MODE) ) {
enableDebugging();
trace("Adding a debug port for environment " + getEnvironment().getEnvironmentName());
}
ElasticBeanstalkPlugin.getDefault().syncEnvironments();
}
@Override
protected void publishStart(IProgressMonitor monitor) throws CoreException {
trace("PublishStart: " + getEnvironment().getEnvironmentName());
currentUpdateEnvironmentJob = new UpdateEnvironmentJob(getEnvironment(), getServer());
}
@Override
protected void publishModule(int publishKind, int deltaKind, IModule[] moduleTree, IProgressMonitor monitor)
throws CoreException {
trace("PublishModule:"
+ " (publishKind: " + WtpConstantsUtils.lookupPublishKind(publishKind)
+ " deltaKind: " + WtpConstantsUtils.lookupDeltaKind(deltaKind)
+ " moduleTree: " + Arrays.asList(moduleTree) + ")");
// Ignore automatic publishes
if (publishKind == IServer.PUBLISH_AUTO) return;
// If the module doesn't need any publishing, and we don't need a full publish, don't do anything
if (publishKind == IServer.PUBLISH_INCREMENTAL && deltaKind == NO_CHANGE) return;
// If we're just removing a module, we don't need to do anything
if (deltaKind == REMOVED) return;
// TODO: If we can ask the job what module its uploading, we can check and not export twice
IPath exportedWar = WTPWarUtils.exportProjectToWar(moduleTree[0].getProject(), getTempDirectory());
monitor.worked(100);
trace("Created war: " + exportedWar.toOSString());
currentUpdateEnvironmentJob.setModuleToPublish(moduleTree[0], exportedWar);
updateModuleState(moduleTree[0], IServer.STATE_STARTING, IServer.PUBLISH_STATE_NONE);
}
@Override
protected void publishFinish(IProgressMonitor monitor) throws CoreException {
trace("PublishFinish(" + getEnvironment().getEnvironmentName() + ")");
try {
if ( currentUpdateEnvironmentJob.needsToDeployNewVersion() ) {
Display.getDefault().syncExec(new Runnable() {
public void run() {
Shell shell = Display.getDefault().getActiveShell();
if ( shell == null )
shell = Display.getDefault().getShells()[0];
/*
* Use the deployment dialog from earlier, if we have
* one. Otherwise create a new one.
*/
if ( deploymentInformationDialog == null ) {
final List<ConfigurationSettingsDescription> settings = getEnvironment().getCurrentSettings();
String debugPort = Environment.getDebugPort(settings);
String securityGroup = Environment.getSecurityGroup(settings);
boolean confirmIngress = false;
if ( debugPort != null && securityGroup != null
&& !getEnvironment().isIngressAllowed(debugPort, settings) ) {
confirmIngress = true;
}
boolean letUserSelectVersionLabel = !getEnvironment().getIncrementalDeployment();
if (letUserSelectVersionLabel || confirmIngress) {
deploymentInformationDialog = new DeploymentInformationDialog(shell, getEnvironment(),
getServer().getMode(), letUserSelectVersionLabel, false, confirmIngress);
deploymentInformationDialog.open();
if ( deploymentInformationDialog.getReturnCode() != MessageDialog.OK ) return;
// Allow ingress on their security group if necessary
if ( confirmIngress ) getEnvironment().openSecurityGroupPort(debugPort, securityGroup);
}
}
if (deploymentInformationDialog != null) {
currentUpdateEnvironmentJob.setVersionLabel(deploymentInformationDialog.getVersionLabel());
currentUpdateEnvironmentJob.setDebugInstanceId(deploymentInformationDialog.getDebugInstanceId());
}
setServerState(IServer.STATE_STARTING);
currentUpdateEnvironmentJob.schedule();
}
});
if ( deploymentInformationDialog != null
&& deploymentInformationDialog.getReturnCode() != MessageDialog.OK ) {
updateModuleState(currentUpdateEnvironmentJob.getModuleToPublish(), IServer.STATE_UNKNOWN,
IServer.PUBLISH_STATE_UNKNOWN);
throw new CoreException(new Status(Status.ERROR, ElasticBeanstalkPlugin.PLUGIN_ID,
"Publish canceled"));
}
}
} finally {
currentUpdateEnvironmentJob = null;
deploymentInformationDialog = null;
}
}
private int translateStatus(EnvironmentStatus status) {
if (status == null) return IServer.STATE_STOPPED;
switch (status) {
case Launching:
case Updating:
return IServer.STATE_STARTING;
case Ready:
return IServer.STATE_STARTED;
case Terminated:
return IServer.STATE_STOPPED;
case Terminating:
return IServer.STATE_STOPPING;
default:
return IServer.STATE_UNKNOWN;
}
}
@Override
public IStatus canRestart(String mode) {
trace("canRestart(launchMode: " + mode + ", environment: " + getEnvironment().getEnvironmentName() + ")");
if (latestEnvironmentStatus == null) return ERROR_STATUS;
return super.canRestart(mode);
}
@Override
public IStatus canStop() {
trace("canStop(environment: " + getEnvironment().getEnvironmentName() + ")");
if (latestEnvironmentStatus == null) return ERROR_STATUS;
return super.canStop();
}
@Override
public IStatus canStart(String launchMode) {
trace("canStart(launchMode: " + launchMode + ", environment: " + getEnvironment().getEnvironmentName() + ")");
// Don't allow the user to start the server if no projects are added yet
if (getServer().getModules().length == 0) return ERROR_STATUS;
if (latestEnvironmentStatus == null) return super.canStart(launchMode);
if (latestEnvironmentStatus == EnvironmentStatus.Launching ||
latestEnvironmentStatus == EnvironmentStatus.Updating ||
latestEnvironmentStatus == EnvironmentStatus.Terminating) {
return ERROR_STATUS;
}
return super.canStart(launchMode);
}
@Override
public IStatus canPublish() {
trace("canPublish(environment: " + getEnvironment().getEnvironmentName() + ")");
// Don't allow the user to publish to the server if no projects are added yet
if (getServer().getModules().length == 0) return ERROR_STATUS;
if (latestEnvironmentStatus == null) return super.canPublish();
if (latestEnvironmentStatus == EnvironmentStatus.Launching ||
latestEnvironmentStatus == EnvironmentStatus.Updating ||
latestEnvironmentStatus == EnvironmentStatus.Terminating) {
return ERROR_STATUS;
}
return super.canPublish();
}
public void updateServer(EnvironmentDescription environmentDescription, List<ConfigurationSettingsDescription> settings) {
trace("Updating server with latest AWS Elastic Beanstalk environment description (server: " + getServer().getName() + ")");
if (environmentDescription == null) {
latestEnvironmentStatus = null;
} else {
try {
latestEnvironmentStatus = EnvironmentStatus.fromValue(environmentDescription.getStatus());
} catch (IllegalArgumentException e) {
Status status = new Status(Status.INFO, ElasticBeanstalkPlugin.PLUGIN_ID,
"Unknown environment status: " + environmentDescription.getStatus());
StatusManager.getManager().handle(status, StatusManager.LOG);
}
setServerStatus(new Status(Status.WARNING, ElasticBeanstalkPlugin.PLUGIN_ID,
environmentDescription.getSolutionStackName() + " : " + environmentDescription.getStatus()));
if ( settings != null ) {
String debugPort = Environment.getDebugPort(settings);
if ( debugPort != null )
setMode(ILaunchManager.DEBUG_MODE);
else
setMode(ILaunchManager.RUN_MODE);
}
}
setServerState(translateStatus(latestEnvironmentStatus));
getEnvironment().setCachedEnvironmentDescription(environmentDescription);
for (IModule module : getServer().getModules()) {
setModuleStatus(new IModule[] {module}, new Status(IStatus.OK, ElasticBeanstalkPlugin.PLUGIN_ID, getEnvironment().getApplicationName()));
}
}
protected Environment getEnvironment() {
return (Environment)getServer().loadAdapter(ServerDelegate.class, null);
}
// This is called by our ElasticBeanstalkLaunchConfigurationDelegate, but only when
// the server is moving from stopped -> starting (if we have that flag set in plugin.xml).
public void setupLaunch(ILaunch launch, String launchMode, IProgressMonitor monitor) throws CoreException {
trace("EnvironmentBehavior:setupLaunch(" + launch + ", " + launchMode + ")");
setServerRestartState(false);
setServerState(IServer.STATE_STARTING);
setMode(launchMode);
setServerState(IServer.STATE_STARTED);
}
public void updateServerState(int state) {
setServerState(state);
}
public void updateModuleState(IModule module, int moduleState, int modulePublishState) {
setModuleState(new IModule[] {module}, moduleState);
setModulePublishState(new IModule[] {module}, modulePublishState);
for (IModule module2 : getEnvironment().getChildModules(new IModule[] {module})) {
setModuleState(new IModule[] {module, module2}, moduleState);
setModulePublishState(new IModule[] {module, module2}, modulePublishState);
}
}
/**
* Enables debugging on a port the user chooses.
*/
public void enableDebugging() {
final List<ConfigurationSettingsDescription> settings = getEnvironment().getCurrentSettings();
ConfigurationOptionSetting opt = Environment.getJVMOptions(settings);
if ( opt == null ) {
opt = new ConfigurationOptionSetting().withNamespace(ConfigurationOptionConstants.JVMOPTIONS)
.withOptionName("JVM Options");
}
String currentOptions = opt.getValue();
if ( currentOptions == null ) {
currentOptions = "";
}
if ( !currentOptions.contains("-Xdebug") ) {
currentOptions += " " + "-Xdebug";
}
if ( !currentOptions.contains("-Xrunjdwp:") ) {
Display.getDefault().syncExec(new Runnable() {
public void run() {
// Incremental deployments are automatically assigned version labels
boolean letUserSelectVersionLabel = !getEnvironment().getIncrementalDeployment();
deploymentInformationDialog = new DeploymentInformationDialog(Display.getDefault().getActiveShell(), getEnvironment(), ILaunchManager.DEBUG_MODE, letUserSelectVersionLabel, true, true);
deploymentInformationDialog.open();
}
});
int result = deploymentInformationDialog.getReturnCode();
if ( result == Dialog.OK ) {
String debugPort = deploymentInformationDialog.getDebugPort();
currentOptions += " " + "-Xrunjdwp:transport=dt_socket,address=" + debugPort + ",server=y,suspend=n";
} else {
deploymentInformationDialog = null;
setServerState(IServer.STATE_UNKNOWN);
ElasticBeanstalkPlugin.getDefault().syncEnvironments();
throw new RuntimeException("Operation canceled");
}
} else {
deploymentInformationDialog = null;
throw new RuntimeException("Environment JVM options already contains -Xrunjdwp argument, " +
"but we were unable to determine the remote debugging port");
}
opt.setValue(currentOptions);
UpdateEnvironmentRequest rq = new UpdateEnvironmentRequest();
rq.setEnvironmentName(getEnvironment().getEnvironmentName());
Collection<ConfigurationOptionSetting> outgoingSettings = new ArrayList<ConfigurationOptionSetting>();
outgoingSettings.add(opt);
rq.setOptionSettings(outgoingSettings);
getEnvironment().getClient().updateEnvironment(rq);
if ( !getEnvironment().isIngressAllowed(deploymentInformationDialog.getDebugPort(), settings) ) {
getEnvironment().openSecurityGroupPort(deploymentInformationDialog.getDebugPort(),
Environment.getSecurityGroup(settings));
}
}
}