com.francetelecom.clara.cloud.activation.plugin.cf.infrastructure.AbstractCfAdapterIT.java Source code

Java tutorial

Introduction

Here is the source code for com.francetelecom.clara.cloud.activation.plugin.cf.infrastructure.AbstractCfAdapterIT.java

Source

/**
 * Copyright (C) 2015 Orange
 * Licensed under 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.
 */
package com.francetelecom.clara.cloud.activation.plugin.cf.infrastructure;

import com.francetelecom.clara.cloud.commons.MavenReference;
import com.francetelecom.clara.cloud.commons.TechnicalException;
import com.francetelecom.clara.cloud.logicalmodel.samplecatalog.SampleAppProperties;
import com.francetelecom.clara.cloud.mvn.consumer.MvnRepoDao;
import com.francetelecom.clara.cloud.techmodel.cf.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.cloudfoundry.client.lib.CloudCredentials;
import org.cloudfoundry.client.lib.CloudFoundryException;
import org.cloudfoundry.client.lib.CloudFoundryOperations;
import org.cloudfoundry.client.lib.HttpProxyConfiguration;
import org.cloudfoundry.client.lib.domain.CloudApplication;
import org.cloudfoundry.client.lib.domain.CloudDomain;
import org.cloudfoundry.client.lib.domain.CloudRoute;
import org.cloudfoundry.client.lib.domain.CloudService;
import org.fest.assertions.Assertions;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import static org.fest.assertions.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

/*
 * Copyright 2009-2012 the original author or authors.
 *
 * Licensed under 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.
 */

/**
 * Test class for V2 cloud controller.
 * 
 * You need to create organization, space and user account via the portal and
 * set these values using system properties.
 * 
 * @author Thomas Risberg
 */
public abstract class AbstractCfAdapterIT {

    public static final Logger logger = LoggerFactory.getLogger(AbstractCfAdapterIT.class);

    @Autowired
    private MvnRepoDao mvnRepoDao;

    @Autowired
    private SampleAppProperties sampleAppProperties;

    @Autowired
    @Qualifier("datacenter")
    protected String datacenter;

    protected String ccEmail;

    protected CfAdapterImpl cfAdapter;

    protected String cfSubdomain;

    protected String cfDefaultSpace;

    @Value("${cf.ccng.use_proxy}")
    protected boolean isUsingHttpProxy;

    @Value("${cf.ccng.proxyHost}")
    protected String httpProxyHost;

    @Value("${cf.ccng.proxyPort}")
    protected int httpProxyPort;

    private CloudFoundryOperations cfClient;
    /**
     * The default domain without owner, i.e. cfapps.io
     */
    protected static String defaultDomainName = null;

    protected static String defaultNamespace(String email) {
        String name_without_domain = email.substring(0, email.indexOf('@')).replaceAll("\\.", "-")
                .replaceAll("\\+", "-").replaceAll("_", "-");
        // Keep it short, 5 chars max
        name_without_domain = StringUtils.left(name_without_domain, 5);
        return name_without_domain;
    }

    @Test
    public void creates_the_requested_domain_if_missing() {
        // given
        String domainNameToCreate = "newdomain.tocreate.com";
        try {

            if (cfAdapter.domainExists(domainNameToCreate, cfDefaultSpace)) {
                cfClient.deleteDomain(domainNameToCreate);
            }

            // when
            cfAdapter.addDomain(domainNameToCreate, cfDefaultSpace);

            // then
            boolean domainWasCreated = cfAdapter.domainExists(domainNameToCreate, cfDefaultSpace);
            assertThat(domainWasCreated).as(domainNameToCreate).isTrue();
        } finally {
            if (cfAdapter.domainExists(domainNameToCreate, cfDefaultSpace)) {
                cfClient.deleteDomain(domainNameToCreate);
            }
        }

    }

