org.jenkinsci.plugins.skytap.ConnectToVPNTunnelStep.java Source code

Java tutorial

Introduction

Here is the source code for org.jenkinsci.plugins.skytap.ConnectToVPNTunnelStep.java

Source

//
// Copyright (c) 2013, Skytap, Inc
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//                        
package org.jenkinsci.plugins.skytap;

import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;

import hudson.Extension;
import hudson.model.AbstractBuild;

import org.apache.commons.httpclient.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.BasicHttpEntity;
import org.jenkinsci.plugins.skytap.SkytapBuilder.SkytapAction;
import org.jenkinsci.plugins.skytap.SkytapBuilder.SkytapActionDescriptor;
import org.kohsuke.stapler.DataBoundConstructor;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.thoughtworks.xstream.annotations.XStreamOmitField;

public class ConnectToVPNTunnelStep extends SkytapAction {

    private final String configurationID;
    private final String configurationFile;
    private final String configurationNetworkName;
    private final String vpnID;

    // number of times it will poll Skytap to see if template is busy
    private static final int NUMBER_OF_RETRIES = 18;
    private static final int RETRY_INTERVAL_SECONDS = 10;

    // these vars will be initialized when the step is run

    @XStreamOmitField
    private SkytapGlobalVariables globalVars;

    @XStreamOmitField
    private String authCredentials;

    // the runtime config id will be set one of two ways:
    // either the user has provided just a config id, so we use it,
    // or the user provided a file, in which case we read the file and extract
    // the
    // id from the json element
    @XStreamOmitField
    private String runtimeConfigurationID;

    @DataBoundConstructor
    public ConnectToVPNTunnelStep(String configurationID, String configurationFile, String configurationNetworkName,
            String vpnID) {
        super("Connect to VPN Tunnel");

        this.configurationID = configurationID;
        this.configurationFile = configurationFile;
        this.configurationNetworkName = configurationNetworkName;
        this.vpnID = vpnID;

    }

    private String buildIsConnectedCheckURL(String confId, String netId, String vpnId) {

        StringBuilder sb = new StringBuilder("https://cloud.skytap.com/");
        sb.append("configurations/");
        sb.append(confId);
        sb.append("/networks/");
        sb.append(netId);
        sb.append("/vpns/");
        sb.append(vpnId);

        String checkUrl = sb.toString();

        // https://cloud.skytap.com/configurations/1168708/networks/805882/vpns/vpn-817994

        return checkUrl;
    }

    private Boolean checkIsAlreadyConnected(String confId, String netId, String vpnId) throws SkytapException {

        JenkinsLogger.log("Verifying if network " + netId + " is already connected to VPN " + vpnId);

        // assume its not connected
        Boolean isConnected = false;

        // build check url
        String reqURL = buildIsConnectedCheckURL(confId, netId, vpnId);

        // build request
        HttpGet hg = SkytapUtils.buildHttpGetRequest(reqURL, authCredentials);

        // execute request
        String httpRespBody = SkytapUtils.executeHttpRequest(hg);

        // check for error indicating config is not connected to VPN
        try {
            SkytapUtils.checkResponseForErrors(httpRespBody);
        } catch (SkytapException e) {

            // this is what we expect normally - return isconnected=false
            if (e.getMessage().contains("Configuration not attached to VPN")) {
                return isConnected;
            } else {
                throw e;
            }

        }

        // get json object from response
        JsonParser parser = new JsonParser();
        JsonElement je = parser.parse(httpRespBody);
        JsonObject jo = je.getAsJsonObject();

        isConnected = jo.get("connected").getAsBoolean();

        return isConnected;
    }

