/*******************************************************************************
* Copyright (c) 2015 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.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.contains;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.springframework.ide.eclipse.boot.dash.test.BootDashModelTest.waitForJobsToComplete;
import static org.springframework.ide.eclipse.boot.dash.test.CloudFoundryTestHarness.APP_DELETE_TIMEOUT;
import static org.springframework.ide.eclipse.boot.dash.test.CloudFoundryTestHarness.APP_DEPLOY_TIMEOUT;
import static org.springframework.ide.eclipse.boot.dash.test.CloudFoundryTestHarness.APP_IS_VISIBLE_TIMEOUT;
import static org.springframework.ide.eclipse.boot.dash.test.CloudFoundryTestHarness.SERVICE_DELETE_TIMEOUT;
import static org.springframework.ide.eclipse.boot.test.BootProjectTestHarness.withStarters;
import static org.springsource.ide.eclipse.commons.tests.util.StsTestCase.createFile;
import java.io.File;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.debug.core.DebugPlugin;
import org.junit.After;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CloudAppDashElement;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CloudFoundryBootDashModel;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CloudServiceInstanceDashElement;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.CFClientParams;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.CFCredentials;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.CFCredentials.CFCredentialType;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.CFServiceInstance;
import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.v2.DefaultClientRequestsV2;
import org.springframework.ide.eclipse.boot.dash.dialogs.StoreCredentialsMode;
import org.springframework.ide.eclipse.boot.dash.model.BootDashElement;
import org.springframework.ide.eclipse.boot.dash.model.BootProjectDashElement;
import org.springframework.ide.eclipse.boot.dash.model.RunState;
import org.springframework.ide.eclipse.boot.dash.model.UserInteractions;
import org.springframework.ide.eclipse.boot.test.AutobuildingEnablement;
import org.springframework.ide.eclipse.boot.test.BootProjectTestHarness;
import org.springframework.ide.eclipse.boot.test.util.TestBracketter;
import org.springframework.ide.eclipse.editor.support.util.StringUtil;
import org.springsource.ide.eclipse.commons.frameworks.test.util.ACondition;
import org.springsource.ide.eclipse.commons.tests.util.StsTestUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
/**
* @author Kris De Volder
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class CloudFoundryBootDashModelIntegrationTest {
private TestBootDashModelContext context;
private BootProjectTestHarness projects;
private UserInteractions ui;
private CloudFoundryTestHarness harness;
////////////////////////////////////////////////////////////
private CFClientParams clientParams = CfTestTargetParams.fromEnv();
private DefaultClientRequestsV2 client = CloudFoundryClientTest.createClient(clientParams);
public CloudFoundryApplicationHarness appHarness = new CloudFoundryApplicationHarness(client);
@Rule
public AutobuildingEnablement disableAutoBuild = new AutobuildingEnablement(false);
@Rule
public TestBracketter testBracketter = new TestBracketter();
public CloudFoundryServicesHarness services = new CloudFoundryServicesHarness(clientParams, client);
@Before
public void setup() throws Exception {
StsTestUtil.deleteAllProjects();
this.context = new TestBootDashModelContext(
ResourcesPlugin.getWorkspace(),
DebugPlugin.getDefault().getLaunchManager()
);
this.harness = CloudFoundryTestHarness.create(context);
this.projects = new BootProjectTestHarness(context.getWorkspace());
this.ui = mock(UserInteractions.class);
}
@After
public void tearDown() throws Exception {
appHarness.deleteOwnedApps();
services.dispose();
client.logout();
harness.dispose();
}
////////////////////////////////////////////////////////////
@Test
public void testCreateCfTarget() throws Exception {
CloudFoundryBootDashModel target = harness.createCfTarget(CfTestTargetParams.fromEnv());
CFCredentials credentials = target.getRunTarget().getTargetProperties().getCredentials();
assertNotNull(target);
assertEquals(CFCredentialType.PASSWORD, credentials.getType());
assertNotNull(credentials.getSecret());
assertEquals(1, harness.getCfRunTargetModels().size());
}
@Test public void testCreateCfTargetAndStoreToken() throws Exception {
CFClientParams targetParams = CfTestTargetParams.fromEnv();
CloudFoundryBootDashModel target = harness.createCfTarget(targetParams, StoreCredentialsMode.STORE_TOKEN);
assertNotNull(target);
assertTrue(target.isConnected());
CFCredentials credentials = target.getRunTarget().getTargetProperties().getCredentials();
assertEquals(CFCredentialType.REFRESH_TOKEN, credentials.getType());
assertNotNull(credentials.getSecret());
assertEquals(1, harness.getCfRunTargetModels().size());
}
/**
* Test that tests a bunch of things.
* TODO: It isn't good practice to create 'test everything' tests...
* but we do it anyway because ramping up a test that deploys an app takes about 90 seconds...
* Maybe we can factor this better somehow so we have separate tests, but only deploy app once?
*/
@Test
public void testDeployAppAndDeleteAndStuff() throws Exception {
harness.createCfTarget(CfTestTargetParams.fromEnv());
final CloudFoundryBootDashModel model = harness.getCfTargetModel();
final BootProjectDashElement project = harness.getElementFor(
projects.createBootProject("to-deploy", withStarters("actuator", "web"))
);
final String appName = appHarness.randomAppName();
harness.answerDeploymentPrompt(ui, appName, appName);
model.add(ImmutableList.<Object>of(project), ui);
//The resulting deploy is asynchronous
new ACondition("wait for app '"+ appName +"'to appear", APP_IS_VISIBLE_TIMEOUT) {
public boolean test() throws Exception {
assertNotNull(model.getApplication(appName));
return true;
}
};
new ACondition("wait for app '"+ appName +"'to be RUNNING", APP_DEPLOY_TIMEOUT) {
public boolean test() throws Exception {
CloudAppDashElement element = model.getApplication(appName);
assertEquals(RunState.RUNNING, element.getRunState());
return true;
}
};
//Try to get request mappings
//TODO: make this work again in recent boot version (the rm endpoints are now not accessible anymore over http/https by default
// must actuator + ssh tunnel
// new ACondition("wait for request mappings", FETCH_REQUEST_MAPPINGS_TIMEOUT) {
// public boolean test() throws Exception {
// CloudAppDashElement element = model.getApplication(appName);
// List<RequestMapping> mappings = element.getLiveRequestMappings();
// assertNotNull(mappings); //Why is the test sometimes failing here?
// assertTrue(!mappings.isEmpty()); //Even though this is an 'empty' app should have some mappings,
// // for example an 'error' page.
// return true;
// }
// };
//Try to delete the app...
reset(ui);
when(ui.confirmOperation(eq("Deleting Elements"), anyString())).thenReturn(true);
CloudAppDashElement app = model.getApplication(appName);
app.getCloudModel().delete(ImmutableList.<BootDashElement>of(app), ui);
new ACondition("wait for app to be deleted", APP_DELETE_TIMEOUT) {
@Override
public boolean test() throws Exception {
assertNull(model.getApplication(appName));
return true;
}
};
}
@Test public void testDeployAppIntoDebugMode() throws Exception {
harness.createCfTarget(CfTestTargetParams.fromEnv());
final CloudFoundryBootDashModel model = harness.getCfTargetModel();
final BootProjectDashElement project = harness.getElementFor(
projects.createBootProject("to-deploy", withStarters("actuator", "web"))
);
final String appName = appHarness.randomAppName();
harness.answerDeploymentPrompt(ui, appName, appName);
model.performDeployment(ImmutableSet.of(project.getProject()), ui, RunState.DEBUGGING);
new ACondition("wait for app '"+ appName +"'to be DEBUGGING", APP_DEPLOY_TIMEOUT) {
public boolean test() throws Exception {
CloudAppDashElement element = model.getApplication(appName);
assertEquals(RunState.DEBUGGING, element.getRunState());
return true;
}
};
}
//This test commented because it uses 'createApplication' which no longer exists in V2 client.
// @Test
// public void testPreexistingApplicationInModel() throws Exception {
// // Create external client and deploy app "externally"
// ClientRequests externalClient = harness.createExternalClient(CfTestTargetParams.fromEnv());
//
// List<CFCloudDomain> domains = externalClient.getDomains();
//
// final String preexistingAppName = harness.randomAppName();
//
// CloudApplicationDeploymentProperties deploymentProperties = new CloudApplicationDeploymentProperties();
// deploymentProperties.setAppName(preexistingAppName);
// deploymentProperties.setMemory(1024);
// deploymentProperties.setUris(ImmutableList.of(preexistingAppName + "." + domains.get(0).getName()));
// deploymentProperties.setServices(ImmutableList.<String>of());
// externalClient.createApplication(deploymentProperties);
//
// // Create the boot dash target and model
// harness.createCfTarget(CfTestTargetParams.fromEnv());
//
// final CloudFoundryBootDashModel model = harness.getCfTargetModel();
//
// final BootProjectDashElement project = harness
// .getElementFor(projects.createBootWebProject("testPreexistingApplicationInModel"));
// final String newAppName = harness.randomAppName();
//
// // Create a new one too
// harness.answerDeploymentPrompt(ui, newAppName, newAppName);
//
// model.add(ImmutableList.<Object> of(project), ui);
//
// // The resulting deploy is asynchronous
// new ACondition("wait for apps '" + newAppName + "' and '" + preexistingAppName + "' to appear",
// APP_IS_VISIBLE_TIMEOUT) {
// public boolean test() throws Exception {
// assertNotNull(model.getApplication(newAppName));
// assertNotNull(model.getApplication(preexistingAppName));
//
// // check project mapping
// assertEquals("Expected new element in model to have workspace project mapping",
// model.getApplication(newAppName).getProject(), project.getProject());
//
// // No project mapping for the "external" app
// assertNull(model.getApplication(preexistingAppName).getProject());
//
// // check the actual CloudApplication
// CFApplication actualNewApp = model.getApplication(newAppName).getSummaryData();
// assertEquals("No CloudApplication mapping found", actualNewApp.getName(), newAppName);
//
// CFApplication actualPreexistingApp = model.getApplication(preexistingAppName).getSummaryData();
// assertEquals("No CloudApplication mapping found", actualPreexistingApp.getName(), preexistingAppName);
//
// return true;
// }
// };
// }
@Test
public void testEnvVarsSetOnFirstDeploy() throws Exception {
CloudFoundryBootDashModel target = harness.createCfTarget(CfTestTargetParams.fromEnv());
final CloudFoundryBootDashModel model = harness.getCfTargetModel();
IProject project = projects.createBootProject("to-deploy", withStarters("actuator", "web"));
final String appName = appHarness.randomAppName();
Map<String, String> env = new HashMap<>();
env.put("FOO", "something");
harness.answerDeploymentPrompt(ui, appName, appName, env);
model.performDeployment(ImmutableSet.of(project), ui, RunState.RUNNING);
new ACondition("wait for app '"+ appName +"'to be RUNNING", APP_DEPLOY_TIMEOUT) {
public boolean test() throws Exception {
CloudAppDashElement element = model.getApplication(appName);
assertEquals(RunState.RUNNING, element.getRunState());
return true;
}
};
Map<String,String> actualEnv = harness.fetchEnvironment(target, appName);
assertEquals("something", actualEnv.get("FOO"));
}
@Test
public void testServicesBoundOnFirstDeploy() throws Exception {
CloudFoundryBootDashModel target = harness.createCfTarget(CfTestTargetParams.fromEnv());
final CloudFoundryBootDashModel model = harness.getCfTargetModel();
IProject project = projects.createBootProject("to-deploy", withStarters("actuator", "web"));
List<String> bindServices = ImmutableList.of(
services.createTestService(), services.createTestService()
);
ACondition.waitFor("services exist "+bindServices, 30_000, () -> {
Set<String> services = client.getServices().stream()
.map(CFServiceInstance::getName)
.collect(Collectors.toSet());
System.out.println("services = "+services);
assertTrue(services.containsAll(bindServices));
});
final String appName = appHarness.randomAppName();
harness.answerDeploymentPrompt(ui, appName, appName, bindServices);
model.performDeployment(ImmutableSet.of(project), ui, RunState.RUNNING);
new ACondition("wait for app '"+ appName +"'to be RUNNING", APP_DEPLOY_TIMEOUT) {
public boolean test() throws Exception {
CloudAppDashElement element = model.getApplication(appName);
assertEquals(RunState.RUNNING, element.getRunState());
return true;
}
};
Set<String> actualServices = client.getBoundServicesSet(appName).block();
assertEquals(ImmutableSet.copyOf(bindServices), actualServices);
}
@Test public void testDeployManifestWithAbsolutePathAttribute() throws Exception {
CFClientParams targetParams = CfTestTargetParams.fromEnv();
IProject project = projects.createProject("to-deploy");
CloudFoundryBootDashModel model = harness.createCfTarget(targetParams);
waitForJobsToComplete();
File zipFile = getTestZip("testapp");
final String appName = appHarness.randomAppName();
IFile manifestFile = createFile(project, "manifest.yml",
"applications:\n" +
"- name: "+appName+"\n" +
" path: "+zipFile.getAbsolutePath() + "\n" +
" buildpack: staticfile_buildpack"
);
harness.answerDeploymentPrompt(ui, manifestFile);
model.performDeployment(ImmutableSet.of(project), ui, RunState.RUNNING);
ACondition.waitFor("app to appear", APP_IS_VISIBLE_TIMEOUT, () -> {
assertNotNull(model.getApplication(appName));
});
CloudAppDashElement app = model.getApplication(appName);
ACondition.waitFor("app to be running", APP_DEPLOY_TIMEOUT, () -> {
assertEquals(RunState.RUNNING, app.getRunState());
String url = pathJoin(app.getUrl(),"test.txt");
assertEquals(url, "some content here\n", IOUtils.toString(new URL(url)));
});
verify(ui).promptApplicationDeploymentProperties(any());
verifyNoMoreInteractions(ui);
}
@Test public void randomRoute() throws Exception {
CFClientParams targetParams = CfTestTargetParams.fromEnv();
IProject project = projects.createBootProject("to-deploy", withStarters("actuator", "web"));
CloudFoundryBootDashModel model = harness.createCfTarget(targetParams);
waitForJobsToComplete();
final String appName = appHarness.randomAppName();
IFile manifestFile = createFile(project, "manifest.yml",
"applications:\n" +
"- name: "+appName+"\n" +
" random-route: true\n" +
" buildpack: java_buildpack"
);
harness.answerDeploymentPrompt(ui, manifestFile);
model.performDeployment(ImmutableSet.of(project), ui, RunState.RUNNING);
ACondition.waitFor("app to appear", APP_IS_VISIBLE_TIMEOUT, () -> {
assertNotNull(model.getApplication(appName));
});
CloudAppDashElement app = model.getApplication(appName);
ACondition.waitFor("app to be running", APP_DEPLOY_TIMEOUT, () -> {
assertEquals(RunState.RUNNING, app.getRunState());
});
String host = app.getLiveHost();
assertTrue("app has host", StringUtil.hasText(host));
assertTrue("app has default domain", host.endsWith("."+CFAPPS_IO()));
host = host.substring(0, host.length()-CFAPPS_IO().length()-1);
assertTrue("host is random generated on push", !host.equals(appName));
}
private String CFAPPS_IO() {
return CloudFoundryClientTest.get_CFAPPS_IO(clientParams);
}
@Test public void randomRouteWithDomain() throws Exception {
CFClientParams targetParams = CfTestTargetParams.fromEnv();
IProject project = projects.createBootProject("to-deploy", withStarters("actuator", "web"));
CloudFoundryBootDashModel model = harness.createCfTarget(targetParams);
waitForJobsToComplete();
final String appName = appHarness.randomAppName();
IFile manifestFile = createFile(project, "manifest.yml",
"applications:\n" +
"- name: "+appName+"\n" +
" domain: "+CFAPPS_IO() + "\n" +
" random-route: true\n" +
" buildpack: java_buildpack"
);
harness.answerDeploymentPrompt(ui, manifestFile);
model.performDeployment(ImmutableSet.of(project), ui, RunState.RUNNING);
ACondition.waitFor("app to appear", APP_IS_VISIBLE_TIMEOUT, () -> {
assertNotNull(model.getApplication(appName));
});
CloudAppDashElement app = model.getApplication(appName);
ACondition.waitFor("app to be running", APP_DEPLOY_TIMEOUT, () -> {
assertEquals(RunState.RUNNING, app.getRunState());
});
String host = app.getLiveHost();
assertTrue("app has host", StringUtil.hasText(host));
assertTrue("app has default domain", host.endsWith("."+CFAPPS_IO()));
host = host.substring(0, host.length()-CFAPPS_IO().length()-1);
assertTrue("host is random generated on push", !host.equals(appName));
}
@Test public void httpRoute() throws Exception {
CFClientParams targetParams = CfTestTargetParams.fromEnv();
IProject project = projects.createBootProject("to-deploy", withStarters("actuator", "web"));
CloudFoundryBootDashModel model = harness.createCfTarget(targetParams);
waitForJobsToComplete();
final String appName = appHarness.randomAppName();
IFile manifestFile = createFile(project, "manifest.yml",
"applications:\n" +
"- name: "+appName+"\n" +
" buildpack: java_buildpack\n" +
" routes:\n" +
" - route: " + appName + '.'+CFAPPS_IO()
);
harness.answerDeploymentPrompt(ui, manifestFile);
model.performDeployment(ImmutableSet.of(project), ui, RunState.RUNNING);
ACondition.waitFor("app to appear", APP_IS_VISIBLE_TIMEOUT, () -> {
assertNotNull(model.getApplication(appName));
});
CloudAppDashElement app = model.getApplication(appName);
ACondition.waitFor("app to be running", APP_DEPLOY_TIMEOUT, () -> {
assertEquals(RunState.RUNNING, app.getRunState());
});
String host = app.getLiveHost();
assertEquals(appName + '.'+CFAPPS_IO(), host);
}
@Ignore
@Test public void httpRouteWithPath() throws Exception {
// This fails because the path part is not obtained from element. Bug already raised
CFClientParams targetParams = CfTestTargetParams.fromEnv();
IProject project = projects.createBootProject("to-deploy", withStarters("actuator", "web"));
CloudFoundryBootDashModel model = harness.createCfTarget(targetParams);
waitForJobsToComplete();
final String appName = appHarness.randomAppName();
IFile manifestFile = createFile(project, "manifest.yml",
"applications:\n" +
"- name: "+appName+"\n" +
" buildpack: java_buildpack\n" +
" routes:\n" +
" - route: " + appName + '.'+CFAPPS_IO() + "/myapppath"
);
harness.answerDeploymentPrompt(ui, manifestFile);
model.performDeployment(ImmutableSet.of(project), ui, RunState.RUNNING);
ACondition.waitFor("app to appear", APP_IS_VISIBLE_TIMEOUT, () -> {
assertNotNull(model.getApplication(appName));
});
CloudAppDashElement app = model.getApplication(appName);
ACondition.waitFor("app to be running", APP_DEPLOY_TIMEOUT, () -> {
assertEquals(RunState.RUNNING, app.getRunState());
});
String host = app.getLiveHost();
assertEquals(appName + '.'+CFAPPS_IO()+ "/myapppath", host);
}
private String pathJoin(String url, String append) {
while (url.endsWith("/")) {
url = url.substring(0, url.length()-1);
}
while (append.startsWith("/")) {
append = append.substring(1);
}
return url+"/"+append;
}
@Test public void deleteService() throws Exception {
String serviceName = services.createTestService();
CFClientParams targetParams = CfTestTargetParams.fromEnv();
CloudFoundryBootDashModel model = harness.createCfTarget(targetParams);
ACondition.waitFor("service to appear", APP_IS_VISIBLE_TIMEOUT, () -> {
assertNotNull(model.getService(serviceName));
});
when(ui.confirmOperation(contains("Deleting"), contains("Are you sure that you want to delete")))
.thenReturn(true);
CloudServiceInstanceDashElement service = model.getService(serviceName);
model.canDelete(service);
model.delete(ImmutableSet.of(service), ui);
ACondition.waitFor("service to disapear", SERVICE_DELETE_TIMEOUT, () -> {
assertNull(model.getService(serviceName));
});
}
///////////////////////////////////////////////////////////////////////////////////
private File getTestZip(String fileName) {
File sourceWorkspace = new File(
StsTestUtil.getSourceWorkspacePath("org.springframework.ide.eclipse.boot.dash.test"));
File file = new File(sourceWorkspace, fileName + ".zip");
Assert.isTrue(file.exists(), ""+ file);
return file;
}
}