Skip to content

Commit

Permalink
plugins: Add Custom hypervisor minimal changes (apache#7692)
Browse files Browse the repository at this point in the history
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)

Replace the HypervisorType from a fixed enum to an extensible registry mechanism, registered from the hypervisor plugin

- 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.

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

(cherry picked from commit 8b5ba13)
Signed-off-by: Rohit Yadav <[email protected]>
  • Loading branch information
nvazquez authored and rohityadavcloud committed Sep 27, 2023
1 parent fe50018 commit 4dfa38a
Show file tree
Hide file tree
Showing 33 changed files with 271 additions and 66 deletions.
18 changes: 16 additions & 2 deletions api/src/main/java/com/cloud/hypervisor/Hypervisor.java
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -40,6 +40,7 @@ public static enum HypervisorType {
Ovm,
Ovm3,
LXC,
Custom,

Any; /*If you don't care about the hypervisor type*/

Expand All @@ -57,6 +58,7 @@ public static enum HypervisorType {
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);
Expand All @@ -68,7 +70,19 @@ public static enum HypervisorType {

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();
}

/**
Expand Down
5 changes: 5 additions & 0 deletions api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand All @@ -35,6 +36,10 @@

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();

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;
Expand Down Expand Up @@ -342,9 +343,11 @@ protected void validateParameters() {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

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;

Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -295,7 +294,7 @@ public void setVersion(String version) {
this.version = version;
}

public void setHypervisor(HypervisorType hypervisor) {
public void setHypervisor(String hypervisor) {
this.hypervisor = hypervisor;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@

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;

Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -335,7 +334,7 @@ public void setVersion(String version) {
this.version = version;
}

public void setHypervisor(HypervisorType hypervisor) {
public void setHypervisor(String hypervisor) {
this.hypervisor = hypervisor;
}

Expand Down Expand Up @@ -602,7 +601,7 @@ public String getVersion() {
return version;
}

public HypervisorType getHypervisor() {
public String getHypervisor() {
return hypervisor;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
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;
Expand All @@ -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")
Expand Down Expand Up @@ -83,11 +82,11 @@ public void setHypervisorVersion(String hypervisorVersion) {
this.hypervisorVersion = hypervisorVersion;
}

public HypervisorType getHypervisor() {
public String getHypervisor() {
return hypervisor;
}

public void setHypervisor(HypervisorType hypervisor) {
public void setHypervisor(String hypervisor) {
this.hypervisor = hypervisor;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
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;
Expand Down Expand Up @@ -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>();
Expand Down Expand Up @@ -266,11 +265,11 @@ public void setTags(Set<ResourceTagResponse> tags) {
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import javax.persistence.Id;
import javax.persistence.Table;

import com.cloud.hypervisor.Hypervisor;
import com.cloud.utils.db.GenericDao;

@Entity
Expand Down Expand Up @@ -72,7 +73,7 @@ public String getHypervisorVersion() {

@Override
public String getHypervisorType() {
return hypervisorType;
return Hypervisor.HypervisorType.getType(hypervisorType).toString();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
3 changes: 3 additions & 0 deletions server/src/main/java/com/cloud/api/ApiDBUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,9 @@ public static HypervisorType getHypervisorTypeFromFormat(long dcId, ImageFormat
// 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;
}
}
}
Expand Down
13 changes: 7 additions & 6 deletions server/src/main/java/com/cloud/api/ApiResponseHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

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;
Expand Down Expand Up @@ -730,7 +731,7 @@ public VMSnapshotResponse createVMSnapshotResponse(VMSnapshot vmSnapshot) {
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());
Expand Down Expand Up @@ -1393,7 +1394,7 @@ public ClusterResponse createClusterResponse(Cluster cluster, Boolean showCapaci
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());
Expand Down Expand Up @@ -1589,7 +1590,7 @@ public SystemVmResponse createSystemVmResponse(VirtualMachine vm) {
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());
Expand Down Expand Up @@ -2752,7 +2753,7 @@ public NetworkACLItemResponse createNetworkACLItemResponse(NetworkACLItem aclIte
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());
Expand Down Expand Up @@ -3660,7 +3661,7 @@ public GuestOSResponse createGuestOSResponse(GuestOS guestOS) {
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());
Expand Down Expand Up @@ -4888,7 +4889,7 @@ public DirectDownloadCertificateResponse createDirectDownloadCertificateResponse
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public DomainRouterResponse newDomainRouterResponse(DomainRouterJoinVO router, A
}

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())));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,10 @@ public HostResponse newHostResponse(HostJoinVO host, EnumSet<HostDetails> detail
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();
Expand Down Expand Up @@ -239,7 +242,8 @@ public HostResponse newHostResponse(HostJoinVO host, EnumSet<HostDetails> detail
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);
Expand Down Expand Up @@ -303,7 +307,7 @@ public HostForMigrationResponse newHostForMigrationResponse(HostJoinVO host, Enu
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public StoragePoolResponse newStoragePoolResponse(StoragePoolJoinVO pool) {
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");
Expand Down Expand Up @@ -201,7 +201,7 @@ public StoragePoolResponse newStoragePoolForMigrationResponse(StoragePoolJoinVO
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ public TemplateResponse newTemplateResponse(EnumSet<ApiConstants.DomainDetails>
templateResponse.setTemplateType(template.getTemplateType().toString());
}

templateResponse.setHypervisor(template.getHypervisorType().toString());
templateResponse.setHypervisor(template.getHypervisorType().getHypervisorDisplayName());

templateResponse.setOsTypeId(template.getGuestOSUuid());
templateResponse.setOsTypeName(template.getGuestOSName());
Expand Down Expand Up @@ -330,7 +330,7 @@ public TemplateResponse newUpdateResponse(TemplateJoinVO result) {
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.
Expand Down
Loading

0 comments on commit 4dfa38a

Please sign in to comment.