    public Boolean executeStep(AbstractBuild build, SkytapGlobalVariables globalVars) {

        JenkinsLogger.defaultLogMessage("----------------------------------------");
        JenkinsLogger.defaultLogMessage("Connecting to VPN Tunnel");
        JenkinsLogger.defaultLogMessage("----------------------------------------");

        if (preFlightSanityChecks() == false) {
            return false;
        }

        this.globalVars = globalVars;
        this.authCredentials = SkytapUtils.getAuthCredentials(build);

        // reset step parameters with env vars resolved at runtime
        String expConfigurationFile = SkytapUtils.expandEnvVars(build, configurationFile);

        // if user has provided just a filename with no path, default to
        // place it in their Jenkins workspace

        if (!expConfigurationFile.equals("")) {
            expConfigurationFile = SkytapUtils.convertFileNameToFullPath(build, expConfigurationFile);
        }

        // get runtime config id
        try {
            runtimeConfigurationID = SkytapUtils.getRuntimeId(configurationID, expConfigurationFile);
        } catch (FileNotFoundException e) {
            JenkinsLogger.error("Error obtaining runtime id: " + e.getMessage());
            return false;
        }

        // get the network id from the provided network name
        JenkinsLogger.log("Getting the ID for network: " + configurationNetworkName);
        String runtimeNetworkID = "";

        try {
            runtimeNetworkID = SkytapUtils.getNetworkIdFromName(runtimeConfigurationID, configurationNetworkName,
                    this.authCredentials);
        } catch (SkytapException e1) {
            JenkinsLogger.error(e1.getError());
            return false;
        }

        if (runtimeNetworkID.equals("")) {
            JenkinsLogger.error("Failed to obtain network ID.");
            return false;
        }

        JenkinsLogger.log("Configuration ID: " + runtimeConfigurationID);
        JenkinsLogger.log("Configuration File: " + expConfigurationFile);
        JenkinsLogger.log("Network ID: " + runtimeNetworkID);
        JenkinsLogger.log("Network Name: " + this.configurationNetworkName);
        JenkinsLogger.log("VPN ID: " + this.vpnID);

        // check whether network is already connected, if it is, output a
        // message and pass the build step
        Boolean alreadyConnected = false;

        try {
            alreadyConnected = checkIsAlreadyConnected(runtimeConfigurationID, runtimeNetworkID, vpnID);
        } catch (SkytapException e1) {
            JenkinsLogger.error("Skytap Error: " + e1.getMessage());
            return false;
        }

        if (alreadyConnected) {
            JenkinsLogger.log("Network is already connected to VPN: " + vpnID + ". Passing build step.");
            return true;
        } else {
            JenkinsLogger.log("Network is not currently connected to VPN: " + vpnID);
        }

        // attach the VPN to the configuration
        JenkinsLogger.log("Attaching VPN to Configuration ...");

        if (executeVPNAttach(runtimeConfigurationID, runtimeNetworkID, vpnID)) {

            JenkinsLogger.log(
                    "VPN with ID " + vpnID + " successfully attached to Network with ID " + runtimeNetworkID + ".");

        } else {

            JenkinsLogger.error("VPN attach has failed. Failing build step.");
            return false;

        }

        // connect the configuration to the VPN
        JenkinsLogger.log("Connecting VPN to Configuration ...");

        if (executeVPNConnect(runtimeConfigurationID, runtimeNetworkID, vpnID)) {

            JenkinsLogger.defaultLogMessage("VPN with ID " + vpnID + " successfully connected to Network with ID "
                    + runtimeNetworkID + ".");

        } else {
            JenkinsLogger.error("VPN connect has failed. Failing build step.");
            return false;
        }

        // pause before exiting to allow VPN and config to settle down
        int sleepTime = this.RETRY_INTERVAL_SECONDS;
        JenkinsLogger.log("Pausing for " + sleepTime + " seconds.");

        try {
            Thread.sleep(sleepTime * 1000);
        } catch (InterruptedException e1) {
            JenkinsLogger.error(e1.getMessage());
        }

        JenkinsLogger.defaultLogMessage("----------------------------------------");
        return true;
    }

    private Boolean executeVPNConnect(String confId, String netId, String vpnId) {

        String connectResponse = "";
        Boolean vpnConnectedSuccessfully = false;
        int pollAttempts = 0;

        while (!vpnConnectedSuccessfully && pollAttempts < this.NUMBER_OF_RETRIES) {

            connectResponse = this.connectVPNToConfiguration(confId, netId, vpnId);

            JenkinsLogger.log("Connect Response: " + connectResponse);

            if (connectResponse.equals("") || connectResponse == null) {
                JenkinsLogger.error("Response was null or empty.");
            }

            // check response for errors
            try {
                SkytapUtils.checkResponseForErrors(connectResponse);

                // no errors.. then connect succeeded.
                vpnConnectedSuccessfully = true;

            } catch (SkytapException e) {
                JenkinsLogger.error("Skytap Error: " + e.getError());

                // wait before trying again
                int sleepTime = this.RETRY_INTERVAL_SECONDS;
                JenkinsLogger.log("Sleeping for " + sleepTime + " seconds.");

                try {
                    Thread.sleep(sleepTime * 1000);
                } catch (InterruptedException e1) {
                    JenkinsLogger.error(e1.getMessage());
                }
            }

            pollAttempts++;

        }

        return vpnConnectedSuccessfully;
    }

    private Boolean executeVPNAttach(String confId, String netId, String vpnId) {

        Boolean vpnAttachedSuccessfully = false;
        int pollAttempts = 0;
        String attachResponse = "";

        while (!vpnAttachedSuccessfully && pollAttempts < this.NUMBER_OF_RETRIES) {

            attachResponse = this.attachVPNToConfiguration(confId, netId, vpnId);

            JenkinsLogger.log("Attach Response: " + attachResponse);

            if (attachResponse.equals("") || attachResponse == null) {
                JenkinsLogger.error("Response was null or empty.");
            } else {

                try {
                    SkytapUtils.checkResponseForErrors(attachResponse);

                    // if no exception is thrown, attach succeeded
                    vpnAttachedSuccessfully = true;

                } catch (SkytapException e) {
                    JenkinsLogger.error("Skytap Error: " + e.getError());

                    // wait before trying again
                    int sleepTime = this.RETRY_INTERVAL_SECONDS;
                    JenkinsLogger.log("Sleeping for " + sleepTime + " seconds.");

                    try {
                        Thread.sleep(sleepTime * 1000);
                    } catch (InterruptedException e1) {
                        JenkinsLogger.error(e1.getMessage());
                    }

                }

            }

            pollAttempts++;

        }
        return vpnAttachedSuccessfully;
    }

