org.springframework.xd.integration.util.StreamUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.xd.integration.util.StreamUtils.java

Source

/*
 * Copyright 2011-2014 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.
 */

package org.springframework.xd.integration.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jclouds.ContextBuilder;
import org.jclouds.aws.ec2.AWSEC2Api;
import org.jclouds.aws.ec2.domain.AWSRunningInstance;
import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.domain.Credentials;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.ec2.domain.Reservation;
import org.jclouds.ec2.domain.RunningInstance;
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
import org.jclouds.io.payloads.ByteSourcePayload;
import org.jclouds.sshj.SshjSshClient;

import org.springframework.hateoas.PagedResources;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.xd.rest.client.impl.SpringXDTemplate;
import org.springframework.xd.rest.domain.DetailedContainerResource;
import org.springframework.xd.rest.domain.ModuleMetadataResource;
import org.springframework.xd.rest.domain.StreamDefinitionResource;

import com.google.common.collect.Iterables;
import com.google.common.io.ByteSource;
import com.google.common.net.HostAndPort;
import org.springframework.xd.rest.domain.metrics.CounterResource;
import org.springframework.xd.rest.domain.metrics.MetricResource;

/**
 * Utilities for creating and monitoring streams and the JMX hooks for those strings.
 *
 * @author Glenn Renfro
 */
public class StreamUtils {

    public final static String TMP_DIR = "result/";

    /**
     * Creates the stream definition and deploys it to the cluster being tested.
     *
     * @param streamName The name of the stream
     * @param streamDefinition The definition that needs to be deployed for this stream.
     * @param adminServer The admin server that this stream will be deployed against.
     */
    public static void stream(final String streamName, final String streamDefinition, final URL adminServer) {
        Assert.hasText(streamName, "The stream name must be specified.");
        Assert.hasText(streamDefinition, "a stream definition must be supplied.");
        Assert.notNull(adminServer, "The admin server must be specified.");
        createSpringXDTemplate(adminServer).streamOperations().createStream(streamName, streamDefinition, true);
    }

    /**
     * Executes a http get for the client and returns the results as a string
     *
     * @param url The location to execute the get against.
     * @return the string result of the get
     */
    public static String httpGet(final URL url) {
        Assert.notNull(url, "The URL must be specified");
        RestTemplate template = new RestTemplate();

        try {
            String result = template.getForObject(url.toURI(), String.class);
            return result;
        } catch (URISyntaxException uriException) {
            throw new IllegalStateException(uriException.getMessage(), uriException);
        }
    }

    /**
     * Removes all the streams from the cluster. Used to guarantee a clean acceptance test.
     *
     * @param adminServer The admin server that the command will be executed against.
     */
    public static void destroyAllStreams(final URL adminServer) {
        Assert.notNull(adminServer, "The admin server must be specified.");
        createSpringXDTemplate(adminServer).streamOperations().destroyAll();
    }

    /**
     * Undeploys the specified stream name
     *
     * @param adminServer The admin server that the command will be executed against.
     * @param streamName The name of the stream to undeploy
     */
    public static void undeployStream(final URL adminServer, final String streamName) {
        Assert.notNull(adminServer, "The admin server must be specified.");
        Assert.hasText(streamName, "The streamName must not be empty nor null");
        createSpringXDTemplate(adminServer).streamOperations().undeploy(streamName);
    }

    /**
     * Retrieves the value for the counter
     *
     * @param adminServer the admin server for the cluster being tested.
     * @param name        the name of the counter in the metric repo.
     * @return the count associated with the name.
     */
    public static long getCount(final URL adminServer, final String name) {
        Assert.notNull(adminServer, "The admin server must be specified.");
        Assert.hasText(name, "The name must not be empty nor null");
        CounterResource resource = createSpringXDTemplate(adminServer).counterOperations().retrieve(name);
        return resource.getValue();
    }

