package com.urbancode.terraform.tasks.vcloud; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.savvis.sdk.oauth.connections.HttpApiResponse; import com.urbancode.terraform.tasks.common.exceptions.EnvironmentCreationException; import com.urbancode.terraform.tasks.common.exceptions.EnvironmentDestructionException; import com.urbancode.x2o.tasks.SubTask; import com.urbancode.x2o.xml.XmlParsingException; public class VAppTask extends SubTask { //********************************************************************************************** // CLASS //********************************************************************************************** static private final Logger log = Logger.getLogger(VAppTask.class); //********************************************************************************************** // INSTANCE //********************************************************************************************** private EnvironmentTaskVCloud env; private List<VMTask> vmTasks = new ArrayList<VMTask>(); private String name; private String templateName; private String catalogName; private String templateId; private String description; private String id; //---------------------------------------------------------------------------------------------- public VAppTask(EnvironmentTaskVCloud env) { super(); this.env = env; } //---------------------------------------------------------------------------------------------- public List<VMTask> getVmTasks() { return vmTasks; } //---------------------------------------------------------------------------------------------- public String getName() { return name; } //---------------------------------------------------------------------------------------------- public String getTemplateName() { return templateName; } //---------------------------------------------------------------------------------------------- public String getCatalogName() { return catalogName; } //---------------------------------------------------------------------------------------------- public String getDescription() { return description; } //---------------------------------------------------------------------------------------------- public String getId() { return id; } //---------------------------------------------------------------------------------------------- public String getTemplateId() { return templateId; } //---------------------------------------------------------------------------------------------- public void setName(String name) { this.name = name; } //---------------------------------------------------------------------------------------------- public void setTemplateName(String templateName) { this.templateName = templateName; } //---------------------------------------------------------------------------------------------- public void setCatalogName(String catalogName) { this.catalogName = catalogName; } //---------------------------------------------------------------------------------------------- public void setDescription(String description) { this.description = description; } //---------------------------------------------------------------------------------------------- public void setTemplateId(String templateId) { this.templateId = templateId; } //---------------------------------------------------------------------------------------------- public void setId(String id) { this.id = id; } //---------------------------------------------------------------------------------------------- public VMTask createVm() { VMTask vmTask = new VMTask(); vmTasks.add(vmTask); return vmTask; } //---------------------------------------------------------------------------------------------- @Override public void create() throws Exception { if (templateId == null) { findTemplateId(); } String suffixString = "-" + env.fetchSuffix(); name = name.contains(suffixString) ? name : name + suffixString; for (VMTask vmTask : vmTasks) { vmTask.create(); } String requestBody = generateCreateRequest(); String contentType = "application/vnd.vmware.vcloud.instantiateVAppTemplateParams+xml"; String urlSuffix = "/vdc/" + env.getVdcId() + "/action/instantiateVAppTemplate"; HttpApiResponse response = null; try { response = SavvisClient.getInstance().makeApiCallWithSuffix(urlSuffix, SavvisClient.POST_METHOD, requestBody, contentType); } catch (Exception e) { env.fetchContext().setWriteContext(false); throw e; } log.debug("response: " + response.getResponseString()); id = findHref(response.getResponseString()); try { log.debug("Finding VMs..."); boolean foundVMs = false; boolean taskDone = false; long timeout = System.currentTimeMillis() + 720000L; while (!foundVMs) { log.trace("Waiting for VMs to be created."); Thread.sleep(5000); HttpApiResponse vAppResponse = fetchVAppResponse(); foundVMs = unmarshalVMs(vAppResponse.getResponseString()); if (!foundVMs && System.currentTimeMillis() > timeout) { throw new EnvironmentCreationException("Timeout waiting for vApp to create VMs"); } } while (!taskDone) { log.trace("Waiting for VMs to be started."); Thread.sleep(5000); HttpApiResponse vAppResponse = fetchVAppResponse(); taskDone = isCreateTaskDone(vAppResponse.getResponseString()); if (!taskDone && System.currentTimeMillis() > timeout) { throw new EnvironmentCreationException("Timeout waiting for vApp to start"); } } } catch (EnvironmentCreationException e) { throw e; } catch (Exception e) { log.debug("error while finding VMs from vApp", e); } } //---------------------------------------------------------------------------------------------- @Override public void destroy() throws Exception { for (VMTask vmTask : vmTasks) { vmTask.destroy(); } HttpApiResponse response = fetchVAppResponse(); log.trace("response: " + response.getResponseString()); response = SavvisClient.getInstance().makeApiCallWithSuffix("/vApp/" + id + "/action/undeploy", SavvisClient.POST_METHOD, undeployBody(), "application/vnd.vmware.vcloud.undeployVAppParams+xml"); log.trace("response: " + response.getResponseString()); boolean found = false; long timeout = System.currentTimeMillis() + 180000L; while (!found) { log.trace("Waiting for undeployment."); Thread.sleep(5000); response = SavvisClient.getInstance().makeApiCallWithSuffix("/vApp/" + id, SavvisClient.GET_METHOD, "", ""); try { found = hasRemoveLink(response.getResponseString()); } catch (Exception e) { log.warn("Exception while checking to see if vApp was undeployed", e); } if (!found && System.currentTimeMillis() > timeout) { throw new EnvironmentDestructionException("Timeout waiting for vApp to undeploy"); } } response = SavvisClient.getInstance().makeApiCallWithSuffix("/vApp/" + id, SavvisClient.DELETE_METHOD, "", ""); log.trace("response: " + response.getResponseString()); } //---------------------------------------------------------------------------------------------- private HttpApiResponse fetchVAppResponse() throws Exception { return SavvisClient.getInstance().makeApiCallWithSuffix("/vApp/" + id, SavvisClient.GET_METHOD, "", ""); } //---------------------------------------------------------------------------------------------- public String generateCreateRequest() throws ParserConfigurationException, TransformerException { Document resultDoc = env.blankDocument(); resultDoc.setXmlVersion("1.0"); Element root = resultDoc.createElement("InstantiateVAppTemplateParams"); resultDoc.appendChild(root); root.setAttribute("xmlns", "http://www.vmware.com/vcloud/v1.5"); root.setAttribute("name", name); root.setAttribute("deploy", String.valueOf(true)); root.setAttribute("powerOn", String.valueOf(true)); root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); root.setAttribute("xmlns:ovf", "http://schemas.dmtf.org/ovf/envelope/1"); if (!StringUtils.isEmpty(description)) { Element descriptionElement = resultDoc.createElement("Description"); descriptionElement.setTextContent(description); root.appendChild(descriptionElement); } Element source = resultDoc.createElement("Source"); String baseUrl = SavvisClient.getInstance().getCredentials().getApiBaseLocation(); String cleanTemplateId = cleanTemplateId(); source.setAttribute("href", baseUrl + "/vAppTemplate/" + cleanTemplateId); root.appendChild(source); Element eulas = resultDoc.createElement("AllEULAsAccepted"); eulas.setTextContent(String.valueOf(true)); root.appendChild(eulas); TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); StringWriter writer = new StringWriter(); transformer.transform(new DOMSource(resultDoc), new StreamResult(writer)); String result = writer.getBuffer().toString().replaceAll("\n|\r", ""); log.trace(result); return result; } //---------------------------------------------------------------------------------------------- private String findHref(String vAppBody) throws XmlParsingException { String result = null; try { Document doc = env.buildDocument(vAppBody); XPath xpath = XPathFactory.newInstance().newXPath(); XPathExpression networkQuery = xpath.compile("/VApp/@href"); Object preResult = networkQuery.evaluate(doc, XPathConstants.STRING); String href = (String) preResult; result = href.substring(href.indexOf("vapp-")); } catch (Exception e) { log.error("exception while finding vApp link", e); throw new XmlParsingException(e); } return result; } //---------------------------------------------------------------------------------------------- private boolean hasRemoveLink(String vAppBody) throws XmlParsingException { boolean result = false; try { Document doc = env.buildDocument(vAppBody); XPath xpath = XPathFactory.newInstance().newXPath(); XPathExpression networkQuery = xpath.compile("/VApp/Link[@rel='remove']/@href"); String preResult = (String) networkQuery.evaluate(doc, XPathConstants.STRING); if (preResult.contains(id)) { result = true; } } catch (Exception e) { log.error("exception while finding vApp link", e); throw new XmlParsingException(e); } return result; } //---------------------------------------------------------------------------------------------- protected Node getNetworkConfigSection(Document templateDoc) throws XPathExpressionException { Node result; XPath xpath = XPathFactory.newInstance().newXPath(); XPathExpression networkQuery = xpath.compile("//NetworkConfigSection"); Object preResult = networkQuery.evaluate(templateDoc, XPathConstants.NODE); if (preResult == null) { throw new XPathExpressionException("The network config node could not be found."); } result = (Node) preResult; return result; } //---------------------------------------------------------------------------------------------- protected Document fetchvAppTemplate(String templateToFetch) throws Exception { HttpApiResponse response = SavvisClient.getInstance().makeApiCallWithSuffix( "/vAppTemplate/" + templateToFetch, SavvisClient.GET_METHOD, "", ""); String responseBody = response.getResponseString(); return env.buildDocument(responseBody); } //---------------------------------------------------------------------------------------------- private String cleanTemplateId() { String cleanTemplateId = templateId; if (!cleanTemplateId.contains("vappTemplate")) { cleanTemplateId = "vappTemplate-" + cleanTemplateId; } return cleanTemplateId; } //---------------------------------------------------------------------------------------------- private String undeployBody() throws ParserConfigurationException, TransformerException { String result = ""; Document doc = env.blankDocument(); Element undeployParams = doc.createElement("UndeployVAppParams"); undeployParams.setAttribute("xmlns", "http://www.vmware.com/vcloud/v1.5"); Element undeployAction = doc.createElement("UndeployPowerAction"); undeployAction.setTextContent("powerOff"); undeployParams.appendChild(undeployAction); doc.appendChild(undeployParams); TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer; transformer = tf.newTransformer(); StringWriter writer = new StringWriter(); transformer.transform(new DOMSource(doc), new StreamResult(writer)); result = writer.getBuffer().toString().replaceAll("\n|\r", ""); log.trace("sending undeploy body: " + result); return result; } //---------------------------------------------------------------------------------------------- /** * @param vAppBody the XML response from the GET request * @return true if VMs were found, false otherwise */ private boolean unmarshalVMs(String vAppBody) throws XPathExpressionException, ParserConfigurationException, SAXException, IOException { log.trace(vAppBody); boolean result = false; Document doc = env.buildDocument(vAppBody); XPath xpath = XPathFactory.newInstance().newXPath(); XPathExpression networkQuery = xpath.compile("/VApp/Children/Vm"); Object preResult = networkQuery.evaluate(doc, XPathConstants.NODESET); NodeList childrenNodes = (NodeList) preResult; result = childrenNodes.getLength() > 0; Set<String> currentVMNames = new HashSet<String>(); for (VMTask vmTask : vmTasks) { currentVMNames.add(vmTask.getName()); } for (int i=0; i<childrenNodes.getLength(); i++) { Element vmElement = (Element) childrenNodes.item(i); String vmName = vmElement.getAttribute("name"); String vmHref = vmElement.getAttribute("href"); if (!currentVMNames.contains(vmName)) { VMTask vm = createVm(); vm.setName(vmName); vm.setHref(vmHref); } } return result; } //---------------------------------------------------------------------------------------------- private boolean isCreateTaskDone(String vAppBody) throws XPathExpressionException, ParserConfigurationException, SAXException, IOException { log.trace(vAppBody); boolean result = true; Document doc = env.buildDocument(vAppBody); XPath xpath = XPathFactory.newInstance().newXPath(); XPathExpression networkQuery = xpath.compile("/VApp/Tasks/Task"); Object preResult = networkQuery.evaluate(doc, XPathConstants.NODESET); NodeList childrenNodes = (NodeList) preResult; for (int i=0; i<childrenNodes.getLength(); i++) { Element vmElement = (Element) childrenNodes.item(i); String taskOperation = vmElement.getAttribute("operationName"); if ("vdcInstantiateVapp".equals(taskOperation)) { log.debug("The vApp has started."); result = false; break; } } return result; } //---------------------------------------------------------------------------------------------- private String findCatalogId() throws Exception { String result = null; HttpApiResponse response = SavvisClient.getInstance().makeApiCallWithSuffix( "/org/" + env.fetchOrgId(), "GET", "", ""); try { Document doc = env.buildDocument(response.getResponseString()); XPath xpath = XPathFactory.newInstance().newXPath(); XPathExpression query = xpath.compile("/Org/Link"); Object preResult = query.evaluate(doc, XPathConstants.NODESET); NodeList childrenNodes = (NodeList) preResult; for (int i=0; i<childrenNodes.getLength(); i++) { Element linkElement = (Element) childrenNodes.item(i); String foundName = linkElement.getAttribute("name"); if (catalogName.equals(foundName)) { String href = linkElement.getAttribute("href"); result = href.substring(href.indexOf("/catalog")); break; } } } catch (Exception e) { log.error("exception while finding catalog ID", e); throw new XmlParsingException(e); } return result; } //---------------------------------------------------------------------------------------------- private String findCatalogItemId() throws Exception { String result = null; String catalogId = findCatalogId(); HttpApiResponse response = SavvisClient.getInstance().makeApiCallWithSuffix( catalogId, "GET", "", ""); try { Document doc = env.buildDocument(response.getResponseString()); XPath xpath = XPathFactory.newInstance().newXPath(); XPathExpression query = xpath.compile("/Catalog/CatalogItems/CatalogItem"); Object preResult = query.evaluate(doc, XPathConstants.NODESET); NodeList childrenNodes = (NodeList) preResult; for (int i=0; i<childrenNodes.getLength(); i++) { Element linkElement = (Element) childrenNodes.item(i); String foundName = linkElement.getAttribute("name"); if (templateName.equals(foundName)) { String href = linkElement.getAttribute("href"); result = href.substring(href.indexOf("/catalogItem")); break; } } } catch (Exception e) { log.error("exception while finding catalog item ID", e); throw new XmlParsingException(e); } return result; } //---------------------------------------------------------------------------------------------- private void findTemplateId() throws Exception { String catalogItemId = findCatalogItemId(); HttpApiResponse response = SavvisClient.getInstance().makeApiCallWithSuffix( catalogItemId, "GET", "", ""); try { Document doc = env.buildDocument(response.getResponseString()); XPath xpath = XPathFactory.newInstance().newXPath(); XPathExpression query = xpath.compile("/CatalogItem/Entity"); Object preResult = query.evaluate(doc, XPathConstants.NODE); Element entityElement = (Element) preResult; String href = entityElement.getAttribute("href"); templateId = href.substring(href.indexOf("/vAppTemplate/") + "/vAppTemplate/".length()); } catch (Exception e) { log.error("exception while finding catalog item ID", e); throw new XmlParsingException(e); } } }