    private String connectVPNToConfiguration(String confId, String networkId, String vpnId) {

        // build url
        String reqUrl = this.buildConnectRequestURL(confId, networkId, vpnId);

        // create request
        HttpPut hp = SkytapUtils.buildHttpPutRequest(reqUrl, this.authCredentials);

        // add content to request - vpn identifier
        BasicHttpEntity he = new BasicHttpEntity();
        he.setContentEncoding("gzip");
        he.setContentType("application/json");

        // json string for connected attribute
        String jsonString = "{\"connected\" :true}";

        InputStream stream;
        try {
            stream = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
            Integer len = jsonString.getBytes("UTF-8").length;
            long llen = len.longValue();

            he.setContent(stream);
            he.setContentLength(llen);

        } catch (UnsupportedEncodingException e) {
            JenkinsLogger.error("Error encoding json string for connected attribute: " + e.getMessage());

        }

        hp.setEntity(he);
        String response = "";

        try {
            response = SkytapUtils.executeHttpRequest(hp);
        } catch (SkytapException e) {
            JenkinsLogger.error("Skytap Exception: " + e.getMessage());
        }

        return response;

    }

    private String buildConnectRequestURL(String confId, String networkId, String vpnId) {

        String req = this.buildRequestURL(confId, networkId);
        StringBuilder sb = new StringBuilder(req);
        sb.append("/");
        sb.append(vpnId);
        sb.append("/");

        JenkinsLogger.log(sb.toString());
        return sb.toString();

    }

    private String buildRequestURL(String confId, String networkId) {

        StringBuilder sb = new StringBuilder("https://cloud.skytap.com/");
        sb.append("configurations/");
        sb.append(confId);
        sb.append("/networks/");
        sb.append(networkId);
        sb.append("/vpns");

        String requestUrl = sb.toString();
        return requestUrl;
    }

    private String attachVPNToConfiguration(String confId, String networkId, String vpnId) {

        // build url
        String requestUrl = this.buildRequestURL(confId, networkId);

        // create request
        HttpPost hp = SkytapUtils.buildHttpPostRequest(requestUrl, this.authCredentials);

        // add content to request - vpn identifier
        BasicHttpEntity he = new BasicHttpEntity();
        he.setContentEncoding("gzip");
        he.setContentType("application/json");

        // json string for vpn id
        String jsonString = "{\"id\":\"" + vpnId + "\"}";

        InputStream stream;
        try {
            stream = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
            Integer len = jsonString.getBytes("UTF-8").length;
            long llen = len.longValue();

            he.setContent(stream);
            he.setContentLength(llen);

        } catch (UnsupportedEncodingException e) {
            JenkinsLogger.error("Error encoding json string for vpn id: " + e.getMessage());

        }

        hp.setEntity(he);

        JenkinsLogger.log("HTTP POST request: " + hp.toString());

        // execute request
        String httpRespBody = "";

        try {
            httpRespBody = SkytapUtils.executeHttpRequest(hp);
        } catch (SkytapException e) {
            JenkinsLogger.error("Skytap Exception: " + e.getMessage());
        }

        // return response
        return httpRespBody;

    }

    /**
     * This method is a final check to ensure that user inputs are legitimate.
     * Any situation where the user has entered both inputs in an either/or
     * scenario will fail the build. If the user has left both blank where we
     * need one, it will also fail.
     * 
     * @return Boolean sanityCheckPassed
     */
    private Boolean preFlightSanityChecks() {

        // check whether user entered both values for conf id/conf file
        if (!this.configurationID.equals("") && !this.configurationFile.equals("")) {
            JenkinsLogger.error(
                    "Values were provided for both configuration ID and file. Please provide just one or the other.");
            return false;
        }

        // check whether we have neither conf id or file
        if (this.configurationFile.equals("") && this.configurationID.equals("")) {
            JenkinsLogger.error(
                    "No value was provided for configuration ID or file. Please provide either a valid Skytap configuration ID, or a valid configuration file.");
            return false;
        }

        // if either VPN ID or network name are blank, fail the build
        if (this.vpnID.equals("")) {
            JenkinsLogger.error("No value was provided for the VPN ID. Please provide a valid Skytap VPN ID.");
            return false;
        }

        if (this.configurationNetworkName.equals("")) {
            JenkinsLogger.error(
                    "No value was provided for the network name. Please provide a valid Skytap Network name.");
            return false;
        }

        return true;
    }

    public String getConfigurationID() {
        return configurationID;
    }

    public String getConfigurationFile() {
        return configurationFile;
    }

    public String getConfigurationNetworkName() {
        return configurationNetworkName;
    }

    public String getVpnID() {
        return vpnID;
    }

    @Extension
    public static final SkytapActionDescriptor D = new SkytapActionDescriptor(ConnectToVPNTunnelStep.class,
            "Connect to VPN Tunnel");

}