    @Test
    @Ignore("Re-enable if you need to debug jonas buildpack and compare against native buildpack")
    public void provisions_starts_stops_deletes_a_small_war_in_tomcat() throws IOException {
        MavenReference cfWicketJpa = sampleAppProperties.getMavenReference("cf-wicket-jpa", "war");

        String testRequestPath = "/"; // default buildpacks "mount" any wars to
        // ROOT
        provisionStartStopDeletesApp(cfWicketJpa, getJavaBuildpackUrl(),
                getTestAppName() + "-" + "travel_test-" + "upload1", 512, testRequestPath);
    }

    @Test
    @Ignore("Re-enable if you need to debug jeeprobe and compare against hello-env")
    public void provisions_starts_stops_deletes_a_small_war_on_jonas() throws IOException {
        MavenReference cfWicketJpa = sampleAppProperties.getMavenReference("cf-wicket-jpa", "war");

        String testRequestPath = "app/"; // buildpacks "mount" any wars to
        // app.war, and jonas exposes them
        // as app
        provisionStartStopDeletesApp(cfWicketJpa, getJonasBuildpackUrl(),
                getTestAppName() + "-" + "travel_test-" + "upload1", 512, testRequestPath);
    }

    @Test
    public void provisions_starts_stops_deletes_jeeprobe_ear() throws IOException {
        MavenReference jeeProbeMavenReference = sampleAppProperties.getMavenReference("jeeprobe", "ear");
        MavenReference jeeProbeEarRef = mvnRepoDao.resolveUrl(jeeProbeMavenReference);

        String testRequestPath = "/jeeprobe/"; // JeeProbe EAR specified a
        // context-root which is honored
        // by Jonas
        int ramMb = 1024; // temporary oversized to 1 Gb to workaround warden
                          // bug
        provisionStartStopDeletesApp(jeeProbeEarRef, getJonasBuildpackUrl(), getTestAppName() + "-" + "jeeProbe",
                ramMb, testRequestPath);
    }

    @Test
    public void should_create_then_delete_a_route() {
        Space space = new Space();

        Route route = new Route(new RouteUri("demo-elpaasofrontend13beta." + getTestDomainName()), "root1", space);

        Assertions.assertThat(cfAdapter.routeExists(route, cfDefaultSpace)).isFalse();

        cfAdapter.createRoute(route, cfDefaultSpace);
        Assertions.assertThat(cfAdapter.routeExists(route, cfDefaultSpace)).isTrue();

        cfAdapter.deleteRoute(route, cfDefaultSpace);
        Assertions.assertThat(cfAdapter.routeExists(route, cfDefaultSpace)).isFalse();

    }

    @Test(expected = CloudFoundryException.class)
    @Ignore
    public void fail_to_create_2_routes_with_same_host_for_same_domain() {
        final Space space = new Space();
        Route route1 = new Route(new RouteUri("test-host-conflict." + getTestDomainName()), "root1", space);
        Route route2 = new Route(new RouteUri("test-host-conflict." + getTestDomainName()), "root1", space);

        cfAdapter.createRoute(route1, cfDefaultSpace);
        cfAdapter.createRoute(route2, cfDefaultSpace);

        //then it should fail

    }

