package com.urbancode.terraform.tasks.rackspace; import java.io.IOException; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.DeleteMethod; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.RequestEntity; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.log4j.Logger; import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import com.urbancode.x2o.tasks.SubTask; public class ServerTask extends SubTask { //********************************************************************************************** // CLASS //********************************************************************************************** static private final Logger log = Logger.getLogger(ServerTask.class); //********************************************************************************************** // INSTANCE //********************************************************************************************** private EnvironmentTaskRackspace env; private String name; private boolean appendSuffix = false;//intentionally has no getter private String region; private String image; private String flavor; private String id; private String publicIp = ""; private String privateIp = ""; //---------------------------------------------------------------------------------------------- public ServerTask(EnvironmentTaskRackspace env) { this.env = env; } //---------------------------------------------------------------------------------------------- public String getName() { return name; } //---------------------------------------------------------------------------------------------- public String getRegion() { return region; } //---------------------------------------------------------------------------------------------- public String getImage() { return image; } //---------------------------------------------------------------------------------------------- public String getFlavor() { return flavor; } //---------------------------------------------------------------------------------------------- public String getId() { return id; } //---------------------------------------------------------------------------------------------- public String getPublicIp() { return publicIp; } //---------------------------------------------------------------------------------------------- public String getPrivateIp() { return privateIp; } //---------------------------------------------------------------------------------------------- public void setName(String name) { this.name = name; } //---------------------------------------------------------------------------------------------- public void setAppendSuffix(boolean appendSuffix) { this.appendSuffix = appendSuffix; } //---------------------------------------------------------------------------------------------- public void setRegion(String region) { if(Region.containsIgnoreCase(region)) { this.region = region.toLowerCase(); } else { log.warn("You did not enter a valid region for a server."); } } //---------------------------------------------------------------------------------------------- public void setImage(String image) { this.image = image; } //---------------------------------------------------------------------------------------------- public void setFlavor(String flavor) { if (ServerFlavor.contains(flavor)) { this.flavor = flavor; } else { log.warn("Flavor type is not valid."); } } //---------------------------------------------------------------------------------------------- public void setId(String id) { this.id = id; } //---------------------------------------------------------------------------------------------- public void setPublicIp(String publicIp) { this.publicIp = publicIp; } //---------------------------------------------------------------------------------------------- public void setPrivateIp(String privateIp) { this.privateIp = privateIp; } //---------------------------------------------------------------------------------------------- private String resolveImageIDForImageName(String imageName) throws IOException, JSONException { String result = null; RackspaceRestClient client = env.fetchContext().client; String uri = "https://" + client.encodePath(region) + ".servers.api.rackspacecloud.com/v2/" + client.encodePath(client.getTenantID()) + "/images/detail"; GetMethod method = new GetMethod(uri); method.setRequestHeader("X-Auth-Token", client.getAuthToken()); method.setRequestHeader("Content-Type", "application/json"); int status = client.invokeMethod(method); String body = client.getBody(method); method.releaseConnection(); if (200 <= status && status <= 204) { JSONArray imagesJSON = new JSONObject(body).getJSONArray("images"); for (int i=0; i<imagesJSON.length(); i++) { JSONObject currentImage = imagesJSON.getJSONObject(i); String nameToTest = currentImage.getString("name"); if (imageName.equals(nameToTest)) { result = currentImage.getString("id"); break; } } } else { log.warn("Exception when fetching image IDs."); throw new IOException(String.format("%d %s %s", status, HttpStatus.getStatusText(status), body)); } return result; } //---------------------------------------------------------------------------------------------- private JSONObject pollForServerStatus() throws IOException, JSONException { JSONObject result = null; RackspaceRestClient client = env.fetchContext().client; String uri = "https://" + client.encodePath(region) + ".servers.api.rackspacecloud.com/v2/" + client.encodePath(client.getTenantID()) + "/servers/" + client.encodePath(id); GetMethod method = new GetMethod(uri); method.setRequestHeader("X-Auth-Token", client.getAuthToken()); method.setRequestHeader("Content-Type", "application/json"); int status = client.invokeMethod(method); String body = client.getBody(method); method.releaseConnection(); if (200 <= status && status <= 204) { JSONObject serverJSON = new JSONObject(body).getJSONObject("server"); result = serverJSON; } else { log.warn("Exception when polling server for status."); throw new IOException(String.format("%d %s %s", status, HttpStatus.getStatusText(status), body)); } return result; } //---------------------------------------------------------------------------------------------- private void updateIpAddresses(JSONObject addressesJSON) throws JSONException { JSONArray publicAddressArray = addressesJSON.getJSONArray("public"); for (int i=0; i<publicAddressArray.length(); i++) { JSONObject publicAddressJSON = publicAddressArray.getJSONObject(i); if (publicAddressJSON.getInt("version") == 4) { publicIp = publicAddressJSON.getString("addr"); break; } } JSONArray privateAddressArray = addressesJSON.getJSONArray("private"); for (int i=0; i<privateAddressArray.length(); i++) { JSONObject privateAddressJSON = privateAddressArray.getJSONObject(i); if (privateAddressJSON.getInt("version") == 4) { privateIp = privateAddressJSON.getString("addr"); break; } } } //---------------------------------------------------------------------------------------------- private void createServerRestCall(String imageId) throws JSONException, IOException { RackspaceRestClient client = env.fetchContext().client; String uri = "https://" + client.encodePath(region) + ".servers.api.rackspacecloud.com/v2/" + client.encodePath(client.getTenantID()) + "/servers"; JSONObject data = new JSONObject(); JSONObject server = new JSONObject(); server.put("name", name); server.put("imageRef", imageId); server.put("flavorRef", ServerFlavor.lookupFlavorID(flavor)); data.put("server", server); log.debug("Sending body " + data.toString()); PostMethod method = new PostMethod(uri); RequestEntity entity = new StringRequestEntity(data.toString(), "application/json", null); method.setRequestEntity(entity); method.setRequestHeader("X-Auth-Token", client.getAuthToken()); int status = client.invokeMethod(method); String body = client.getBody(method); method.releaseConnection(); if (200 <= status && status <= 202) { log.info("Server creation request succeeded."); JSONObject resultJSON = new JSONObject(body); id = resultJSON.getJSONObject("server").getString("id"); } else { log.warn("Exception when creating server."); throw new IOException(String.format("%d %s %s", status, HttpStatus.getStatusText(status), body)); } } //---------------------------------------------------------------------------------------------- private void deleteServerRestCall() throws JSONException, IOException { RackspaceRestClient client = env.fetchContext().client; String uri = "https://" + client.encodePath(region) + ".servers.api.rackspacecloud.com/v2/" + client.encodePath(client.getTenantID()) + "/servers/" + client.encodePath(id); log.debug("deletion uri: " + uri); DeleteMethod method = new DeleteMethod(uri); method.setRequestHeader("X-Auth-Token", client.getAuthToken()); int status = client.invokeMethod(method); String body = client.getBody(method); method.releaseConnection(); if (202 <= status && status <= 204) { log.info("Server deletion request succeeded."); } else { log.warn("Exception when deleting server."); throw new IOException(String.format("%d %s %s", status, HttpStatus.getStatusText(status), body)); } } //---------------------------------------------------------------------------------------------- @Override public void create() throws Exception { if (appendSuffix) { name = name + "-" + env.fetchSuffix(); } String imageId; //Check if they supplied an image ID. If they didn't then try to match image name to an ID. if (image.matches("[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}")) { imageId = image; } else { imageId = resolveImageIDForImageName(image); } log.info("Creating server with name " + name + " from image " + image); createServerRestCall(imageId); log.info("Server request succeeded. Polling for server to come online..."); boolean active = false; long pollInterval = 5000L; while (!active) { Thread.sleep(pollInterval); JSONObject serverJSON = pollForServerStatus(); if (serverJSON.getString("status").equalsIgnoreCase("ACTIVE")) { try { updateIpAddresses(serverJSON.getJSONObject("addresses")); env.fetchContext().setProperty(name + "-public-ip", publicIp); env.fetchContext().setProperty(name + "-private-ip", privateIp); } catch(JSONException e) { log.warn("Exception while finding IP addresses. Continuing...", e); } finally { active = true; } } } log.info("Server " + name + " is online at IP " + publicIp); } //---------------------------------------------------------------------------------------------- @Override public void destroy() throws Exception { log.info("deleting server with name " + name); deleteServerRestCall(); } }