    /**
     * Copies the specified file from a remote machine to local machine.
     *
     * @param privateKey the ssh private key to the remote machine
     * @param url The remote machine's url.
     * @param fileName The fully qualified file name of the file to be transferred.
     * @return The location to the fully qualified file name where the remote file was copied.
     */
    public static String transferResultsToLocal(final String privateKey, final URL url, final String fileName) {
        Assert.hasText(privateKey, "The Acceptance Test, can not be empty nor null.");
        Assert.notNull(url, "The remote machine's URL must be specified.");
        Assert.hasText(fileName, "The remote file name must be specified.");

        File file = new File(fileName);
        FileOutputStream fileOutputStream = null;
        InputStream inputStream = null;

        try {
            File tmpFile = createTmpDir();
            String fileLocation = tmpFile.getAbsolutePath() + file.getName();
            fileOutputStream = new FileOutputStream(fileLocation);

            final SshjSshClient client = getSSHClient(url, privateKey);
            inputStream = client.get(fileName).openStream();

            FileCopyUtils.copy(inputStream, fileOutputStream);
            return fileLocation;
        } catch (IOException ioException) {
            throw new IllegalStateException(ioException.getMessage(), ioException);
        }
    }

    /**
     * Creates a file on a remote EC2 machine with the payload as its contents.
     *
     * @param privateKey The ssh private key for the remote container
     * @param host The remote machine's ip.
     * @param dir The directory to write the file
     * @param fileName The fully qualified file name of the file to be created.
     * @param payload the data to write to the file
     * @param retryTime the time in millis to retry to push data file to remote system.
     */
    public static boolean createDataFileOnRemote(String privateKey, String host, String dir, String fileName,
            String payload, int retryTime) {
        Assert.hasText(privateKey, "privateKey must not be empty nor null.");
        Assert.hasText(host, "The remote machine's URL must be specified.");
        Assert.notNull(dir, "dir should not be null");
        Assert.hasText(fileName, "The remote file name must be specified.");
        boolean isFileCopied = false;
        long timeout = System.currentTimeMillis() + retryTime;
        while (!isFileCopied && System.currentTimeMillis() < timeout) {
            SshjSshClient client = getSSHClient(host, privateKey);
            client.exec("mkdir -p " + dir);
            ExecResponse response = client.exec("ls -al " + dir);
            if (response.getExitStatus() > 0) {
                continue; //directory was not created
            }
            client.put(dir + "/" + fileName, payload);
            response = client.exec("ls -al " + dir + "/" + fileName);
            if (response.getExitStatus() > 0) {
                continue; //file was not created
            }
            response = client.exec("cat " + dir + "/" + fileName);
            if (response.getExitStatus() > 0 || !payload.equals(response.getOutput())) {
                continue;//data stored on machine is different than was expected.
            }
            isFileCopied = true;
        }
        return isFileCopied;
    }

    /**
     * Copy a file from test machine to remote machine.
     *
     * @param privateKey The ssh private key for the remote container
     * @param host The remote machine's ip.
     * @param uri to the location where the file will be copied to remote machine.
     * @param file The file to migrate to remote machine. 
     */
    public static void copyFileToRemote(String privateKey, String host, URI uri, File file, long waitTime) {
        Assert.hasText(privateKey, "privateKey must not be empty nor null.");
        Assert.hasText(host, "The remote machine's URL must be specified.");
        Assert.notNull(uri, "uri should not be null");
        Assert.notNull(file, "file must not be null");
        boolean isFileCopied = false;
        long timeout = System.currentTimeMillis() + waitTime;
        while (!isFileCopied && System.currentTimeMillis() < timeout) {
            final SshjSshClient client = getSSHClient(host, privateKey);
            Assert.isTrue(file.exists(), "File to be copied to remote machine does not exist");
            ByteSource byteSource = com.google.common.io.Files.asByteSource(file);
            ByteSourcePayload payload = new ByteSourcePayload(byteSource);
            long byteSourceSize = -1;
            try {
                byteSourceSize = byteSource.size();
                payload.getContentMetadata().setContentLength(byteSourceSize);
            } catch (IOException ioe) {
                throw new IllegalStateException("Unable to retrieve size for file to be copied to remote machine.",
                        ioe);
            }
            client.put(uri.getPath(), payload);
            if (client.exec("ls -al " + uri.getPath()).getExitStatus() == 0) {
                ExecResponse statResponse = client.exec("stat --format=%s " + uri.getPath());
                long copySize = Long.valueOf(statResponse.getOutput().trim());
                if (copySize == byteSourceSize) {
                    isFileCopied = true;
                }
            }
        }

    }

