This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new 8b5ba13b817 plugins: Add Custom hypervisor minimal changes (#7692)
8b5ba13b817 is described below

commit 8b5ba13b817e7d53beb0e0012c27d065e96a708c
Author: Nicolas Vazquez <[email protected]>
AuthorDate: Wed Aug 16 12:23:24 2023 -0300

    plugins: Add Custom hypervisor minimal changes (#7692)
    
    ### Description
    
    Design document: 
https://cwiki.apache.org/confluence/display/CLOUDSTACK/%5BDRAFT%5D+Minimal+changes+to+allow+new+dynamic+hypervisor+type%3A+Custom+Hypervisor
    
    This PR introduces the minimal changes to add a new hypervisor type 
(internally named Custom in the codebase, and configurable display name), 
allowing to write an external hypervisor plugin as a Custom Hypervisor to 
CloudStack
    
    The custom hypervisor name is set by the setting: 
'hypervisor.custom.display.name'. The new hypervisor type does not affect the 
behaviour of any CloudStack operation, it simply introduces a new hypervisor 
type into the system.
    
    CloudStack does not have any means to dynamically add new hypervisor types. 
The hypervisor types are internally preset by an enum defined within the 
CloudStack codebase and unless a new version supports a new hypervisor it is 
not possible to add a host of a hypervisor that is not in part of the enum. It 
is possible to implement minimal changes in CloudStack to support a new 
hypervisor plugin that may be developed privately
    
    This PR is an initial work on allowing new dynamic hypervisor types (adds a 
new element to the HypervisorType enum, but allows variable display name for 
the hypervisor)
    
    ##### Proposed Future work:
    Replace the HypervisorType from a fixed enum to an extensible registry 
mechanism, registered from the hypervisor plugin
    
    #### Feature Specifications
    - The new hypervisor type is internally named 'Custom' to the CloudStack 
services (management server and agent services, database records).
    - A new global setting ‘hypervisor.custom.display.name’ allows 
administrators to set the display name of the hypervisor type. The display name 
will be shown in the CloudStack UI and API.
       - In case the ‘hypervisor.list’ setting contains the display name of the 
new hypervisor type, the setting value is automatically updated after the 
‘hypervisor.custom.display.name’ setting is updated.
    - The new Custom hypervisor type supports:
       - Direct downloads (the ability to download templates into primary 
storage from the hypervisor hosts without using secondary storage)
       - Local storage (use hypervisor hosts local storage as primary storage)
       - Template format: RAW format (the templates to be registered on the new 
hypervisor type must be in RAW format)
    - The UI is also extended to display the new hypervisor type and the 
supported features listed above.
    - The above are the minimal changes for CloudStack to support the new 
hypervisor type, which can be tested by integrating the plugin codebase with 
this feature.
    
    
    #### Use cases
    This PR allows the cloud administrators to test custom hypervisor plugins 
implementations in CloudStack and easily integrate it into CloudStack as a new 
hypervisor type ("Custom"), reducing the implementation to only the hypervisor 
supported specific storage/networking and the hypervisor resource to 
communicate with the management server.
    
    - CloudStack admin should be able to create a zone for the new custom 
hypervisor and add clusters, hosts into the zone with normal operations
    - CloudStack users should be able to execute normal 
VMs/volumes/network/storage operations on VMs/volumes running on the custom 
hypervisor hosts
---
 .../main/java/com/cloud/hypervisor/Hypervisor.java | 18 ++++++++++--
 .../java/com/cloud/hypervisor/HypervisorGuru.java  |  5 ++++
 .../command/user/template/RegisterTemplateCmd.java |  9 ++++--
 .../api/response/HostForMigrationResponse.java     |  5 ++--
 .../cloudstack/api/response/HostResponse.java      |  7 ++---
 .../response/HypervisorCapabilitiesResponse.java   |  7 ++---
 .../api/response/VMSnapshotResponse.java           |  7 ++---
 .../com/cloud/storage/GuestOSHypervisorVO.java     |  3 +-
 .../src/test/resources/component.xml               |  5 ++++
 .../src/main/resources/components-example.xml      |  1 +
 server/src/main/java/com/cloud/api/ApiDBUtils.java |  3 ++
 .../main/java/com/cloud/api/ApiResponseHelper.java | 13 +++++----
 .../api/query/dao/DomainRouterJoinDaoImpl.java     |  2 +-
 .../com/cloud/api/query/dao/HostJoinDaoImpl.java   | 10 +++++--
 .../api/query/dao/StoragePoolJoinDaoImpl.java      |  4 +--
 .../cloud/api/query/dao/TemplateJoinDaoImpl.java   |  4 +--
 .../com/cloud/api/query/dao/UserVmJoinDaoImpl.java |  2 +-
 .../com/cloud/api/query/dao/VolumeJoinDaoImpl.java | 12 +++++---
 .../configuration/ConfigurationManagerImpl.java    | 18 ++++++++++++
 .../com/cloud/hypervisor/HypervisorGuruBase.java   |  5 +++-
 .../discoverer/CustomServerDiscoverer.java         | 32 ++++++++++++++++++++++
 .../com/cloud/resource/ResourceManagerImpl.java    |  5 +++-
 .../com/cloud/server/ManagementServerImpl.java     |  6 ++--
 .../cloud/template/HypervisorTemplateAdapter.java  | 16 +++++++----
 .../com/cloud/template/TemplateManagerImpl.java    |  8 ++++--
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  | 23 +++++++++++++++-
 .../spring-server-discoverer-context.xml           |  5 ++++
 .../java/com/cloud/vm/UserVmManagerImplTest.java   | 29 ++++++++++++++++----
 ui/src/config/section/infra/hosts.js               |  4 ++-
 ui/src/store/getters.js                            |  3 +-
 ui/src/store/modules/user.js                       | 27 +++++++++++++++++-
 ui/src/views/image/RegisterOrUploadTemplate.vue    | 32 ++++++++++++++++++++--
 ui/src/views/infra/zone/ZoneWizardAddResources.vue |  7 +++--
 33 files changed, 271 insertions(+), 66 deletions(-)

diff --git a/api/src/main/java/com/cloud/hypervisor/Hypervisor.java 
b/api/src/main/java/com/cloud/hypervisor/Hypervisor.java
index 429de5774a4..2f0cc736af3 100644
--- a/api/src/main/java/com/cloud/hypervisor/Hypervisor.java
+++ b/api/src/main/java/com/cloud/hypervisor/Hypervisor.java
@@ -27,7 +27,7 @@ public class Hypervisor {
     static Map<String, HypervisorType> hypervisorTypeMap;
     static Map<HypervisorType, ImageFormat> supportedImageFormatMap;
 
-    public static enum HypervisorType {
+    public enum HypervisorType {
         None, //for storage hosts
         XenServer,
         KVM,
@@ -40,6 +40,7 @@ public class Hypervisor {
         Ovm,
         Ovm3,
         LXC,
+        Custom,
 
         Any; /*If you don't care about the hypervisor type*/
 
@@ -57,6 +58,7 @@ public class Hypervisor {
             hypervisorTypeMap.put("lxc", HypervisorType.LXC);
             hypervisorTypeMap.put("any", HypervisorType.Any);
             hypervisorTypeMap.put("ovm3", HypervisorType.Ovm3);
+            hypervisorTypeMap.put("custom", HypervisorType.Custom);
 
             supportedImageFormatMap = new HashMap<>();
             supportedImageFormatMap.put(HypervisorType.XenServer, 
ImageFormat.VHD);
@@ -68,7 +70,19 @@ public class Hypervisor {
 
         public static HypervisorType getType(String hypervisor) {
             return hypervisor == null ? HypervisorType.None :
-                    
hypervisorTypeMap.getOrDefault(hypervisor.toLowerCase(Locale.ROOT), 
HypervisorType.None);
+                    (hypervisor.toLowerCase(Locale.ROOT).equalsIgnoreCase(
+                            
HypervisorGuru.HypervisorCustomDisplayName.value()) ? Custom :
+                            
hypervisorTypeMap.getOrDefault(hypervisor.toLowerCase(Locale.ROOT), 
HypervisorType.None));
+        }
+
+        /**
+         * Returns the display name of a hypervisor type in case the custom 
hypervisor is used,
+         * using the 'hypervisor.custom.display.name' setting. Otherwise, 
returns hypervisor name
+         */
+        public String getHypervisorDisplayName() {
+            return !Hypervisor.HypervisorType.Custom.equals(this) ?
+                    this.toString() :
+                    HypervisorGuru.HypervisorCustomDisplayName.value();
         }
 
         /**
diff --git a/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java 
b/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java
index c7dc0bba109..2dfa707b57b 100644
--- a/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java
+++ b/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java
@@ -20,6 +20,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.cloudstack.backup.Backup;
+import org.apache.cloudstack.framework.config.ConfigKey;
 
 import com.cloud.agent.api.Command;
 import com.cloud.agent.api.to.NicTO;
@@ -35,6 +36,10 @@ import com.cloud.vm.VirtualMachineProfile;
 
 public interface HypervisorGuru extends Adapter {
 
+    ConfigKey<String> HypervisorCustomDisplayName = new 
ConfigKey<>(String.class,
+            "hypervisor.custom.display.name", ConfigKey.CATEGORY_ADVANCED, 
"Custom",
+            "Display name for custom hypervisor", true, 
ConfigKey.Scope.Global, null);
+
     HypervisorType getHypervisorType();
 
     /**
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java
index 1a5e851d884..5709e3ed900 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java
@@ -23,6 +23,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
+import com.cloud.hypervisor.HypervisorGuru;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.ApiConstants;
@@ -342,9 +343,11 @@ public class RegisterTemplateCmd extends BaseCmd 
implements UserCmd {
             throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
                     "Parameter zoneids cannot combine all zones (-1) option 
with other zones");
 
-        if (isDirectDownload() && 
!getHypervisor().equalsIgnoreCase(Hypervisor.HypervisorType.KVM.toString())) {
-            throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
-                    "Parameter directdownload is only allowed for KVM 
templates");
+        String customHypervisor = 
HypervisorGuru.HypervisorCustomDisplayName.value();
+        if (isDirectDownload() && 
!(getHypervisor().equalsIgnoreCase(Hypervisor.HypervisorType.KVM.toString())
+                || getHypervisor().equalsIgnoreCase(customHypervisor))) {
+            throw new ServerApiException(ApiErrorCode.PARAM_ERROR, 
String.format("Parameter directdownload " +
+                    "is only allowed for KVM or %s templates", 
customHypervisor));
         }
 
         if (!isDeployAsIs() && osTypeId == null) {
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/HostForMigrationResponse.java
 
b/api/src/main/java/org/apache/cloudstack/api/response/HostForMigrationResponse.java
index 4ed0cdd8d74..41a0fdc4567 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/response/HostForMigrationResponse.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/response/HostForMigrationResponse.java
@@ -24,7 +24,6 @@ import org.apache.cloudstack.api.EntityReference;
 
 import com.cloud.host.Host;
 import com.cloud.host.Status;
-import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.serializer.Param;
 import com.google.gson.annotations.SerializedName;
 
@@ -84,7 +83,7 @@ public class HostForMigrationResponse extends BaseResponse {
 
     @SerializedName(ApiConstants.HYPERVISOR)
     @Param(description = "the host hypervisor")
-    private HypervisorType hypervisor;
+    private String hypervisor;
 
     @SerializedName("cpunumber")
     @Param(description = "the CPU number of the host")
@@ -295,7 +294,7 @@ public class HostForMigrationResponse extends BaseResponse {
         this.version = version;
     }
 
-    public void setHypervisor(HypervisorType hypervisor) {
+    public void setHypervisor(String hypervisor) {
         this.hypervisor = hypervisor;
     }
 
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java 
b/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java
index 5d809cf1553..e1f1e5ee241 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java
@@ -29,7 +29,6 @@ import 
org.apache.cloudstack.outofbandmanagement.OutOfBandManagement;
 
 import com.cloud.host.Host;
 import com.cloud.host.Status;
-import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.serializer.Param;
 import com.google.gson.annotations.SerializedName;
 
@@ -89,7 +88,7 @@ public class HostResponse extends BaseResponseWithAnnotations 
{
 
     @SerializedName(ApiConstants.HYPERVISOR)
     @Param(description = "the host hypervisor")
-    private HypervisorType hypervisor;
+    private String hypervisor;
 
     @SerializedName("cpusockets")
     @Param(description = "the number of CPU sockets on the host")
@@ -335,7 +334,7 @@ public class HostResponse extends 
BaseResponseWithAnnotations {
         this.version = version;
     }
 
-    public void setHypervisor(HypervisorType hypervisor) {
+    public void setHypervisor(String hypervisor) {
         this.hypervisor = hypervisor;
     }
 
@@ -602,7 +601,7 @@ public class HostResponse extends 
BaseResponseWithAnnotations {
         return version;
     }
 
-    public HypervisorType getHypervisor() {
+    public String getHypervisor() {
         return hypervisor;
     }
 
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/HypervisorCapabilitiesResponse.java
 
b/api/src/main/java/org/apache/cloudstack/api/response/HypervisorCapabilitiesResponse.java
index b5e5624bbe5..c19397e0c83 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/response/HypervisorCapabilitiesResponse.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/response/HypervisorCapabilitiesResponse.java
@@ -20,7 +20,6 @@ import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.EntityReference;
 
-import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.hypervisor.HypervisorCapabilities;
 import com.cloud.serializer.Param;
 import com.google.gson.annotations.SerializedName;
@@ -37,7 +36,7 @@ public class HypervisorCapabilitiesResponse extends 
BaseResponse {
 
     @SerializedName(ApiConstants.HYPERVISOR)
     @Param(description = "the hypervisor type")
-    private HypervisorType hypervisor;
+    private String hypervisor;
 
     @SerializedName(ApiConstants.MAX_GUESTS_LIMIT)
     @Param(description = "the maximum number of guest vms recommended for this 
hypervisor")
@@ -83,11 +82,11 @@ public class HypervisorCapabilitiesResponse extends 
BaseResponse {
         this.hypervisorVersion = hypervisorVersion;
     }
 
-    public HypervisorType getHypervisor() {
+    public String getHypervisor() {
         return hypervisor;
     }
 
-    public void setHypervisor(HypervisorType hypervisor) {
+    public void setHypervisor(String hypervisor) {
         this.hypervisor = hypervisor;
     }
 
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/VMSnapshotResponse.java 
b/api/src/main/java/org/apache/cloudstack/api/response/VMSnapshotResponse.java
index 37670cf6224..9b553ed0744 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/response/VMSnapshotResponse.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/response/VMSnapshotResponse.java
@@ -25,7 +25,6 @@ import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.BaseResponseWithTagInformation;
 import org.apache.cloudstack.api.EntityReference;
 
-import com.cloud.hypervisor.Hypervisor;
 import com.cloud.serializer.Param;
 import com.cloud.vm.snapshot.VMSnapshot;
 import com.google.gson.annotations.SerializedName;
@@ -111,7 +110,7 @@ public class VMSnapshotResponse extends 
BaseResponseWithTagInformation implement
 
     @SerializedName(ApiConstants.HYPERVISOR)
     @Param(description = "the type of hypervisor on which snapshot is stored")
-    private Hypervisor.HypervisorType hypervisor;
+    private String hypervisor;
 
     public VMSnapshotResponse() {
         tags = new LinkedHashSet<ResourceTagResponse>();
@@ -266,11 +265,11 @@ public class VMSnapshotResponse extends 
BaseResponseWithTagInformation implement
         this.tags = tags;
     }
 
-    public Hypervisor.HypervisorType getHypervisor() {
+    public String getHypervisor() {
         return hypervisor;
     }
 
-    public void setHypervisor(Hypervisor.HypervisorType hypervisor) {
+    public void setHypervisor(String hypervisor) {
         this.hypervisor = hypervisor;
     }
 }
diff --git 
a/engine/schema/src/main/java/com/cloud/storage/GuestOSHypervisorVO.java 
b/engine/schema/src/main/java/com/cloud/storage/GuestOSHypervisorVO.java
index 087649b887a..e900d28a864 100644
--- a/engine/schema/src/main/java/com/cloud/storage/GuestOSHypervisorVO.java
+++ b/engine/schema/src/main/java/com/cloud/storage/GuestOSHypervisorVO.java
@@ -26,6 +26,7 @@ import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.Table;
 
+import com.cloud.hypervisor.Hypervisor;
 import com.cloud.utils.db.GenericDao;
 
 @Entity
@@ -72,7 +73,7 @@ public class GuestOSHypervisorVO implements GuestOSHypervisor 
{
 
     @Override
     public String getHypervisorType() {
-        return hypervisorType;
+        return Hypervisor.HypervisorType.getType(hypervisorType).toString();
     }
 
     @Override
diff --git a/engine/storage/integration-test/src/test/resources/component.xml 
b/engine/storage/integration-test/src/test/resources/component.xml
index aee37145114..d384d546665 100644
--- a/engine/storage/integration-test/src/test/resources/component.xml
+++ b/engine/storage/integration-test/src/test/resources/component.xml
@@ -121,6 +121,11 @@
     <property name="name" value="KVM Agent"/>
   </bean>
 
+  <bean id="CustomServerDiscoverer"
+        class="com.cloud.hypervisor.discoverer.CustomServerDiscoverer">
+    <property name="name" value="CustomHW Agent" />
+  </bean>
+
   <bean id="BareMetalDiscoverer" 
class="com.cloud.baremetal.BareMetalDiscoverer">
     <property name="name" value="Bare Metal Agent"/>
   </bean>
diff --git 
a/plugins/network-elements/dns-notifier/src/main/resources/components-example.xml
 
b/plugins/network-elements/dns-notifier/src/main/resources/components-example.xml
index c53c0b14ef1..76a6cad9b4a 100755
--- 
a/plugins/network-elements/dns-notifier/src/main/resources/components-example.xml
+++ 
b/plugins/network-elements/dns-notifier/src/main/resources/components-example.xml
@@ -111,6 +111,7 @@ under the License.
             <adapter name="XCP Agent" 
class="com.cloud.hypervisor.xenserver.discoverer.XcpServerDiscoverer"/>
             <adapter name="SecondaryStorage" 
class="com.cloud.storage.secondary.SecondaryStorageDiscoverer"/>
             <adapter name="KVM Agent" 
class="com.cloud.hypervisor.kvm.discoverer.KvmServerDiscoverer"/>
+            <adapter name="CustomHW Agent" 
class="com.cloud.hypervisor.discoverer.CustomServerDiscoverer"/>
             <adapter name="Bare Metal Agent" 
class="com.cloud.baremetal.BareMetalDiscoverer"/>
                        <adapter name="Ovm Discover" 
class="com.cloud.ovm.hypervisor.OvmDiscoverer" />
         </adapters>
diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java 
b/server/src/main/java/com/cloud/api/ApiDBUtils.java
index 162b1f38add..16377e13b03 100644
--- a/server/src/main/java/com/cloud/api/ApiDBUtils.java
+++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java
@@ -1283,6 +1283,9 @@ public class ApiDBUtils {
                   // If this check is not passed, the hypervisor type will 
remain OVM.
                   type = HypervisorType.KVM;
                   break;
+                } else if (pool.getHypervisor() == HypervisorType.Custom) {
+                    type = HypervisorType.Custom;
+                    break;
                 }
             }
         }
diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java 
b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
index 568625c856b..8cd80dd9a52 100644
--- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
@@ -38,6 +38,7 @@ import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
+import com.cloud.hypervisor.Hypervisor;
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.acl.ControlledEntity.ACLType;
 import org.apache.cloudstack.affinity.AffinityGroup;
@@ -735,7 +736,7 @@ public class ApiResponseHelper implements ResponseGenerator 
{
         if (vm != null) {
             vmSnapshotResponse.setVirtualMachineId(vm.getUuid());
             
vmSnapshotResponse.setVirtualMachineName(StringUtils.isEmpty(vm.getDisplayName())
 ? vm.getHostName() : vm.getDisplayName());
-            vmSnapshotResponse.setHypervisor(vm.getHypervisorType());
+            
vmSnapshotResponse.setHypervisor(vm.getHypervisorType().getHypervisorDisplayName());
             DataCenterVO datacenter = 
ApiDBUtils.findZoneById(vm.getDataCenterId());
             if (datacenter != null) {
                 vmSnapshotResponse.setZoneId(datacenter.getUuid());
@@ -1437,7 +1438,7 @@ public class ApiResponseHelper implements 
ResponseGenerator {
             clusterResponse.setZoneId(dc.getUuid());
             clusterResponse.setZoneName(dc.getName());
         }
-        
clusterResponse.setHypervisorType(cluster.getHypervisorType().toString());
+        
clusterResponse.setHypervisorType(cluster.getHypervisorType().getHypervisorDisplayName());
         clusterResponse.setClusterType(cluster.getClusterType().toString());
         
clusterResponse.setAllocationState(cluster.getAllocationState().toString());
         clusterResponse.setManagedState(cluster.getManagedState().toString());
@@ -1633,7 +1634,7 @@ public class ApiResponseHelper implements 
ResponseGenerator {
                 vmResponse.setTemplateName(template.getName());
             }
             vmResponse.setCreated(vm.getCreated());
-            vmResponse.setHypervisor(vm.getHypervisorType().toString());
+            
vmResponse.setHypervisor(vm.getHypervisorType().getHypervisorDisplayName());
 
             if (vm.getHostId() != null) {
                 Host host = ApiDBUtils.findHostById(vm.getHostId());
@@ -2792,7 +2793,7 @@ public class ApiResponseHelper implements 
ResponseGenerator {
     public HypervisorCapabilitiesResponse 
createHypervisorCapabilitiesResponse(HypervisorCapabilities hpvCapabilities) {
         HypervisorCapabilitiesResponse hpvCapabilitiesResponse = new 
HypervisorCapabilitiesResponse();
         hpvCapabilitiesResponse.setId(hpvCapabilities.getUuid());
-        
hpvCapabilitiesResponse.setHypervisor(hpvCapabilities.getHypervisorType());
+        
hpvCapabilitiesResponse.setHypervisor(hpvCapabilities.getHypervisorType().getHypervisorDisplayName());
         
hpvCapabilitiesResponse.setHypervisorVersion(hpvCapabilities.getHypervisorVersion());
         
hpvCapabilitiesResponse.setIsSecurityGroupEnabled(hpvCapabilities.isSecurityGroupEnabled());
         
hpvCapabilitiesResponse.setMaxGuestsLimit(hpvCapabilities.getMaxGuestsLimit());
@@ -3690,7 +3691,7 @@ public class ApiResponseHelper implements 
ResponseGenerator {
     public GuestOsMappingResponse 
createGuestOSMappingResponse(GuestOSHypervisor guestOSHypervisor) {
         GuestOsMappingResponse response = new GuestOsMappingResponse();
         response.setId(guestOSHypervisor.getUuid());
-        response.setHypervisor(guestOSHypervisor.getHypervisorType());
+        
response.setHypervisor(Hypervisor.HypervisorType.getType(guestOSHypervisor.getHypervisorType()).getHypervisorDisplayName());
         
response.setHypervisorVersion(guestOSHypervisor.getHypervisorVersion());
         response.setOsNameForHypervisor((guestOSHypervisor.getGuestOsName()));
         
response.setIsUserDefined(Boolean.valueOf(guestOSHypervisor.getIsUserDefined()).toString());
@@ -4940,7 +4941,7 @@ public class ApiResponseHelper implements 
ResponseGenerator {
         response.setId(certificate.getUuid());
         response.setAlias(certificate.getAlias());
         handleCertificateResponse(certificate.getCertificate(), response);
-        response.setHypervisor(certificate.getHypervisorType().name());
+        
response.setHypervisor(certificate.getHypervisorType().getHypervisorDisplayName());
         response.setObjectName("directdownloadcertificate");
         return response;
     }
diff --git 
a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java 
b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java
index 83a89622bd2..e3011bc4d66 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java
@@ -126,7 +126,7 @@ public class DomainRouterJoinDaoImpl extends 
GenericDaoBase<DomainRouterJoinVO,
         }
 
         if (router.getHypervisorType() != null) {
-            
routerResponse.setHypervisor(router.getHypervisorType().toString());
+            
routerResponse.setHypervisor(router.getHypervisorType().getHypervisorDisplayName());
         }
         
routerResponse.setHasAnnotation(annotationDao.hasAnnotations(router.getUuid(), 
AnnotationService.EntityType.VR.name(),
                 
_accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId())));
diff --git a/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java 
b/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java
index 7b52007e15e..bf3ded1fef0 100644
--- a/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java
@@ -125,7 +125,10 @@ public class HostJoinDaoImpl extends 
GenericDaoBase<HostJoinVO, Long> implements
         hostResponse.setCpuNumber(host.getCpus());
         hostResponse.setZoneId(host.getZoneUuid());
         hostResponse.setDisconnectedOn(host.getDisconnectedOn());
-        hostResponse.setHypervisor(host.getHypervisorType());
+        if (host.getHypervisorType() != null) {
+            String hypervisorType = 
host.getHypervisorType().getHypervisorDisplayName();
+            hostResponse.setHypervisor(hypervisorType);
+        }
         hostResponse.setHostType(host.getType());
         hostResponse.setLastPinged(new Date(host.getLastPinged()));
         Long mshostId = host.getManagementServerId();
@@ -239,7 +242,8 @@ public class HostJoinDaoImpl extends 
GenericDaoBase<HostJoinVO, Long> implements
                     hostResponse.setUefiCapabilty(new Boolean(false));
                 }
             }
-            if (details.contains(HostDetails.all) && host.getHypervisorType() 
== Hypervisor.HypervisorType.KVM) {
+            if (details.contains(HostDetails.all) && (host.getHypervisorType() 
== Hypervisor.HypervisorType.KVM ||
+                    host.getHypervisorType() == 
Hypervisor.HypervisorType.Custom)) {
                 //only kvm has the requirement to return host details
                 try {
                     hostResponse.setDetails(hostDetails);
@@ -303,7 +307,7 @@ public class HostJoinDaoImpl extends 
GenericDaoBase<HostJoinVO, Long> implements
         hostResponse.setCpuNumber(host.getCpus());
         hostResponse.setZoneId(host.getZoneUuid());
         hostResponse.setDisconnectedOn(host.getDisconnectedOn());
-        hostResponse.setHypervisor(host.getHypervisorType());
+        
hostResponse.setHypervisor(host.getHypervisorType().getHypervisorDisplayName());
         hostResponse.setHostType(host.getType());
         hostResponse.setLastPinged(new Date(host.getLastPinged()));
         hostResponse.setManagementServerId(host.getManagementServerId());
diff --git 
a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java 
b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
index 527cc949ed1..de469d21a11 100644
--- a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
@@ -110,7 +110,7 @@ public class StoragePoolJoinDaoImpl extends 
GenericDaoBase<StoragePoolJoinVO, Lo
             poolResponse.setScope(pool.getScope().toString());
         }
         if (pool.getHypervisor() != null) {
-            poolResponse.setHypervisor(pool.getHypervisor().toString());
+            
poolResponse.setHypervisor(pool.getHypervisor().getHypervisorDisplayName());
         }
 
         StoragePoolDetailVO poolType = 
storagePoolDetailsDao.findDetail(pool.getId(), "pool_type");
@@ -201,7 +201,7 @@ public class StoragePoolJoinDaoImpl extends 
GenericDaoBase<StoragePoolJoinVO, Lo
         poolResponse.setCreated(pool.getCreated());
         poolResponse.setScope(pool.getScope().toString());
         if (pool.getHypervisor() != null) {
-            poolResponse.setHypervisor(pool.getHypervisor().toString());
+            
poolResponse.setHypervisor(pool.getHypervisor().getHypervisorDisplayName());
         }
 
         long allocatedSize = pool.getUsedCapacity();
diff --git 
a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java 
b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java
index 8195592640d..f20a9aa2e13 100644
--- a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java
@@ -208,7 +208,7 @@ public class TemplateJoinDaoImpl extends 
GenericDaoBaseWithTagInformation<Templa
             
templateResponse.setTemplateType(template.getTemplateType().toString());
         }
 
-        
templateResponse.setHypervisor(template.getHypervisorType().toString());
+        
templateResponse.setHypervisor(template.getHypervisorType().getHypervisorDisplayName());
 
         templateResponse.setOsTypeId(template.getGuestOSUuid());
         templateResponse.setOsTypeName(template.getGuestOSName());
@@ -330,7 +330,7 @@ public class TemplateJoinDaoImpl extends 
GenericDaoBaseWithTagInformation<Templa
         response.setOsTypeId(result.getGuestOSUuid());
         response.setOsTypeName(result.getGuestOSName());
         response.setBootable(result.isBootable());
-        response.setHypervisor(result.getHypervisorType().toString());
+        
response.setHypervisor(result.getHypervisorType().getHypervisorDisplayName());
         response.setDynamicallyScalable(result.isDynamicallyScalable());
 
         // populate owner.
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 49489c2bfeb..b97ae779abc 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
@@ -128,7 +128,7 @@ public class UserVmJoinDaoImpl extends 
GenericDaoBaseWithTagInformation<UserVmJo
         UserVmResponse userVmResponse = new UserVmResponse();
 
         if (userVm.getHypervisorType() != null) {
-            
userVmResponse.setHypervisor(userVm.getHypervisorType().toString());
+            
userVmResponse.setHypervisor(userVm.getHypervisorType().getHypervisorDisplayName());
         }
         userVmResponse.setId(userVm.getUuid());
         userVmResponse.setName(userVm.getName());
diff --git 
a/server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java 
b/server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java
index 46a1b20384e..8fcad6ed45a 100644
--- a/server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java
@@ -21,6 +21,7 @@ import java.util.List;
 
 import javax.inject.Inject;
 
+import com.cloud.hypervisor.Hypervisor;
 import com.cloud.offering.DiskOffering;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
@@ -147,8 +148,10 @@ public class VolumeJoinDaoImpl extends 
GenericDaoBaseWithTagInformation<VolumeJo
             volResponse.setSize(volume.getVolumeStoreSize());
             volResponse.setCreated(volume.getCreatedOnStore());
 
-            if (view == ResponseView.Full)
-                
volResponse.setHypervisor(ApiDBUtils.getHypervisorTypeFromFormat(volume.getDataCenterId(),
 volume.getFormat()).toString());
+            if (view == ResponseView.Full) {
+                Hypervisor.HypervisorType hypervisorTypeFromFormat = 
ApiDBUtils.getHypervisorTypeFromFormat(volume.getDataCenterId(), 
volume.getFormat());
+                
volResponse.setHypervisor(hypervisorTypeFromFormat.getHypervisorDisplayName());
+            }
             if (volume.getDownloadState() != Status.DOWNLOADED) {
                 String volumeStatus = "Processing";
                 if (volume.getDownloadState() == Status.DOWNLOAD_IN_PROGRESS) {
@@ -209,9 +212,10 @@ public class VolumeJoinDaoImpl extends 
GenericDaoBaseWithTagInformation<VolumeJo
         if (view == ResponseView.Full) {
             if (volume.getState() != Volume.State.UploadOp) {
                 if (volume.getHypervisorType() != null) {
-                    
volResponse.setHypervisor(volume.getHypervisorType().toString());
+                    
volResponse.setHypervisor(volume.getHypervisorType().getHypervisorDisplayName());
                 } else {
-                    
volResponse.setHypervisor(ApiDBUtils.getHypervisorTypeFromFormat(volume.getDataCenterId(),
 volume.getFormat()).toString());
+                    Hypervisor.HypervisorType hypervisorTypeFromFormat = 
ApiDBUtils.getHypervisorTypeFromFormat(volume.getDataCenterId(), 
volume.getFormat());
+                    
volResponse.setHypervisor(hypervisorTypeFromFormat.getHypervisorDisplayName());
                 }
             }
             Long poolId = volume.getPoolId();
diff --git 
a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java 
b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
index 890fb1195e2..6722b0a1e8b 100644
--- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
+++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
@@ -46,6 +46,7 @@ import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
 
+import com.cloud.hypervisor.HypervisorGuru;
 import org.apache.cloudstack.acl.SecurityChecker;
 import org.apache.cloudstack.affinity.AffinityGroup;
 import org.apache.cloudstack.affinity.AffinityGroupService;
@@ -773,6 +774,7 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
         final TransactionLegacy txn = TransactionLegacy.currentTxn();
         txn.start();
 
+        String previousValue = _configDao.getValue(name);
         if (!_configDao.update(name, category, value)) {
             s_logger.error("Failed to update configuration option, name: " + 
name + ", value:" + value);
             throw new CloudRuntimeException("Failed to update configuration 
value. Please contact Cloud Support.");
@@ -853,6 +855,8 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
             } catch (final Throwable e) {
                 throw new CloudRuntimeException("Failed to clean up download 
URLs in template_store_ref or volume_store_ref due to exception ", e);
             }
+        } else if 
(HypervisorGuru.HypervisorCustomDisplayName.key().equals(name)) {
+            updateCustomDisplayNameOnHypervisorsList(previousValue, value);
         }
 
         txn.commit();
@@ -860,6 +864,20 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
         return _configDao.getValue(name);
     }
 
+    /**
+     * Updates the 'hypervisor.list' value to match the new custom hypervisor 
name set as newValue if the previous value was set
+     */
+    private void updateCustomDisplayNameOnHypervisorsList(String 
previousValue, String newValue) {
+        String hypervisorListConfigName = Config.HypervisorList.key();
+        String hypervisors = _configDao.getValue(hypervisorListConfigName);
+        if (Arrays.asList(hypervisors.split(",")).contains(previousValue)) {
+            hypervisors = hypervisors.replace(previousValue, newValue);
+            s_logger.info(String.format("Updating the hypervisor list 
configuration '%s' " +
+                    "to match the new custom hypervisor display name", 
hypervisorListConfigName));
+            _configDao.update(hypervisorListConfigName, hypervisors);
+        }
+    }
+
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_CONFIGURATION_VALUE_EDIT, 
eventDescription = "updating configuration")
     public Configuration updateConfiguration(final UpdateCfgCmd cmd) throws 
InvalidParameterValueException {
diff --git a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java 
b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java
index 37b430a4cfc..fb0702d2f47 100644
--- a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java
+++ b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java
@@ -360,7 +360,10 @@ public abstract class HypervisorGuruBase extends 
AdapterBase implements Hypervis
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[] 
{VmMinMemoryEqualsMemoryDividedByMemOverprovisioningFactor, 
VmMinCpuSpeedEqualsCpuSpeedDividedByCpuOverprovisioningFactor };
+        return new ConfigKey<?>[] 
{VmMinMemoryEqualsMemoryDividedByMemOverprovisioningFactor,
+                VmMinCpuSpeedEqualsCpuSpeedDividedByCpuOverprovisioningFactor,
+                HypervisorCustomDisplayName
+        };
     }
 
 }
diff --git 
a/server/src/main/java/com/cloud/hypervisor/discoverer/CustomServerDiscoverer.java
 
b/server/src/main/java/com/cloud/hypervisor/discoverer/CustomServerDiscoverer.java
new file mode 100644
index 00000000000..534947f092e
--- /dev/null
+++ 
b/server/src/main/java/com/cloud/hypervisor/discoverer/CustomServerDiscoverer.java
@@ -0,0 +1,32 @@
+// 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.hypervisor.discoverer;
+
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.hypervisor.kvm.discoverer.LibvirtServerDiscoverer;
+
+public class CustomServerDiscoverer extends LibvirtServerDiscoverer {
+    @Override
+    public Hypervisor.HypervisorType getHypervisorType() {
+        return Hypervisor.HypervisorType.Custom;
+    }
+
+    @Override
+    protected String getPatchPath() {
+        return "scripts/vm/hypervisor/kvm/";
+    }
+}
diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java 
b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java
index 150aed65a36..d1697646c39 100755
--- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java
+++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java
@@ -42,6 +42,7 @@ import com.cloud.exception.StorageUnavailableException;
 import com.cloud.storage.Volume;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.VolumeDao;
+import com.cloud.hypervisor.HypervisorGuru;
 import org.apache.cloudstack.alert.AlertService;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
@@ -652,7 +653,9 @@ public class ResourceManagerImpl extends ManagerBase 
implements ResourceManager,
             }
         }
 
-        return discoverHostsFull(dcId, podId, clusterId, clusterName, url, 
username, password, cmd.getHypervisor(), hostTags, cmd.getFullUrlParams(), 
false);
+        String hypervisorType = 
cmd.getHypervisor().equalsIgnoreCase(HypervisorGuru.HypervisorCustomDisplayName.value())
 ?
+                "Custom" : cmd.getHypervisor();
+        return discoverHostsFull(dcId, podId, clusterId, clusterName, url, 
username, password, hypervisorType, hostTags, cmd.getFullUrlParams(), false);
     }
 
     @Override
diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java 
b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
index c16dc4eb2f4..5791f4bf789 100644
--- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
@@ -1264,7 +1264,9 @@ public class ManagementServerImpl extends ManagerBase 
implements ManagementServe
         }
 
         if (hypervisorType != null) {
-            sc.setParameters("hypervisorType", hypervisorType);
+            String hypervisorStr = (String) hypervisorType;
+            String hypervisorSearch = 
HypervisorType.getType(hypervisorStr).toString();
+            sc.setParameters("hypervisorType", hypervisorSearch);
         }
 
         if (clusterType != null) {
@@ -4470,7 +4472,7 @@ public class ManagementServerImpl extends ManagerBase 
implements ManagementServe
             } else {
                 final List<ClusterVO> clustersForZone = 
_clusterDao.listByZoneId(zoneId);
                 for (final ClusterVO cluster : clustersForZone) {
-                    result.add(cluster.getHypervisorType().toString());
+                    
result.add(cluster.getHypervisorType().getHypervisorDisplayName());
                 }
             }
 
diff --git 
a/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java 
b/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java
index f5c19ecff1d..3f45505bcc5 100644
--- a/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java
+++ b/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java
@@ -148,20 +148,21 @@ public class HypervisorTemplateAdapter extends 
TemplateAdapterBase {
     }
 
     /**
-     * Validate on random running KVM host that URL is reachable
+     * Validate on random running host that URL is reachable
      * @param url url
      */
-    private Long performDirectDownloadUrlValidation(final String format, final 
String url, final List<Long> zoneIds) {
+    private Long performDirectDownloadUrlValidation(final String format, final 
Hypervisor.HypervisorType hypervisor,
+                                                    final String url, final 
List<Long> zoneIds) {
         HostVO host = null;
         if (zoneIds != null && !zoneIds.isEmpty()) {
             for (Long zoneId : zoneIds) {
-                host = 
resourceManager.findOneRandomRunningHostByHypervisor(Hypervisor.HypervisorType.KVM,
 zoneId);
+                host = 
resourceManager.findOneRandomRunningHostByHypervisor(hypervisor, zoneId);
                 if (host != null) {
                     break;
                 }
             }
         } else {
-            host = 
resourceManager.findOneRandomRunningHostByHypervisor(Hypervisor.HypervisorType.KVM,
 null);
+            host = 
resourceManager.findOneRandomRunningHostByHypervisor(hypervisor, null);
         }
 
         if (host == null) {
@@ -198,7 +199,8 @@ public class HypervisorTemplateAdapter extends 
TemplateAdapterBase {
                 zoneIds =  new ArrayList<>();
                 zoneIds.add(cmd.getZoneId());
             }
-            Long templateSize = 
performDirectDownloadUrlValidation(ImageFormat.ISO.getFileExtension(), url, 
zoneIds);
+            Long templateSize = 
performDirectDownloadUrlValidation(ImageFormat.ISO.getFileExtension(),
+                    Hypervisor.HypervisorType.KVM, url, zoneIds);
             profile.setSize(templateSize);
         }
         profile.setUrl(url);
@@ -221,9 +223,11 @@ public class HypervisorTemplateAdapter extends 
TemplateAdapterBase {
         TemplateProfile profile = super.prepare(cmd);
         String url = profile.getUrl();
         UriUtils.validateUrl(cmd.getFormat(), url, cmd.isDirectDownload());
+        Hypervisor.HypervisorType hypervisor = 
Hypervisor.HypervisorType.getType(cmd.getHypervisor());
         if (cmd.isDirectDownload()) {
             DigestHelper.validateChecksumString(cmd.getChecksum());
-            Long templateSize = 
performDirectDownloadUrlValidation(cmd.getFormat(), url, cmd.getZoneIds());
+            Long templateSize = 
performDirectDownloadUrlValidation(cmd.getFormat(),
+                    hypervisor, url, cmd.getZoneIds());
             profile.setSize(templateSize);
         }
         profile.setUrl(url);
diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java 
b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java
index 1a360c88edb..790d9163bf0 100755
--- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java
+++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java
@@ -309,8 +309,12 @@ public class TemplateManagerImpl extends ManagerBase 
implements TemplateManager,
         if (type == HypervisorType.BareMetal) {
             adapter = AdapterBase.getAdapterByName(_adapters, 
TemplateAdapterType.BareMetal.getName());
         } else {
-            // see HypervisorTemplateAdapter
-            adapter = AdapterBase.getAdapterByName(_adapters, 
TemplateAdapterType.Hypervisor.getName());
+            // Get template adapter according to hypervisor
+            adapter = AdapterBase.getAdapterByName(_adapters, type.name());
+            // Otherwise, default to generic hypervisor template adapter
+            if (adapter == null) {
+                adapter = AdapterBase.getAdapterByName(_adapters, 
TemplateAdapterType.Hypervisor.getName());
+            }
         }
 
         if (adapter == null) {
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java 
b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index 1dd6d6541e9..3ddaf6b58d3 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -650,6 +650,14 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
             HypervisorType.Simulator
     ));
 
+    protected static final List<HypervisorType> 
ROOT_DISK_SIZE_OVERRIDE_SUPPORTING_HYPERVISORS = Arrays.asList(
+            HypervisorType.KVM,
+            HypervisorType.XenServer,
+            HypervisorType.VMware,
+            HypervisorType.Simulator,
+            HypervisorType.Custom
+    );
+
     private static final List<HypervisorType> 
HYPERVISORS_THAT_CAN_DO_STORAGE_MIGRATION_ON_NON_USER_VMS = 
Arrays.asList(HypervisorType.KVM, HypervisorType.VMware);
 
     @Override
@@ -4338,7 +4346,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
      * @throws InvalidParameterValueException if the hypervisor does not 
support rootdisksize override
      */
     protected void 
verifyIfHypervisorSupportsRootdiskSizeOverride(HypervisorType hypervisorType) {
-        if (!(hypervisorType == HypervisorType.KVM || hypervisorType == 
HypervisorType.XenServer || hypervisorType == HypervisorType.VMware || 
hypervisorType == HypervisorType.Simulator)) {
+        if 
(!ROOT_DISK_SIZE_OVERRIDE_SUPPORTING_HYPERVISORS.contains(hypervisorType)) {
             throw new InvalidParameterValueException("Hypervisor " + 
hypervisorType + " does not support rootdisksize override");
         }
     }
@@ -5004,6 +5012,8 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         Answer startAnswer = cmds.getAnswer(StartAnswer.class);
         String returnedIp = null;
         String originalIp = null;
+        String originalVncPassword = 
profile.getVirtualMachine().getVncPassword();
+        String returnedVncPassword = null;
         if (startAnswer != null) {
             StartAnswer startAns = (StartAnswer)startAnswer;
             VirtualMachineTO vmTO = startAns.getVirtualMachine();
@@ -5012,6 +5022,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
                     returnedIp = nicTO.getIp();
                 }
             }
+            returnedVncPassword = vmTO.getVncPassword();
         }
 
         List<NicVO> nics = _nicDao.listByVmId(vm.getId());
@@ -5063,6 +5074,8 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
             }
         }
 
+        updateVncPasswordIfItHasChanged(originalVncPassword, 
returnedVncPassword, profile);
+
         // get system ip and create static nat rule for the vm
         try {
             
_rulesMgr.getSystemIpAndEnableStaticNatForVm(profile.getVirtualMachine(), 
false);
@@ -5097,6 +5110,14 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         return true;
     }
 
+    protected void updateVncPasswordIfItHasChanged(String originalVncPassword, 
String returnedVncPassword, VirtualMachineProfile profile) {
+        if (returnedVncPassword != null && 
!originalVncPassword.equals(returnedVncPassword)) {
+            UserVmVO userVm = _vmDao.findById(profile.getId());
+            userVm.setVncPassword(returnedVncPassword);
+            _vmDao.update(userVm.getId(), userVm);
+        }
+    }
+
     @Override
     public void finalizeExpunge(VirtualMachine vm) {
     }
diff --git 
a/server/src/main/resources/META-INF/cloudstack/server-discoverer/spring-server-discoverer-context.xml
 
b/server/src/main/resources/META-INF/cloudstack/server-discoverer/spring-server-discoverer-context.xml
index 3a7e0ffb04a..98abb08ae11 100644
--- 
a/server/src/main/resources/META-INF/cloudstack/server-discoverer/spring-server-discoverer-context.xml
+++ 
b/server/src/main/resources/META-INF/cloudstack/server-discoverer/spring-server-discoverer-context.xml
@@ -38,6 +38,11 @@
         <property name="name" value="Lxc Discover" />
     </bean>
 
+    <bean id="CustomServerDiscoverer"
+          class="com.cloud.hypervisor.discoverer.CustomServerDiscoverer">
+        <property name="name" value="CustomHW Agent" />
+    </bean>
+
     <bean id="dummyHostDiscoverer" 
class="com.cloud.resource.DummyHostDiscoverer">
         <property name="name" value="dummyHostDiscoverer" />
     </bean>
diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java 
b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
index ef001906d98..9c591690bbb 100644
--- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
+++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
@@ -221,6 +221,9 @@ public class UserVmManagerImplTest {
     @Mock
     UserDataManager userDataManager;
 
+    @Mock
+    VirtualMachineProfile virtualMachineProfile;
+
     private static final long vmId = 1l;
     private static final long zoneId = 2L;
     private static final long accountId = 3L;
@@ -248,6 +251,8 @@ public class UserVmManagerImplTest {
         customParameters.put(VmDetailConstants.ROOT_DISK_SIZE, "123");
         
lenient().doNothing().when(resourceLimitMgr).incrementResourceCount(anyLong(), 
any(Resource.ResourceType.class));
         
lenient().doNothing().when(resourceLimitMgr).decrementResourceCount(anyLong(), 
any(Resource.ResourceType.class), anyLong());
+
+        Mockito.when(virtualMachineProfile.getId()).thenReturn(vmId);
     }
 
     @After
@@ -561,13 +566,10 @@ public class UserVmManagerImplTest {
     public void verifyIfHypervisorSupportRootdiskSizeOverrideTest() {
         Hypervisor.HypervisorType[] hypervisorTypeArray = 
Hypervisor.HypervisorType.values();
         int exceptionCounter = 0;
-        int expectedExceptionCounter = hypervisorTypeArray.length - 4;
+        int expectedExceptionCounter = hypervisorTypeArray.length - 5;
 
         for(int i = 0; i < hypervisorTypeArray.length; i++) {
-            if (Hypervisor.HypervisorType.KVM == hypervisorTypeArray[i]
-                    || Hypervisor.HypervisorType.XenServer == 
hypervisorTypeArray[i]
-                    || Hypervisor.HypervisorType.VMware == 
hypervisorTypeArray[i]
-                    || Hypervisor.HypervisorType.Simulator == 
hypervisorTypeArray[i]) {
+            if 
(UserVmManagerImpl.ROOT_DISK_SIZE_OVERRIDE_SUPPORTING_HYPERVISORS.contains(hypervisorTypeArray[i]))
 {
                 
userVmManagerImpl.verifyIfHypervisorSupportsRootdiskSizeOverride(hypervisorTypeArray[i]);
             } else {
                 try {
@@ -1041,4 +1043,21 @@ public class UserVmManagerImplTest {
         Pair<VMInstanceVO, Host> pair = 
mockObjectsForChooseVmMigrationDestinationUsingVolumePoolMapTest(false, 
destinationHost);
         Assert.assertEquals(destinationHost, 
userVmManagerImpl.chooseVmMigrationDestinationUsingVolumePoolMap(pair.first(), 
pair.second(), null));
     }
+
+    @Test
+    public void testUpdateVncPasswordIfItHasChanged() {
+        String vncPassword = "12345678";
+        userVmManagerImpl.updateVncPasswordIfItHasChanged(vncPassword, 
vncPassword, virtualMachineProfile);
+        Mockito.verify(userVmDao, Mockito.never()).update(vmId, userVmVoMock);
+    }
+
+    @Test
+    public void testUpdateVncPasswordIfItHasChangedNewPassword() {
+        String vncPassword = "12345678";
+        String newPassword = "87654321";
+        Mockito.when(userVmVoMock.getId()).thenReturn(vmId);
+        userVmManagerImpl.updateVncPasswordIfItHasChanged(vncPassword, 
newPassword, virtualMachineProfile);
+        Mockito.verify(userVmDao).findById(vmId);
+        Mockito.verify(userVmDao).update(vmId, userVmVoMock);
+    }
 }
diff --git a/ui/src/config/section/infra/hosts.js 
b/ui/src/config/section/infra/hosts.js
index 60f30e3f3e0..22a9afc79cd 100644
--- a/ui/src/config/section/infra/hosts.js
+++ b/ui/src/config/section/infra/hosts.js
@@ -81,7 +81,9 @@ export default {
       label: 'label.action.secure.host',
       message: 'message.action.secure.host',
       dataView: true,
-      show: (record) => { return record.hypervisor === 'KVM' },
+      show: (record) => {
+        return record.hypervisor === 'KVM' || record.hypervisor === 
store.getters.customHypervisorName
+      },
       args: ['hostid'],
       mapping: {
         hostid: {
diff --git a/ui/src/store/getters.js b/ui/src/store/getters.js
index d795e3318c1..e6f02028ab1 100644
--- a/ui/src/store/getters.js
+++ b/ui/src/store/getters.js
@@ -48,7 +48,8 @@ const getters = {
   twoFaEnabled: state => state.user.twoFaEnabled,
   twoFaProvider: state => state.user.twoFaProvider,
   twoFaIssuer: state => state.user.twoFaIssuer,
-  loginFlag: state => state.user.loginFlag
+  loginFlag: state => state.user.loginFlag,
+  customHypervisorName: state => state.user.customHypervisorName
 }
 
 export default getters
diff --git a/ui/src/store/modules/user.js b/ui/src/store/modules/user.js
index 051380231a2..91861a87a41 100644
--- a/ui/src/store/modules/user.js
+++ b/ui/src/store/modules/user.js
@@ -64,7 +64,8 @@ const user = {
     shutdownTriggered: false,
     twoFaEnabled: false,
     twoFaProvider: '',
-    twoFaIssuer: ''
+    twoFaIssuer: '',
+    customHypervisorName: 'Custom'
   },
 
   mutations: {
@@ -151,6 +152,9 @@ const user = {
     },
     SET_LOGIN_FLAG: (state, flag) => {
       state.loginFlag = flag
+    },
+    SET_CUSTOM_HYPERVISOR_NAME (state, name) {
+      state.customHypervisorName = name
     }
   },
 
@@ -298,6 +302,15 @@ const user = {
           commit('SET_CLOUDIAN', cloudian)
         }).catch(ignored => {
         })
+
+        api('listConfigurations', { name: 'hypervisor.custom.display.name' 
}).then(json => {
+          if (json.listconfigurationsresponse.configuration !== null) {
+            const config = json.listconfigurationsresponse.configuration[0]
+            commit('SET_CUSTOM_HYPERVISOR_NAME', config.value)
+          }
+        }).catch(error => {
+          reject(error)
+        })
       })
     },
 
@@ -391,6 +404,15 @@ const user = {
         }).catch(error => {
           reject(error)
         })
+
+        api('listConfigurations', { name: 'hypervisor.custom.display.name' 
}).then(json => {
+          if (json.listconfigurationsresponse.configuration !== null) {
+            const config = json.listconfigurationsresponse.configuration[0]
+            commit('SET_CUSTOM_HYPERVISOR_NAME', config.value)
+          }
+        }).catch(error => {
+          reject(error)
+        })
       })
     },
     UpdateConfiguration ({ commit }) {
@@ -411,6 +433,9 @@ const user = {
     },
     SetLoginFlag ({ commit }, loggedIn) {
       commit('SET_LOGIN_FLAG', loggedIn)
+    },
+    SetCustomHypervisorName ({ commit }, name) {
+      commit('SET_CUSTOM_HYPERVISOR_NAME', name)
     }
   }
 }
diff --git a/ui/src/views/image/RegisterOrUploadTemplate.vue 
b/ui/src/views/image/RegisterOrUploadTemplate.vue
index 4ed33afce9a..06ca207a0ab 100644
--- a/ui/src/views/image/RegisterOrUploadTemplate.vue
+++ b/ui/src/views/image/RegisterOrUploadTemplate.vue
@@ -154,7 +154,7 @@
             </a-form-item>
           </a-col>
         </a-row>
-        <a-row :gutter="12" v-if="allowed && hyperKVMShow && currentForm !== 
'Upload'">
+        <a-row :gutter="12" v-if="allowed && (hyperKVMShow || hyperCustomShow) 
&& currentForm !== 'Upload'">
           <a-col :md="24" :lg="12">
             <a-form-item ref="directdownload" name="directdownload" 
:label="$t('label.directdownload')">
               <a-switch v-model:checked="form.directdownload" 
@change="handleChangeDirect" />
@@ -398,6 +398,7 @@ export default {
       userdatapolicylist: {},
       defaultOsId: null,
       hyperKVMShow: false,
+      hyperCustomShow: false,
       hyperXenServerShow: false,
       hyperVMWShow: false,
       selectedFormat: '',
@@ -408,7 +409,8 @@ export default {
       allowed: false,
       allowDirectDownload: false,
       uploadParams: null,
-      currentForm: ['plus-outlined', 
'PlusOutlined'].includes(this.action.currentAction.icon) ? 'Create' : 'Upload'
+      currentForm: ['plus-outlined', 
'PlusOutlined'].includes(this.action.currentAction.icon) ? 'Create' : 'Upload',
+      customHypervisorName: 'Custom'
     }
   },
   beforeCreate () {
@@ -456,6 +458,7 @@ export default {
       })
     },
     fetchData () {
+      this.fetchCustomHypervisorName()
       this.fetchZone()
       this.fetchOsTypes()
       this.fetchUserData()
@@ -516,6 +519,23 @@ export default {
         })
       })
     },
