org.fusesource.cloudmix.testing.TestController.java Source code

Java tutorial

Introduction

Here is the source code for org.fusesource.cloudmix.testing.TestController.java

Source

/**************************************************************************************
 * Copyright (C) 2009 Progress Software, Inc. All rights reserved.                    *
 * http://fusesource.com                                                              *
 * ---------------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the AGPL license      *
 * a copy of which has been included with this distribution in the license.txt file.  *
 **************************************************************************************/
package org.fusesource.cloudmix.testing;

import com.sun.jersey.api.client.filter.LoggingFilter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.fusesource.cloudmix.agent.RestGridClient;
import org.fusesource.cloudmix.agent.logging.LogRecord;
import org.fusesource.cloudmix.common.CloudmixHelper;
import org.fusesource.cloudmix.common.GridClient;
import org.fusesource.cloudmix.common.GridClients;
import org.fusesource.cloudmix.common.ProcessClient;
import org.fusesource.cloudmix.common.dto.AgentDetails;
import org.fusesource.cloudmix.common.dto.Dependency;
import org.fusesource.cloudmix.common.dto.DependencyStatus;
import org.fusesource.cloudmix.common.dto.FeatureDetails;
import org.fusesource.cloudmix.common.dto.ProfileDetails;
import org.fusesource.cloudmix.common.dto.ProfileStatus;
import org.junit.After;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.rules.TestName;

import javax.ws.rs.core.UriBuilder;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Creates a new temporary environment for a distributed test,
 * initialises the system, then runs the test and kills the enviroment
 *
 * @version $Revision: 1.1 $
 */
public abstract class TestController {

    /**
     * The name of the file which all the newly created profile IDs are written on each test run.
     * You can then clean up your test cloud by deleting all of the profiles in this file
     */
    public static final String PROFILE_ID_FILENAME = "cloudmix.profiles";

    private static final transient Log LOG = LogFactory.getLog(TestController.class);

    //CHECKSTYLE:OFF
    @Rule
    public TestName testName = new TestName();
    //CHECKSTYLE:ON

    protected long startupTimeout = 60 * 1000;
    protected String controllerUrl = CloudmixHelper.getDefaultRootUrl();

    protected List<FeatureDetails> features = new CopyOnWriteArrayList<FeatureDetails>();
    protected RestGridClient gridClient;
    protected ProfileDetails profile;
    protected String profileId;
    protected boolean provisioned;
    protected boolean destroyProfileAfter;
    protected boolean destroyOtherProfilesOnStartup = true;
    protected boolean logRestOperations;

    protected String getTestName() {
        String answer = testName.getMethodName();
        if (answer == null || answer.length() == 0) {
            return "Unknown";
        }
        return answer;
    }

    /**
     * Registers any features which are required for this system test
     */
    protected abstract void installFeatures();

    /**
     * Factory method to create a new feature which is unique to the current test's profile
     */
    protected FeatureDetails createFeatureDetails(String featureId, String uri) {
        FeatureDetails answer = new FeatureDetails(featureId, uri);
        ensureFeatureIdLocalToProfile(answer);
        return answer;
    }

    /**
     * Asserts that the test cloud is setup and provisioned properly within the given {@link #startupTimeout}.
     * <p/>
     * This method should be called within each test method so that the profile is setup
     * correctly with the class of the test and the test method name.
     */
    public void checkProvisioned() throws Exception {
        try {
            if (provisioned) {
                return;
            }

            // lets get the default URL for cloudmix
            System.out.println("Using controller URL: " + controllerUrl);

            // lets register the features
            GridClient controller = getGridClient();

            // allow system property to override this value
            String systemProperty = "cloudmix.destroyOtherProfilesOnStartup";
            String flag = System.getProperty(systemProperty);
            if (flag != null) {
                try {
                    destroyOtherProfilesOnStartup = Boolean.parseBoolean(flag);
                } catch (Exception e) {
                    LOG.error("Failed to parse boolean system property " + systemProperty + " with value: " + flag
                            + ". Reason: " + e, e);
                }
            } else if (destroyOtherProfilesOnStartup) {
                LOG.info("About to destroy all previous JUnit profiles on the CloudMix server. "
                        + "To disable this behaviour set the destroyOtherProfilesOnStartup field to false on your JUnit class or set the '"
                        + systemProperty + "' system property to 'false''");
            }
            if (destroyOtherProfilesOnStartup) {
                destroyCurrentProfiles();
            }
            if (profileId == null) {
                profileId = UUID.randomUUID().toString();
            }

            // lets append the profileId to the file!
            onProfileIdCreated(profileId);
            profile = new ProfileDetails(profileId);

            installFeatures();

            for (FeatureDetails feature : features) {
                ensureFeatureIdLocalToProfile(feature);

                profile.getFeatures().add(new Dependency(feature.getId()));

                System.out.println("Adding feature: " + feature.getId());
                controller.addFeature(feature);
            }

            profile.setDescription(createProfileDescription(profile));

            controller.addProfile(profile);

            // now lets start the remote grid
            assertProvisioned();
            provisioned = true;

            System.out.println("All features provisioned!!");
        } catch (Exception e) {
            System.out.println("Caught: " + e);
            e.printStackTrace();
            Throwable t = e;
            while (true) {
                Throwable throwable = t.getCause();
                if (throwable == t || throwable == null) {
                    break;
                }
                System.out.println("Caused by : " + throwable);
                throwable.printStackTrace();
                t = throwable;
            }
            LOG.error("Caught: " + e, e);
            throw e;
        }
    }

