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
This commit is contained in:
Nicolas Vazquez 2023-08-16 12:23:24 -03:00 committed by GitHub
parent fe70f4d801
commit 8b5ba13b81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 271 additions and 66 deletions

View File

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

View File

@ -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();
/**

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

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

View File

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

View File

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

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {

View File

@ -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
};
}
}

View File

@ -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/";
}
}

View File

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

View File

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

View File

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

View File

@ -309,9 +309,13 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
if (type == HypervisorType.BareMetal) {
adapter = AdapterBase.getAdapterByName(_adapters, TemplateAdapterType.BareMetal.getName());
} else {
// see HypervisorTemplateAdapter
// 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) {
throw new CloudRuntimeException("Cannot find template adapter for " + type.toString());

View File

@ -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) {
}

View File

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

View File

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

View File

@ -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: {

View File

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

View File

@ -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)
}
}
}

View File

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

View File

@ -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'
}
},