Java tutorial
/* * Copyright (c) 2015-2016 VMware, Inc. All Rights Reserved. * * 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 com.vmware.photon.controller.model.adapters.vsphere.ovf; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.UUID; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.apache.commons.io.IOUtils; import org.apache.http.impl.client.CloseableHttpClient; 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.vmware.photon.controller.model.adapters.vsphere.CustomProperties; import com.vmware.photon.controller.model.resources.ComputeDescriptionService.ComputeDescription; import com.vmware.xenon.common.Utils; public class OvfParser { public static final String PROP_OVF_CONFIGURATION = "ovf.configuration"; public static final String PROP_OVF_URI = "ovf.uri"; public static final String PREFIX_OVF_PROP = "ovf.prop:"; public static final String PREFIX_OVF_NET = "ovf.net:"; public static final String PROP_OVF_ARCHIVE_URI = "ova.uri"; private XPath xpath; /** * http://blogs.vmware.com/vapp/2009/11/virtual-hardware-in-ovf-part-1.html */ private static final String RESOURCE_TYPE_CPU = "3"; private static final String RESOURCE_TYPE_MEMORY = "4"; /** * Allocation units defined by OVF. A simpler guide can be found here: * http://opennodecloud.com/howto/2013/12/25/howto-ON-ovf-reference.html */ private static Map<String, Long> multipliersByMemoryAllocationUnit = new HashMap<String, Long>() { private static final long serialVersionUID = 1L; { put("", 1L); put("KB", pow2(10)); put("KILOBYTE", pow2(10)); put("byte*2^10", pow2(10)); put("MB", pow2(20)); put("MEGABYTE", pow2(20)); put("byte*2^20", pow2(20)); put("GB", pow2(30)); put("GIGABYTE", pow2(30)); put("byte*2^30", pow2(30)); put("TERABYTE", pow2(40)); put("GIGABYTE", pow2(40)); put("byte*2^40", pow2(40)); } private Long pow2(long i) { return 1L << i; } }; /** * Produces several descriptions based on the different hardware configuration defined in the OVF * descriptor. * @param doc OVF descriptor to parse * @param template use as a basis of the ComputeDescription. * @return */ public List<ComputeDescription> parse(Document doc, ComputeDescription template) { NodeList networks = nodes(doc, "/ovf:Envelope/ovf:NetworkSection/ovf:Network"); CustomProperties cust = CustomProperties.of(template); for (Element network : iterableElements(networks)) { cust.put(network(attr("ovf:name", network)), text(network, "ovf:Description/text()")); } NodeList props = nodes(doc, "/ovf:Envelope/ovf:VirtualSystem/ovf:ProductSection/ovf:Property"); for (Element prop : iterableElements(props)) { String userConfigurable = attr("ovf:userConfigurable", prop); if (!"true".equals(userConfigurable)) { continue; } String key = attr("ovf:key", prop); Element section = (Element) prop.getParentNode(); String instanceId = attr("ovf:instance", section); String classId = attr("ovf:class", section); String description = text(prop, "ovf:Description/text()"); cust.put(property(classId, key, instanceId), description); } String productName = text(doc, "/ovf:Envelope/ovf:VirtualSystem/ovf:ProductSection/ovf:Product/text()"); String productVersion = text(doc, "/ovf:Envelope/ovf:VirtualSystem/ovf:ProductSection/ovf:Version/text()"); template.name = productName + " " + productVersion; NodeList hwItems = nodes(doc, "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item"); Map<String, ComputeDescription> hwByConfigName = new HashMap<>(); for (Element item : iterableElements(hwItems)) { String configName = attr("ovf:configuration", item); ComputeDescription desc = hwByConfigName.get(configName); if (desc == null) { desc = Utils.clone(template); desc.documentSelfLink = UUID.randomUUID().toString(); desc.id = "ovf-imported-" + desc.documentSelfLink; desc.customProperties.put(PROP_OVF_CONFIGURATION, configName); hwByConfigName.put(configName, desc); } String resourceType = text(item, "rasd:ResourceType/text()"); if (RESOURCE_TYPE_CPU.equals(resourceType)) { long qty = Long.parseLong(text(item, "rasd:VirtualQuantity/text()")); desc.cpuCount = qty; } if (RESOURCE_TYPE_MEMORY.equals(resourceType)) { double qty = Double.parseDouble(text(item, "rasd:VirtualQuantity/text()")); long mult = memAllocationUnit2Multiplier(text(item, "rasd:AllocationUnits/text()")); desc.totalMemoryBytes = (long) (qty * mult); } } for (Iterator<ComputeDescription> it = hwByConfigName.values().iterator(); it.hasNext();) { ComputeDescription desc = it.next(); if (desc.cpuCount <= 0) { it.remove(); } } return new ArrayList<>(hwByConfigName.values()); } /** * Convert a an AllocationUnit string to bytes multiplier, for example KB would produce 1024. * @param unit * @return */ private long memAllocationUnit2Multiplier(String unit) { // remove spaces from unit, just in case Long res = multipliersByMemoryAllocationUnit.get(unit.replace(" ", "")); if (res == null) { throw new IllegalArgumentException("Cannot map " + unit + " to a known unit"); } else { return res; } } /** * Extracts an attribute by name. Returns empty string if attribute was not found. * If the attr name is prefixed and not found, an attempt is made to find it without prefix * @param name * @param element * @return */ private String attr(String name, Element element) { String res = text(element, "@" + name); int i = name.indexOf(':'); if (res.length() == 0 && i >= 0) { return attr(name.substring(i + 1), element); } return res; } /** * Queries for a list nodes matching the xpath expression. * * @param root * @param xpathExpr * @return */ private NodeList nodes(Node root, String xpathExpr) { try { return (NodeList) xpath().evaluate(xpathExpr, root, XPathConstants.NODESET); } catch (XPathExpressionException e) { throw new RuntimeException(e); } } /** * Returns a single string matching the xpath expression * @param root * @param xpathExpr * @return */ private String text(Node root, String xpathExpr) { try { return (String) xpath().evaluate(xpathExpr, root, XPathConstants.STRING); } catch (XPathExpressionException e) { throw new RuntimeException(e); } } public Document retrieveDescriptor(URI uri) throws IOException, SAXException { DocumentBuilder documentBuilder = newDocumentBuilder(); CloseableHttpClient client = OvfRetriever.newInsecureClient(); try { return documentBuilder.parse(new OvfRetriever(client).retrieveAsStream(uri)); } finally { IOUtils.closeQuietly(client); } } /** * Produces a string usable as key in customProperties. * The pattern is: ovf.prop:$classId.$keyId.$instanceId. * If a component is undefined it is not included in the key and also * The pattern used by the ovftool is used. * See https://www.mylesgray.com/virtualisation/deploying-ovaovf-remote-vcenter-using-ovftool/ * @param classId * @param key * @param instanceId * @return */ private String property(String classId, String key, String instanceId) { return PREFIX_OVF_PROP + makePropertyKey(classId, key, instanceId); } private String makePropertyKey(String classId, String key, String instanceId) { StringBuilder sb = new StringBuilder(); if (classId != null && classId.length() > 0) { sb.append(classId); sb.append("."); } if (key != null && key.length() > 0) { sb.append(key); } if (instanceId != null && instanceId.length() > 0) { sb.append("."); sb.append(instanceId); } return sb.toString(); } private String network(String name) { return PREFIX_OVF_NET + name; } private static XPath newXpath() { XPathFactory xf = XPathFactory.newInstance(); XPath xpath = xf.newXPath(); NamespaceContextImpl ctx = new NamespaceContextImpl(); ctx.addNamespace("ovf", "http://schemas.dmtf.org/ovf/envelope/1"); ctx.addNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance"); ctx.addNamespace("rasd", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"); ctx.addNamespace("vssd", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"); ctx.addNamespace("vmw", "http://www.vmware.com/schema/ovf"); xpath.setNamespaceContext(ctx); return xpath; } private DocumentBuilder newDocumentBuilder() { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); dbf.setValidating(false); try { return dbf.newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new RuntimeException(e); } } /** * Make a NodeList behave like Iterable<Element>. * @param n * @return */ private Iterable<Element> iterableElements(final NodeList n) { return () -> new Iterator<Element>() { private int index = 0; @Override public boolean hasNext() { return index < n.getLength(); } @Override public Element next() { if (hasNext()) { return (Element) n.item(index++); } else { throw new NoSuchElementException(); } } @Override public void remove() { throw new UnsupportedOperationException(); } }; } private XPath xpath() { if (this.xpath == null) { this.xpath = newXpath(); } return this.xpath; } /** * Coverts the string ovf.prop:abc into abc. If the propame is not prefixed return null. * @param propName * @return */ public static String stripPrefix(String propName) { if (propName == null) { return null; } if (propName.startsWith(PREFIX_OVF_PROP)) { return propName.substring(PREFIX_OVF_NET.length() + 1); } return null; } }