/*******************************************************************************
* Copyright (c) 2015, 2017 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.net.URI;
import java.security.GeneralSecurityException;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.HttpClients;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.ide.eclipse.boot.dash.BootDashActivator;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CloudAppDashElement.CloudAppIdentity;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.OperationTracker.Task;
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.CFInstanceStats;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.ClientRequests;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.HealthChecks;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.v2.CFPushArguments;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.console.LogType;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.debug.DebugSupport;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.deployment.CloudApplicationDeploymentProperties;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.deployment.DeploymentProperties;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.ops.CloudApplicationOperation;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.ops.Operation;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.ops.RemoteDevClientStartOperation;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.ops.SetHealthCheckOperation;
import org.springframework.ide.eclipse.boot.dash.metadata.IPropertyStore;
import org.springframework.ide.eclipse.boot.dash.metadata.PropertyStoreApi;
import org.springframework.ide.eclipse.boot.dash.metadata.PropertyStoreFactory;
import org.springframework.ide.eclipse.boot.dash.model.BootDashViewModel;
import org.springframework.ide.eclipse.boot.dash.model.Deletable;
import org.springframework.ide.eclipse.boot.dash.model.RunState;
import org.springframework.ide.eclipse.boot.dash.model.RunTarget;
import org.springframework.ide.eclipse.boot.dash.model.UserInteractions;
import org.springframework.ide.eclipse.boot.dash.model.WrappingBootDashElement;
import org.springframework.ide.eclipse.boot.dash.util.CancelationTokens;
import org.springframework.ide.eclipse.boot.dash.util.CancelationTokens.CancelationToken;
import org.springframework.ide.eclipse.boot.dash.util.LogSink;
import org.springframework.ide.eclipse.boot.dash.views.BootDashModelConsoleManager;
import org.springframework.ide.eclipse.boot.util.Log;
import org.springframework.web.client.RestTemplate;
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
/**
* A handle to a Cloud application. NOTE: This element should NOT hold Cloud
* application state as it may be discarded and created multiple times for the
* same app for any reason.
* <p/>
* Cloud application state should always be resolved from external sources
*/
public class CloudAppDashElement extends CloudDashElement<CloudAppIdentity> implements Deletable, LogSink {
private static final boolean DEBUG = (""+Platform.getLocation()).contains("kdvolder");
private static void debug(String string) {
if (DEBUG) {
System.out.println(string);
}
}
static final private String DEPLOYMENT_MANIFEST_FILE_PATH = "deploymentManifestFilePath"; //$NON-NLS-1$
private static final String PROJECT_NAME = "PROJECT_NAME";
private CancelationTokens cancelationTokens;
private final CloudFoundryRunTarget cloudTarget;
private final CloudFoundryBootDashModel cloudModel;
private PropertyStoreApi persistentProperties;
private final LiveVariable<Throwable> error = new LiveVariable<>();
private final OperationTracker startOperationTracker = new OperationTracker(()-> this.getName(),error);
private final LiveVariable<CFApplication> appData = new LiveVariable<>();
private final LiveVariable<List<CFInstanceStats>> instanceData = new LiveVariable<>();
private final LiveExpression<RunState> baseRunState = new LiveExpression<RunState>() {
{
dependsOn(appData);
dependsOn(instanceData);
dependsOn(startOperationTracker.inProgress);
dependsOn(error);
}
@Override
protected RunState compute() {
if (error.getValue()!=null) {
return RunState.UNKNOWN;
}
if (startOperationTracker.inProgress.getValue()>0) {
return RunState.STARTING;
}
CFApplication app = appData.getValue();
List<CFInstanceStats> instances = instanceData.getValue();
if (instances!=null && app!=null) {
return ApplicationRunningStateTracker.getRunState(app, instances);
}
return RunState.UNKNOWN;
}
};
/**
* Used as a temporary override of health-check info fetched from CF. This is cleared when element gets 'proper'
* data fetched from CF. This exists so that we can implement 'setHealthCheck' which is called to update
* model state after changing the health-check value individually. It makes sense in that case to only update
* this one bit of the model state rather than refresh all the data from CF.
*/
private final LiveVariable<String> healthCheckOverride = new LiveVariable<>();
{
appData.addListener((e, v) -> {
healthCheckOverride.setValue(null);
});
}
protected void showConsole() {
try {
getCloudModel().getElementConsoleManager().showConsole(this);
} catch (Exception e) {
Log.log(e);
}
}
public CloudAppDashElement(CloudFoundryBootDashModel model, String appName, IPropertyStore modelStore) {
super(model, new CloudAppIdentity(appName, model.getRunTarget()));
this.cancelationTokens = new CancelationTokens();
this.cloudTarget = model.getRunTarget();
this.cloudModel = model;
IPropertyStore backingStore = PropertyStoreFactory.createSubStore("A"+getName(), modelStore);
this.persistentProperties = PropertyStoreFactory.createApi(backingStore);
addElementNotifier(baseRunState);
addElementNotifier(appData);
addElementNotifier(healthCheckOverride);
this.addDisposableChild(baseRunState);
}
public CloudFoundryBootDashModel getCloudModel() {
return (CloudFoundryBootDashModel) getBootDashModel();
}
@Override
public void stopAsync(UserInteractions ui) throws Exception {
cancelOperations();
String appName = getName();
getCloudModel().runAsynch("Stopping application " + appName, appName, (IProgressMonitor monitor) -> {
stop(createCancelationToken(), monitor);
}, ui);
}
public void stop(CancelationToken cancelationToken, IProgressMonitor monitor) throws Exception {
checkTerminationRequested(cancelationToken, monitor);
getClient().stopApplication(getName());
getCloudModel().getElementConsoleManager().terminateConsole(getName());
refresh();
}
@Override
public void restart(RunState runningOrDebugging, UserInteractions ui) throws Exception {
cancelOperations();
CancelationToken cancelationToken = createCancelationToken();
if (getProject() != null) {
cloudModel.runAsynch("Restarting, goal state: " + runningOrDebugging, getName(), (IProgressMonitor monitor) -> {
// Let push and debug resolve deployment properties
CloudApplicationDeploymentProperties deploymentProperties = null;
pushAndDebug(deploymentProperties, runningOrDebugging, ui, cancelationToken, monitor);
}, ui);
} else {
cloudModel.runAsynch("Restarting, goal state: " + runningOrDebugging, getName(), (IProgressMonitor monitor) -> {
restartOnly(ui, cancelationToken, monitor);
}, ui);
}
}
public DebugSupport getDebugSupport() {
//In the future we may need to choose between multiple strategies here.
return getViewModel().getCfDebugSupport();
}
public BootDashViewModel getViewModel() {
return getBootDashModel().getViewModel();
}
public void restartWithRemoteClient(UserInteractions ui, CancelationToken cancelationToken) {
String opName = "Restart Remote DevTools Client for application '" + getName() + "'";
getCloudModel().runAsynch(opName, getName(), (IProgressMonitor monitor) -> {
doRestartWithRemoteClient(RunState.RUNNING, ui, cancelationToken, monitor);
}, ui);
}
protected void doRestartWithRemoteClient(RunState runningOrDebugging, UserInteractions ui, CancelationToken cancelationToken, IProgressMonitor monitor)
throws Exception {
CloudFoundryBootDashModel model = getCloudModel();
Map<String, String> envVars = model.getRunTarget().getClient().getApplicationEnvironment(getName());
if (getProject() == null) {
ExceptionUtil.coreException(new Status(IStatus.ERROR, BootDashActivator.PLUGIN_ID,
"Local project not associated to CF app '" + getName() + "'"));
}
new SetHealthCheckOperation(this, HealthChecks.HC_NONE, ui, /* confirmChange */true, cancelationToken)
.run(monitor);
if (!DevtoolsUtil.isEnvVarSetupForRemoteClient(envVars, DevtoolsUtil.getSecret(getProject()))) {
// Let the push and debug operation resolve default properties
CloudApplicationDeploymentProperties deploymentProperties = null;
pushAndDebug(deploymentProperties , runningOrDebugging, ui, cancelationToken, monitor);
/*
* Restart and push op resets console anyway, no need to reset it
* again
*/
} else if (getRunState() == RunState.INACTIVE) {
restartOnly(ui, cancelationToken, monitor);
}
new RemoteDevClientStartOperation(model, getName(), runningOrDebugging, cancelationToken).run(monitor);
}
public void restartOnly(UserInteractions ui, CancelationToken cancelationToken, IProgressMonitor monitor) throws Exception {
whileStarting(ui, cancelationToken, monitor, () -> {
if (!getClient().applicationExists(getName())) {
throw ExceptionUtil.coreException(
"Unable to start the application. Application does not exist anymore in Cloud Foundry: "
+ getName());
}
checkTerminationRequested(cancelationToken, monitor);
log("Starting application: " + getName());
getClient().restartApplication(getName(), CancelationTokens.merge(cancelationToken, monitor));
new ApplicationRunningStateTracker(cancelationToken, this).startTracking(monitor);
CFApplicationDetail updatedInstances = getClient().getApplication(getName());
setDetailedData(updatedInstances);
});
}
public void restartOnlyAsynch(UserInteractions ui, CancelationToken cancelationToken) {
String opName = "Restarting application " + getName();
getCloudModel().runAsynch(opName, getName(), (IProgressMonitor monitor) -> {
restartOnly(ui, cancelationToken, monitor);
}, ui);
}
public void pushAndDebug(CloudApplicationDeploymentProperties deploymentProperties, RunState runningOrDebugging,
UserInteractions ui, CancelationToken cancelationToken, IProgressMonitor monitor) throws Exception {
String opName = "Starting application '" + getName() + "' in "
+ (runningOrDebugging == RunState.DEBUGGING ? "DEBUG" : "RUN") + " mode";
DebugSupport debugSupport = getDebugSupport();
if (runningOrDebugging == RunState.DEBUGGING) {
if (debugSupport != null && debugSupport.isSupported(this)) {
Operation<?> debugOp = debugSupport.createOperation(this, opName, ui, cancelationToken);
push(deploymentProperties, runningOrDebugging, debugSupport, cancelationToken, ui, monitor);
debugOp.run(monitor);
} else {
String title = "Debugging is not supported for '" + getName() + "'";
String msg = debugSupport.getNotSupportedMessage(this);
if (msg == null) {
msg = title;
}
ui.errorPopup(title, msg);
throw ExceptionUtil.coreException(msg);
}
} else {
push(deploymentProperties, runningOrDebugging, debugSupport, cancelationToken, ui, monitor);
}
}
@Override
public void openConfig(UserInteractions ui) {
}
@Override
public String getName() {
return delegate.getAppName();
}
/**
* Returns the project associated with this element or null. If includeNonExistingProjects is
* true, then the project is returned even it no longer exists.
*/
public IProject getProject(boolean includeNonExistingProjects) {
String name = getPersistentProperties().get(PROJECT_NAME);
if (name!=null) {
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
if (includeNonExistingProjects || project.exists()) {
return project;
}
}
return null;
}
/**
* Returns the project associated with this element or null. The project returned is
* guaranteed to exist.
*/
@Override
public IProject getProject() {
return getProject(false);
}
/**
* Set the project 'binding' for this element.
* @return true if the element was changed by this operation.
*/
public boolean setProject(IProject project) {
try {
PropertyStoreApi props = getPersistentProperties();
String oldValue = props.get(PROJECT_NAME);
String newValue = project==null?null:project.getName();
if (!Objects.equals(oldValue, newValue)) {
props.put(PROJECT_NAME, newValue);
return true;
}
return false;
} catch (Exception e) {
Log.log(e);
return false;
}
}
@Override
public RunState getRunState() {
RunState state = baseRunState.getValue();
if (state == RunState.RUNNING) {
DebugSupport debugSupport = getDebugSupport();
if (debugSupport.isDebuggerAttached(CloudAppDashElement.this)) {
// if (DevtoolsUtil.isDevClientAttached(this, ILaunchManager.DEBUG_MODE)) {
state = RunState.DEBUGGING;
}
}
return state;
}
/**
* This method is mostly meant just for test purposes. The 'baseRunState' is really
* part of how this class internally computes runstate. Clients should have no business
* using it separate from the runtstate.
*/
public LiveExpression<RunState> getBaseRunStateExp() {
return baseRunState;
}
@Override
public CloudFoundryRunTarget getTarget() {
return cloudTarget;
}
@Override
public int getLivePort() {
return 80;
}
@Override
public String getLiveHost() {
CFApplication app = getSummaryData();
if (app != null) {
List<String> uris = app.getUris();
if (uris != null) {
for (String uri : uris) {
return uri;
}
}
}
return null;
}
public Integer getMemory() {
CFApplication app = getSummaryData();
if (app != null) {
return app.getMemory();
}
return null;
}
public CFApplication getSummaryData() {
return appData.getValue();
}
@Override
public ILaunchConfiguration getActiveConfig() {
return null;
}
@Override
public int getActualInstances() {
CFApplication data = getSummaryData();
return data != null ? data.getRunningInstances() : 0;
}
@Override
public int getDesiredInstances() {
CFApplication data = getSummaryData();
return data != null ? data.getInstances() : 0;
}
public String getHealthCheck() {
String hc = healthCheckOverride.getValue();
if (hc!=null) {
return hc;
}
CFApplication data = getSummaryData();
return data!=null ? data.getHealthCheckType() : DeploymentProperties.DEFAULT_HEALTH_CHECK_TYPE;
}
/**
* Changes the cached health-check value for this model element. Note that this
* doesnt *not* change the real value of the health-check.
*/
public void setHealthCheck(String hc) {
this.healthCheckOverride.setValue(hc);
}
public UUID getAppGuid() {
CFApplication app = getSummaryData();
if (app!=null) {
return app.getGuid();
}
return null;
}
@Override
public PropertyStoreApi getPersistentProperties() {
return persistentProperties;
}
static class CloudAppIdentity {
private final String appName;
private final RunTarget runTarget;
public String toString() {
return appName + "@" + runTarget;
};
CloudAppIdentity(String appName, RunTarget runTarget) {
this.appName = appName;
this.runTarget = runTarget;
}
public String getAppName() {
return this.appName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((appName == null) ? 0 : appName.hashCode());
result = prime * result + ((runTarget == null) ? 0 : runTarget.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CloudAppIdentity other = (CloudAppIdentity) obj;
if (appName == null) {
if (other.appName != null)
return false;
} else if (!appName.equals(other.appName))
return false;
if (runTarget == null) {
if (other.runTarget != null)
return false;
} else if (!runTarget.equals(other.runTarget))
return false;
return true;
}
}
public void log(String message) {
log(message, LogType.LOCALSTDOUT);
}
public void log(String message, LogType logType) {
try {
getCloudModel().getElementConsoleManager().writeToConsole(this, message, logType);
} catch (Exception e) {
Log.log(e);
}
}
@Override
public Object getParent() {
return getBootDashModel();
}
protected LiveExpression<URI> getActuatorUrl() {
LiveExpression<URI> urlExp = getCloudModel().getActuatorUrlFactory().createOrGet(this);
if (urlExp!=null) {
return urlExp;
}
//only happens when this element is not valid anymore, but return something harmless / usable anyhow
return LiveExpression.constant(null);
}
@Override
protected RestTemplate getRestTemplate() {
CloudFoundryTargetProperties props = getTarget().getTargetProperties();
boolean skipSsl = props.isSelfsigned() || props.skipSslValidation();
if (skipSsl) {
HttpClient httpClient = HttpClients.custom()
.setHostnameVerifier(new AllowAllHostnameVerifier())
.setSslcontext(buildSslContext())
.build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(requestFactory);
} else {
//This worked before so lets not try to fix that case.
return super.getRestTemplate();
}
}
private javax.net.ssl.SSLContext buildSslContext() {
try {
return new SSLContextBuilder().useSSL().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
} catch (GeneralSecurityException gse) {
throw new RuntimeException("An error occurred setting up the SSLContext", gse);
}
}
public IFile getDeploymentManifestFile() {
String text = getPersistentProperties().get(DEPLOYMENT_MANIFEST_FILE_PATH);
try {
return text == null ? null : ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(text));
} catch (IllegalArgumentException e) {
Log.log(e);
return null;
}
}
public void setDeploymentManifestFile(IFile file) {
try {
if (file == null) {
getPersistentProperties().put(DEPLOYMENT_MANIFEST_FILE_PATH, (String) null);
} else {
getPersistentProperties().put(DEPLOYMENT_MANIFEST_FILE_PATH, file.getFullPath().toString());
}
} catch (Exception e) {
Log.log(e);
}
}
public void setDetailedData(CFApplicationDetail appDetails) {
if (appDetails!=null) {
this.appData.setValue(appDetails);
this.instanceData.setValue(appDetails.getInstanceDetails());
} else {
this.appData.setValue(null);
this.instanceData.setValue(null);
}
}
public List<CFInstanceStats> getInstanceData() {
return this.instanceData.getValue();
}
public void setError(Throwable t) {
error.setValue(t);
}
public CancelationToken createCancelationToken() {
return cancelationTokens.create();
}
public void cancelOperations() {
cancelationTokens.cancelAll();
}
/**
* Print a message to the console associated with this application.
*/
public void print(String msg) {
print(msg, LogType.LOCALSTDOUT);
}
/**
* Print a message to the console associated with this application.
*/
public void printError(String string) {
print(string, LogType.LOCALSTDERROR);
}
/**
* Print a message to the console associated with this application.
*/
public void print(String msg, LogType type) {
try {
BootDashModelConsoleManager consoles = getCloudModel().getElementConsoleManager();
consoles.writeToConsole(this, msg+"\n", type);
} catch (Exception e) {
Log.log(e);
}
}
/**
* Attempt to refresh the data associated with this app in the model. Returns the
* refreshed element if this was succesful, null if the element was deleted (because during the
* refresh we discovered it not longer exists) and if something failed trying to refresh the element.
*/
public CloudAppDashElement refresh() throws Exception {
debug("Refreshing element: "+this.getName());
CFApplicationDetail data = getClient().getApplication(getName());
if (data==null) {
//Looks like element no longer exist in CF so remove it from the model
CloudFoundryBootDashModel model = getCloudModel();
model.removeApplication(getName());
return null;
}
getCloudModel().updateApplication(data);
return this;
}
private ClientRequests getClient() {
return getTarget().getClient();
}
public void push(CloudApplicationDeploymentProperties deploymentProperties, RunState runningOrDebugging,
DebugSupport debugSupport, CancelationToken cancelationToken, UserInteractions ui, IProgressMonitor monitor)
throws Exception {
boolean isDebugging = runningOrDebugging == RunState.DEBUGGING;
whileStarting(ui, cancelationToken, monitor, () -> {
// Refresh app data and check that the application (still) exists in
// Cloud Foundry
// This also ensures that the 'diff change dialog' will pick up on
// the latest changes.
// TODO: should this refresh be moved closer to the where we
// actually compute the diff?
CloudAppDashElement updatedApp = this.refresh();
if (updatedApp == null) {
ExceptionUtil.coreException(new Status(IStatus.ERROR, BootDashActivator.PLUGIN_ID,
"No Cloud Application found for '" + getName() + "'"));
}
IProject project = getProject();
if (project == null) {
ExceptionUtil.coreException(new Status(IStatus.ERROR, BootDashActivator.PLUGIN_ID,
"Local project not associated to CF app '" + getName() + "'"));
}
checkTerminationRequested(cancelationToken, monitor);
CloudApplicationDeploymentProperties properties = deploymentProperties == null
? getCloudModel().resolveDeploymentProperties(updatedApp, ui, monitor) : deploymentProperties;
// Update JAVA_OPTS env variable with Remote DevTools Client secret
DevtoolsUtil.setupEnvVarsForRemoteClient(properties.getEnvironmentVariables(),
DevtoolsUtil.getSecret(project));
if (debugSupport != null) {
if (isDebugging) {
debugSupport.setupEnvVars(properties.getEnvironmentVariables());
} else {
debugSupport.clearEnvVars(properties.getEnvironmentVariables());
}
}
checkTerminationRequested(cancelationToken, monitor);
CFPushArguments pushArgs = properties.toPushArguments(getCloudModel().getCloudDomains(monitor));
getClient().push(pushArgs, CancelationTokens.merge(cancelationToken, monitor));
log("Application pushed to Cloud Foundry: " + getName());
});
}
public void whileStarting(UserInteractions ui, CancelationToken cancelationToken, IProgressMonitor monitor, Task task) throws Exception {
showConsole();
startOperationTracker.whileExecuting(ui, cancelationToken, monitor, task);
refresh();
}
public void checkTerminationRequested(CancelationToken cancelationToken, IProgressMonitor mon)
throws OperationCanceledException {
if (mon != null && mon.isCanceled() || cancelationToken != null && cancelationToken.isCanceled()) {
throw new OperationCanceledException();
}
}
@Override
public void delete(UserInteractions ui) {
CloudFoundryBootDashModel model = getCloudModel();
CloudAppDashElement cloudElement = this;
cloudElement.cancelOperations();
CancelationToken cancelToken = cloudElement.createCancelationToken();
CloudApplicationOperation operation = new CloudApplicationOperation("Deleting: " + cloudElement.getName(), model,
cloudElement.getName(), cancelToken) {
@Override
protected void doCloudOp(IProgressMonitor monitor) throws Exception, OperationCanceledException {
// Delete from CF first. Do it outside of synch block to avoid
// deadlock
model.getRunTarget().getClient().deleteApplication(appName);
model.getElementConsoleManager().terminateConsole(cloudElement.getName());
model.removeApplication(cloudElement.getName());
cloudElement.setProject(null);
}
};
// Allow deletions to occur concurrently with any other application
// operation
operation.setSchedulingRule(null);
getCloudModel().runAsynch(operation, ui);
}
@Override
public EnumSet<RunState> supportedGoalStates() {
return CloudFoundryRunTarget.RUN_GOAL_STATES;
}
}