    /**
     * Creates a directory on a remote machine.  It a failure occurs it will retry until waitTime is exhausted.
     * @param path the directory that will be created
     * @param host the IP where the directory will be created
     * @param privateKey Private key to be used for signing onto remote machine.
     * @param waitTime The max time to try creating the directory
     * @return true if the directory was successfully created else false.  
     */
    public static boolean createRemoteDirectory(String path, String host, String privateKey, int waitTime) {
        boolean isDirectoryCreated = false;
        Assert.hasText(path, "path must not be empty nor null");
        Assert.hasText(host, "host must not be empty nor null");
        Assert.hasText(privateKey, "privateKey must not be empty nor null");
        long timeout = System.currentTimeMillis() + waitTime;
        while (!isDirectoryCreated && System.currentTimeMillis() < timeout) {
            SshjSshClient client = getSSHClient(host, privateKey);
            client.exec("mkdir " + path);
            ExecResponse response = client.exec("ls -al " + path);
            if (response.getExitStatus() > 0) {
                continue; //directory was not created
            }
            isDirectoryCreated = true;
        }
        return isDirectoryCreated;
    }

    /**
     * Appends the payload to an existing file on a remote EC2 Instance.
     *
     * @param privateKey The ssh private key for the remote container
     * @param host The remote machine's ip.
     * @param dir The directory to write the file
     * @param fileName The fully qualified file name of the file to be created.
     * @param payload the data to append to the file
     */
    public static void appendToRemoteFile(String privateKey, String host, String dir, String fileName,
            String payload) {
        Assert.hasText(privateKey, "privateKey must not be empty nor null.");
        Assert.hasText(host, "The remote machine's URL must be specified.");
        Assert.notNull(dir, "dir should not be null");
        Assert.hasText(fileName, "The remote file name must be specified.");

        final SshjSshClient client = getSSHClient(host, privateKey);
        client.exec("echo '" + payload + "' >> " + dir + "/" + fileName);
    }

    /**
     * Returns a list of active instances from the specified ec2 region.
     * @param awsAccessKey the unique id of the ec2 user.
     * @param awsSecretKey the password of ec2 user.
     * @param awsRegion The aws region to inspect for acceptance test instances.
     * @return a list of active instances in the account and region specified.
     */
    public static List<RunningInstance> getEC2RunningInstances(String awsAccessKey, String awsSecretKey,
            String awsRegion) {
        Assert.hasText(awsAccessKey, "awsAccessKey must not be empty nor null");
        Assert.hasText(awsSecretKey, "awsSecretKey must not be empty nor null");
        Assert.hasText(awsRegion, "awsRegion must not be empty nor null");

        AWSEC2Api client = ContextBuilder.newBuilder("aws-ec2").credentials(awsAccessKey, awsSecretKey)
                .buildApi(AWSEC2Api.class);
        Set<? extends Reservation<? extends AWSRunningInstance>> reservations = client.getInstanceApi().get()
                .describeInstancesInRegion(awsRegion);
        int instanceCount = reservations.size();
        ArrayList<RunningInstance> result = new ArrayList<RunningInstance>();
        for (int awsRunningInstanceCount = 0; awsRunningInstanceCount < instanceCount; awsRunningInstanceCount++) {
            Reservation<? extends AWSRunningInstance> instances = Iterables.get(reservations,
                    awsRunningInstanceCount);
            int groupCount = instances.size();
            for (int runningInstanceCount = 0; runningInstanceCount < groupCount; runningInstanceCount++) {
                result.add(Iterables.get(instances, runningInstanceCount));
            }
        }
        return result;
    }

    /**
     * Substitutes the port associated with the URL with another port.
     *
     * @param url The URL that needs a port replaced.
     * @param port The new port number
     * @return A new URL with the host from the URL passed in and the new port.
     */
    public static URL replacePort(final URL url, final int port) {
        Assert.notNull(url, "the url must not be null");
        try {
            return new URL("http://" + url.getHost() + ":" + port);
        } catch (MalformedURLException malformedUrlException) {
            throw new IllegalStateException(malformedUrlException.getMessage(), malformedUrlException);
        }
    }