    @Test
    @Ignore("should be tested at upper level")
    public void handles_uri_conflicts() {
        MavenReference simpleProbe = mvnRepoDao
                .resolveUrl(sampleAppProperties.getMavenReference("simple-probe", "jar"));

        Space space = new Space();
        space.activate(new SpaceName(cfDefaultSpace));

        final App cfApp = new App(space, getTestAppName(), simpleProbe, getJonasBuildpackUrl(), 512, 1);
        Route route1 = new Route(new RouteUri("demo-elpaasofrontend13beta." + getTestDomainName()), "root1", space);
        Route route2 = new Route(new RouteUri("demo-elpaasobackend13beta." + getTestDomainName()), "root2", space);

        cfApp.mapRoute(route1);
        cfApp.mapRoute(route2);

        cfAdapter.createApp(cfApp, cfDefaultSpace);

        CloudApplication app = cfClient.getApplication(cfApp.getAppName());
        assertNotNull(app);
        assertEquals(CloudApplication.AppState.STOPPED, app.getState());
        for (String uri : cfApp.getRouteURIs()) {
            assertThat(uri.startsWith("c")).isFalse(); // should not have uri
            // conflicts on the
            // first app.
        }

        // Try to provision a second app with with same params (name and uri)
        final App secondApp = new App(space, getTestAppName() + "-2", cfApp.getAppBinaries(),
                cfApp.getBuildPackUrl(), cfApp.getRamMb(), 1);
        secondApp.mapRoute(route1);
        secondApp.mapRoute(route2);

        cfAdapter.createApp(secondApp, cfDefaultSpace);

        app = cfClient.getApplication(secondApp.getAppName());
        assertNotNull(app);
        assertEquals(CloudApplication.AppState.STOPPED, app.getState());

        for (String uri : secondApp.getRouteURIs()) {
            assertThat(uri).as("conflict-prefixed uri").startsWith("c");
        }
    }

    private void provisionStartStopDeletesApp(MavenReference mavenReference, String buildpackUrl, String appName,
            int ramMb, String testRequestPath) throws IOException {

        Space space = new Space();

        App cfApp = new App(space, appName, mavenReference, buildpackUrl, ramMb, 1);
        Route route = new Route(new RouteUri("webgui." + getTestDomainName()), testRequestPath, space);
        cfApp.mapRoute(route);

        // when
        cfAdapter.createRoute(route, cfDefaultSpace);
        // then route should have been created
        assertThat(cfAdapter.routeExists(route, cfDefaultSpace)).isTrue();

        // when
        cfAdapter.createApp(cfApp, cfDefaultSpace);
        // then app should have been created
        assertThat(cfAdapter.appExists(appName, cfDefaultSpace)).isTrue();
        // then app should be stopped
        assertThat(cfAdapter.isAppStopped(appName, cfDefaultSpace)).isTrue();

        cfAdapter.startApp(cfApp, cfDefaultSpace);
        // because starting an app is async
        pollAppStartStatus(cfApp);
        // then app should be started
        assertThat(cfAdapter.isAppStarted(appName, cfDefaultSpace)).isTrue();

        String firstUri = cfApp.getRouteURIs().get(0);

        try {
            Map<String, String> logs = cfClient.getLogs(appName);
            logger.info("logs for " + appName + "are:\n" + logs);
        } catch (Exception e) {
            logger.info("unable to get app log for " + appName, e);
        }

        testRemoteAppWebGui(firstUri, testRequestPath, appName);

        // when
        cfAdapter.stopApp(cfApp, cfDefaultSpace);
        // then app should be stopped
        assertThat(cfAdapter.isAppStopped(appName, cfDefaultSpace)).isTrue();

        // when
        cfAdapter.deleteApp(cfApp, cfDefaultSpace);
        // then app should have been removed
        assertThat(cfAdapter.appExists(appName, cfDefaultSpace)).isFalse();
        // then domain should have been removed
        assertThat(cfAdapter.domainExists(route.getDomain(), cfDefaultSpace)).isFalse();
        // then route should have been removed
        assertThat(cfAdapter.routeExists(route, cfDefaultSpace)).isFalse();

    }

