Skip to content

Commit 982eef2

Browse files
Merge branch '4.16' into main
2 parents 97d6cd5 + 84f5768 commit 982eef2

18 files changed

Lines changed: 698 additions & 321 deletions

File tree

api/src/main/java/com/cloud/agent/api/storage/OVFHelper.java

Lines changed: 91 additions & 180 deletions
Large diffs are not rendered by default.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package com.cloud.agent.api.storage;
18+
19+
import org.apache.commons.lang3.StringUtils;
20+
import org.apache.log4j.Logger;
21+
import org.w3c.dom.Document;
22+
import org.w3c.dom.Element;
23+
import org.w3c.dom.Node;
24+
import org.w3c.dom.NodeList;
25+
import org.xml.sax.InputSource;
26+
import org.xml.sax.SAXException;
27+
28+
import javax.xml.parsers.DocumentBuilder;
29+
import javax.xml.parsers.DocumentBuilderFactory;
30+
import javax.xml.parsers.ParserConfigurationException;
31+
import java.io.File;
32+
import java.io.IOException;
33+
import java.io.StringReader;
34+
import java.util.Map;
35+
36+
public class OVFParser {
37+
private static final Logger s_logger = Logger.getLogger(OVFParser.class);
38+
39+
private static final String DEFAULT_OVF_SCHEMA = "http://schemas.dmtf.org/ovf/envelope/1";
40+
private static final String VMW_SCHEMA = "http://www.vmware.com/schema/ovf";
41+
42+
private static final Map<String, String> ATTRIBUTE_SCHEMA_MAP = Map.of(
43+
"osType", VMW_SCHEMA
44+
);
45+
46+
private DocumentBuilder documentBuilder;
47+
48+
public OVFParser() {
49+
try {
50+
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
51+
documentBuilderFactory.setNamespaceAware(true);
52+
documentBuilder = documentBuilderFactory.newDocumentBuilder();
53+
} catch (ParserConfigurationException e) {
54+
s_logger.error("Cannot start the OVF parser: " + e.getMessage(), e);
55+
}
56+
}
57+
58+
public Document parseOVF(String ovfString) throws IOException, SAXException {
59+
InputSource is = new InputSource(new StringReader(ovfString));
60+
return documentBuilder.parse(is);
61+
}
62+
63+
public Document parseOVFFile(String ovfFilePath) {
64+
if (StringUtils.isBlank(ovfFilePath)) {
65+
return null;
66+
}
67+
try {
68+
return documentBuilder.parse(new File(ovfFilePath));
69+
} catch (SAXException | IOException e) {
70+
s_logger.error("Error parsing " + ovfFilePath + " " + e.getMessage(), e);
71+
return null;
72+
}
73+
}
74+
75+
/**
76+
* Retrieve elements with tag name from the document, according to the OVF schema definition
77+
*/
78+
public NodeList getElementsFromOVFDocument(Document doc, String tagName) {
79+
return doc != null ? doc.getElementsByTagNameNS(DEFAULT_OVF_SCHEMA, tagName) : null;
80+
}
81+
82+
/**
83+
* Retrieve an attribute value from an OVF element
84+
*/
85+
public String getNodeAttribute(Element element, String attr) {
86+
return element != null ? element.getAttributeNS(ATTRIBUTE_SCHEMA_MAP.getOrDefault(attr, DEFAULT_OVF_SCHEMA), attr) : null;
87+
}
88+
89+
/**
90+
* Get the text value of a node's child with name or suffix "childNodeName", null if not present
91+
* Example:
92+
* <Node>
93+
* <childNodeName>Text value</childNodeName>
94+
* <rasd:childNodeName>Text value</rasd:childNodeName>
95+
* </Node>
96+
*/
97+
public String getChildNodeValue(Node node, String childNodeName) {
98+
if (node == null || !node.hasChildNodes()) {
99+
return null;
100+
}
101+
NodeList childNodes = node.getChildNodes();
102+
for (int i = 0; i < childNodes.getLength(); i++) {
103+
Node value = childNodes.item(i);
104+
// Also match if the child's name has a suffix:
105+
// Example: <rasd:AllocationUnits>
106+
if (value != null && (value.getNodeName().equals(childNodeName)) || value.getNodeName().endsWith(":" + childNodeName)) {
107+
return value.getTextContent();
108+
}
109+
}
110+
return null;
111+
}
112+
}

