/*******************************************************************************
* Copyright (c) 2012, 2015 Pivotal Software, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.
*
* Contributors:
* Pivotal Software, Inc. - initial API and implementation
********************************************************************************/
package org.cloudfoundry.ide.eclipse.server.tests.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import org.cloudfoundry.client.lib.CloudFoundryOperations;
import org.cloudfoundry.client.lib.domain.CloudDomain;
import org.cloudfoundry.client.lib.domain.CloudRoute;
import org.cloudfoundry.client.lib.domain.CloudService;
import org.cloudfoundry.client.lib.domain.CloudSpace;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudErrorUtil;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryPlugin;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryServer;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudUtil;
import org.cloudfoundry.ide.eclipse.server.core.internal.application.EnvironmentVariable;
import org.cloudfoundry.ide.eclipse.server.core.internal.client.CloudFoundryServerBehaviour;
import org.cloudfoundry.ide.eclipse.server.core.internal.spaces.CloudOrgsAndSpaces;
import org.cloudfoundry.ide.eclipse.server.tests.AllCloudFoundryTests;
import org.cloudfoundry.ide.eclipse.server.tests.server.TestServlet;
import org.cloudfoundry.ide.eclipse.server.tests.server.WebApplicationContainerBean;
import org.cloudfoundry.ide.eclipse.server.tests.sts.util.StsTestUtil;
import org.cloudfoundry.ide.eclipse.server.ui.internal.CloudUiUtil;
import org.cloudfoundry.ide.eclipse.server.ui.internal.ServerDescriptor;
import org.cloudfoundry.ide.eclipse.server.ui.internal.ServerHandler;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.wst.server.core.IModule;
import org.eclipse.wst.server.core.IServer;
import org.eclipse.wst.server.core.IServerWorkingCopy;
import org.eclipse.wst.server.core.ServerUtil;
import org.osgi.framework.Bundle;
/**
* Holds connection properties and provides utility methods for testing. The
* Fixture is intended to set up parts of the CF Eclipse plugin prior to a test
* case performing an operation, that would normally require user input via UI
* (for example, the deployment wizard). The fixture is also responsible for
* creating a per-test-case Harness, that sets up a server instance and web
* project for deployment
* <p/>
* Only one instance of a test fixture is intended to exist for a set of test
* cases, as opposed to a test {@link Harness} , which is created for EACH test
* case. The fixture is not intended to hold per-test-case state, but rather
* common properties (like credentials) that are only loaded once for a set of
* test cases (in other words, for the entire junit runtime).
* <p/>
* If state needs to be held on a per-test-case basis, use the {@link Harness}
* to store state.
*
* @author Steffen Pingel
*/
public class CloudFoundryTestFixture {
public static final String DYNAMIC_WEBAPP_NAME = "basic-dynamic-webapp";
public static final String PASSWORD_PROPERTY = "password";
public static final String USEREMAIL_PROPERTY = "username";
public static final String ORG_PROPERTY = "org";
public static final String SPACE_PROPERTY = "space";
public static final String URL_PROPERTY = "url";
public static final String CLOUDFOUNDRY_TEST_CREDENTIALS_PROPERTY = "test.credentials";
/**
*
* The intention of the harness is to create a web project and server
* instance PER test case, and which holds state that is relevant only
* during the lifetime of a single test case. It should NOT hold state that
* is shared across different test cases. This means that a new harness
* should be created for each test case that is run.
* <p/>
* IMPORTANT: Since the harness is responsible for creating the server
* instance and project, to ensure proper behaviour of each test case, it's
* important to use the SAME harness instance throughout the entire test
* case.
* <p/>
* In addition, the harness provides a default web project creation, that
* can be reused in each test case, and deploys an application based on the
* same web project, but using different application names each time. Note
* that application names need not match the project name, as application
* names can be user defined rather than generated.
* <p/>
* The harness provides a mechanism to generate an application name and URL
* based on the default web project by requiring callers to pass in an
* application name "prefix"
* <p/>
* The purpose of the prefix is to reuse the same web project for each tests
* but assign a different name to avoid "URL taken/Host taken" errors in
* case the server does not clear the routing of the app by the time the
* next test runs that will deploy the same project. By having different
* names for each application deployment, the routing problem is avoided or
* minimised.
*/
public class Harness {
private boolean projectCreated;
private IServer server;
private WebApplicationContainerBean webContainer;
private String applicationDomain;
// Added to the application name in order to avoid host name taken
// errors
// Even when clearing routes, host name taken errors are occasionally
// thrown
// if the tests are run within short intervals of one another.
private int randomPrefix = 0;
/**
* Creates a default web application project in the workspace, and
* prepares it for deployment by creating WST IModule for it. This does
* NOT do the actual application deployment to the CF server, it only
* prepares it for deployment locally.
* @return
* @throws Exception
*/
public IProject createDefaultProjectAndAddModule() throws Exception {
IProject project = createProject(getDefaultWebAppProjectName());
projectCreated = true;
addModule(project);
return project;
}
protected String getDomain() throws CoreException {
if (applicationDomain == null) {
List<CloudDomain> domains = getBehaviour().getDomainsForSpace(new NullProgressMonitor());
// Get a default domain
applicationDomain = domains.get(0).getName();
applicationDomain = applicationDomain.replace("http://", "");
}
return applicationDomain;
}
public void addModule(IProject project) throws CoreException {
Assert.isNotNull(server, "Invoke createServer() first");
IModule[] modules = ServerUtil.getModules(project);
IServerWorkingCopy wc = server.createWorkingCopy();
wc.modifyModules(modules, new IModule[0], null);
wc.save(true, null);
}
public IProject createProject(String projectName) throws CoreException, IOException {
return StsTestUtil.createPredefinedProject(projectName, CloudFoundryTestFixture.PLUGIN_ID);
}
public IServer createServer() throws CoreException {
Assert.isTrue(server == null, "createServer() already invoked");
server = handler.createServer(new NullProgressMonitor(), ServerHandler.ALWAYS_OVERWRITE);
IServerWorkingCopy serverWC = server.createWorkingCopy();
CloudFoundryServer cloudFoundryServer = (CloudFoundryServer) serverWC.loadAdapter(CloudFoundryServer.class,
null);
CredentialProperties credentials = getCredentials();
cloudFoundryServer.setPassword(credentials.password);
cloudFoundryServer.setUsername(credentials.userEmail);
cloudFoundryServer.setUrl(getUrl());
setCloudSpace(cloudFoundryServer, credentials.organization, credentials.space);
serverWC.save(true, null);
return server;
}
protected void setCloudSpace(CloudFoundryServer cloudServer, String orgName, String spaceName)
throws CoreException {
CloudOrgsAndSpaces spaces = CloudUiUtil.getCloudSpaces(cloudServer.getUsername(),
cloudServer.getPassword(), cloudServer.getUrl(), false, cloudServer.getSelfSignedCertificate(),
null);
Assert.isTrue(spaces != null, "Failed to resolve orgs and spaces.");
Assert.isTrue(spaces.getDefaultCloudSpace() != null,
"No default space selected in cloud space lookup handler.");
CloudSpace cloudSpace = spaces.getSpace(orgName, spaceName);
if (cloudSpace == null) {
throw CloudErrorUtil.toCoreException("Failed to resolve cloud space when running junits: " + orgName
+ " - " + spaceName);
}
cloudServer.setSpace(cloudSpace);
}
public String getUrl() {
if (webContainer != null) {
return "http://localhost:" + webContainer.getPort() + "/";
}
else {
return url;
}
}
protected CloudFoundryServerBehaviour getBehaviour() {
return (CloudFoundryServerBehaviour) server.loadAdapter(CloudFoundryServerBehaviour.class, null);
}
public void setup() throws CoreException {
Random random = new Random(100);
randomPrefix = Math.abs(random.nextInt(1000000));
// Clean up all projects from workspace
StsTestUtil.cleanUpProjects();
// Perform clean up on existing published apps and services
if (server != null) {
CloudFoundryServerBehaviour serverBehavior = getBehaviour();
// Delete all applications
serverBehavior.deleteAllApplications(null);
// Delete all services
deleteAllServices();
// Clear all domains and routes to avoid host taken errors
clearTestDomainAndRoutes();
}
}
public CloudFoundryOperations createExternalClient() throws CoreException {
CredentialProperties cred = getTestFixture().getCredentials();
StsTestUtil.validateCredentials(cred);
CloudFoundryServer cfServer = (CloudFoundryServer) server.getAdapter(CloudFoundryServer.class);
return StsTestUtil.createStandaloneClient(cred, getTestFixture().getUrl(),
cfServer.getSelfSignedCertificate());
}
private void clearTestDomainAndRoutes() throws CoreException {
CloudFoundryOperations client = createExternalClient();
client.login();
String domain = getDomain();
if (domain != null) {
List<CloudRoute> routes = client.getRoutes(domain);
for (CloudRoute route : routes) {
client.deleteRoute(route.getHost(), route.getDomain().getName());
}
}
}
public void deleteService(CloudService serviceToDelete) throws CoreException {
CloudFoundryServerBehaviour serverBehavior = getBehaviour();
String serviceName = serviceToDelete.getName();
List<String> services = new ArrayList<String>();
services.add(serviceName);
serverBehavior.operations().deleteServices(services).run(new NullProgressMonitor());
}
public void deleteAllServices() throws CoreException {
List<CloudService> services = getAllServices();
for (CloudService service : services) {
deleteService(service);
CloudFoundryTestUtil.waitIntervals(1000);
}
}
public List<CloudService> getAllServices() throws CoreException {
List<CloudService> services = getBehaviour().getServices(new NullProgressMonitor());
if (services == null) {
services = new ArrayList<CloudService>(0);
}
return services;
}
public void dispose() throws CoreException {
if (webContainer != null) {
// FIXNS: Commented out because of STS-3159
// webContainer.stop();
}
if (server != null) {
CloudFoundryServerBehaviour cloudFoundryServer = (CloudFoundryServerBehaviour) server.loadAdapter(
CloudFoundryServerBehaviour.class, null);
if (projectCreated) {
try {
cloudFoundryServer.deleteAllApplications(null);
}
catch (CoreException e) {
e.printStackTrace();
}
}
try {
cloudFoundryServer.disconnect(null);
}
catch (CoreException e) {
e.printStackTrace();
}
}
try {
handler.deleteServerAndRuntime(new NullProgressMonitor());
}
catch (CoreException e) {
e.printStackTrace();
}
if (projectCreated) {
StsTestUtil.deleteAllProjects();
}
}
/**
* Given a prefix for an application name (e.g. "test01" in
* "test01myprojectname") constructs the expected URL based on the
* default web project name ("myprojectname") and the domain. E.g:
*
* <p/>
* Arg = test01
* <p/>
* Default project name = myprojectname
* <p/>
* domain = run.pivotal.io URL = test01myprojectname.run.pivotal.io
* <p/>
* The purpose of the prefix is to reuse the same web project for each
* tests but assign a different name to avoid "URL taken/Host taken"
* errors in case the server does not clear the routing of the app by
* the time the next test runs that will deploy the same project. By
* having different names for each application deployment, the routing
* problem is avoided or minimised.
* @param appPrefix
* @return
* @throws CoreException
*/
public String getExpectedDefaultURL(String appPrefix) throws CoreException {
return getDefaultWebAppName(appPrefix) + '.' + getDomain();
}
public String getDefaultWebAppProjectName() {
return DYNAMIC_WEBAPP_NAME;
}
public String getDefaultWebAppName(String appPrefix) {
return appPrefix + '_' + randomPrefix + '_' + getDefaultWebAppProjectName();
}
public TestServlet startMockServer() throws Exception {
Bundle bundle = Platform.getBundle(AllCloudFoundryTests.PLUGIN_ID);
URL resourceUrl = bundle.getResource("webapp");
URL localURL = FileLocator.toFileURL(resourceUrl);
File file = new File(localURL.getFile());
webContainer = new WebApplicationContainerBean(file);
// FIXNS: Commented out because of STS-3159
// webContainer.start();
return getServer();
}
public TestServlet getServer() {
return TestServlet.getInstance();
}
}
public static final String PLUGIN_ID = "org.cloudfoundry.ide.eclipse.server.tests";
private static CloudFoundryTestFixture current;
public static CloudFoundryTestFixture getTestFixture() throws CoreException {
if (current == null) {
current = new CloudFoundryTestFixture("run.pivotal.io");
}
return current;
}
/**
* This test fixture hould not be used to configure to application
* deployment.
* @return
* @throws CoreException
*/
public CloudFoundryTestFixture baseConfiguration() throws CoreException {
return configureForApplicationDeployment(null, CloudUtil.DEFAULT_MEMORY, false);
}
/**
* Configures a test fixture to deploy an application with the given
* application name. The full application name must be used.
* @param fullApplicationName
* @param deployStopped true if the application should be deployed in
* stopped state. False if it should also be started
* @return
* @throws CoreException
*/
public CloudFoundryTestFixture configureForApplicationDeployment(String fullApplicationName, int memory,
boolean deployStopped) throws CoreException {
CloudFoundryPlugin.setCallback(new TestCallback(fullApplicationName, memory, deployStopped));
return getTestFixture();
}
public CloudFoundryTestFixture configureForApplicationDeployment(String fullApplicationName, int memory,
boolean deployStopped, List<EnvironmentVariable> variables, List<CloudService> services)
throws CoreException {
CloudFoundryPlugin
.setCallback(new TestCallback(fullApplicationName, memory, deployStopped, variables, services));
return getTestFixture();
}
private final ServerHandler handler;
private static CredentialProperties credentials;
private final String url;
/**
* This will create a Cloud server instances based either on the URL in a
* properties file that also contains the credentials, or the default server
* domain passed into the fixture. The URL in the properties file is
* optional, if it is not found, the default server Domain will be used
* instead
* @param serverDomain default domain to use for the Cloud space.
*/
public CloudFoundryTestFixture(String serverDomain) {
String urlFromProperties = null;
try {
urlFromProperties = getCredentials().url;
}
catch (CoreException e) {
e.printStackTrace();
}
if (urlFromProperties != null) {
if (urlFromProperties.startsWith("http://") || urlFromProperties.startsWith("https://")) {
this.url = urlFromProperties;
}
else {
this.url = "http://" + urlFromProperties;
}
}
else {
this.url = "http://api." + serverDomain;
}
ServerDescriptor descriptor = new ServerDescriptor("server") {
{
setRuntimeTypeId("org.cloudfoundry.cloudfoundryserver.test.runtime.10");
setServerTypeId("org.cloudfoundry.cloudfoundryserver.test.10");
setRuntimeName("Cloud Foundry Test Runtime");
setServerName("Cloud Foundry Test Server");
setForceCreateRuntime(true);
}
};
handler = new ServerHandler(descriptor);
}
public CredentialProperties getCredentials() throws CoreException {
if (credentials == null) {
credentials = getUserTestCredentials();
}
return credentials;
}
public String getUrl() {
return url;
}
public boolean getSelfSignedCertificate() {
return false;
}
/**
* New harness is created. To ensure proper behaviour for each test case.
* Create the harness in the test setup, and use this SAME harness
* throughout the lifetime of the same test case. Therefore, a harness
* should ideally only be created ONCE throughout the lifetime of a single
* test case.
* @return new Harness. Never null
*/
public Harness createHarness() {
return new Harness();
}
public static class CredentialProperties {
public final String userEmail;
public final String password;
public final String organization;
public final String space;
public final String url;
public CredentialProperties(String url, String userEmail, String password, String organization, String space) {
this.url = url;
this.userEmail = userEmail;
this.password = password;
this.organization = organization;
this.space = space;
}
}
/**
* Returns non-null credentials, although values of the credentials may be
* empty if failed to read credentials
* @return
*/
private static CredentialProperties getUserTestCredentials() throws CoreException {
String propertiesLocation = System.getProperty(CLOUDFOUNDRY_TEST_CREDENTIALS_PROPERTY);
String userEmail = null;
String password = null;
String org = null;
String space = null;
String url = null;
if (propertiesLocation != null) {
File propertiesFile = new File(propertiesLocation);
InputStream fileInputStream = null;
try {
if (propertiesFile.exists() && propertiesFile.canRead()) {
fileInputStream = new FileInputStream(propertiesFile);
Properties properties = new Properties();
properties.load(fileInputStream);
userEmail = properties.getProperty(USEREMAIL_PROPERTY);
password = properties.getProperty(PASSWORD_PROPERTY);
org = properties.getProperty(ORG_PROPERTY);
space = properties.getProperty(SPACE_PROPERTY);
url = properties.getProperty(URL_PROPERTY);
}
}
catch (FileNotFoundException e) {
throw CloudErrorUtil.toCoreException(e);
}
catch (IOException e) {
throw CloudErrorUtil.toCoreException(e);
}
finally {
try {
if (fileInputStream != null) {
fileInputStream.close();
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
CredentialProperties cred = new CredentialProperties(url, userEmail, password, org, space);
StsTestUtil.validateCredentials(cred);
return cred;
}
}