    /**
     * 
     * @param virtualHost
     * @param testRequestPath
     * @param appNameToDumpDiagnosticLogs
     *            the name of the app to display the logs of if the webgui is
     *            unreacheable, or null to no display such logs.
     * @throws IOException
     */
    protected void testRemoteAppWebGui(String virtualHost, String testRequestPath,
            String appNameToDumpDiagnosticLogs) throws IOException {

        HttpClientConfig defaultProxyConfig = getHttpProxyConfigToQueryWebGuiRoutes();
        int retry = 0;
        int maxRetries = 10;

        String testResponse = null;
        StringBuffer testFailureDetails = new StringBuffer();
        do {
            logger.info("Querying " + getWebGuiURL(virtualHost, testRequestPath) + " using proxyConfig="
                    + defaultProxyConfig + " ...");
            try {
                testResponse = fetchRoutedContentAsString(getWebGuiURL(virtualHost, testRequestPath),
                        defaultProxyConfig);
                break;
            } catch (HttpResponseException e) {
                String msg = "Querying " + getWebGuiURL(virtualHost, testRequestPath) + " ... done. Caught: " + e;
                logger.info(msg);
                testFailureDetails.append(msg);
                testFailureDetails.append("\n");
                if (e.getStatusCode() == 404) {
                    logger.info("Sleeping for 10s before next retry (" + retry + "/" + maxRetries + ")");

                    if (appNameToDumpDiagnosticLogs != null) {
                        cfAdapter.logAppDiagnostics(appNameToDumpDiagnosticLogs, cfDefaultSpace);
                    }
                    try {
                        Thread.sleep(10 * 1000);
                    } catch (InterruptedException e1) {
                        // Ignore
                    }
                    retry++;
                } else {
                    break; // no retries for unexpected errors
                }
            }
        } while (retry < maxRetries);

        logger.info("Querying " + getWebGuiURL(virtualHost, testRequestPath) + " ... done. Returned: "
                + StringUtils.abbreviate(testResponse, 50));
        assertThat(testResponse).as("webGui response").isNotNull().isNotEmpty();
        assertThat(retry).overridingErrorMessage("Expecting zero retries on webGui polling, got " + retry
                + " retries. Details:" + testFailureDetails).isLessThanOrEqualTo(1); // Expect
        // the app to immediately return a valid response, retries are only here
        // to help diagnostics
    }

    protected HttpClientConfig getHttpProxyConfigToQueryWebGuiRoutes() {
        // By default consider routes are reacheable without proxies
        HttpClientConfig defaultProxyConfig = new HttpClientConfig() {
            @Override
            public void applyConfig(DefaultHttpClient httpclient) {
            }

            @Override
            public String toString() {
                return "HttpClientConfig { direct connection, no proxy }";
            }
        };
        return defaultProxyConfig;
    }

    interface HttpClientConfig {
        void applyConfig(DefaultHttpClient httpclient);
    }

    private String fetchRoutedContentAsString(String uri, HttpClientConfig httpClientConfig) throws IOException {
        String contentA;

        DefaultHttpClient httpclient = new DefaultHttpClient();
        if (httpClientConfig != null) {
            httpClientConfig.applyConfig(httpclient);
        }
        try {
            HttpGet httpget = new HttpGet(uri);
            contentA = httpclient.execute(httpget, new BasicResponseHandler());
        } finally {
            httpclient.getConnectionManager().shutdown();
        }
        return contentA;
    }

    public String getTestAppName() {
        return "AbstractCfAdapterIT-" + datacenter + "-" + defaultNamespace(ccEmail) + cfSubdomain;
    }

    public String getTestDomainName() {
        return "cfconsummerit." + datacenter + "." + defaultNamespace(ccEmail) + "." + cfSubdomain; // lower
        // case
    }

    protected String getWebGuiURL(String virtualHost, String testRequestPath) {
        if (testRequestPath != null && testRequestPath.startsWith("/"))
            return "http://" + virtualHost + testRequestPath;
        else
            return "http://" + virtualHost + "/" + testRequestPath;
    }

    public HttpProxyConfiguration httpProxyConfiguration() {
        logger.info("cfAdapter Connection settings: isUsingHttpProxy=" + cfAdapter.isUsingHttpProxy
                + " httpProxyHost=" + cfAdapter.httpProxyHost + " httpProxyPort=" + cfAdapter.httpProxyPort);
        HttpProxyConfiguration httpProxyConfiguration;

        if (cfAdapter.isUsingHttpProxy && (cfAdapter.httpProxyHost != null)) {
            httpProxyConfiguration = new HttpProxyConfiguration(cfAdapter.httpProxyHost, cfAdapter.httpProxyPort);
        } else {
            httpProxyConfiguration = null;
        }
        return httpProxyConfiguration;
    }