    /**
     * Creates a map of container Id's and the associated host.
     * @param adminServer The admin server to be queried.
     * @return Map where the key is the container id and the value is the host ip.
     */
    public static Map<String, String> getAvailableContainers(URL adminServer) {
        Assert.notNull(adminServer, "adminServer must not be null");
        HashMap<String, String> results = new HashMap<String, String>();
        Iterator<DetailedContainerResource> iter = createSpringXDTemplate(adminServer).runtimeOperations()
                .listContainers().iterator();
        while (iter.hasNext()) {
            DetailedContainerResource container = iter.next();
            results.put(container.getAttribute("id"), container.getAttribute("host"));
        }
        return results;
    }

    /**
     * Return a list of container id's where the module is deployed
     * @param adminServer The admin server that will be queried.
     * @return A list of containers where the module is deployed.
     */
    public static PagedResources<ModuleMetadataResource> getRuntimeModules(URL adminServer) {
        Assert.notNull(adminServer, "adminServer must not be null");
        return createSpringXDTemplate(adminServer).runtimeOperations().listDeployedModules();
    }

    /**
     * Waits up to the wait time for a stream to be deployed.
     *
     * @param streamName The name of the stream to be evaluated.
     * @param adminServer The admin server URL that will be queried.
     * @param waitTime the amount of time in millis to wait.
     * @return true if the stream is deployed else false.
     */
    public static boolean waitForStreamDeployment(String streamName, URL adminServer, int waitTime) {
        boolean result = isStreamDeployed(streamName, adminServer);
        long timeout = System.currentTimeMillis() + waitTime;
        while (!result && System.currentTimeMillis() < timeout) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new IllegalStateException(e.getMessage(), e);
            }
            result = isStreamDeployed(streamName, adminServer);
        }

        return result;
    }

    /**
     * Checks to see if the specified stream is deployed on the XD cluster.
     *
     * @param streamName The name of the stream to be evaluated.
     * @param adminServer The admin server URL that will be queried.
     * @return true if the stream is deployed else false
     */
    public static boolean isStreamDeployed(String streamName, URL adminServer) {
        Assert.hasText(streamName, "The stream name must be specified.");
        Assert.notNull(adminServer, "The admin server must be specified.");
        boolean result = false;
        SpringXDTemplate xdTemplate = createSpringXDTemplate(adminServer);
        PagedResources<StreamDefinitionResource> resources = xdTemplate.streamOperations().list();
        Iterator<StreamDefinitionResource> resourceIter = resources.iterator();
        while (resourceIter.hasNext()) {
            StreamDefinitionResource resource = resourceIter.next();
            if (streamName.equals(resource.getName())) {
                if ("deployed".equals(resource.getStatus())) {
                    result = true;
                    break;
                } else {
                    result = false;
                    break;
                }
            }
        }
        return result;
    }

    /**
     * Waits up to the wait time for a metric to be created.
     *
     * @param name        The name of the metric to be evaluated.
     * @param adminServer The admin server URL that will be queried.
     * @param waitTime    the amount of time in millis to wait.
     * @return true if the metric is created else false.
     */
    public static boolean waitForMetric(String name, URL adminServer, int waitTime) {
        Assert.hasText(name, "name must not be empty nor null");
        Assert.notNull(adminServer, "The admin server must be specified.");

        SpringXDTemplate xdTemplate = createSpringXDTemplate(adminServer);
        boolean result = isMetricPresent(xdTemplate, name);
        long timeout = System.currentTimeMillis() + waitTime;
        while (!result && System.currentTimeMillis() < timeout) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new IllegalStateException(e.getMessage(), e);
            }
            result = isMetricPresent(xdTemplate, name);
        }
        return result;
    }

    /**
     * Retrieves the container pids on the local machine.  
     * @param jpsCommand  jps command that will reveal the pids.
     * @return An Integer array that contains the pids.
     */
    public static Integer[] getLocalContainerPids(String jpsCommand) {
        Assert.hasText(jpsCommand, "jpsCommand can not be empty nor null");
        Integer[] result = null;
        try {
            Process p = Runtime.getRuntime().exec(jpsCommand);
            p.waitFor();
            String pidInfo = org.springframework.util.StreamUtils.copyToString(p.getInputStream(),
                    Charset.forName("UTF-8"));
            result = extractPidsFromJPS(pidInfo);
        } catch (IOException e) {
            throw new IllegalStateException(e.getMessage(), e);
        } catch (InterruptedException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        return result;
    }

    /**
     * Retrieves the container pids for a remote machine.      
     * @param url The URL where the containers are deployed.
     * @param privateKey ssh private key credential
     * @param jpsCommand The command to retrieve java processes on container machine.
     * @return An Integer array that contains the pids.
     */
    public static Integer[] getContainerPidsFromURL(URL url, String privateKey, String jpsCommand) {
        Assert.notNull(url, "url can not be null");
        Assert.hasText(privateKey, "privateKey can not be empty nor null");
        SshjSshClient client = getSSHClient(url, privateKey);
        ExecResponse response = client.exec(jpsCommand);
        return extractPidsFromJPS(response.getOutput());
    }

    /**
     * Verifies the specified path exists on remote machine.
     * @param host The host where the file exists.
     * @param privateKey ssh private key credential
     * @param path path to file or directory
     * @return true if file exists else false.
     */
    public static boolean fileExists(String host, String privateKey, String path) {
        Assert.hasText(host, "host must not be null nor empty");
        Assert.hasText(privateKey, "privateKey must not be null nor empty");
        Assert.hasText(path, "path must not be null nor empty");
        boolean result = true;
        final SshjSshClient client = getSSHClient(host, privateKey);
        ExecResponse response = client.exec("ls -al " + path);
        if (response.getExitStatus() > 0) {
            result = false;
        }
        return result;
    }

    private static Integer[] extractPidsFromJPS(String jpsResult) {
        String[] pidList = StringUtils.tokenizeToStringArray(jpsResult, "\n");
        ArrayList<Integer> pids = new ArrayList<Integer>();
        for (String pidData : pidList) {
            if (pidData.contains("ContainerServerApplication")) {
                pids.add(Integer.valueOf(StringUtils.tokenizeToStringArray(pidData, " ")[0]));
            }
        }
        return pids.toArray(new Integer[pids.size()]);
    }

    private static File createTmpDir() throws IOException {
        File tmpFile = new File(System.getProperty("user.dir") + "/" + TMP_DIR);
        if (!tmpFile.exists()) {
            tmpFile.createNewFile();
        }
        return tmpFile;
    }

    private static SshjSshClient getSSHClient(URL url, String privateKey) {
        return getSSHClient(url.getHost(), privateKey);
    }

    private static SshjSshClient getSSHClient(String host, String privateKey) {
        final LoginCredentials credential = LoginCredentials.fromCredentials(new Credentials("ubuntu", privateKey));
        final HostAndPort socket = HostAndPort.fromParts(host, 22);
        final SshjSshClient client = new SshjSshClient(new BackoffLimitedRetryHandler(), socket, credential, 5000);
        return client;
    }

    /**
     * Create an new instance of the SpringXDTemplate given the Admin Server URL
     *
     * @param adminServer URL of the Admin Server
     * @return A new instance of SpringXDTemplate
     */
    private static SpringXDTemplate createSpringXDTemplate(URL adminServer) {
        try {
            return new SpringXDTemplate(adminServer.toURI());
        } catch (URISyntaxException uriException) {
            throw new IllegalStateException(uriException.getMessage(), uriException);
        }
    }

    private static boolean isMetricPresent(SpringXDTemplate xdTemplate, String name) {
        boolean result = false;
        PagedResources<MetricResource> resources = xdTemplate.counterOperations().list();
        Iterator<MetricResource> resourceIter = resources.iterator();
        while (resourceIter.hasNext()) {
            MetricResource resource = resourceIter.next();
            if (name.equals(resource.getName())) {
                result = true;
                break;
            }
        }
        return result;
    }

}