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{&quot;Default Route&quot;,&quot;Remote HTTP and SSH 
Client Routes&quot;}" 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;
 

Reply via email to