    @Before
    public void setUp() {
        cfClient = CFClientFactory.login(new CloudCredentials(cfAdapter.getEmail(), cfAdapter.getPassword()),
                cfAdapter.getTarget(), cfAdapter.getSpace(), cfAdapter.getOrg(), cfAdapter.getDomain(),
                cfAdapter.trustSelfSignedCerts, httpProxyConfiguration());
        List<CloudApplication> applications = cfClient.getApplications();
        for (CloudApplication application : applications) {
            if (application.getName().contains(getTestAppName())) {
                cfClient.deleteApplication(application.getName());
            }
            List<String> boundServices = application.getServices();
            List<String> services = boundServices;
            for (String service : services) {
                cfClient.deleteService(service);
            }
            clearDomain(getTestDomainName(), true);
            clearDomain(getDefaultDomain(), false);
        }
        cfAdapter.addDomain(getTestDomainName(), cfDefaultSpace);
    }

    private String getDefaultDomain() {
        for (CloudDomain domain : cfClient.getDomainsForOrg()) {
            if (domain.getOwner().getName().equals("none")) {
                return domain.getName();
            }
        }
        return null;
    }

    @After
    public void tearDown() {
        List<CloudApplication> cloudApps = cfClient.getApplications();
        for (CloudApplication cloudApp : cloudApps) {
            if (cloudApp.getName().contains(getTestAppName())) {
                cfClient.deleteApplication(cloudApp.getName());
            }
        }

        List<CloudService> cloudServices = cfClient.getServices();
        for (CloudService cloudService : cloudServices) {
            if (cloudService.getName().contains(getTestAppName())) {
                cfClient.deleteService(cloudService.getName());
            }
        }

        clearDomain(getTestDomainName(), true);
        clearDomain(getDefaultDomain(), false);

        cfClient.logout();
    }

    private void clearDomain(String domainToClear, boolean deleteDomain) {
        for (CloudDomain domain : cfClient.getDomainsForOrg()) {
            String domainName = domain.getName();
            if (domainName.contains(domainToClear)) {
                List<CloudRoute> routes = cfClient.getRoutes(domainName);
                for (CloudRoute route : routes) {
                    cfClient.deleteRoute(route.getHost(), route.getDomain().getName());
                }
                if (deleteDomain) {
                    cfClient.deleteDomain(domainName);
                }
            }
        }
    }

    public abstract String getJonasBuildpackUrl();

    public abstract String getJavaBuildpackUrl();

    private void pollAppStartStatus(App app) {
        try {

            int timeoutMs = 5 * 60 * 1000;
            long start = System.currentTimeMillis();
            boolean pass = false;
            int i;
            for (i = 0; i < 50 && pass == false; i++) {
                int nbPass = cfAdapter.peekAppStartStatus(app.getInstanceCount(), app.getAppName(), cfDefaultSpace);
                pass = (nbPass == app.getInstanceCount());
                if (!pass) {
                    long elapsed = System.currentTimeMillis() - start;
                    if (elapsed > timeoutMs) {
                        logger.info("timeout waiting for app" + app.getAppName() + " to start: polled " + i
                                + "times and waited " + elapsed + "ms (max is:" + timeoutMs + " ms)");
                        break;
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // ignore
                    }
                }
            }
            boolean startWasOk = pass;
            if (!startWasOk) {
                cfAdapter.logAppDiagnostics(app.getAppName(), cfDefaultSpace);
                long elapsed = System.currentTimeMillis() - start;
                throw new TechnicalException("Unable to successfully start " + app.getAppName() + ": polled " + i
                        + " times and waited " + elapsed + " ms (max is:" + timeoutMs + " ms)");
            } else {
                logger.info("all " + app.getInstanceCount() + " instance(s) have properly started");
            }
        } catch (Exception e) {
            throw new TechnicalException("unable to start app:" + app.getAppName(), e);
        }
    }

}