    /**
     * Destroys
     */
    protected void destroyCurrentProfiles() {
        File file = new File(PROFILE_ID_FILENAME);
        if (file.exists()) {
            try {
                BufferedReader reader = new BufferedReader(new FileReader(file));
                while (true) {
                    String line = reader.readLine();
                    if (line == null) {
                        break;
                    }
                    line = line.trim();
                    if (line.startsWith("#") || line.length() == 0) {
                        continue;
                    }
                    LOG.info("Destroying old profile: " + line);
                    gridClient.removeFeature(line);
                }
                file.delete();
            } catch (IOException e) {
                LOG.error("Failed to read old profiles to file: " + file);
            }
        }
    }

    protected List<? extends ProcessClient> getProcessClientsFor(FeatureDetails featureDetails)
            throws URISyntaxException {

        return getProcessClientsFor(id(featureDetails));
    }

    private List<? extends ProcessClient> getProcessClientsFor(String featureId) throws URISyntaxException {
        return getGridClient().getProcessClientsForFeature(featureId);
    }

    /**
     * Returns all the agents which are running the given feature
     */
    protected List<AgentDetails> getAgentsFor(FeatureDetails featureDetails) throws URISyntaxException {
        return getAgentsFor(id(featureDetails));
    }

    protected String id(FeatureDetails featureDetails) {
        String id = featureDetails.getId();
        Assert.assertNotNull("Feature should have an ID " + featureDetails, id);
        return id;
    }

    /**
     * Returns all the agents which are running the given feature  ID
     */
    protected List<AgentDetails> getAgentsFor(String featureId) throws URISyntaxException {
        return GridClients.getAgentDetailsAssignedToFeature(getGridClient(), featureId);
    }

    protected String createProfileDescription(ProfileDetails pd) {
        return "CloudMix test case for class <b>" + getClass().getName() + "</b> with test method <b>"
                + getTestName() + "</b>";
    }

    /**
     * associate the feature with the profile, so that when the profile is deleted, so is the feature
     */
    protected void ensureFeatureIdLocalToProfile(FeatureDetails feature) {
        Assert.assertNotNull("profile ID should be defined!", profileId);

        // lets ensure the feature ID is unique (though the code could be smart enough to deduce it!)
        String featureId = feature.getId();
        if (!featureId.startsWith(profileId)) {
            featureId = profileId + ":" + featureId;
            feature.setId(featureId);
        }
        feature.setOwnedByProfileId(profileId);
    }

    protected void onProfileIdCreated(String profileid) throws IOException {
        String fileName = PROFILE_ID_FILENAME;
        try {
            FileWriter writer = new FileWriter(fileName, true);
            writer.append(profileid);
            writer.append("\n");
            writer.close();
        } catch (IOException e) {
            LOG.error("Failed to write profileId to file: " + fileName);
        }
    }

    @After
    public void tearDown() throws Exception {
        if (destroyProfileAfter) {
            if (gridClient != null) {
                if (profile != null) {
                    gridClient.removeProfile(profile);
                }
            }
            provisioned = false;
        }
    }

    public RestGridClient getGridClient() throws URISyntaxException {
        if (gridClient == null) {
            gridClient = createGridController();
        }
        return gridClient;
    }

    public void setGridClient(RestGridClient gridClient) {
        this.gridClient = gridClient;
    }

