Skip to content

Commit 3c2af55

Browse files
nvazquezyadvr
authored andcommitted
vmware: allow configuring appliances on the VM instance wizard when OVF properties are available (#3271)
Problem: In Vmware, appliances that have options that are required to be answered before deployments are configurable through vSphere vCenter user interface but it is not possible from the CloudStack user interface. Root cause: CloudStack does not handle vApp configuration options during deployments if the appliance contains configurable options. These configurations are mandatory for VM deployment from the appliance on Vmware vSphere vCenter. As shown in the image below, Vmware detects there are mandatory configurations that the administrator must set before deploy the VM from the appliance (in red on the image below): Solution: On template registration, after it is downloaded to secondary storage, the OVF file is examined and OVF properties are extracted from the file when available. OVF properties extracted from templates after being downloaded to secondary storage are stored on the new table 'template_ovf_properties'. A new optional section is added to the VM deployment wizard in the UI: If the selected template does not contain OVF properties, then the optional section is not displayed on the wizard. If the selected template contains OVF properties, then the optional new section is displayed. Each OVF property is displayed and the user must complete every property before proceeding to the next section. If any configuration property is empty, then a dialog is displayed indicating that there are empty properties which must be set before proceeding image The specific OVF properties set on deployment are stored on the 'user_vm_details' table with the prefix: 'ovfproperties-'. The VM is configured with the vApp configuration section containing the values that the user provided on the wizard.
1 parent f56c50f commit 3c2af55

File tree

42 files changed

+1521
-41
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1521
-41
lines changed

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

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.File;
2020
import java.io.IOException;
2121
import java.io.PrintWriter;
22+
import java.io.StringReader;
2223
import java.io.StringWriter;
2324
import java.util.ArrayList;
2425
import java.util.List;
@@ -38,7 +39,9 @@
3839
import org.apache.log4j.Logger;
3940
import org.w3c.dom.Document;
4041
import org.w3c.dom.Element;
42+
import org.w3c.dom.Node;
4143
import org.w3c.dom.NodeList;
44+
import org.xml.sax.InputSource;
4245
import org.xml.sax.SAXException;
4346

4447
import com.cloud.agent.api.to.DatadiskTO;
@@ -69,6 +72,92 @@ public static Long getDiskVirtualSize(Long capacity, String allocationUnits, Str
6972
}
7073
}
7174

