From 8b5ba13b817e7d53beb0e0012c27d065e96a708c Mon Sep 17 00:00:00 2001 From: Nicolas Vazquez Date: Wed, 16 Aug 2023 12:23:24 -0300 Subject: [PATCH] plugins: Add Custom hypervisor minimal changes (#7692) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### 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 --- .../java/com/cloud/hypervisor/Hypervisor.java | 18 +++++++++-- .../com/cloud/hypervisor/HypervisorGuru.java | 5 +++ .../user/template/RegisterTemplateCmd.java | 9 ++++-- .../response/HostForMigrationResponse.java | 5 ++- .../cloudstack/api/response/HostResponse.java | 7 ++-- .../HypervisorCapabilitiesResponse.java | 7 ++-- .../api/response/VMSnapshotResponse.java | 7 ++-- .../cloud/storage/GuestOSHypervisorVO.java | 3 +- .../src/test/resources/component.xml | 5 +++ .../src/main/resources/components-example.xml | 1 + .../main/java/com/cloud/api/ApiDBUtils.java | 3 ++ .../java/com/cloud/api/ApiResponseHelper.java | 13 ++++---- .../query/dao/DomainRouterJoinDaoImpl.java | 2 +- .../cloud/api/query/dao/HostJoinDaoImpl.java | 10 ++++-- .../api/query/dao/StoragePoolJoinDaoImpl.java | 4 +-- .../api/query/dao/TemplateJoinDaoImpl.java | 4 +-- .../api/query/dao/UserVmJoinDaoImpl.java | 2 +- .../api/query/dao/VolumeJoinDaoImpl.java | 12 ++++--- .../ConfigurationManagerImpl.java | 18 +++++++++++ .../cloud/hypervisor/HypervisorGuruBase.java | 5 ++- .../discoverer/CustomServerDiscoverer.java | 32 +++++++++++++++++++ .../cloud/resource/ResourceManagerImpl.java | 5 ++- .../cloud/server/ManagementServerImpl.java | 6 ++-- .../template/HypervisorTemplateAdapter.java | 16 ++++++---- .../cloud/template/TemplateManagerImpl.java | 8 +++-- .../java/com/cloud/vm/UserVmManagerImpl.java | 23 ++++++++++++- .../spring-server-discoverer-context.xml | 5 +++ .../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 +++++++++++++++- .../views/image/RegisterOrUploadTemplate.vue | 32 +++++++++++++++++-- .../infra/zone/ZoneWizardAddResources.vue | 7 ++-- 33 files changed, 271 insertions(+), 66 deletions(-) create mode 100644 server/src/main/java/com/cloud/hypervisor/discoverer/CustomServerDiscoverer.java 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 hypervisorTypeMap; static Map 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 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(); @@ -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 @@ + + + + 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. + 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 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 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 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[] 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 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 zoneIds) { + private Long performDirectDownloadUrlValidation(final String format, final Hypervisor.HypervisorType hypervisor, + final String url, final List 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 ROOT_DISK_SIZE_OVERRIDE_SUPPORTING_HYPERVISORS = Arrays.asList( + HypervisorType.KVM, + HypervisorType.XenServer, + HypervisorType.VMware, + HypervisorType.Simulator, + HypervisorType.Custom + ); + private static final List 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 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 @@ + + + + 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 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 @@ - + @@ -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' } },