api/src/test/java/com/cloud/agent/api/storage/OVFHelperTest.java

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,13 @@
2727
import org.xml.sax.SAXException;
2828
import org.xml.sax.SAXParseException;
2929

30-
import javax.xml.parsers.ParserConfigurationException;
3130
import java.io.IOException;
3231
import java.util.List;
3332

3433
public class OVFHelperTest {
3534

3635
private String ovfFileProductSection =
37-
"<ProductSection>" +
36+
"<ProductSection xmlns=\"http://schemas.dmtf.org/ovf/envelope/1\" xmlns:cim=\"http://schemas.dmtf.org/wbem/wscim/1/common\" xmlns:ovf=\"http://schemas.dmtf.org/ovf/envelope/1\" xmlns:rasd=\"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData\" xmlns:vmw=\"http://www.vmware.com/schema/ovf\" xmlns:vssd=\"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
3837
"<Info>VM Arguments</Info>" +
3938
"<Property ovf:key=\"va-ssh-public-key\" ovf:type=\"string\" ovf:userConfigurable=\"true\" ovf:value=\"\">" +
4039
"<Label>Set the SSH public key allowed to access the appliance</Label>" +
@@ -47,7 +46,7 @@ public class OVFHelperTest {
4746
"</ProductSection>";
4847

4948
private String ovfFileDeploymentOptionsSection =
50-
"<DeploymentOptionSection>\n" +
49+
"<DeploymentOptionSection xmlns=\"http://schemas.dmtf.org/ovf/envelope/1\" xmlns:cim=\"http://schemas.dmtf.org/wbem/wscim/1/common\" xmlns:ovf=\"http://schemas.dmtf.org/ovf/envelope/1\" xmlns:rasd=\"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData\" xmlns:vmw=\"http://www.vmware.com/schema/ovf\" xmlns:vssd=\"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
5150
" <Info>Deployment Configuration information</Info>\n" +
5251
" <Configuration ovf:id=\"ASAv5\">\n" +
5352
" <Label>100 Mbps (ASAv5)</Label>\n" +
@@ -61,10 +60,10 @@ public class OVFHelperTest {
6160
" <Label>2 Gbps (ASAv30)</Label>\n" +
6261
" <Description>Use this option to deploy an ASAv with a maximum throughput of 2 Gbps (uses 4 vCPUs and 8 GB of memory).</Description>\n" +
6362
" </Configuration>\n" +
64-
" </DeploymentOptionSection>";
63+
" </DeploymentOptionSection>";
6564

6665
private String ovfFileVirtualHardwareSection =
67-
"<VirtualSystem>\n" +
66+
"<VirtualSystem xmlns=\"http://schemas.dmtf.org/ovf/envelope/1\" xmlns:cim=\"http://schemas.dmtf.org/wbem/wscim/1/common\" xmlns:ovf=\"http://schemas.dmtf.org/ovf/envelope/1\" xmlns:rasd=\"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData\" xmlns:vmw=\"http://www.vmware.com/schema/ovf\" xmlns:vssd=\"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
6867
"<OperatingSystemSection ovf:id=\"100\" vmw:osType=\"other26xLinux64Guest\">\n" +
6968
" <Info>The kind of installed guest operating system</Info>\n" +
7069
" <Description>Other 2.6x Linux (64-bit)</Description>\n" +
@@ -270,7 +269,7 @@ public class OVFHelperTest {
270269
"</VirtualSystem>";
271270

272271
private String eulaSections =
273-
"<VirtualSystem>\n" +
272+
"<VirtualSystem xmlns=\"http://schemas.dmtf.org/ovf/envelope/1\" xmlns:cim=\"http://schemas.dmtf.org/wbem/wscim/1/common\" xmlns:ovf=\"http://schemas.dmtf.org/ovf/envelope/1\" xmlns:rasd=\"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData\" xmlns:vmw=\"http://www.vmware.com/schema/ovf\" xmlns:vssd=\"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
274273
"<EulaSection>\n" +
275274
" <Info>end-user license agreement</Info>\n" +
276275
" <License>END USER LICENSE AGREEMENT\n" +
@@ -395,7 +394,7 @@ public class OVFHelperTest {
395394
"</VirtualSystem>";
396395

397396
private String productSectionWithCategories =
398-
"<VirtualSystem ovf:id=\"VMware-vCenter-Server-Appliance\">\n" +
397+
"<VirtualSystem ovf:id=\"VMware-vCenter-Server-Appliance\" xmlns=\"http://schemas.dmtf.org/ovf/envelope/1\" xmlns:cim=\"http://schemas.dmtf.org/wbem/wscim/1/common\" xmlns:ovf=\"http://schemas.dmtf.org/ovf/envelope/1\" xmlns:rasd=\"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData\" xmlns:vmw=\"http://www.vmware.com/schema/ovf\" xmlns:vssd=\"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
399398
"<ProductSection ovf:required=\"false\">\n" +
400399
" <Info>Appliance ISV branding information</Info>\n" +
401400
" <Product>VMware vCenter Server Appliance</Product>\n" +
@@ -704,49 +703,49 @@ public class OVFHelperTest {
704703
private OVFHelper ovfHelper = new OVFHelper();
705704

706705
@Test
707-
public void testGetOVFPropertiesValidOVF() throws IOException, SAXException, ParserConfigurationException {
706+
public void testGetOVFPropertiesValidOVF() throws IOException, SAXException {
708707
List<OVFPropertyTO> props = ovfHelper.getOVFPropertiesFromXmlString(ovfFileProductSection);
709708
Assert.assertEquals(2, props.size());
710709
}
711710

712711
@Test(expected = SAXParseException.class)
713-
public void testGetOVFPropertiesInvalidOVF() throws IOException, SAXException, ParserConfigurationException {
712+
public void testGetOVFPropertiesInvalidOVF() throws IOException, SAXException {
714713
ovfHelper.getOVFPropertiesFromXmlString(ovfFileProductSection + "xxxxxxxxxxxxxxxxx");
715714
}
716715

717716
@Test
718-
public void testGetOVFDeploymentOptionsValidOVF() throws IOException, SAXException, ParserConfigurationException {
717+
public void testGetOVFDeploymentOptionsValidOVF() throws IOException, SAXException {
719718
List<OVFConfigurationTO> options = ovfHelper.getOVFDeploymentOptionsFromXmlString(ovfFileDeploymentOptionsSection);
720719
Assert.assertEquals(3, options.size());
721720
}
722721

723722
@Test
724-
public void testGetOVFVirtualHardwareSectionValidOVF() throws IOException, SAXException, ParserConfigurationException {
723+
public void testGetOVFVirtualHardwareSectionValidOVF() throws IOException, SAXException {
725724
List<OVFVirtualHardwareItemTO> items = ovfHelper.getOVFVirtualHardwareSectionFromXmlString(ovfFileVirtualHardwareSection);
726725
Assert.assertEquals(20, items.size());
727726
}
728727

729728
@Test
730-
public void testGetOVFEulaSectionValidOVF() throws IOException, SAXException, ParserConfigurationException {
729+
public void testGetOVFEulaSectionValidOVF() throws IOException, SAXException {
731730
List<OVFEulaSectionTO> eulas = ovfHelper.getOVFEulaSectionFromXmlString(eulaSections);
732731
Assert.assertEquals(2, eulas.size());
733732
}
734733

735734
@Test
736-
public void testGetOVFPropertiesWithCategories() throws IOException, SAXException, ParserConfigurationException {
735+
public void testGetOVFPropertiesWithCategories() throws IOException, SAXException {
737736
List<OVFPropertyTO> props = ovfHelper.getOVFPropertiesFromXmlString(productSectionWithCategories);
738737
Assert.assertEquals(18, props.size());
739738
}
740739

741740
@Test
742-
public void testGetOperatingSystemInfo() throws IOException, SAXException, ParserConfigurationException {
741+
public void testGetOperatingSystemInfo() throws IOException, SAXException {
743742
Pair<String, String> guestOsPair = ovfHelper.getOperatingSystemInfoFromXmlString(ovfFileVirtualHardwareSection);
744743
Assert.assertEquals("other26xLinux64Guest", guestOsPair.first());
745744
Assert.assertEquals("Other 2.6x Linux (64-bit)", guestOsPair.second());
746745
}
747746

748747
@Test
749-
public void testGetMinimumHardwareVersion() throws IOException, SAXException, ParserConfigurationException {
748+
public void testGetMinimumHardwareVersion() throws IOException, SAXException {
750749
OVFVirtualHardwareSectionTO hardwareSection = ovfHelper.getVirtualHardwareSectionFromXmlString(ovfFileVirtualHardwareSection);
751750
Assert.assertEquals("vmx-08", hardwareSection.getMinimiumHardwareVersion());
752751
}

core/src/main/java/com/cloud/storage/template/OVAProcessor.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,10 @@
2020
package com.cloud.storage.template;
2121

2222
import java.io.File;
23-
import java.io.IOException;
2423
import java.util.List;
2524
import java.util.Map;
2625

2726
import javax.naming.ConfigurationException;
28-
import javax.xml.parsers.DocumentBuilderFactory;
29-
import javax.xml.parsers.ParserConfigurationException;
3027

3128
import com.cloud.agent.api.to.OVFInformationTO;
3229
import com.cloud.agent.api.to.deployasis.OVFConfigurationTO;
@@ -50,7 +47,6 @@
5047
import com.cloud.utils.Pair;
5148
import com.cloud.utils.component.AdapterBase;
5249
import com.cloud.utils.script.Script;
53-
import org.xml.sax.SAXException;
5450

5551
/**
5652
* processes the content of an OVA for registration of a template
@@ -107,7 +103,7 @@ private FormatInfo createFormatInfo(String templatePath, String templateName, St
107103
private void validateOva(String templateFileFullPath, FormatInfo info) throws InternalErrorException {
108104
String ovfFilePath = getOVFFilePath(templateFileFullPath);
109105
OVFHelper ovfHelper = new OVFHelper();
110-
Document doc = ovfHelper.getDocumentFromFile(ovfFilePath);
106+
Document doc = ovfHelper.getOvfParser().parseOVFFile(ovfFilePath);
111107

112108
OVFInformationTO ovfInformationTO = createOvfInformationTO(ovfHelper, doc, ovfFilePath);
113109
info.ovfInformationTO = ovfInformationTO;
@@ -235,15 +231,15 @@ public long getTemplateVirtualSize(String templatePath, String templateName) thr
235231
String templateFileFullPath = templatePath.endsWith(File.separator) ? templatePath : templatePath + File.separator;
236232
templateFileFullPath += templateName.endsWith(ImageFormat.OVA.getFileExtension()) ? templateName : templateName + "." + ImageFormat.OVA.getFileExtension();
237233
String ovfFileName = getOVFFilePath(templateFileFullPath);
234+
OVFHelper ovfHelper = new OVFHelper();
238235
if (ovfFileName == null) {
239236
String msg = "Unable to locate OVF file in template package directory: " + templatePath;
240237
LOGGER.error(msg);
241238
throw new InternalErrorException(msg);
242239
}
243240
try {
244-
Document ovfDoc = null;
245-
ovfDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new File(ovfFileName));
246-
NodeList diskElements = new OVFHelper().getElementsByTagNameAndPrefix(ovfDoc, "Disk", "ovf");
241+
Document ovfDoc = ovfHelper.getOvfParser().parseOVFFile(ovfFileName);
242+
NodeList diskElements = ovfHelper.getOvfParser().getElementsFromOVFDocument(ovfDoc, "Disk");
247243
for (int i = 0; i < diskElements.getLength(); i++) {
248244
Element disk = (Element)diskElements.item(i);
249245
String diskSizeValue = disk.getAttribute("ovf:capacity");
@@ -262,7 +258,7 @@ public long getTemplateVirtualSize(String templatePath, String templateName) thr
262258
virtualSize += diskSize;
263259
}
264260
return virtualSize;
265-
} catch (InternalErrorException | IOException | NumberFormatException | ParserConfigurationException | SAXException e) {
261+
} catch (InternalErrorException | NumberFormatException e) {
266262
String msg = "getTemplateVirtualSize: Unable to parse OVF XML document " + templatePath + " to get the virtual disk " + templateName + " size due to " + e;
267263
LOGGER.error(msg);
268264
throw new InternalErrorException(msg);

core/src/main/java/org/apache/cloudstack/storage/command/UploadStatusAnswer.java

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
package org.apache.cloudstack.storage.command;
2121

2222
import com.cloud.agent.api.Answer;
23-
import com.cloud.utils.Pair;
23+
import com.cloud.agent.api.to.OVFInformationTO;
2424

2525
public class UploadStatusAnswer extends Answer {
2626
public static enum UploadStatus {
@@ -32,8 +32,7 @@ public static enum UploadStatus {
3232
private long physicalSize = 0;
3333
private String installPath = null;
3434
private int downloadPercent = 0;
35-
private Pair<String, String> guestOsInfo;
36-
private String minimumHardwareVersion;
35+
private OVFInformationTO ovfInformationTO;
3736

3837
protected UploadStatusAnswer() {
3938
}
@@ -89,19 +88,11 @@ public void setDownloadPercent(int downloadPercent) {
8988
this.downloadPercent = downloadPercent;
9089
}
9190

92-
public Pair<String, String> getGuestOsInfo() {
93-
return guestOsInfo;
91+
public OVFInformationTO getOvfInformationTO() {
92+
return ovfInformationTO;
9493
}
9594

96-
public void setGuestOsInfo(Pair<String, String> guestOsInfo) {
97-
this.guestOsInfo = guestOsInfo;
98-
}
99-
100-
public void setMinimumHardwareVersion(String minimumHardwareVersion) {
101-
this.minimumHardwareVersion = minimumHardwareVersion;
102-
}
103-
104-
public String getMinimumHardwareVersion() {
105-
return minimumHardwareVersion;
95+
public void setOvfInformationTO(OVFInformationTO ovfInformationTO) {
96+
this.ovfInformationTO = ovfInformationTO;
10697
}
10798
}

core/src/test/java/com/cloud/storage/template/OVAProcessorTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.mockito.Mock;
3131
import org.mockito.Mockito;
3232
import org.powermock.api.mockito.PowerMockito;
33+
import org.powermock.core.classloader.annotations.PowerMockIgnore;
3334
import org.powermock.core.classloader.annotations.PrepareForTest;
3435
import org.powermock.modules.junit4.PowerMockRunner;
3536

@@ -38,6 +39,7 @@
3839
import java.util.Map;
3940

4041
@RunWith(PowerMockRunner.class)
42+
@PowerMockIgnore({"javax.xml.*", "java.xml.*", "javax.management.*", "org.apache.xerces.*"})
4143
@PrepareForTest(OVAProcessor.class)
4244
public class OVAProcessorTest {
4345
OVAProcessor processor;

engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import javax.inject.Inject;
3434

3535
import com.cloud.agent.api.to.NfsTO;
36+
import com.cloud.agent.api.to.OVFInformationTO;
3637
import com.cloud.storage.DataStoreRole;
3738
import com.cloud.storage.Upload;
3839
import org.apache.cloudstack.storage.image.deployasis.DeployAsIsHelper;
@@ -207,8 +208,9 @@ protected Void createTemplateAsyncCallback(AsyncCallbackDispatcher<? extends Bas
207208
TemplateDataStoreVO tmpltStoreVO = _templateStoreDao.findByStoreTemplate(store.getId(), obj.getId());
208209
if (tmpltStoreVO != null) {
209210
if (tmpltStoreVO.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED) {
210-
if (template.isDeployAsIs()) {
211-
boolean persistDeployAsIs = deployAsIsHelper.persistTemplateDeployAsIsDetails(template.getId(), answer, tmpltStoreVO);
211+
if (template.isDeployAsIs() && answer != null) {
212+
OVFInformationTO ovfInformationTO = answer.getOvfInformationTO();
213+
boolean persistDeployAsIs = deployAsIsHelper.persistTemplateOVFInformationAndUpdateGuestOS(template.getId(), ovfInformationTO, tmpltStoreVO);
212214
if (!persistDeployAsIs) {
213215
LOGGER.info("Failed persisting deploy-as-is template details for template " + template.getName());
214216
return null;

0 commit comments

Comments
 (0)