This is an automated email from the ASF dual-hosted git repository.
rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/master by this push:
new 3c2af55 vmware: allow configuring appliances on the VM instance
wizard when OVF properties are available (#3271)
3c2af55 is described below
commit 3c2af55d81dbfaf17a3bf0b6247ecd830df12996
Author: Nicolas Vazquez <[email protected]>
AuthorDate: Fri Aug 9 07:44:46 2019 -0300
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.
---
.../com/cloud/agent/api/storage/OVFHelper.java | 89 +++++++++++
.../com/cloud/agent/api/storage/OVFProperty.java | 33 ++++
.../com/cloud/agent/api/storage/OVFPropertyTO.java | 134 +++++++++++++++++
.../com/cloud/agent/api/to/VirtualMachineTO.java | 13 ++
api/src/main/java/com/cloud/vm/UserVmService.java | 9 +-
.../org/apache/cloudstack/api/ApiConstants.java | 4 +
.../user/template/ListTemplateOVFProperties.java | 68 +++++++++
.../api/command/user/vm/DeployVMCmd.java | 19 +++
.../api/response/TemplateOVFPropertyResponse.java | 124 +++++++++++++++
.../cloudstack/api/response/TemplateResponse.java | 2 +-
.../org/apache/cloudstack/query/QueryService.java | 4 +
.../com/cloud/agent/api/storage/OVFHelperTest.java | 55 +++++++
.../cloud/agent/api/storage/DownloadAnswer.java | 11 ++
.../com/cloud/storage/template/OVAProcessor.java | 7 +
.../java/com/cloud/storage/template/Processor.java | 3 +
.../com/cloud/storage/TemplateOVFPropertyVO.java | 167 +++++++++++++++++++++
.../storage/dao/TemplateOVFPropertiesDao.java | 31 ++++
.../storage/dao/TemplateOVFPropertiesDaoImpl.java | 78 ++++++++++
.../spring-engine-schema-core-daos-context.xml | 1 +
.../resources/META-INF/db/schema-41200to41300.sql | 16 ++
.../storage/image/BaseImageStoreDriverImpl.java | 37 +++++
.../java/com/cloud/hypervisor/guru/VMwareGuru.java | 70 +++++++++
.../hypervisor/vmware/resource/VmwareResource.java | 111 ++++++++++++++
.../storage/resource/VmwareStorageProcessor.java | 11 +-
.../java/com/cloud/api/query/QueryManagerImpl.java | 31 ++++
.../com/cloud/api/query/dao/UserVmJoinDaoImpl.java | 7 +-
.../com/cloud/network/as/AutoScaleManagerImpl.java | 6 +-
.../com/cloud/server/ManagementServerImpl.java | 2 +
.../src/main/java/com/cloud/vm/UserVmManager.java | 10 +-
.../main/java/com/cloud/vm/UserVmManagerImpl.java | 78 ++++++++--
.../storage/template/DownloadManagerImpl.java | 17 +++
ui/css/cloudstack3.css | 5 +
ui/index.html | 9 ++
ui/l10n/en.js | 2 +
ui/scripts/instanceWizard.js | 54 ++++++-
ui/scripts/templates.js | 67 ++++++++-
ui/scripts/ui-custom/instanceWizard.js | 150 ++++++++++++++++++
.../com/cloud/hypervisor/vmware/mo/ClusterMO.java | 2 +-
.../com/cloud/hypervisor/vmware/mo/HostMO.java | 2 +-
.../hypervisor/vmware/mo/HypervisorHostHelper.java | 5 +-
.../hypervisor/vmware/mo/VirtualMachineMO.java | 14 +-
.../hypervisor/vmware/mo/VmwareHypervisorHost.java | 4 +-
42 files changed, 1521 insertions(+), 41 deletions(-)
diff --git a/api/src/main/java/com/cloud/agent/api/storage/OVFHelper.java
b/api/src/main/java/com/cloud/agent/api/storage/OVFHelper.java
index 389800b..15d6358 100644
--- a/api/src/main/java/com/cloud/agent/api/storage/OVFHelper.java
+++ b/api/src/main/java/com/cloud/agent/api/storage/OVFHelper.java
@@ -19,6 +19,7 @@ package com.cloud.agent.api.storage;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
+import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
@@ -38,7 +39,9 @@ import org.apache.commons.lang.math.NumberUtils;
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.InputSource;
import org.xml.sax.SAXException;
import com.cloud.agent.api.to.DatadiskTO;
@@ -69,6 +72,92 @@ public class OVFHelper {
}
}
+ /**
+ * Get the text value of a node's child with name "childNodeName", null if
not present
+ * Example:
+ * <Node>
+ * <childNodeName>Text value</childNodeName>
+ * </Node>
+ */
+ private String getChildNodeValue(Node node, String childNodeName) {
+ if (node != null && node.hasChildNodes()) {
+ NodeList childNodes = node.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node value = childNodes.item(i);
+ if (value != null &&
value.getNodeName().equals(childNodeName)) {
+ return value.getTextContent();
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Create OVFProperty class from the parsed node. Note that some fields
may not be present.
+ * The key attribute is required
+ */
+ protected OVFPropertyTO createOVFPropertyFromNode(Node node) {
+ Element property = (Element) node;
+ String key = property.getAttribute("ovf:key");
+ if (StringUtils.isBlank(key)) {
+ return null;
+ }
+
+ String value = property.getAttribute("ovf:value");
+ String type = property.getAttribute("ovf:type");
+ String qualifiers = property.getAttribute("ovf:qualifiers");
+ String userConfigurableStr =
property.getAttribute("ovf:userConfigurable");
+ boolean userConfigurable = StringUtils.isNotBlank(userConfigurableStr)
&&
+ userConfigurableStr.equalsIgnoreCase("true");
+ String passStr = property.getAttribute("ovf:password");
+ boolean password = StringUtils.isNotBlank(passStr) &&
passStr.equalsIgnoreCase("true");
+ String label = getChildNodeValue(node, "Label");
+ String description = getChildNodeValue(node, "Description");
+ return new OVFPropertyTO(key, type, value, qualifiers,
userConfigurable, label, description, password);
+ }
+
+ /**
+ * Retrieve OVF properties from a parsed OVF file, with attribute
'ovf:userConfigurable' set to true
+ */
+ private List<OVFPropertyTO>
getConfigurableOVFPropertiesFromDocument(Document doc) {
+ List<OVFPropertyTO> props = new ArrayList<>();
+ NodeList properties = doc.getElementsByTagName("Property");
+ if (properties != null) {
+ for (int i = 0; i < properties.getLength(); i++) {
+ Node node = properties.item(i);
+ if (node == null) {
+ continue;
+ }
+ OVFPropertyTO prop = createOVFPropertyFromNode(node);
+ if (prop != null && prop.isUserConfigurable()) {
+ props.add(prop);
+ }
+ }
+ }
+ return props;
+ }
+
+ /**
+ * Get properties from OVF file located on ovfFilePath
+ */
+ public List<OVFPropertyTO> getOVFPropertiesFromFile(String ovfFilePath)
throws ParserConfigurationException, IOException, SAXException {
+ if (StringUtils.isBlank(ovfFilePath)) {
+ return new ArrayList<>();
+ }
+ File ovfFile = new File(ovfFilePath);
+ final Document doc =
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(ovfFile);
+ return getConfigurableOVFPropertiesFromDocument(doc);
+ }
+
+ /**
+ * Get properties from OVF XML string
+ */
+ protected List<OVFPropertyTO> getOVFPropertiesXmlString(final String
ovfFilePath) throws ParserConfigurationException, IOException, SAXException {
+ InputSource is = new InputSource(new StringReader(ovfFilePath));
+ final Document doc =
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
+ return getConfigurableOVFPropertiesFromDocument(doc);
+ }
+
public List<DatadiskTO> getOVFVolumeInfo(final String ovfFilePath) {
if (StringUtils.isBlank(ovfFilePath)) {
return new ArrayList<DatadiskTO>();
diff --git a/api/src/main/java/com/cloud/agent/api/storage/OVFProperty.java
b/api/src/main/java/com/cloud/agent/api/storage/OVFProperty.java
new file mode 100644
index 0000000..ac9ae77
--- /dev/null
+++ b/api/src/main/java/com/cloud/agent/api/storage/OVFProperty.java
@@ -0,0 +1,33 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.cloud.agent.api.storage;
+
+public interface OVFProperty {
+
+ Long getTemplateId();
+ String getKey();
+ String getType();
+ String getValue();
+ String getQualifiers();
+ Boolean isUserConfigurable();
+ String getLabel();
+ String getDescription();
+ Boolean isPassword();
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/cloud/agent/api/storage/OVFPropertyTO.java
b/api/src/main/java/com/cloud/agent/api/storage/OVFPropertyTO.java
new file mode 100644
index 0000000..abf743a
--- /dev/null
+++ b/api/src/main/java/com/cloud/agent/api/storage/OVFPropertyTO.java
@@ -0,0 +1,134 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.cloud.agent.api.storage;
+
+import com.cloud.agent.api.LogLevel;
+
+/**
+ * Used to represent travel objects like:
+ * <Property ovf:key="RouteDefault" ovf:type="string"
ovf:qualifiers="ValueMap{"Default Route","Remote HTTP and SSH
Client Routes"}" ovf:value="Default Route" ovf:userConfigurable="true">
+ * <Label>Select Route Type</Label>
+ * <Description>Select the route/gateway type.
+ * Choose "Default Route" to route all traffic through the Management gateway.
Use this option when enabling Smart Licensing registration at initial
deployment.
+ * Choose "Remote HTTP and SSH Client Routes" to route only traffic destined
for the management client(s), when they are on remote networks.</Description>
+ * </Property>
+ */
+public class OVFPropertyTO implements OVFProperty {
+
+ private String key;
+ private String type;
+ @LogLevel(LogLevel.Log4jLevel.Off)
+ private String value;
+ private String qualifiers;
+ private Boolean userConfigurable;
+ private String label;
+ private String description;
+ private Boolean password;
+
+ public OVFPropertyTO() {
+ }
+
+ public OVFPropertyTO(String key, String value, boolean password) {
+ this.key = key;
+ this.value = value;
+ this.password = password;
+ }
+
+ public OVFPropertyTO(String key, String type, String value, String
qualifiers, boolean userConfigurable,
+ String label, String description, boolean password) {
+ this.key = key;
+ this.type = type;
+ this.value = value;
+ this.qualifiers = qualifiers;
+ this.userConfigurable = userConfigurable;
+ this.label = label;
+ this.description = description;
+ this.password = password;
+ }
+
+ @Override
+ public Long getTemplateId() {
+ return null;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getQualifiers() {
+ return qualifiers;
+ }
+
+ public void setQualifiers(String qualifiers) {
+ this.qualifiers = qualifiers;
+ }
+
+ public Boolean isUserConfigurable() {
+ return userConfigurable;
+ }
+
+ public void setUserConfigurable(Boolean userConfigurable) {
+ this.userConfigurable = userConfigurable;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Boolean isPassword() {
+ return password;
+ }
+
+ public void setPassword(Boolean password) {
+ this.password = password;
+ }
+}
diff --git a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java
b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java
index e5623ac..d25ffe3 100644
--- a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java
+++ b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java
@@ -20,7 +20,10 @@ import java.util.List;
import java.util.Map;
import java.util.HashMap;
+import com.cloud.agent.api.LogLevel;
+import com.cloud.agent.api.storage.OVFPropertyTO;
import com.cloud.template.VirtualMachineTemplate.BootloaderType;
+import com.cloud.utils.Pair;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachine.Type;
@@ -75,6 +78,8 @@ public class VirtualMachineTO {
Map<String, String> guestOsDetails = new HashMap<String, String>();
Map<String, String> extraConfig = new HashMap<>();
+ @LogLevel(LogLevel.Log4jLevel.Off)
+ Pair<String, List<OVFPropertyTO>> ovfProperties;
public VirtualMachineTO(long id, String instanceName, VirtualMachine.Type
type, int cpus, Integer speed, long minRam, long maxRam, BootloaderType
bootloader,
String os, boolean enableHA, boolean limitCpuUse, String
vncPassword) {
@@ -367,4 +372,12 @@ public class VirtualMachineTO {
public Map<String, String> getExtraConfig() {
return extraConfig;
}
+
+ public Pair<String, List<OVFPropertyTO>> getOvfProperties() {
+ return ovfProperties;
+ }
+
+ public void setOvfProperties(Pair<String, List<OVFPropertyTO>>
ovfProperties) {
+ this.ovfProperties = ovfProperties;
+ }
}
diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java
b/api/src/main/java/com/cloud/vm/UserVmService.java
index 74090ec..99eb827 100644
--- a/api/src/main/java/com/cloud/vm/UserVmService.java
+++ b/api/src/main/java/com/cloud/vm/UserVmService.java
@@ -217,7 +217,8 @@ public interface UserVmService {
Account owner, String hostName, String displayName, Long
diskOfferingId, Long diskSize, String group, HypervisorType hypervisor,
HTTPMethod httpmethod,
String userData, String sshKeyPair, Map<Long, IpAddresses>
requestedIps, IpAddresses defaultIp, Boolean displayVm, String keyboard,
List<Long> affinityGroupIdList, Map<String, String> customParameter,
String customId, Map<String, Map<Integer, String>> dhcpOptionMap,
- Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap) throws
InsufficientCapacityException,
+ Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap,
+ Map<String, String> userVmOVFProperties) throws
InsufficientCapacityException,
ConcurrentOperationException, ResourceUnavailableException,
StorageUnavailableException, ResourceAllocationException;
/**
@@ -298,7 +299,8 @@ public interface UserVmService {
List<Long> securityGroupIdList, Account owner, String hostName, String
displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType
hypervisor,
HTTPMethod httpmethod, String userData, String sshKeyPair, Map<Long,
IpAddresses> requestedIps, IpAddresses defaultIps, Boolean displayVm, String
keyboard,
List<Long> affinityGroupIdList, Map<String, String> customParameters,
String customId, Map<String, Map<Integer, String>> dhcpOptionMap,
- Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap) throws
InsufficientCapacityException,
+ Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap,
+ Map<String, String> userVmOVFProperties) throws
InsufficientCapacityException,
ConcurrentOperationException, ResourceUnavailableException,
StorageUnavailableException, ResourceAllocationException;
/**
@@ -376,7 +378,8 @@ public interface UserVmService {
UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering
serviceOffering, VirtualMachineTemplate template, List<Long> networkIdList,
Account owner,
String hostName, String displayName, Long diskOfferingId, Long
diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod,
String userData,
String sshKeyPair, Map<Long, IpAddresses> requestedIps, IpAddresses
defaultIps, Boolean displayVm, String keyboard, List<Long> affinityGroupIdList,
- Map<String, String> customParameters, String customId, Map<String,
Map<Integer, String>> dhcpOptionMap, Map<Long, DiskOffering>
dataDiskTemplateToDiskOfferingMap)
+ Map<String, String> customParameters, String customId, Map<String,
Map<Integer, String>> dhcpOptionMap, Map<Long, DiskOffering>
dataDiskTemplateToDiskOfferingMap,
+ Map<String, String> templateOvfPropertiesMap)
throws InsufficientCapacityException, ConcurrentOperationException,
ResourceUnavailableException, StorageUnavailableException,
ResourceAllocationException;
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index d9e2e7f..fb44a8a 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -199,6 +199,7 @@ public class ApiConstants {
public static final String ISO_GUEST_OS_NONE = "None";
public static final String JOB_ID = "jobid";
public static final String JOB_STATUS = "jobstatus";
+ public static final String LABEL = "label";
public static final String LASTNAME = "lastname";
public static final String LEVEL = "level";
public static final String LENGTH = "length";
@@ -233,6 +234,7 @@ public class ApiConstants {
public static final String OS_NAME_FOR_HYPERVISOR = "osnameforhypervisor";
public static final String OUTOFBANDMANAGEMENT_POWERSTATE =
"outofbandmanagementpowerstate";
public static final String OUTOFBANDMANAGEMENT_ENABLED =
"outofbandmanagementenabled";
+ public static final String OVF_PROPERTIES = "ovfproperties";
public static final String PARAMS = "params";
public static final String PARENT_ID = "parentid";
public static final String PARENT_DOMAIN_ID = "parentdomainid";
@@ -277,6 +279,7 @@ public class ApiConstants {
public static final String RESPONSE = "response";
public static final String REVERTABLE = "revertable";
public static final String REGISTERED = "registered";
+ public static final String QUALIFIERS = "qualifiers";
public static final String QUERY_FILTER = "queryfilter";
public static final String SCHEDULE = "schedule";
public static final String SCOPE = "scope";
@@ -334,6 +337,7 @@ public class ApiConstants {
public static final String USER_ID = "userid";
public static final String USE_SSL = "ssl";
public static final String USERNAME = "username";
+ public static final String USER_CONFIGURABLE = "userconfigurable";
public static final String USER_SECURITY_GROUP_LIST =
"usersecuritygrouplist";
public static final String USE_VIRTUAL_NETWORK = "usevirtualnetwork";
public static final String Update_IN_SEQUENCE = "updateinsequence";
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplateOVFProperties.java
b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplateOVFProperties.java
new file mode 100644
index 0000000..2a620c9
--- /dev/null
+++
b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplateOVFProperties.java
@@ -0,0 +1,68 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 org.apache.cloudstack.api.command.user.template;
+
+import com.cloud.exception.ConcurrentOperationException;
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.NetworkRuleConflictException;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.exception.ResourceUnavailableException;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.BaseListCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.TemplateOVFPropertyResponse;
+import org.apache.cloudstack.api.response.TemplateResponse;
+import org.apache.cloudstack.context.CallContext;
+
+@APICommand(name = ListTemplateOVFProperties.APINAME,
+ description = "List template OVF properties if available.",
+ responseObject = TemplateOVFPropertyResponse.class,
+ authorized = {RoleType.Admin, RoleType.DomainAdmin,
RoleType.ResourceAdmin, RoleType.User})
+public class ListTemplateOVFProperties extends BaseListCmd {
+
+ public static final String APINAME = "listTemplateOvfProperties";
+
+ @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType =
TemplateResponse.class,
+ description = "the template ID", required = true)
+ private Long templateId;
+
+ public Long getTemplateId() {
+ return templateId;
+ }
+
+ @Override
+ public void execute() throws ResourceUnavailableException,
InsufficientCapacityException, ServerApiException,
ConcurrentOperationException, ResourceAllocationException,
NetworkRuleConflictException {
+ ListResponse<TemplateOVFPropertyResponse> response =
_queryService.listTemplateOVFProperties(this);
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ }
+
+ @Override
+ public String getCommandName() {
+ return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return CallContext.current().getCallingAccount().getId();
+ }
+}
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java
index 06acc32..ec1dc81 100644
---
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java
+++
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java
@@ -24,6 +24,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import com.cloud.agent.api.LogLevel;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
import org.apache.cloudstack.api.ACL;
@@ -205,6 +206,11 @@ public class DeployVMCmd extends
BaseAsyncCreateCustomIdCmd implements SecurityG
@Parameter(name = ApiConstants.COPY_IMAGE_TAGS, type =
CommandType.BOOLEAN, since = "4.13", description = "if true the image tags (if
any) will be copied to the VM, default value is false")
private Boolean copyImageTags;
+ @Parameter(name = ApiConstants.OVF_PROPERTIES, type = CommandType.MAP,
since = "4.13",
+ description = "used to specify the OVF properties.")
+ @LogLevel(LogLevel.Log4jLevel.Off)
+ private Map vmOvfProperties;
+
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@@ -253,6 +259,19 @@ public class DeployVMCmd extends
BaseAsyncCreateCustomIdCmd implements SecurityG
return customparameterMap;
}
+ public Map<String, String> getVmOVFProperties() {
+ Map<String, String> map = new HashMap<>();
+ if (MapUtils.isNotEmpty(vmOvfProperties)) {
+ Collection parameterCollection = vmOvfProperties.values();
+ Iterator iterator = parameterCollection.iterator();
+ while (iterator.hasNext()) {
+ HashMap<String, String> entry = (HashMap<String,
String>)iterator.next();
+ map.put(entry.get("key"), entry.get("value"));
+ }
+ }
+ return map;
+ }
+
public String getGroup() {
return group;
}
diff --git
a/api/src/main/java/org/apache/cloudstack/api/response/TemplateOVFPropertyResponse.java
b/api/src/main/java/org/apache/cloudstack/api/response/TemplateOVFPropertyResponse.java
new file mode 100644
index 0000000..83455a3
--- /dev/null
+++
b/api/src/main/java/org/apache/cloudstack/api/response/TemplateOVFPropertyResponse.java
@@ -0,0 +1,124 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 org.apache.cloudstack.api.response;
+
+import com.cloud.agent.api.storage.OVFProperty;
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseResponse;
+import org.apache.cloudstack.api.EntityReference;
+
+@EntityReference(value = OVFProperty.class)
+public class TemplateOVFPropertyResponse extends BaseResponse {
+
+ @SerializedName(ApiConstants.KEY)
+ @Param(description = "the ovf property key")
+ private String key;
+
+ @SerializedName(ApiConstants.TYPE)
+ @Param(description = "the ovf property type")
+ private String type;
+
+ @SerializedName(ApiConstants.VALUE)
+ @Param(description = "the ovf property value")
+ private String value;
+
+ @SerializedName(ApiConstants.PASSWORD)
+ @Param(description = "is the ovf property a password")
+ private Boolean password;
+
+ @SerializedName(ApiConstants.QUALIFIERS)
+ @Param(description = "the ovf property qualifiers")
+ private String qualifiers;
+
+ @SerializedName(ApiConstants.USER_CONFIGURABLE)
+ @Param(description = "is the ovf property user configurable")
+ private Boolean userConfigurable;
+
+ @SerializedName(ApiConstants.LABEL)
+ @Param(description = "the ovf property label")
+ private String label;
+
+ @SerializedName(ApiConstants.DESCRIPTION)
+ @Param(description = "the ovf property label")
+ private String description;
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getQualifiers() {
+ return qualifiers;
+ }
+
+ public void setQualifiers(String qualifiers) {
+ this.qualifiers = qualifiers;
+ }
+
+ public Boolean getUserConfigurable() {
+ return userConfigurable;
+ }
+
+ public void setUserConfigurable(Boolean userConfigurable) {
+ this.userConfigurable = userConfigurable;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Boolean getPassword() {
+ return password;
+ }
+
+ public void setPassword(Boolean password) {
+ this.password = password;
+ }
+}
diff --git
a/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java
b/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java
index a83fe42..81fc2f3 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java
@@ -202,7 +202,7 @@ public class TemplateResponse extends
BaseResponseWithTagInformation implements
private Boolean requiresHvm;
public TemplateResponse() {
- tags = new LinkedHashSet<ResourceTagResponse>();
+ tags = new LinkedHashSet<>();
}
@Override
diff --git a/api/src/main/java/org/apache/cloudstack/query/QueryService.java
b/api/src/main/java/org/apache/cloudstack/query/QueryService.java
index e5ef658..68dc31f 100644
--- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java
+++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java
@@ -43,6 +43,7 @@ import
org.apache.cloudstack.api.command.user.project.ListProjectsCmd;
import org.apache.cloudstack.api.command.user.resource.ListDetailOptionsCmd;
import
org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCmd;
import org.apache.cloudstack.api.command.user.tag.ListTagsCmd;
+import
org.apache.cloudstack.api.command.user.template.ListTemplateOVFProperties;
import org.apache.cloudstack.api.command.user.template.ListTemplatesCmd;
import org.apache.cloudstack.api.command.user.vm.ListVMsCmd;
import org.apache.cloudstack.api.command.user.vmgroup.ListVMGroupsCmd;
@@ -71,6 +72,7 @@ import
org.apache.cloudstack.api.response.SecurityGroupResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.cloudstack.api.response.StorageTagResponse;
+import org.apache.cloudstack.api.response.TemplateOVFPropertyResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.UserResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
@@ -166,4 +168,6 @@ public interface QueryService {
ListResponse<HostTagResponse> searchForHostTags(ListHostTagsCmd cmd);
ListResponse<ManagementServerResponse> listManagementServers(ListMgmtsCmd
cmd);
+
+ ListResponse<TemplateOVFPropertyResponse>
listTemplateOVFProperties(ListTemplateOVFProperties cmd);
}
diff --git a/api/src/test/java/com/cloud/agent/api/storage/OVFHelperTest.java
b/api/src/test/java/com/cloud/agent/api/storage/OVFHelperTest.java
new file mode 100644
index 0000000..8aa9852
--- /dev/null
+++ b/api/src/test/java/com/cloud/agent/api/storage/OVFHelperTest.java
@@ -0,0 +1,55 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.cloud.agent.api.storage;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.IOException;
+import java.util.List;
+
+public class OVFHelperTest {
+
+ private String ovfFileProductSection =
+ "<ProductSection>" +
+ "<Info>VM Arguments</Info>" +
+ "<Property ovf:key=\"va-ssh-public-key\" ovf:type=\"string\"
ovf:userConfigurable=\"true\" ovf:value=\"\">" +
+ "<Label>Set the SSH public key allowed to access the
appliance</Label>" +
+ "<Description>This will enable the SSHD service and configure
the specified public key</Description>" +
+ "</Property>" +
+ "<Property ovf:key=\"user-data\" ovf:type=\"string\"
ovf:userConfigurable=\"true\" ovf:value=\"\">" +
+ "<Label>User data to be made available inside the
instance</Label>" +
+ "<Description>This allows to pass any text to the appliance.
The value should be encoded in base64</Description>" +
+ "</Property>" +
+ "</ProductSection>";
+
+ private OVFHelper ovfHelper = new OVFHelper();
+
+ @Test
+ public void testGetOVFPropertiesValidOVF() throws IOException,
SAXException, ParserConfigurationException {
+ List<OVFPropertyTO> props =
ovfHelper.getOVFPropertiesXmlString(ovfFileProductSection);
+ Assert.assertEquals(2, props.size());
+ }
+
+ @Test(expected = SAXParseException.class)
+ public void testGetOVFPropertiesInvalidOVF() throws IOException,
SAXException, ParserConfigurationException {
+ ovfHelper.getOVFPropertiesXmlString(ovfFileProductSection +
"xxxxxxxxxxxxxxxxx");
+ }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/storage/DownloadAnswer.java
b/core/src/main/java/com/cloud/agent/api/storage/DownloadAnswer.java
index fdc47b4..9859c3f 100644
--- a/core/src/main/java/com/cloud/agent/api/storage/DownloadAnswer.java
+++ b/core/src/main/java/com/cloud/agent/api/storage/DownloadAnswer.java
@@ -20,9 +20,11 @@
package com.cloud.agent.api.storage;
import java.io.File;
+import java.util.List;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
+import com.cloud.agent.api.LogLevel;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
@@ -36,6 +38,8 @@ public class DownloadAnswer extends Answer {
private long templateSize = 0L;
private long templatePhySicalSize = 0L;
private String checkSum;
+ @LogLevel(LogLevel.Log4jLevel.Off)
+ private List<OVFPropertyTO> ovfProperties;
public String getCheckSum() {
return checkSum;
@@ -146,4 +150,11 @@ public class DownloadAnswer extends Answer {
return templatePhySicalSize;
}
+ public List<OVFPropertyTO> getOvfProperties() {
+ return ovfProperties;
+ }
+
+ public void setOvfProperties(List<OVFPropertyTO> ovfProperties) {
+ this.ovfProperties = ovfProperties;
+ }
}
diff --git a/core/src/main/java/com/cloud/storage/template/OVAProcessor.java
b/core/src/main/java/com/cloud/storage/template/OVAProcessor.java
index f29efb4..d771c67 100644
--- a/core/src/main/java/com/cloud/storage/template/OVAProcessor.java
+++ b/core/src/main/java/com/cloud/storage/template/OVAProcessor.java
@@ -26,6 +26,8 @@ import java.util.Map;
import javax.naming.ConfigurationException;
import javax.xml.parsers.DocumentBuilderFactory;
+import com.cloud.agent.api.storage.OVFPropertyTO;
+import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -106,6 +108,11 @@ public class OVAProcessor extends AdapterBase implements
Processor {
try {
OVFHelper ovfHelper = new OVFHelper();
List<DatadiskTO> disks = ovfHelper.getOVFVolumeInfo(ovfFile);
+ List<OVFPropertyTO> ovfProperties =
ovfHelper.getOVFPropertiesFromFile(ovfFile);
+ if (CollectionUtils.isNotEmpty(ovfProperties)) {
+ s_logger.info("Found " + ovfProperties.size() + " configurable
OVF properties");
+ info.ovfProperties = ovfProperties;
+ }
} catch (Exception e) {
s_logger.info("The ovf file " + ovfFile + " is invalid ", e);
throw new InternalErrorException("OVA package has bad ovf file " +
e.getMessage(), e);
diff --git a/core/src/main/java/com/cloud/storage/template/Processor.java
b/core/src/main/java/com/cloud/storage/template/Processor.java
index c8ee181..4bb714a 100644
--- a/core/src/main/java/com/cloud/storage/template/Processor.java
+++ b/core/src/main/java/com/cloud/storage/template/Processor.java
@@ -21,7 +21,9 @@ package com.cloud.storage.template;
import java.io.File;
import java.io.IOException;
+import java.util.List;
+import com.cloud.agent.api.storage.OVFPropertyTO;
import com.cloud.exception.InternalErrorException;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.utils.component.Adapter;
@@ -52,6 +54,7 @@ public interface Processor extends Adapter {
public long virtualSize;
public String filename;
public boolean isCorrupted;
+ public List<OVFPropertyTO> ovfProperties;
}
long getVirtualSize(File file) throws IOException;
diff --git
a/engine/schema/src/main/java/com/cloud/storage/TemplateOVFPropertyVO.java
b/engine/schema/src/main/java/com/cloud/storage/TemplateOVFPropertyVO.java
new file mode 100644
index 0000000..425b1f2
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/storage/TemplateOVFPropertyVO.java
@@ -0,0 +1,167 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.cloud.storage;
+
+import com.cloud.agent.api.storage.OVFProperty;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "template_ovf_properties")
+public class TemplateOVFPropertyVO implements OVFProperty {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private long id;
+
+ @Column(name = "template_id")
+ private Long templateId;
+
+ @Column(name = "key")
+ private String key;
+
+ @Column(name = "type")
+ private String type;
+
+ @Column(name = "value")
+ private String value;
+
+ @Column(name = "qualifiers")
+ private String qualifiers;
+
+ @Column(name = "password")
+ private Boolean password;
+
+ @Column(name = "user_configurable")
+ private Boolean userConfigurable;
+
+ @Column(name = "label")
+ private String label;
+
+ @Column(name = "description")
+ private String description;
+
+ public TemplateOVFPropertyVO() {
+ }
+
+ public TemplateOVFPropertyVO(Long templateId, String key, String type,
String value, String qualifiers,
+ Boolean userConfigurable, String label,
String description, Boolean password) {
+ this.templateId = templateId;
+ this.key = key;
+ this.type = type;
+ this.value = value;
+ this.qualifiers = qualifiers;
+ this.userConfigurable = userConfigurable;
+ this.label = label;
+ this.description = description;
+ this.password = password;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public Long getTemplateId() {
+ return templateId;
+ }
+
+ public void setTemplateId(Long templateId) {
+ this.templateId = templateId;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getQualifiers() {
+ return qualifiers;
+ }
+
+ public void setQualifiers(String qualifiers) {
+ this.qualifiers = qualifiers;
+ }
+
+ @Override
+ public Boolean isUserConfigurable() {
+ return userConfigurable;
+ }
+
+ public void setUserConfigurable(Boolean userConfigurable) {
+ this.userConfigurable = userConfigurable;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Boolean isPassword() {
+ return password;
+ }
+
+ public void setPassword(Boolean password) {
+ this.password = password;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PROP - templateId=%s> key=%s value=%s type=%s
qual=%s conf=%s label=%s desc=%s password=%s",
+ templateId, key, value, type, qualifiers, userConfigurable,
label, description, password);
+ }
+}
diff --git
a/engine/schema/src/main/java/com/cloud/storage/dao/TemplateOVFPropertiesDao.java
b/engine/schema/src/main/java/com/cloud/storage/dao/TemplateOVFPropertiesDao.java
new file mode 100644
index 0000000..eb78f20
--- /dev/null
+++
b/engine/schema/src/main/java/com/cloud/storage/dao/TemplateOVFPropertiesDao.java
@@ -0,0 +1,31 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.cloud.storage.dao;
+
+import com.cloud.storage.TemplateOVFPropertyVO;
+import com.cloud.utils.db.GenericDao;
+
+import java.util.List;
+
+public interface TemplateOVFPropertiesDao extends
GenericDao<TemplateOVFPropertyVO, Long> {
+
+ boolean existsOption(long templateId, String key);
+ TemplateOVFPropertyVO findByTemplateAndKey(long templateId, String key);
+ void saveOptions(List<TemplateOVFPropertyVO> opts);
+ List<TemplateOVFPropertyVO> listByTemplateId(long templateId);
+}
diff --git
a/engine/schema/src/main/java/com/cloud/storage/dao/TemplateOVFPropertiesDaoImpl.java
b/engine/schema/src/main/java/com/cloud/storage/dao/TemplateOVFPropertiesDaoImpl.java
new file mode 100644
index 0000000..cf6a280
--- /dev/null
+++
b/engine/schema/src/main/java/com/cloud/storage/dao/TemplateOVFPropertiesDaoImpl.java
@@ -0,0 +1,78 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.cloud.storage.dao;
+
+import com.cloud.storage.TemplateOVFPropertyVO;
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.db.TransactionLegacy;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+public class TemplateOVFPropertiesDaoImpl extends
GenericDaoBase<TemplateOVFPropertyVO, Long> implements TemplateOVFPropertiesDao
{
+
+ private final static Logger s_logger =
Logger.getLogger(TemplateOVFPropertiesDaoImpl.class);
+
+ SearchBuilder<TemplateOVFPropertyVO> OptionsSearchBuilder;
+
+ public TemplateOVFPropertiesDaoImpl() {
+ super();
+ OptionsSearchBuilder = createSearchBuilder();
+ OptionsSearchBuilder.and("templateid",
OptionsSearchBuilder.entity().getTemplateId(), SearchCriteria.Op.EQ);
+ OptionsSearchBuilder.and("key",
OptionsSearchBuilder.entity().getKey(), SearchCriteria.Op.EQ);
+ OptionsSearchBuilder.done();
+ }
+
+ @Override
+ public boolean existsOption(long templateId, String key) {
+ return findByTemplateAndKey(templateId, key) != null;
+ }
+
+ @Override
+ public TemplateOVFPropertyVO findByTemplateAndKey(long templateId, String
key) {
+ SearchCriteria<TemplateOVFPropertyVO> sc =
OptionsSearchBuilder.create();
+ sc.setParameters("templateid", templateId);
+ sc.setParameters("key", key);
+ return findOneBy(sc);
+ }
+
+ @Override
+ public void saveOptions(List<TemplateOVFPropertyVO> opts) {
+ if (CollectionUtils.isEmpty(opts)) {
+ return;
+ }
+ TransactionLegacy txn = TransactionLegacy.currentTxn();
+ txn.start();
+ for (TemplateOVFPropertyVO opt : opts) {
+ persist(opt);
+ }
+ txn.commit();
+ }
+
+ @Override
+ public List<TemplateOVFPropertyVO> listByTemplateId(long templateId) {
+ SearchCriteria<TemplateOVFPropertyVO> sc =
OptionsSearchBuilder.create();
+ sc.setParameters("templateid", templateId);
+ return listBy(sc);
+ }
+}
diff --git
a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
index 1cea7aa..3e0d67b 100644
---
a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
+++
b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
@@ -287,4 +287,5 @@
<bean id="annotationDaoImpl"
class="org.apache.cloudstack.annotation.dao.AnnotationDaoImpl" />
<bean id="directDownloadCertificateDaoImpl"
class="org.apache.cloudstack.direct.download.DirectDownloadCertificateDaoImpl"
/>
<bean id="directDownloadCertificateHostMapDaoImpl"
class="org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMapDaoImpl"
/>
+ <bean id="templateOVFPropertiesDaoImpl"
class="com.cloud.storage.dao.TemplateOVFPropertiesDaoImpl" />
</beans>
diff --git
a/engine/schema/src/main/resources/META-INF/db/schema-41200to41300.sql
b/engine/schema/src/main/resources/META-INF/db/schema-41200to41300.sql
index 904e76e..cdd17da 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41200to41300.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41200to41300.sql
@@ -386,3 +386,19 @@ CREATE TABLE
`cloud`.`direct_download_certificate_host_map` (
CONSTRAINT `fk_direct_download_certificate_host_map__certificate_id` FOREIGN
KEY (`certificate_id`) REFERENCES `direct_download_certificate` (`id`) ON
DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+-- [Vmware] Allow configuring appliances on the VM instance wizard when OVF
properties are available
+CREATE TABLE `cloud`.`template_ovf_properties` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `template_id` bigint(20) unsigned NOT NULL,
+ `key` VARCHAR(100) NOT NULL,
+ `type` VARCHAR(45) DEFAULT NULL,
+ `value` VARCHAR(100) DEFAULT NULL,
+ `password` TINYINT(1) NOT NULL DEFAULT '0',
+ `qualifiers` TEXT DEFAULT NULL,
+ `user_configurable` TINYINT(1) NOT NULL DEFAULT '0',
+ `label` TEXT DEFAULT NULL,
+ `description` TEXT DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ CONSTRAINT `fk_template_ovf_properties__template_id` FOREIGN KEY
(`template_id`) REFERENCES `vm_template`(`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
diff --git
a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java
b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java
index 1c6f1e7..dec9b76 100644
---
a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java
+++
b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java
@@ -27,7 +27,12 @@ import java.util.Map;
import javax.inject.Inject;
+import com.cloud.agent.api.storage.OVFPropertyTO;
import com.cloud.storage.Upload;
+import com.cloud.storage.dao.TemplateOVFPropertiesDao;
+import com.cloud.storage.TemplateOVFPropertyVO;
+import com.cloud.utils.crypt.DBEncryptionUtil;
+import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
@@ -99,6 +104,8 @@ public abstract class BaseImageStoreDriverImpl implements
ImageStoreDriver {
AccountDao _accountDao;
@Inject
ResourceLimitService _resourceLimitMgr;
+ @Inject
+ TemplateOVFPropertiesDao templateOvfPropertiesDao;
protected String _proxy = null;
@@ -162,6 +169,29 @@ public abstract class BaseImageStoreDriverImpl implements
ImageStoreDriver {
}
}
+ /**
+ * Persist OVF properties as template details for template with id =
templateId
+ */
+ private void persistOVFProperties(List<OVFPropertyTO> ovfProperties, long
templateId) {
+ List<TemplateOVFPropertyVO> listToPersist = new ArrayList<>();
+ for (OVFPropertyTO property : ovfProperties) {
+ if (!templateOvfPropertiesDao.existsOption(templateId,
property.getKey())) {
+ TemplateOVFPropertyVO option = new
TemplateOVFPropertyVO(templateId, property.getKey(), property.getType(),
+ property.getValue(), property.getQualifiers(),
property.isUserConfigurable(),
+ property.getLabel(), property.getDescription(),
property.isPassword());
+ if (property.isPassword()) {
+ String encryptedPassword =
DBEncryptionUtil.encrypt(property.getValue());
+ option.setValue(encryptedPassword);
+ }
+ listToPersist.add(option);
+ }
+ }
+ if (CollectionUtils.isNotEmpty(listToPersist)) {
+ s_logger.debug("Persisting " + listToPersist.size() + " OVF
properties for template " + templateId);
+ templateOvfPropertiesDao.saveOptions(listToPersist);
+ }
+ }
+
protected Void createTemplateAsyncCallback(AsyncCallbackDispatcher<?
extends BaseImageStoreDriverImpl, DownloadAnswer> callback,
CreateContext<CreateCmdResult> context) {
if (s_logger.isDebugEnabled()) {
@@ -170,10 +200,14 @@ public abstract class BaseImageStoreDriverImpl implements
ImageStoreDriver {
DownloadAnswer answer = callback.getResult();
DataObject obj = context.data;
DataStore store = obj.getDataStore();
+ List<OVFPropertyTO> ovfProperties = answer.getOvfProperties();
TemplateDataStoreVO tmpltStoreVO =
_templateStoreDao.findByStoreTemplate(store.getId(), obj.getId());
if (tmpltStoreVO != null) {
if (tmpltStoreVO.getDownloadState() ==
VMTemplateStorageResourceAssoc.Status.DOWNLOADED) {
+ if (CollectionUtils.isNotEmpty(ovfProperties)) {
+ persistOVFProperties(ovfProperties, obj.getId());
+ }
if (s_logger.isDebugEnabled()) {
s_logger.debug("Template is already in DOWNLOADED state,
ignore further incoming DownloadAnswer");
}
@@ -213,6 +247,9 @@ public abstract class BaseImageStoreDriverImpl implements
ImageStoreDriver {
templateDaoBuilder.setChecksum(answer.getCheckSum());
_templateDao.update(obj.getId(), templateDaoBuilder);
}
+ if (CollectionUtils.isNotEmpty(ovfProperties)) {
+ persistOVFProperties(ovfProperties, obj.getId());
+ }
CreateCmdResult result = new CreateCmdResult(null, null);
caller.complete(result);
diff --git
a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java
b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java
index 4777b73..072ab9f 100644
---
a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java
+++
b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java
@@ -23,14 +23,22 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.agent.api.MigrateVmToPoolCommand;
import com.cloud.agent.api.UnregisterVMCommand;
+import com.cloud.agent.api.storage.OVFPropertyTO;
import com.cloud.agent.api.to.VolumeTO;
import com.cloud.dc.ClusterDetailsDao;
import com.cloud.storage.StoragePool;
+import com.cloud.storage.TemplateOVFPropertyVO;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VMTemplateStorageResourceAssoc;
+import com.cloud.storage.dao.TemplateOVFPropertiesDao;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
@@ -43,6 +51,7 @@ import
org.apache.cloudstack.storage.command.StorageSubSystemCommand;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
+import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.log4j.Logger;
@@ -151,6 +160,10 @@ public class VMwareGuru extends HypervisorGuruBase
implements HypervisorGuru, Co
PrimaryDataStoreDao _storagePoolDao;
@Inject
VolumeDataFactory _volFactory;
+ @Inject
+ private VMTemplatePoolDao templateSpoolDao;
+ @Inject
+ private TemplateOVFPropertiesDao templateOVFPropertiesDao;
protected VMwareGuru() {
super();
@@ -354,9 +367,66 @@ public class VMwareGuru extends HypervisorGuruBase
implements HypervisorGuru, Co
} else {
to.setPlatformEmulator(guestOsMapping.getGuestOsName());
}
+
+ List<OVFPropertyTO> ovfProperties = new ArrayList<>();
+ for (String detailKey : details.keySet()) {
+ if (detailKey.startsWith(ApiConstants.OVF_PROPERTIES)) {
+ String ovfPropKey =
detailKey.replace(ApiConstants.OVF_PROPERTIES + "-", "");
+ TemplateOVFPropertyVO templateOVFPropertyVO =
templateOVFPropertiesDao.findByTemplateAndKey(vm.getTemplateId(), ovfPropKey);
+ if (templateOVFPropertyVO == null) {
+ s_logger.warn(String.format("OVF property %s not found on
template, discarding", ovfPropKey));
+ continue;
+ }
+ String ovfValue = details.get(detailKey);
+ boolean isPassword = templateOVFPropertyVO.isPassword();
+ OVFPropertyTO propertyTO = new OVFPropertyTO(ovfPropKey,
ovfValue, isPassword);
+ ovfProperties.add(propertyTO);
+ }
+ }
+
+ if (CollectionUtils.isNotEmpty(ovfProperties)) {
+ removeOvfPropertiesFromDetails(ovfProperties, details);
+ String templateInstallPath = null;
+ List<DiskTO> rootDiskList = vm.getDisks().stream().filter(x ->
x.getType() == Volume.Type.ROOT).collect(Collectors.toList());
+ if (rootDiskList.size() != 1) {
+ throw new CloudRuntimeException("Did not find only one root
disk for VM " + vm.getHostName());
+ }
+
+ DiskTO rootDiskTO = rootDiskList.get(0);
+ DataStoreTO dataStore = rootDiskTO.getData().getDataStore();
+ StoragePoolVO storagePoolVO =
_storagePoolDao.findByUuid(dataStore.getUuid());
+ long dataCenterId = storagePoolVO.getDataCenterId();
+ List<StoragePoolVO> pools =
_storagePoolDao.listByDataCenterId(dataCenterId);
+ for (StoragePoolVO pool : pools) {
+ VMTemplateStoragePoolVO ref =
templateSpoolDao.findByPoolTemplate(pool.getId(), vm.getTemplateId());
+ if (ref != null && ref.getDownloadState() ==
VMTemplateStorageResourceAssoc.Status.DOWNLOADED) {
+ templateInstallPath = ref.getInstallPath();
+ break;
+ }
+ }
+
+ if (templateInstallPath == null) {
+ throw new CloudRuntimeException("Did not find the template
install path for template " +
+ vm.getTemplateId() + " on zone " + dataCenterId);
+ }
+
+ Pair<String, List<OVFPropertyTO>> pair = new
Pair<>(templateInstallPath, ovfProperties);
+ to.setOvfProperties(pair);
+ }
+
return to;
}
+ /*
+ Remove OVF properties from details to be sent to hypervisor (avoid
duplicate data)
+ */
+ private void removeOvfPropertiesFromDetails(List<OVFPropertyTO>
ovfProperties, Map<String, String> details) {
+ for (OVFPropertyTO propertyTO : ovfProperties) {
+ String key = propertyTO.getKey();
+ details.remove(ApiConstants.OVF_PROPERTIES + "-" + key);
+ }
+ }
+
/**
* Decide in which cases nested virtualization should be enabled based on
(1){@code globalNestedV}, (2){@code globalNestedVPerVM}, (3){@code
localNestedV}<br/>
* Nested virtualization should be enabled when one of this cases:
diff --git
a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java
b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java
index 834900f..02f758b 100644
---
a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java
+++
b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java
@@ -44,6 +44,19 @@ import java.util.UUID;
import javax.naming.ConfigurationException;
import javax.xml.datatype.XMLGregorianCalendar;
+import com.cloud.agent.api.storage.OVFPropertyTO;
+import com.cloud.utils.crypt.DBEncryptionUtil;
+import com.vmware.vim25.ArrayUpdateOperation;
+import com.vmware.vim25.VAppOvfSectionInfo;
+import com.vmware.vim25.VAppOvfSectionSpec;
+import com.vmware.vim25.VAppProductInfo;
+import com.vmware.vim25.VAppProductSpec;
+import com.vmware.vim25.VAppPropertyInfo;
+import com.vmware.vim25.VAppPropertySpec;
+import com.vmware.vim25.VmConfigInfo;
+import com.vmware.vim25.VmConfigSpec;
+import org.apache.commons.collections.CollectionUtils;
+
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.storage.command.CopyCommand;
import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
@@ -2229,6 +2242,23 @@ public class VmwareResource implements
StoragePoolResource, ServerResource, Vmwa
// config video card
configureVideoCard(vmMo, vmSpec, vmConfigSpec);
+ // Set OVF properties (if available)
+ Pair<String, List<OVFPropertyTO>> ovfPropsMap =
vmSpec.getOvfProperties();
+ VmConfigInfo templateVappConfig = null;
+ List<OVFPropertyTO> ovfProperties = null;
+ if (ovfPropsMap != null) {
+ String vmTemplate = ovfPropsMap.first();
+ s_logger.info("Find VM template " + vmTemplate);
+ VirtualMachineMO vmTemplateMO = dcMo.findVm(vmTemplate);
+ templateVappConfig =
vmTemplateMO.getConfigInfo().getVAppConfig();
+ ovfProperties = ovfPropsMap.second();
+ // Set OVF properties (if available)
+ if (CollectionUtils.isNotEmpty(ovfProperties)) {
+ s_logger.info("Copying OVF properties from template and
setting them to the values the user provided");
+ copyVAppConfigsFromTemplate(templateVappConfig,
ovfProperties, vmConfigSpec);
+ }
+ }
+
//
// Configure VM
//
@@ -2316,6 +2346,87 @@ public class VmwareResource implements
StoragePoolResource, ServerResource, Vmwa
}
}
+
+ /**
+ * Set the ovf section spec from existing vApp configuration
+ */
+ protected List<VAppOvfSectionSpec>
copyVAppConfigOvfSectionFromOVF(VmConfigInfo vAppConfig) {
+ List<VAppOvfSectionInfo> ovfSection = vAppConfig.getOvfSection();
+ List<VAppOvfSectionSpec> specs = new ArrayList<>();
+ for (VAppOvfSectionInfo info : ovfSection) {
+ VAppOvfSectionSpec spec = new VAppOvfSectionSpec();
+ spec.setInfo(info);
+ spec.setOperation(ArrayUpdateOperation.ADD);
+ specs.add(spec);
+ }
+ return specs;
+ }
+
+ private Map<String, Pair<String, Boolean>> getOVFMap(List<OVFPropertyTO>
props) {
+ Map<String, Pair<String, Boolean>> map = new HashMap<>();
+ for (OVFPropertyTO prop : props) {
+ Pair<String, Boolean> pair = new Pair<>(prop.getValue(),
prop.isPassword());
+ map.put(prop.getKey(), pair);
+ }
+ return map;
+ }
+
+ /**
+ * Set the properties section from existing vApp configuration and values
set on ovfProperties
+ */
+ protected List<VAppPropertySpec>
copyVAppConfigPropertySectionFromOVF(VmConfigInfo vAppConfig,
List<OVFPropertyTO> ovfProperties) {
+ List<VAppPropertyInfo> productFromOvf = vAppConfig.getProperty();
+ List<VAppPropertySpec> specs = new ArrayList<>();
+ Map<String, Pair<String, Boolean>> ovfMap = getOVFMap(ovfProperties);
+ for (VAppPropertyInfo info : productFromOvf) {
+ VAppPropertySpec spec = new VAppPropertySpec();
+ if (ovfMap.containsKey(info.getId())) {
+ Pair<String, Boolean> pair = ovfMap.get(info.getId());
+ String value = pair.first();
+ boolean isPassword = pair.second();
+ info.setValue(isPassword ? DBEncryptionUtil.decrypt(value) :
value);
+ }
+ spec.setInfo(info);
+ spec.setOperation(ArrayUpdateOperation.ADD);
+ specs.add(spec);
+ }
+ return specs;
+ }
+
+ /**
+ * Set the product section spec from existing vApp configuration
+ */
+ protected List<VAppProductSpec>
copyVAppConfigProductSectionFromOVF(VmConfigInfo vAppConfig) {
+ List<VAppProductInfo> productFromOvf = vAppConfig.getProduct();
+ List<VAppProductSpec> specs = new ArrayList<>();
+ for (VAppProductInfo info : productFromOvf) {
+ VAppProductSpec spec = new VAppProductSpec();
+ spec.setInfo(info);
+ spec.setOperation(ArrayUpdateOperation.ADD);
+ specs.add(spec);
+ }
+ return specs;
+ }
+
+ /**
+ * Set the vApp configuration to vmConfig spec, copying existing
configuration from vAppConfig
+ * and seting properties values from ovfProperties
+ */
+ protected void copyVAppConfigsFromTemplate(VmConfigInfo vAppConfig,
+ List<OVFPropertyTO>
ovfProperties,
+ VirtualMachineConfigSpec
vmConfig) throws Exception {
+ VmConfigSpec vmConfigSpec = new VmConfigSpec();
+ vmConfigSpec.getEula().addAll(vAppConfig.getEula());
+
vmConfigSpec.setInstallBootStopDelay(vAppConfig.getInstallBootStopDelay());
+
vmConfigSpec.setInstallBootRequired(vAppConfig.isInstallBootRequired());
+ vmConfigSpec.setIpAssignment(vAppConfig.getIpAssignment());
+
vmConfigSpec.getOvfEnvironmentTransport().addAll(vAppConfig.getOvfEnvironmentTransport());
+
vmConfigSpec.getProduct().addAll(copyVAppConfigProductSectionFromOVF(vAppConfig));
+
vmConfigSpec.getProperty().addAll(copyVAppConfigPropertySectionFromOVF(vAppConfig,
ovfProperties));
+
vmConfigSpec.getOvfSection().addAll(copyVAppConfigOvfSectionFromOVF(vAppConfig));
+ vmConfig.setVAppConfig(vmConfigSpec);
+ }
+
private String appendFileType(String path, String fileType) {
if (path.toLowerCase().endsWith(fileType.toLowerCase())) {
return path;
diff --git
a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java
b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java
index 9d6aca4..420ac44 100644
---
a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java
+++
b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java
@@ -34,6 +34,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+import com.vmware.vim25.VmConfigInfo;
import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
@@ -517,12 +518,18 @@ public class VmwareStorageProcessor implements
StorageProcessor {
hyperHost.importVmFromOVF(srcFileName, vmName, datastoreMo, "thin");
VirtualMachineMO vmMo = hyperHost.findVmOnHyperHost(vmName);
+ VmConfigInfo vAppConfig;
if (vmMo == null) {
String msg =
"Failed to import OVA template. secondaryStorage: " +
secondaryStorageUrl + ", templatePathAtSecondaryStorage: " +
templatePathAtSecondaryStorage +
", templateName: " + templateName + ",
templateUuid: " + templateUuid;
s_logger.error(msg);
throw new Exception(msg);
+ } else {
+ vAppConfig = vmMo.getConfigInfo().getVAppConfig();
+ if (vAppConfig != null) {
+ s_logger.info("Found vApp configuration");
+ }
}
OVAProcessor processor = new OVAProcessor();
@@ -536,7 +543,9 @@ public class VmwareStorageProcessor implements
StorageProcessor {
// the same template may be deployed with multiple copies at
per-datastore per-host basis,
// save the original template name from CloudStack DB as the
UUID to associate them.
vmMo.setCustomFieldValue(CustomFieldConstants.CLOUD_UUID,
templateName);
- vmMo.markAsTemplate();
+ if (vAppConfig == null) {
+ vmMo.markAsTemplate();
+ }
} else {
vmMo.destroy();
diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
index ee56cbb..5515efe 100644
--- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
+++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
@@ -31,6 +31,9 @@ import java.util.stream.Stream;
import javax.inject.Inject;
+import com.cloud.agent.api.storage.OVFProperty;
+import com.cloud.storage.TemplateOVFPropertyVO;
+import com.cloud.storage.dao.TemplateOVFPropertiesDao;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
@@ -70,6 +73,7 @@ import
org.apache.cloudstack.api.command.user.project.ListProjectsCmd;
import org.apache.cloudstack.api.command.user.resource.ListDetailOptionsCmd;
import
org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCmd;
import org.apache.cloudstack.api.command.user.tag.ListTagsCmd;
+import
org.apache.cloudstack.api.command.user.template.ListTemplateOVFProperties;
import org.apache.cloudstack.api.command.user.template.ListTemplatesCmd;
import org.apache.cloudstack.api.command.user.vm.ListVMsCmd;
import org.apache.cloudstack.api.command.user.vmgroup.ListVMGroupsCmd;
@@ -98,6 +102,7 @@ import
org.apache.cloudstack.api.response.SecurityGroupResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.cloudstack.api.response.StorageTagResponse;
+import org.apache.cloudstack.api.response.TemplateOVFPropertyResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.UserResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
@@ -387,6 +392,9 @@ public class QueryManagerImpl extends
MutualExclusiveIdsManagerBase implements Q
@Inject
ManagementServerHostDao managementServerHostDao;
+ @Inject
+ TemplateOVFPropertiesDao templateOVFPropertiesDao;
+
/*
* (non-Javadoc)
*
@@ -3839,6 +3847,29 @@ public class QueryManagerImpl extends
MutualExclusiveIdsManagerBase implements Q
}
@Override
+ public ListResponse<TemplateOVFPropertyResponse>
listTemplateOVFProperties(ListTemplateOVFProperties cmd) {
+ ListResponse<TemplateOVFPropertyResponse> response = new
ListResponse<>();
+ List<TemplateOVFPropertyResponse> result = new ArrayList<>();
+ Long templateId = cmd.getTemplateId();
+ List<TemplateOVFPropertyVO> ovfProperties =
templateOVFPropertiesDao.listByTemplateId(templateId);
+ for (OVFProperty property : ovfProperties) {
+ TemplateOVFPropertyResponse propertyResponse = new
TemplateOVFPropertyResponse();
+ propertyResponse.setKey(property.getKey());
+ propertyResponse.setType(property.getType());
+ propertyResponse.setValue(property.getValue());
+ propertyResponse.setQualifiers(property.getQualifiers());
+
propertyResponse.setUserConfigurable(property.isUserConfigurable());
+ propertyResponse.setLabel(property.getLabel());
+ propertyResponse.setDescription(property.getDescription());
+ propertyResponse.setPassword(property.isPassword());
+ propertyResponse.setObjectName("ovfproperty");
+ result.add(propertyResponse);
+ }
+ response.setResponses(result);
+ return response;
+ }
+
+ @Override
public String getConfigComponentName() {
return QueryService.class.getSimpleName();
}
diff --git
a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
index 0e73743..4ccfce9 100644
--- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
@@ -28,7 +28,9 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
+import com.cloud.vm.UserVmManager;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
+import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiConstants.VMDetails;
import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.response.NicExtraDhcpOptionResponse;
@@ -311,7 +313,10 @@ public class UserVmJoinDaoImpl extends
GenericDaoBaseWithTagInformation<UserVmJo
if (vmDetails != null) {
Map<String, String> resourceDetails = new HashMap<String,
String>();
for (UserVmDetailVO userVmDetailVO : vmDetails) {
- resourceDetails.put(userVmDetailVO.getName(),
userVmDetailVO.getValue());
+ if
(!userVmDetailVO.getName().startsWith(ApiConstants.OVF_PROPERTIES) ||
+ (UserVmManager.DisplayVMOVFProperties.value() &&
userVmDetailVO.getName().startsWith(ApiConstants.OVF_PROPERTIES))) {
+ resourceDetails.put(userVmDetailVO.getName(),
userVmDetailVO.getValue());
+ }
}
// Remove blacklisted settings if user is not admin
if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) {
diff --git
a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
index f381ce0..1b936e1 100644
--- a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
@@ -1325,18 +1325,18 @@ public class AutoScaleManagerImpl<Type> extends
ManagerBase implements AutoScale
vm =
_userVmService.createBasicSecurityGroupVirtualMachine(zone, serviceOffering,
template, null, owner, "autoScaleVm-" + asGroup.getId() + "-" +
getCurrentTimeStampString(),
"autoScaleVm-" + asGroup.getId() + "-" +
getCurrentTimeStampString(), null, null, null, HypervisorType.XenServer,
HTTPMethod.GET, null, null, null,
- null, true, null, null, null, null, null, null);
+ null, true, null, null, null, null, null, null, null);
} else {
if (zone.isSecurityGroupEnabled()) {
vm =
_userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering,
template, null, null,
owner, "autoScaleVm-" + asGroup.getId() + "-" +
getCurrentTimeStampString(),
"autoScaleVm-" + asGroup.getId() + "-" +
getCurrentTimeStampString(), null, null, null, HypervisorType.XenServer,
HTTPMethod.GET, null, null,
- null, null, true, null, null, null, null, null, null);
+ null, null, true, null, null, null, null, null, null,
null);
} else {
vm = _userVmService.createAdvancedVirtualMachine(zone,
serviceOffering, template, null, owner, "autoScaleVm-" + asGroup.getId() + "-" +
getCurrentTimeStampString(), "autoScaleVm-" +
asGroup.getId() + "-" + getCurrentTimeStampString(),
- null, null, null, HypervisorType.XenServer,
HTTPMethod.GET, null, null, null, addrs, true, null, null, null, null, null,
null);
+ null, null, null, HypervisorType.XenServer,
HTTPMethod.GET, null, null, null, addrs, true, null, null, null, null, null,
null, null);
}
}
diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java
b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
index 305c0f1..f8b440a 100644
--- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
@@ -446,6 +446,7 @@ import
org.apache.cloudstack.api.command.user.template.CreateTemplateCmd;
import org.apache.cloudstack.api.command.user.template.DeleteTemplateCmd;
import org.apache.cloudstack.api.command.user.template.ExtractTemplateCmd;
import
org.apache.cloudstack.api.command.user.template.GetUploadParamsForTemplateCmd;
+import
org.apache.cloudstack.api.command.user.template.ListTemplateOVFProperties;
import
org.apache.cloudstack.api.command.user.template.ListTemplatePermissionsCmd;
import org.apache.cloudstack.api.command.user.template.ListTemplatesCmd;
import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd;
@@ -3104,6 +3105,7 @@ public class ManagementServerImpl extends ManagerBase
implements ManagementServe
cmdList.add(RevokeTemplateDirectDownloadCertificateCmd.class);
cmdList.add(ListMgmtsCmd.class);
cmdList.add(GetUploadParamsForIsoCmd.class);
+ cmdList.add(ListTemplateOVFProperties.class);
// Out-of-band management APIs for admins
cmdList.add(EnableOutOfBandManagementForHostCmd.class);
diff --git a/server/src/main/java/com/cloud/vm/UserVmManager.java
b/server/src/main/java/com/cloud/vm/UserVmManager.java
index fc2f558..717ff5a 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManager.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManager.java
@@ -44,12 +44,14 @@ import com.cloud.utils.Pair;
*
*/
public interface UserVmManager extends UserVmService {
- static final String EnableDynamicallyScaleVmCK = "enable.dynamic.scale.vm";
- static final String AllowUserExpungeRecoverVmCK
="allow.user.expunge.recover.vm";
- static final ConfigKey<Boolean> EnableDynamicallyScaleVm = new
ConfigKey<Boolean>("Advanced", Boolean.class, EnableDynamicallyScaleVmCK,
"false",
+ String EnableDynamicallyScaleVmCK = "enable.dynamic.scale.vm";
+ String AllowUserExpungeRecoverVmCK ="allow.user.expunge.recover.vm";
+ ConfigKey<Boolean> EnableDynamicallyScaleVm = new
ConfigKey<Boolean>("Advanced", Boolean.class, EnableDynamicallyScaleVmCK,
"false",
"Enables/Disables dynamically scaling a vm", true,
ConfigKey.Scope.Zone);
- static final ConfigKey<Boolean> AllowUserExpungeRecoverVm = new
ConfigKey<Boolean>("Advanced", Boolean.class, AllowUserExpungeRecoverVmCK,
"false",
+ ConfigKey<Boolean> AllowUserExpungeRecoverVm = new
ConfigKey<Boolean>("Advanced", Boolean.class, AllowUserExpungeRecoverVmCK,
"false",
"Determines whether users can expunge or recover their vm", true,
ConfigKey.Scope.Account);
+ ConfigKey<Boolean> DisplayVMOVFProperties = new
ConfigKey<Boolean>("Advanced", Boolean.class, "vm.display.ovf.properties",
"false",
+ "Set display of VMs OVF properties as part of VM details", true);
static final int MAX_USER_DATA_LENGTH_BYTES = 2048;
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index 1e47e5a..7f9f3d4 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -40,6 +40,8 @@ import java.util.stream.Stream;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
+import com.cloud.storage.TemplateOVFPropertyVO;
+import com.cloud.storage.dao.TemplateOVFPropertiesDao;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.affinity.AffinityGroupService;
@@ -484,6 +486,8 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
private DpdkHelper dpdkHelper;
@Inject
private ResourceTagDao resourceTagDao;
+ @Inject
+ private TemplateOVFPropertiesDao templateOVFPropertiesDao;
private ScheduledExecutorService _executor = null;
private ScheduledExecutorService _vmIpFetchExecutor = null;
@@ -531,7 +535,6 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
private static final ConfigKey<Boolean> VmDestroyForcestop = new
ConfigKey<Boolean>("Advanced", Boolean.class, "vm.destroy.forcestop", "false",
"On destroy, force-stop takes this value ", true);
-
@Override
public UserVmVO getVirtualMachine(long vmId) {
return _vmDao.findById(vmId);
@@ -2482,6 +2485,15 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
}
}
}
+ for (String detailName : details.keySet()) {
+ if (detailName.startsWith(ApiConstants.OVF_PROPERTIES)) {
+ String ovfPropKey =
detailName.replace(ApiConstants.OVF_PROPERTIES + "-", "");
+ TemplateOVFPropertyVO ovfPropertyVO =
templateOVFPropertiesDao.findByTemplateAndKey(vmInstance.getTemplateId(),
ovfPropKey);
+ if (ovfPropertyVO != null &&
ovfPropertyVO.isPassword()) {
+ details.put(detailName,
DBEncryptionUtil.encrypt(details.get(detailName)));
+ }
+ }
+ }
vmInstance.setDetails(details);
_vmDao.saveDetails(vmInstance);
}
@@ -3074,7 +3086,8 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone,
ServiceOffering serviceOffering, VirtualMachineTemplate template, List<Long>
securityGroupIdList,
Account owner, String hostName, String displayName, Long
diskOfferingId, Long diskSize, String group, HypervisorType hypervisor,
HTTPMethod httpmethod,
String userData, String sshKeyPair, Map<Long, IpAddresses>
requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard,
List<Long> affinityGroupIdList,
- Map<String, String> customParametes, String customId, Map<String,
Map<Integer, String>> dhcpOptionMap, Map<Long, DiskOffering>
dataDiskTemplateToDiskOfferingMap) throws InsufficientCapacityException,
ConcurrentOperationException, ResourceUnavailableException,
+ Map<String, String> customParametes, String customId, Map<String,
Map<Integer, String>> dhcpOptionMap,
+ Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap,
Map<String, String> userVmOVFProperties) throws InsufficientCapacityException,
ConcurrentOperationException, ResourceUnavailableException,
StorageUnavailableException, ResourceAllocationException {
Account caller = CallContext.current().getCallingAccount();
@@ -3122,7 +3135,8 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
}
return createVirtualMachine(zone, serviceOffering, template, hostName,
displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList,
group, httpmethod,
- userData, sshKeyPair, hypervisor, caller, requestedIps,
defaultIps, displayVm, keyboard, affinityGroupIdList, customParametes,
customId, dhcpOptionMap, dataDiskTemplateToDiskOfferingMap);
+ userData, sshKeyPair, hypervisor, caller, requestedIps,
defaultIps, displayVm, keyboard, affinityGroupIdList, customParametes,
customId, dhcpOptionMap,
+ dataDiskTemplateToDiskOfferingMap, userVmOVFProperties);
}
@@ -3131,7 +3145,8 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone,
ServiceOffering serviceOffering, VirtualMachineTemplate template, List<Long>
networkIdList,
List<Long> securityGroupIdList, Account owner, String hostName,
String displayName, Long diskOfferingId, Long diskSize, String group,
HypervisorType hypervisor,
HTTPMethod httpmethod, String userData, String sshKeyPair,
Map<Long, IpAddresses> requestedIps, IpAddresses defaultIps, Boolean displayVm,
String keyboard,
- List<Long> affinityGroupIdList, Map<String, String>
customParameters, String customId, Map<String, Map<Integer, String>>
dhcpOptionMap, Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap)
throws InsufficientCapacityException, ConcurrentOperationException,
+ List<Long> affinityGroupIdList, Map<String, String>
customParameters, String customId, Map<String, Map<Integer, String>>
dhcpOptionMap,
+ Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap,
Map<String, String> userVmOVFProperties) throws InsufficientCapacityException,
ConcurrentOperationException,
ResourceUnavailableException, StorageUnavailableException,
ResourceAllocationException {
Account caller = CallContext.current().getCallingAccount();
@@ -3233,7 +3248,8 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
}
return createVirtualMachine(zone, serviceOffering, template, hostName,
displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList,
group, httpmethod,
- userData, sshKeyPair, hypervisor, caller, requestedIps,
defaultIps, displayVm, keyboard, affinityGroupIdList, customParameters,
customId, dhcpOptionMap, dataDiskTemplateToDiskOfferingMap);
+ userData, sshKeyPair, hypervisor, caller, requestedIps,
defaultIps, displayVm, keyboard, affinityGroupIdList, customParameters,
customId, dhcpOptionMap, dataDiskTemplateToDiskOfferingMap,
+ userVmOVFProperties);
}
@Override
@@ -3241,7 +3257,8 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
public UserVm createAdvancedVirtualMachine(DataCenter zone,
ServiceOffering serviceOffering, VirtualMachineTemplate template, List<Long>
networkIdList, Account owner,
String hostName, String displayName, Long diskOfferingId, Long
diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod,
String userData,
String sshKeyPair, Map<Long, IpAddresses> requestedIps,
IpAddresses defaultIps, Boolean displayvm, String keyboard, List<Long>
affinityGroupIdList,
- Map<String, String> customParametrs, String customId, Map<String,
Map<Integer, String>> dhcpOptionsMap, Map<Long, DiskOffering>
dataDiskTemplateToDiskOfferingMap) throws InsufficientCapacityException,
ConcurrentOperationException, ResourceUnavailableException,
+ Map<String, String> customParametrs, String customId, Map<String,
Map<Integer, String>> dhcpOptionsMap, Map<Long, DiskOffering>
dataDiskTemplateToDiskOfferingMap,
+ Map<String, String> userVmOVFPropertiesMap) throws
InsufficientCapacityException, ConcurrentOperationException,
ResourceUnavailableException,
StorageUnavailableException, ResourceAllocationException {
Account caller = CallContext.current().getCallingAccount();
@@ -3338,7 +3355,8 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
verifyExtraDhcpOptionsNetwork(dhcpOptionsMap, networkList);
return createVirtualMachine(zone, serviceOffering, template, hostName,
displayName, owner, diskOfferingId, diskSize, networkList, null, group,
httpmethod, userData,
- sshKeyPair, hypervisor, caller, requestedIps, defaultIps,
displayvm, keyboard, affinityGroupIdList, customParametrs, customId,
dhcpOptionsMap, dataDiskTemplateToDiskOfferingMap);
+ sshKeyPair, hypervisor, caller, requestedIps, defaultIps,
displayvm, keyboard, affinityGroupIdList, customParametrs, customId,
dhcpOptionsMap,
+ dataDiskTemplateToDiskOfferingMap, userVmOVFPropertiesMap);
}
private void verifyExtraDhcpOptionsNetwork(Map<String, Map<Integer,
String>> dhcpOptionsMap, List<NetworkVO> networkList) throws
InvalidParameterValueException {
@@ -3370,7 +3388,9 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
private UserVm createVirtualMachine(DataCenter zone, ServiceOffering
serviceOffering, VirtualMachineTemplate tmplt, String hostName, String
displayName, Account owner,
Long diskOfferingId, Long diskSize, List<NetworkVO> networkList,
List<Long> securityGroupIdList, String group, HTTPMethod httpmethod, String
userData,
String sshKeyPair, HypervisorType hypervisor, Account caller,
Map<Long, IpAddresses> requestedIps, IpAddresses defaultIps, Boolean
isDisplayVm, String keyboard,
- List<Long> affinityGroupIdList, Map<String, String>
customParameters, String customId, Map<String, Map<Integer, String>>
dhcpOptionMap, Map<Long, DiskOffering> datadiskTemplateToDiskOfferringMap)
throws InsufficientCapacityException, ResourceUnavailableException,
+ List<Long> affinityGroupIdList, Map<String, String>
customParameters, String customId, Map<String, Map<Integer, String>>
dhcpOptionMap,
+ Map<Long, DiskOffering> datadiskTemplateToDiskOfferringMap,
+ Map<String, String> userVmOVFPropertiesMap) throws
InsufficientCapacityException, ResourceUnavailableException,
ConcurrentOperationException, StorageUnavailableException,
ResourceAllocationException {
_accountMgr.checkAccess(caller, null, true, owner);
@@ -3744,7 +3764,8 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
}
UserVmVO vm = commitUserVm(zone, template, hostName, displayName,
owner, diskOfferingId, diskSize, userData, caller, isDisplayVm, keyboard,
accountId, userId, offering,
- isIso, sshPublicKey, networkNicMap, id, instanceName,
uuidName, hypervisorType, customParameters, dhcpOptionMap,
datadiskTemplateToDiskOfferringMap);
+ isIso, sshPublicKey, networkNicMap, id, instanceName,
uuidName, hypervisorType, customParameters, dhcpOptionMap,
+ datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap);
// Assign instance to the group
try {
@@ -3804,7 +3825,9 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
private UserVmVO commitUserVm(final DataCenter zone, final
VirtualMachineTemplate template, final String hostName, final String
displayName, final Account owner,
final Long diskOfferingId, final Long diskSize, final String
userData, final Account caller, final Boolean isDisplayVm, final String
keyboard,
final long accountId, final long userId, final ServiceOfferingVO
offering, final boolean isIso, final String sshPublicKey, final
LinkedHashMap<String, NicProfile> networkNicMap,
- final long id, final String instanceName, final String uuidName,
final HypervisorType hypervisorType, final Map<String, String>
customParameters, final Map<String, Map<Integer, String>> extraDhcpOptionMap,
final Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap) throws
InsufficientCapacityException {
+ final long id, final String instanceName, final String uuidName,
final HypervisorType hypervisorType, final Map<String, String>
customParameters, final Map<String,
+ Map<Integer, String>> extraDhcpOptionMap, final Map<Long,
DiskOffering> dataDiskTemplateToDiskOfferingMap,
+ Map<String, String> userVmOVFPropertiesMap) throws
InsufficientCapacityException {
return Transaction.execute(new
TransactionCallbackWithException<UserVmVO, InsufficientCapacityException>() {
@Override
public UserVmVO doInTransaction(TransactionStatus status) throws
InsufficientCapacityException {
@@ -3890,6 +3913,29 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
}
}
vm.setDetail(VmDetailConstants.DEPLOY_VM, "true");
+
+ if (MapUtils.isNotEmpty(userVmOVFPropertiesMap)) {
+ for (String key : userVmOVFPropertiesMap.keySet()) {
+ String detailKey = ApiConstants.OVF_PROPERTIES + "-" +
key;
+ String value = userVmOVFPropertiesMap.get(key);
+
+ // Sanitize boolean values to expected format and
encrypt passwords
+ if (StringUtils.isNotBlank(value)) {
+ if (value.equalsIgnoreCase("True")) {
+ value = "True";
+ } else if (value.equalsIgnoreCase("False")) {
+ value = "False";
+ } else {
+ TemplateOVFPropertyVO ovfPropertyVO =
templateOVFPropertiesDao.findByTemplateAndKey(vm.getTemplateId(), key);
+ if (ovfPropertyVO.isPassword()) {
+ value = DBEncryptionUtil.encrypt(value);
+ }
+ }
+ }
+ vm.setDetail(detailKey, value);
+ }
+ }
+
_vmDao.saveDetails(vm);
s_logger.debug("Allocating in the DB for vm");
@@ -4230,7 +4276,6 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
Map<String, String> details =
userVmDetailsDao.listDetailsKeyPairs(vm.getId());
vm.setDetails(details);
-
// add userdata info into vm profile
Nic defaultNic = _networkModel.getDefaultNic(vm.getId());
if(defaultNic != null) {
@@ -5005,19 +5050,22 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
Boolean displayVm = cmd.isDisplayVm();
String keyboard = cmd.getKeyboard();
Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap =
cmd.getDataDiskTemplateToDiskOfferingMap();
+ Map<String, String> userVmOVFProperties = cmd.getVmOVFProperties();
if (zone.getNetworkType() == NetworkType.Basic) {
if (cmd.getNetworkIds() != null) {
throw new InvalidParameterValueException("Can't specify
network Ids in Basic zone");
} else {
vm = createBasicSecurityGroupVirtualMachine(zone,
serviceOffering, template, getSecurityGroupIdList(cmd), owner, name,
displayName, diskOfferingId,
size , group , cmd.getHypervisor(),
cmd.getHttpMethod(), userData , sshKeyPairName , cmd.getIpToNetworkMap(),
addrs, displayVm , keyboard , cmd.getAffinityGroupIdList(),
- cmd.getDetails(), cmd.getCustomId(),
cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap);
+ cmd.getDetails(), cmd.getCustomId(),
cmd.getDhcpOptionsMap(),
+ dataDiskTemplateToDiskOfferingMap,
userVmOVFProperties);
}
} else {
if (zone.isSecurityGroupEnabled()) {
vm = createAdvancedSecurityGroupVirtualMachine(zone,
serviceOffering, template, cmd.getNetworkIds(), getSecurityGroupIdList(cmd),
owner, name,
displayName, diskOfferingId, size, group,
cmd.getHypervisor(), cmd.getHttpMethod(), userData, sshKeyPairName,
cmd.getIpToNetworkMap(), addrs, displayVm, keyboard,
- cmd.getAffinityGroupIdList(), cmd.getDetails(),
cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap);
+ cmd.getAffinityGroupIdList(), cmd.getDetails(),
cmd.getCustomId(), cmd.getDhcpOptionsMap(),
+ dataDiskTemplateToDiskOfferingMap,
userVmOVFProperties);
} else {
if (cmd.getSecurityGroupIdList() != null &&
!cmd.getSecurityGroupIdList().isEmpty()) {
@@ -5025,7 +5073,7 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
}
vm = createAdvancedVirtualMachine(zone, serviceOffering,
template, cmd.getNetworkIds(), owner, name, displayName, diskOfferingId, size,
group,
cmd.getHypervisor(), cmd.getHttpMethod(), userData,
sshKeyPairName, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard,
cmd.getAffinityGroupIdList(), cmd.getDetails(),
- cmd.getCustomId(), cmd.getDhcpOptionsMap(),
dataDiskTemplateToDiskOfferingMap);
+ cmd.getCustomId(), cmd.getDhcpOptionsMap(),
dataDiskTemplateToDiskOfferingMap, userVmOVFProperties);
}
}
// check if this templateId has a child ISO
@@ -6688,7 +6736,7 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {EnableDynamicallyScaleVm,
AllowUserExpungeRecoverVm, VmIpFetchWaitInterval, VmIpFetchTrialMax,
VmIpFetchThreadPoolMax,
- VmIpFetchTaskWorkers, AllowDeployVmIfGivenHostFails,
EnableAdditionalVmConfig};
+ VmIpFetchTaskWorkers, AllowDeployVmIfGivenHostFails,
EnableAdditionalVmConfig, DisplayVMOVFProperties};
}
@Override
diff --git
a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManagerImpl.java
b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManagerImpl.java
index 2ace37f..bd5c591 100644
---
a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManagerImpl.java
+++
b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManagerImpl.java
@@ -38,6 +38,7 @@ import java.util.concurrent.Executors;
import javax.naming.ConfigurationException;
+import com.cloud.agent.api.storage.OVFPropertyTO;
import com.cloud.storage.template.Processor;
import com.cloud.storage.template.S3TemplateDownloader;
import com.cloud.storage.template.TemplateDownloader;
@@ -61,6 +62,7 @@ import
org.apache.cloudstack.storage.command.DownloadProgressCommand;
import
org.apache.cloudstack.storage.command.DownloadProgressCommand.RequestType;
import org.apache.cloudstack.storage.resource.NfsSecondaryStorageResource;
import org.apache.cloudstack.storage.resource.SecondaryStorageResource;
+import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import com.cloud.agent.api.storage.DownloadAnswer;
@@ -124,6 +126,7 @@ public class DownloadManagerImpl extends ManagerBase
implements DownloadManager
private long templatePhysicalSize;
private final long id;
private final ResourceType resourceType;
+ private List<OVFPropertyTO> ovfProperties;
public DownloadJob(TemplateDownloader td, String jobId, long id,
String tmpltName, ImageFormat format, boolean hvm, Long accountId, String
descr, String cksum,
String installPathPrefix, ResourceType resourceType) {
@@ -218,6 +221,14 @@ public class DownloadManagerImpl extends ManagerBase
implements DownloadManager
public void setCheckSum(String checksum) {
this.checksum = checksum;
}
+
+ public List<OVFPropertyTO> getOvfProperties() {
+ return ovfProperties;
+ }
+
+ public void setOvfProperties(List<OVFPropertyTO> ovfProperties) {
+ this.ovfProperties = ovfProperties;
+ }
}
public static final Logger s_logger =
Logger.getLogger(DownloadManagerImpl.class);
@@ -471,6 +482,9 @@ public class DownloadManagerImpl extends ManagerBase
implements DownloadManager
}
dnld.setTemplatesize(info.virtualSize);
dnld.setTemplatePhysicalSize(info.size);
+ if (CollectionUtils.isNotEmpty(info.ovfProperties)) {
+ dnld.setOvfProperties(info.ovfProperties);
+ }
break;
}
}
@@ -771,6 +785,9 @@ public class DownloadManagerImpl extends ManagerBase
implements DownloadManager
answer =
new DownloadAnswer(jobId, getDownloadPct(jobId),
getDownloadError(jobId), getDownloadStatus2(jobId), getDownloadLocalPath(jobId),
getInstallPath(jobId),
getDownloadTemplateSize(jobId), getDownloadTemplatePhysicalSize(jobId),
getDownloadCheckSum(jobId));
+ if (CollectionUtils.isNotEmpty(dj.getOvfProperties())) {
+ answer.setOvfProperties(dj.getOvfProperties());
+ }
jobs.remove(jobId);
return answer;
default:
diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css
index c1b79b2..4f186ca 100644
--- a/ui/css/cloudstack3.css
+++ b/ui/css/cloudstack3.css
@@ -5819,6 +5819,11 @@ textarea {
overflow: hidden;
}
+.multi-wizard .select-container .select .ovf-property {
+ max-width: 352px;
+ padding-left: 21px;
+}
+
.multi-wizard .select-container .select .select-desc .name {
margin: 0 0 5px;
font-weight: bold;
diff --git a/ui/index.html b/ui/index.html
index c855e67..7c8aec3 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -433,6 +433,15 @@
<div class="select-container"></div>
</div>
</div>
+
+ <!-- Pre-step 8: Configure OVF properties if available
-->
+ <div class="step ovf-properties"
wizard-step-id="ovfProperties">
+ <div class="content">
+ <!-- Existing key pairs -->
+ <div class="select-container"></div>
+ </div>
+ </div>
+
<!-- Step 8: Review -->
<div class="step review" wizard-step-id="review">
<div class="main-desc">
diff --git a/ui/l10n/en.js b/ui/l10n/en.js
index 53e9814..4ce59e0 100644
--- a/ui/l10n/en.js
+++ b/ui/l10n/en.js
@@ -1277,6 +1277,7 @@ var dictionary = {
"label.outofbandmanagement.username":"Username",
"label.override.guest.traffic":"Override Guest-Traffic",
"label.override.public.traffic":"Override Public-Traffic",
+"label.ovf.properties":"OVF Properties",
"label.ovm.traffic.label":"OVM traffic label",
"label.ovm3.cluster":"Native Clustering",
"label.ovm3.pool":"Native Pooling",
@@ -2253,6 +2254,7 @@ var dictionary = {
"message.outofbandmanagement.disable":"Disable Out-of-band Management",
"message.outofbandmanagement.enable":"Enable Out-of-band Management",
"message.outofbandmanagement.issue":"Issue Out-of-band Management Power
Action",
+"message.ovf.properties.available":"There are OVF properties available for
customizing the selected appliance. Please edit the values accordingly.",
"message.password.has.been.reset.to":"Password has been reset to",
"message.password.of.the.vm.has.been.reset.to":"Password of the VM has been
reset to",
"message.pending.projects.1":"You have pending project invitations:",
diff --git a/ui/scripts/instanceWizard.js b/ui/scripts/instanceWizard.js
index 1513d84..a6fdfbb 100644
--- a/ui/scripts/instanceWizard.js
+++ b/ui/scripts/instanceWizard.js
@@ -16,7 +16,7 @@
// under the License.
(function($, cloudStack) {
- var zoneObjs, hypervisorObjs, featuredTemplateObjs, communityTemplateObjs,
myTemplateObjs, sharedTemplateObjs, featuredIsoObjs, communityIsoObjs,
myIsoObjs, sharedIsoObjs, serviceOfferingObjs, community, networkObjs;
+ var zoneObjs, hypervisorObjs, featuredTemplateObjs, communityTemplateObjs,
myTemplateObjs, sharedTemplateObjs, featuredIsoObjs, communityIsoObjs,
myIsoObjs, sharedIsoObjs, serviceOfferingObjs, community, networkObjs, ovfProps;
var selectedZoneObj, selectedTemplateObj, selectedHypervisor,
selectedDiskOfferingObj;
var selectedTemplateOrIso; //'select-template', 'select-iso'
var step6ContainerType = 'nothing-to-select'; //'nothing-to-select',
'select-network', 'select-security-group', 'select-advanced-sg'(advanced
sg-enabled zone)
@@ -533,7 +533,6 @@
// if the user is leveraging a template, then we can show
custom IOPS, if applicable
var canShowCustomIopsForServiceOffering =
(args.currentData["select-template"] != "select-iso" ? true : false);
-
// get serviceOfferingObjs
var zoneid = args.currentData["zoneid"];
$(window).removeData("cloudStack.module.instanceWizard.serviceOfferingObjs");
@@ -960,11 +959,41 @@
});
}
});
+
+ $.ajax({
+ url: createURL("listTemplateOvfProperties&id=" +
selectedTemplateObj.id),
+ dataType: "json",
+ async: false,
+ success: function(json) {
+ ovfProps =
json.listtemplateovfpropertiesresponse.ovfproperty;
+ }
+ });
+
+ var $step = $('.step.sshkeyPairs:visible');
+ if (ovfProps == null || ovfProps.length === 0) {
+ $step.addClass('next-skip-ovf-properties');
+ } else {
+ $step.removeClass('next-skip-ovf-properties');
+ }
+ },
+
+ // Step PRE-8: Configure OVF Properties (if available) for the
template
+ function(args) {
+ args.response.success({
+ data: {
+ ovfProperties: ovfProps
+ }
+ });
},
// Step 8: Review
function(args) {
- return false;
+ var $step = $('.step.review:visible');
+ if (ovfProps == null || ovfProps.length === 0) {
+ $step.addClass('previous-skip-ovf-properties');
+ } else {
+ $step.removeClass('previous-skip-ovf-properties');
+ }
}
],
action: function(args) {
@@ -1004,6 +1033,25 @@
hypervisor : selectedHypervisor
});
+ var deployOvfProperties = [];
+ if (ovfProps != null && ovfProps.length > 0) {
+ $(ovfProps).each(function(index, prop) {
+ var selectField =
args.$wizard.find('select[id="ovf-property-'+prop.key+'"]');
+ var inputField =
args.$wizard.find('input[id="ovf-property-'+prop.key+'"]');
+ var propValue = inputField.val() ? inputField.val() :
selectField.val();
+ if (propValue !== undefined) {
+ deployOvfProperties.push({
+ key: prop.key,
+ value: propValue
+ });
+ }
+ });
+ for (var k = 0; k < deployOvfProperties.length; k++) {
+ deployVmData["ovfproperties[" + k + "].key"] =
deployOvfProperties[k].key;
+ deployVmData["ovfproperties[" + k + "].value"] =
deployOvfProperties[k].value;
+ }
+ }
+
if
(args.$wizard.find('input[name=rootDiskSize]').parent().css('display') !=
'none') {
if (args.$wizard.find('input[name=rootDiskSize]').val().length
> 0) {
$.extend(deployVmData, {
diff --git a/ui/scripts/templates.js b/ui/scripts/templates.js
index 6124ab1..d15a2c1 100644
--- a/ui/scripts/templates.js
+++ b/ui/scripts/templates.js
@@ -1822,6 +1822,21 @@
}
}
},
+ tabFilter: function (args) {
+ $.ajax({
+ url: createURL("listTemplateOvfProperties&id="
+ args.context.templates[0].id),
+ dataType: "json",
+ async: false,
+ success: function(json) {
+ ovfprops =
json.listtemplateovfpropertiesresponse.ovfproperty;
+ }
+ });
+ var hiddenTabs = [];
+ if (ovfprops == null || ovfprops.length === 0) {
+ hiddenTabs.push("ovfpropertiestab");
+ }
+ return hiddenTabs;
+ },
tabs: {
details: {
title: 'label.details',
@@ -2583,7 +2598,57 @@
}
}
})
- }
+ },
+
+ /**
+ * OVF properties tab (only displayed when OVF
properties are available)
+ */
+ ovfpropertiestab: {
+ title: 'label.ovf.properties',
+ listView: {
+ id: 'ovfproperties',
+ fields: {
+ label: {
+ label: 'label.label'
+ },
+ description: {
+ label: 'label.description'
+ },
+ value: {
+ label: 'label.value'
+ }
+ },
+ hideSearchBar: true,
+ dataProvider: function(args) {
+ $.ajax({
+ url:
createURL("listTemplateOvfProperties"),
+ data: {
+ id:
args.context.templates[0].id
+ },
+ success: function(json) {
+ var ovfprops =
json.listtemplateovfpropertiesresponse.ovfproperty;
+ var listDetails = [];
+ for (index in ovfprops){
+ var prop = ovfprops[index];
+ var det = {};
+ det['label'] =
prop['label'];
+ det['description'] =
prop['description'];
+ det['value'] =
prop['value'];
+ listDetails.push(det);
+ }
+ args.response.success({
+ data: listDetails
+ });
+ },
+
+ error: function(json) {
+
args.response.error(parseXMLHttpResponse(json));
+ }
+ });
+
+ }
+ }
+ }
}
}
}
diff --git a/ui/scripts/ui-custom/instanceWizard.js
b/ui/scripts/ui-custom/instanceWizard.js
index b240c37..4fa7e3f 100644
--- a/ui/scripts/ui-custom/instanceWizard.js
+++ b/ui/scripts/ui-custom/instanceWizard.js
@@ -123,6 +123,98 @@
});
};
+ var makeSelectsOvfProperties = function (data, fields) {
+ var $selects = $('<div>');
+
+ $(data).each(function() {
+ var item = this;
+ var key = item[fields.key];
+ var type = item[fields.type];
+ var value = item[fields.value];
+ var qualifiers = item[fields.qualifiers];
+ var label = item[fields.label];
+ var description = item[fields.description];
+ var password = item[fields.password];
+
+ var propertyField;
+
+ var fieldType = password ? "password" : "text";
+ if (type && type.toUpperCase() == "BOOLEAN") {
+ propertyField = $('<select id=ovf-property-' + key
+ '>')
+ .append($('<option>').attr({value:
"True"}).html("True"))
+ .append($('<option>').attr({value:
"False"}).html("False"));
+ } else if (type && (type.includes("int") ||
type.includes("real"))) {
+ if (qualifiers && qualifiers.includes("MinValue")
&& qualifiers.includes("MaxValue")) {
+ var split = qualifiers.split(",");
+ var minValue =
split[0].replace("MinValue(","").slice(0, -1);
+ var maxValue =
split[1].replace("MaxValue(","").slice(0, -1);
+ fieldType = "number";
+ propertyField = $('<input
id=ovf-property-'+key+'>')
+ .attr({type: fieldType, min: minValue,
max:maxValue})
+
.addClass('name').val(_s(this[fields.value]));
+ } else {
+ propertyField = $('<input
id=ovf-property-'+key+'>')
+ .attr({type: fieldType})
+
.addClass('name').val(_s(this[fields.value]))
+ }
+ } else if (type && type.toUpperCase() == "STRING") {
+ if (qualifiers) {
+ propertyField = $('<select
id=ovf-property-'+key+'>')
+ if (qualifiers.startsWith("ValueMap")) {
+ var possibleValues =
qualifiers.replace("ValueMap","").substr(1).slice(0, -1).split(",");
+ $(possibleValues).each(function() {
+ var qualifier =
this.substr(1).slice(0, -1); //remove first and last quotes
+ var option = $('<option>')
+ .attr({
+ value: qualifier,
+ type: fieldType
+ })
+ .html(qualifier)
+ propertyField.append(option);
+ });
+ } else if (qualifiers.startsWith("MaxLen")) {
+ var length =
qualifiers.replace("MaxLen(","").slice(0,-1);
+ propertyField = $('<input
id=ovf-property-'+key+'>')
+ .attr({maxlength : length, type:
fieldType})
+
.addClass('name').val(_s(this[fields.value]))
+ }
+ } else {
+ propertyField = $('<input
id=ovf-property-'+key+'>')
+ .attr({type: fieldType})
+
.addClass('name').val(_s(this[fields.value]))
+ }
+ } else {
+ propertyField = $('<input
id=ovf-property-'+key+'>')
+ .attr({type: fieldType})
+ .addClass('name').val(_s(this[fields.value]))
+ }
+
+ var $select = $('<div>')
+ .addClass('select')
+ .append(
+ $('<div>')
+ .addClass('select-desc')
+ .addClass('ovf-property')
+
.append($('<div>').addClass('name').html(_s(this[fields.label])))
+ .append(propertyField)
+
.append($('<div>').addClass('desc').html(_s(this[fields.description])))
+ .data('json-obj', this)
+ );
+ $selects.append($select);
+ });
+
+ cloudStack.evenOdd($selects, 'div.select', {
+ even: function($elem) {
+ $elem.addClass('even');
+ },
+ odd: function($elem) {
+ $elem.addClass('odd');
+ }
+ });
+
+ return $selects.children();
+ };
+
var makeSelects = function(name, data, fields, options,
selectedObj, selectedObjNonEditable) {
var $selects = $('<div>');
options = options ? options : {};
@@ -1233,10 +1325,31 @@
};
},
+ 'ovfProperties': function($step, formData) {
+ return {
+ response: {
+ success: function(args) {
+ $step.find('.content
.select-container').append(
+
makeSelectsOvfProperties(args.data.ovfProperties, {
+ key: 'key',
+ type: 'type',
+ value: 'value',
+ qualifiers: 'qualifiers',
+ label: 'label',
+ description : 'description',
+ password : 'password'
+ })
+ );
+ }
+ }
+ };
+ },
+
'review': function($step, formData) {
$step.find('[wizard-field]').each(function() {
var field = $(this).attr('wizard-field');
var fieldName;
+
var $input = $wizard.find('[wizard-field=' + field
+ ']').filter(function() {
return ($(this).is(':selected') ||
$(this).is(':checked') ||
@@ -1425,6 +1538,37 @@
}
}
+ // Step 7 - Skip OVF properties tab if there are no
OVF properties for the template
+ if ($activeStep.hasClass('sshkeyPairs')) {
+ if
($activeStep.hasClass('next-skip-ovf-properties')) {
+ showStep(8);
+ }
+ }
+
+ // Optional Step - Pre-step 8
+ if ($activeStep.hasClass('ovf-properties')) {
+ var ok = true;
+ if ($activeStep.find('input').length > 0) { //if
no checkbox is checked
+ $.each($activeStep.find('input'),
function(index, item) {
+ var item = $activeStep.find('input#' +
item.id);
+ var internalCheck = true;
+ if (this.maxLength && this.maxLength !==
-1) {
+ internalCheck = item.val().length <=
this.maxLength;
+ } else if (this.min && this.max) {
+ var numberValue =
parseFloat(item.val());
+ internalCheck = numberValue >=
this.min && numberValue <= this.max;
+ }
+ ok = ok && internalCheck;
+ });
+ }
+ if (!ok) {
+ cloudStack.dialog.notice({
+ message: 'Please enter valid values for
every property'
+ });
+ return false;
+ }
+ }
+
if (!$form.valid()) {
if ($form.find('input.error:visible,
select.error:visible').length) {
return false;
@@ -1459,6 +1603,12 @@
}
}
+ if ($activeStep.hasClass('review')) {
+ if
($activeStep.hasClass('previous-skip-ovf-properties')) {
+ showStep(7);
+ }
+ }
+
return false;
}
diff --git
a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ClusterMO.java
b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ClusterMO.java
index a6e4285..ce9e981 100644
--- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ClusterMO.java
+++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ClusterMO.java
@@ -391,7 +391,7 @@ public class ClusterMO extends BaseMO implements
VmwareHypervisorHost {
@Override
public boolean createBlankVm(String vmName, String vmInternalCSName, int
cpuCount, int cpuSpeedMHz, int cpuReservedMHz, boolean limitCpuUse, int
memoryMB,
- int memoryReserveMB, String guestOsIdentifier,
ManagedObjectReference morDs, boolean snapshotDirToParent, Pair<String, String>
controllerInfo, Boolean systemVm) throws Exception {
+ int memoryReserveMB, String
guestOsIdentifier, ManagedObjectReference morDs, boolean snapshotDirToParent,
Pair<String, String> controllerInfo, Boolean systemVm) throws Exception {
if (s_logger.isTraceEnabled())
s_logger.trace("vCenter API trace - createBlankVm(). target MOR: "
+ _mor.getValue() + ", vmName: " + vmName + ", cpuCount: " + cpuCount + ",
cpuSpeedMhz: " +
diff --git
a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HostMO.java
b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HostMO.java
index 74cef80..cc50b3d 100644
--- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HostMO.java
+++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HostMO.java
@@ -771,7 +771,7 @@ public class HostMO extends BaseMO implements
VmwareHypervisorHost {
@Override
public boolean createBlankVm(String vmName, String vmInternalCSName, int
cpuCount, int cpuSpeedMHz, int cpuReservedMHz, boolean limitCpuUse, int
memoryMB,
- int memoryReserveMB, String guestOsIdentifier,
ManagedObjectReference morDs, boolean snapshotDirToParent, Pair<String, String>
controllerInfo, Boolean systemVm) throws Exception {
+ int memoryReserveMB, String
guestOsIdentifier, ManagedObjectReference morDs, boolean snapshotDirToParent,
Pair<String, String> controllerInfo, Boolean systemVm) throws Exception {
if (s_logger.isTraceEnabled())
s_logger.trace("vCenter API trace - createBlankVm(). target MOR: "
+ _mor.getValue() + ", vmName: " + vmName + ", cpuCount: " + cpuCount + ",
cpuSpeedMhz: " +
diff --git
a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java
b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java
index f003d6a..2eaa55a 100644
---
a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java
+++
b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java
@@ -1439,8 +1439,8 @@ public class HypervisorHostHelper {
}
public static boolean createBlankVm(VmwareHypervisorHost host, String
vmName, String vmInternalCSName, int cpuCount, int cpuSpeedMHz, int
cpuReservedMHz,
- boolean limitCpuUse, int memoryMB, int memoryReserveMB, String
guestOsIdentifier, ManagedObjectReference morDs, boolean snapshotDirToParent,
- Pair<String, String> controllerInfo, Boolean systemVm) throws
Exception {
+ boolean limitCpuUse, int memoryMB, int
memoryReserveMB, String guestOsIdentifier, ManagedObjectReference morDs,
boolean snapshotDirToParent,
+ Pair<String, String> controllerInfo,
Boolean systemVm) throws Exception {
if (s_logger.isInfoEnabled())
s_logger.info("Create blank VM. cpuCount: " + cpuCount + ",
cpuSpeed(MHz): " + cpuSpeedMHz + ", mem(Mb): " + memoryMB);
@@ -1508,6 +1508,7 @@ public class HypervisorHostHelper {
videoDeviceSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD);
vmConfig.getDeviceChange().add(videoDeviceSpec);
+
if (host.createVm(vmConfig)) {
// Here, when attempting to find the VM, we need to use the name
// with which we created it. This is the only such place where
diff --git
a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java
b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java
index 6a42f7f..a1205c2 100644
---
a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java
+++
b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java
@@ -2963,10 +2963,18 @@ public class VirtualMachineMO extends BaseMO {
List<VirtualDevice> devices =
(List<VirtualDevice>)_context.getVimClient().
getDynamicProperty(_mor, "config.hardware.device");
if(devices != null && devices.size() > 0) {
+ long isoDevices = devices.stream()
+ .filter(x -> x instanceof VirtualCdrom && x.getBacking()
instanceof VirtualCdromIsoBackingInfo)
+ .count();
for(VirtualDevice device : devices) {
- if(device instanceof VirtualCdrom && device.getBacking()
instanceof VirtualCdromIsoBackingInfo &&
-
((VirtualCdromIsoBackingInfo)device.getBacking()).getFileName().equals(filename))
{
- return device;
+ if(device instanceof VirtualCdrom && device.getBacking()
instanceof VirtualCdromIsoBackingInfo) {
+ if
(((VirtualCdromIsoBackingInfo)device.getBacking()).getFileName().equals(filename))
{
+ return device;
+ } else if (isoDevices == 1L){
+ s_logger.warn(String.format("VM ISO filename %s
differs from the expected filename %s",
+
((VirtualCdromIsoBackingInfo)device.getBacking()).getFileName(), filename));
+ return device;
+ }
}
}
}
diff --git
a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmwareHypervisorHost.java
b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmwareHypervisorHost.java
index 0767ec0..6f0cd22 100644
---
a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmwareHypervisorHost.java
+++
b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmwareHypervisorHost.java
@@ -58,8 +58,8 @@ public interface VmwareHypervisorHost {
boolean createVm(VirtualMachineConfigSpec vmSpec) throws Exception;
boolean createBlankVm(String vmName, String vmInternalCSName, int
cpuCount, int cpuSpeedMHz, int cpuReservedMHz, boolean limitCpuUse, int
memoryMB,
- int memoryReserveMB, String guestOsIdentifier, ManagedObjectReference
morDs, boolean snapshotDirToParent,
- Pair<String, String> controllerInfo, Boolean systemVm) throws
Exception;
+ int memoryReserveMB, String guestOsIdentifier,
ManagedObjectReference morDs, boolean snapshotDirToParent,
+ Pair<String, String> controllerInfo, Boolean
systemVm) throws Exception;
void importVmFromOVF(String ovfFilePath, String vmName, DatastoreMO dsMo,
String diskOption) throws Exception;