75+
/**
76+
* Get the text value of a node's child with name "childNodeName", null if not present
77+
* Example:
78+
* <Node>
79+
* <childNodeName>Text value</childNodeName>
80+
* </Node>
81+
*/
82+
private String getChildNodeValue(Node node, String childNodeName) {
83+
if (node != null && node.hasChildNodes()) {
84+
NodeList childNodes = node.getChildNodes();
85+
for (int i = 0; i < childNodes.getLength(); i++) {
86+
Node value = childNodes.item(i);
87+
if (value != null && value.getNodeName().equals(childNodeName)) {
88+
return value.getTextContent();
89+
}
90+
}
91+
}
92+
return null;
93+
}
94+
95+
/**
96+
* Create OVFProperty class from the parsed node. Note that some fields may not be present.
97+
* The key attribute is required
98+
*/
99+
protected OVFPropertyTO createOVFPropertyFromNode(Node node) {
100+
Element property = (Element) node;
101+
String key = property.getAttribute("ovf:key");
102+
if (StringUtils.isBlank(key)) {
103+
return null;
104+
}
105+
106+
String value = property.getAttribute("ovf:value");
107+
String type = property.getAttribute("ovf:type");
108+
String qualifiers = property.getAttribute("ovf:qualifiers");
109+
String userConfigurableStr = property.getAttribute("ovf:userConfigurable");
110+
boolean userConfigurable = StringUtils.isNotBlank(userConfigurableStr) &&
111+
userConfigurableStr.equalsIgnoreCase("true");
112+
String passStr = property.getAttribute("ovf:password");
113+
boolean password = StringUtils.isNotBlank(passStr) && passStr.equalsIgnoreCase("true");
114+
String label = getChildNodeValue(node, "Label");
115+
String description = getChildNodeValue(node, "Description");
116+
return new OVFPropertyTO(key, type, value, qualifiers, userConfigurable, label, description, password);
117+
}
118+
119+
/**
120+
* Retrieve OVF properties from a parsed OVF file, with attribute 'ovf:userConfigurable' set to true
121+
*/
122+
private List<OVFPropertyTO> getConfigurableOVFPropertiesFromDocument(Document doc) {
123+
List<OVFPropertyTO> props = new ArrayList<>();
124+
NodeList properties = doc.getElementsByTagName("Property");
125+
if (properties != null) {
126+
for (int i = 0; i < properties.getLength(); i++) {
127+
Node node = properties.item(i);
128+
if (node == null) {
129+
continue;
130+
}
131+
OVFPropertyTO prop = createOVFPropertyFromNode(node);
132+
if (prop != null && prop.isUserConfigurable()) {
133+
props.add(prop);
134+
}
135+
}
136+
}
137+
return props;
138+
}
139+
140+
/**
141+
* Get properties from OVF file located on ovfFilePath
142+
*/
143+
public List<OVFPropertyTO> getOVFPropertiesFromFile(String ovfFilePath) throws ParserConfigurationException, IOException, SAXException {
144+
if (StringUtils.isBlank(ovfFilePath)) {
145+
return new ArrayList<>();
146+
}
147+
File ovfFile = new File(ovfFilePath);
148+
final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(ovfFile);
149+
return getConfigurableOVFPropertiesFromDocument(doc);
150+
}
151+
152+
/**
153+
* Get properties from OVF XML string
154+
*/
155+
protected List<OVFPropertyTO> getOVFPropertiesXmlString(final String ovfFilePath) throws ParserConfigurationException, IOException, SAXException {
156+
InputSource is = new InputSource(new StringReader(ovfFilePath));
157+
final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
158+
return getConfigurableOVFPropertiesFromDocument(doc);
159+
}
160+
72161
public List<DatadiskTO> getOVFVolumeInfo(final String ovfFilePath) {
73162
if (StringUtils.isBlank(ovfFilePath)) {
74163
return new ArrayList<DatadiskTO>();
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
20+
package com.cloud.agent.api.storage;
21+
22+
public interface OVFProperty {
23+
24+
Long getTemplateId();
25+
String getKey();
26+
String getType();
27+
String getValue();
28+
String getQualifiers();
29+
Boolean isUserConfigurable();
30+
String getLabel();
31+
String getDescription();
32+
Boolean isPassword();
33+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
20+
package com.cloud.agent.api.storage;
21+
22+
import com.cloud.agent.api.LogLevel;
23+
24+
/**
25+
* Used to represent travel objects like:
26+
* <Property ovf:key="RouteDefault" ovf:type="string" ovf:qualifiers="ValueMap{&quot;Default Route&quot;,&quot;Remote HTTP and SSH Client Routes&quot;}" ovf:value="Default Route" ovf:userConfigurable="true">
27+
* <Label>Select Route Type</Label>
28+
* <Description>Select the route/gateway type.
29+
* Choose "Default Route" to route all traffic through the Management gateway. Use this option when enabling Smart Licensing registration at initial deployment.
30+
* Choose "Remote HTTP and SSH Client Routes" to route only traffic destined for the management client(s), when they are on remote networks.</Description>
31+
* </Property>
32+
*/
33+
public class OVFPropertyTO implements OVFProperty {
34+
35+
private String key;
36+
private String type;
37+
@LogLevel(LogLevel.Log4jLevel.Off)
38+
private String value;
39+
private String qualifiers;
40+
private Boolean userConfigurable;
41+
private String label;
42+
private String description;
43+
private Boolean password;
44+
45+
public OVFPropertyTO() {
46+
}
47+
48+
public OVFPropertyTO(String key, String value, boolean password) {
49+
this.key = key;
50+
this.value = value;
51+
this.password = password;
52+
}
53+
54+
public OVFPropertyTO(String key, String type, String value, String qualifiers, boolean userConfigurable,
55+
String label, String description, boolean password) {
56+
this.key = key;
57+
this.type = type;
58+
this.value = value;
59+
this.qualifiers = qualifiers;
60+
this.userConfigurable = userConfigurable;
61+
this.label = label;
62+
this.description = description;
63+
this.password = password;
64+
}
65+
66+
@Override
67+
public Long getTemplateId() {
68+
return null;
69+
}
70+
71+
public String getKey() {
72+
return key;
73+
}
74+
75+
public void setKey(String key) {
76+
this.key = key;
77+
}
78+
79+
public String getType() {
80+
return type;
81+
}
82+
83+
public void setType(String type) {
84+
this.type = type;
85+
}
86+
87+
public String getValue() {
88+
return value;
89+
}
90+
91+
public void setValue(String value) {
92+
this.value = value;
93+
}
94+
95+
public String getQualifiers() {
96+
return qualifiers;
97+
}
98+
99+
public void setQualifiers(String qualifiers) {
100+
this.qualifiers = qualifiers;
101+
}
102+
103+
public Boolean isUserConfigurable() {
104+
return userConfigurable;
105+
}
106+
107+
public void setUserConfigurable(Boolean userConfigurable) {
108+
this.userConfigurable = userConfigurable;
109+
}
110+
111+
public String getLabel() {
112+
return label;
113+
}
114+
115+
public void setLabel(String label) {
116+
this.label = label;
117+
}
118+
119+
public String getDescription() {
120+
return description;
121+
}
122+
123+
public void setDescription(String description) {
124+
this.description = description;
125+
}
126+
127+
public Boolean isPassword() {
128+
return password;
129+
}
130+
131+
public void setPassword(Boolean password) {
132+
this.password = password;
133+
}
134+
}

api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
import java.util.Map;
2121
import java.util.HashMap;
2222

23+
import com.cloud.agent.api.LogLevel;
24+
import com.cloud.agent.api.storage.OVFPropertyTO;
2325
import com.cloud.template.VirtualMachineTemplate.BootloaderType;
26+
import com.cloud.utils.Pair;
2427
import com.cloud.vm.VirtualMachine;
2528
import com.cloud.vm.VirtualMachine.Type;
2629

@@ -75,6 +78,8 @@ public class VirtualMachineTO {
7578

7679
Map<String, String> guestOsDetails = new HashMap<String, String>();
7780
Map<String, String> extraConfig = new HashMap<>();
81+
@LogLevel(LogLevel.Log4jLevel.Off)
82+
Pair<String, List<OVFPropertyTO>> ovfProperties;
7883

7984
public VirtualMachineTO(long id, String instanceName, VirtualMachine.Type type, int cpus, Integer speed, long minRam, long maxRam, BootloaderType bootloader,
8085
String os, boolean enableHA, boolean limitCpuUse, String vncPassword) {
@@ -367,4 +372,12 @@ public void addExtraConfig(String key, String value) {
367372
public Map<String, String> getExtraConfig() {
368373
return extraConfig;
369374
}
375+
376+
public Pair<String, List<OVFPropertyTO>> getOvfProperties() {
377+
return ovfProperties;
378+
}
379+
380+
public void setOvfProperties(Pair<String, List<OVFPropertyTO>> ovfProperties) {
381+
this.ovfProperties = ovfProperties;
382+
}
370383
}

api/src/main/java/com/cloud/vm/UserVmService.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,8 @@ UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering s
217217
Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod,
218218
String userData, String sshKeyPair, Map<Long, IpAddresses> requestedIps, IpAddresses defaultIp, Boolean displayVm, String keyboard,
219219
List<Long> affinityGroupIdList, Map<String, String> customParameter, String customId, Map<String, Map<Integer, String>> dhcpOptionMap,
220-
Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap) throws InsufficientCapacityException,
220+
Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap,
221+
Map<String, String> userVmOVFProperties) throws InsufficientCapacityException,
221222
ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException;
222223

223224
/**
@@ -298,7 +299,8 @@ UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOfferin
298299
List<Long> securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor,
299300
HTTPMethod httpmethod, String userData, String sshKeyPair, Map<Long, IpAddresses> requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard,
300301
List<Long> affinityGroupIdList, Map<String, String> customParameters, String customId, Map<String, Map<Integer, String>> dhcpOptionMap,
301-
Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap) throws InsufficientCapacityException,
302+
Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap,
303+
Map<String, String> userVmOVFProperties) throws InsufficientCapacityException,
302304
ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException;
303305

304306
/**
@@ -376,7 +378,8 @@ UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOfferin
376378
UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List<Long> networkIdList, Account owner,
377379
String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData,
378380
String sshKeyPair, Map<Long, IpAddresses> requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List<Long> affinityGroupIdList,
379-
Map<String, String> customParameters, String customId, Map<String, Map<Integer, String>> dhcpOptionMap, Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap)
381+
Map<String, String> customParameters, String customId, Map<String, Map<Integer, String>> dhcpOptionMap, Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap,
382+
Map<String, String> templateOvfPropertiesMap)
380383

381384
throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException;
382385

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ public class ApiConstants {
199199
public static final String ISO_GUEST_OS_NONE = "None";
200200
public static final String JOB_ID = "jobid";
201201
public static final String JOB_STATUS = "jobstatus";
202+
public static final String LABEL = "label";
202203
public static final String LASTNAME = "lastname";
203204
public static final String LEVEL = "level";
204205
public static final String LENGTH = "length";
@@ -233,6 +234,7 @@ public class ApiConstants {
233234
public static final String OS_NAME_FOR_HYPERVISOR = "osnameforhypervisor";
234235
public static final String OUTOFBANDMANAGEMENT_POWERSTATE = "outofbandmanagementpowerstate";
235236
public static final String OUTOFBANDMANAGEMENT_ENABLED = "outofbandmanagementenabled";
237+
public static final String OVF_PROPERTIES = "ovfproperties";
236238
public static final String PARAMS = "params";
237239
public static final String PARENT_ID = "parentid";
238240
public static final String PARENT_DOMAIN_ID = "parentdomainid";
@@ -277,6 +279,7 @@ public class ApiConstants {
277279
public static final String RESPONSE = "response";
278280
public static final String REVERTABLE = "revertable";
279281
public static final String REGISTERED = "registered";
282+
public static final String QUALIFIERS = "qualifiers";
280283
public static final String QUERY_FILTER = "queryfilter";
281284
public static final String SCHEDULE = "schedule";
282285
public static final String SCOPE = "scope";
@@ -334,6 +337,7 @@ public class ApiConstants {
334337
public static final String USER_ID = "userid";
335338
public static final String USE_SSL = "ssl";
336339
public static final String USERNAME = "username";
340+
public static final String USER_CONFIGURABLE = "userconfigurable";
337341
public static final String USER_SECURITY_GROUP_LIST = "usersecuritygrouplist";
338342
public static final String USE_VIRTUAL_NETWORK = "usevirtualnetwork";
339343
public static final String Update_IN_SEQUENCE = "updateinsequence";

0 commit comments

Comments
 (0)