    /**
     * Returns a newly created client. Factory method
     */
    protected RestGridClient createGridController() throws URISyntaxException {
        System.out.println("About to create RestGridClient for: " + controllerUrl);
        RestGridClient answer = new RestGridClient(controllerUrl);
        if (logRestOperations) {
            answer.getClient(null).addFilter(new LoggingFilter());
        }
        return answer;
    }

    /**
     * Allow a feature to be registered prior to starting the profile
     */
    protected void addFeature(FeatureDetails featureDetails) {
        features.add(featureDetails);
    }

    /**
     * Allows feature to be registered prior to starting the profile
     */
    protected void addFeatures(FeatureDetails... featureDetails) {
        for (FeatureDetails featureDetail : featureDetails) {
            addFeature(featureDetail);
        }
    }

    /**
     * Allows feature to be registered prior to starting the profile
     */
    protected void addFeatures(Iterable<FeatureDetails> featureDetails) {
        for (FeatureDetails featureDetail : featureDetails) {
            addFeature(featureDetail);
        }
    }

    protected void getFeatureLogFromAgent(AgentDetails agent, FeatureDetails feature, String relativeLogPath,
            OutputStream os) throws Exception {
        if (!isSupportedAgent(agent)) {
            return;
        }
        URI uri = createRequestURI(agent, feature, relativeLogPath);
        RestGridClient client = new RestGridClient();
        client.setRootUri(uri, false);
        InputStream logStream = new BufferedInputStream(client.getInputStream());
        byte[] buf = new byte[4096];
        int len = 0;
        while ((len = logStream.read(buf)) != -1) {
            os.write(buf, 0, len);
        }

    }

    private boolean isSupportedAgent(AgentDetails agent) {
        if (!"mop".equals(agent.getContainerType().toLowerCase())) {
            LOG.info("Unsupported agent type " + agent.getContainerType());
            return false;
        }
        if (agent.getHref() == null) {
            LOG.info("Agent href is null, no log can be retrieved");
            return false;
        }
        return true;
    }

    private URI createRequestURI(AgentDetails agent, FeatureDetails feature, String relativeLogPath) {
        UriBuilder ub = UriBuilder.fromUri(agent.getHref());
        if ("mop".equals(agent.getContainerType().toLowerCase())) {
            ub.path("directory");
        }
        //else if ("karaf".equals(agent.getContainerType().toLowerCase())) {
        //   ub.path("instance"); ? 
        //}
        return ub.path(feature.getId().replace(':', '_')).path(relativeLogPath).build();
    }

    protected List<LogRecord> getFeatureLogRecordsFromAgent(AgentDetails agent, FeatureDetails feature,
            String relativeLogPath, String queryName, String queryValue) throws Exception {
        return getFeatureLogRecordsFromAgent(agent, feature, relativeLogPath,
                Collections.singletonMap(queryName, Collections.singletonList(queryValue)));
    }

    protected List<LogRecord> getFeatureLogRecordsFromAgent(AgentDetails agent, FeatureDetails feature,
            String relativeLogPath, Map<String, List<String>> queries) throws Exception {
        if (!isSupportedAgent(agent)) {
            return Collections.emptyList();
        }
        URI uri = createRequestURI(agent, feature, relativeLogPath);
        RestGridClient client = new RestGridClient(uri);
        return client.getLogRecords(queries);
    }

    /**
     * Asserts that all the requested features have been provisioned properly
     */
    protected void assertProvisioned() {
        long start = System.currentTimeMillis();

        Set<String> provisionedFeatures = new TreeSet<String>();
        Set<String> failedFeatures = null;
        while (true) {
            failedFeatures = new TreeSet<String>();
            long now = System.currentTimeMillis();

            try {
                ProfileStatus profileStatus = getGridClient().getProfileStatus(profileId);
                if (profileStatus != null) {
                    List<DependencyStatus> dependencyStatus = profileStatus.getFeatures();
                    for (DependencyStatus status : dependencyStatus) {
                        String featureId = status.getFeatureId();
                        if (status.isProvisioned()) {
                            if (provisionedFeatures.add(featureId)) {
                                LOG.info("Provisioned feature: " + featureId);
                            }
                        } else {
                            failedFeatures.add(featureId);
                        }
                    }
                }
                if (failedFeatures.isEmpty()) {
                    return;
                }
            } catch (URISyntaxException e) {
                LOG.warn("Failed to poll profile status: " + e, e);
            }

            long delta = now - start;
            if (delta > startupTimeout) {
                Assert.fail("Provision failure. Not enough instances of features: " + failedFeatures
                        + " after waiting " + (startupTimeout / 1000) + " seconds");
            } else {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // ignore
                }
            }
        }
    }

}