+    fetchCustomHypervisorName () {
+      const params = {
+        name: 'hypervisor.custom.display.name'
+      }
+      this.loading = true
+      api('listConfigurations', params).then(json => {
+        if (json.listconfigurationsresponse.configuration !== null) {
+          const config = json.listconfigurationsresponse.configuration[0]
+          if (config && config.name === params.name) {
+            this.customHypervisorName = config.value
+            store.dispatch('SetCustomHypervisorName', 
this.customHypervisorName)
+          }
+        }
+      }).finally(() => {
+        this.loading = false
+      })
+    },
     fetchZone () {
       const params = {}
       let listZones = []
@@ -781,6 +801,13 @@ export default {
             description: 'TAR'
           })
           break
+        case this.customHypervisorName:
+          this.hyperCustomShow = true
+          format.push({
+            id: 'RAW',
+            description: 'RAW'
+          })
+          break
         default:
           break
       }
@@ -839,6 +866,7 @@ export default {
       this.hyperXenServerShow = false
       this.hyperVMWShow = false
       this.hyperKVMShow = false
+      this.hyperCustomShow = false
       this.deployasis = false
       this.allowDirectDownload = false
       this.selectedFormat = null
diff --git a/ui/src/views/infra/zone/ZoneWizardAddResources.vue 
b/ui/src/views/infra/zone/ZoneWizardAddResources.vue
index 4c919af94ed..24ddd264486 100644
--- a/ui/src/views/infra/zone/ZoneWizardAddResources.vue
+++ b/ui/src/views/infra/zone/ZoneWizardAddResources.vue
@@ -108,6 +108,7 @@ import { nextTick } from 'vue'
 import { api } from '@/api'
 import { mixinDevice } from '@/utils/mixin.js'
 import StaticInputsForm from '@views/infra/zone/StaticInputsForm'
+import store from '@/store'
 
 export default {
   components: {
@@ -283,7 +284,7 @@ export default {
           placeHolder: 'message.error.host.name',
           required: true,
           display: {
-            hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 
'XenServer', 'LXC', 'Simulator']
+            hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 
'XenServer', 'LXC', 'Simulator', store.getters.customHypervisorName]
           }
         },
         {
@@ -292,7 +293,7 @@ export default {
           placeHolder: 'message.error.host.username',
           required: true,
           display: {
-            hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 
'XenServer', 'LXC', 'Simulator']
+            hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 
'XenServer', 'LXC', 'Simulator', store.getters.customHypervisorName]
           }
         },
         {
@@ -329,7 +330,7 @@ export default {
           required: true,
           password: true,
           display: {
-            hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 
'XenServer', 'LXC', 'Simulator'],
+            hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 
'XenServer', 'LXC', 'Simulator', store.getters.customHypervisorName],
             authmethod: 'password'
           }
         },

Reply via email to