mirror of https://github.com/apache/cloudstack.git
Feature: Allow adding delete protection for VMs & volumes (#9633)
Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com>
This commit is contained in:
parent
f8d8a9c7b3
commit
1303a4f323
|
|
@ -271,11 +271,13 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
|
|||
|
||||
void setExternalUuid(String externalUuid);
|
||||
|
||||
public Long getPassphraseId();
|
||||
Long getPassphraseId();
|
||||
|
||||
public void setPassphraseId(Long id);
|
||||
void setPassphraseId(Long id);
|
||||
|
||||
public String getEncryptFormat();
|
||||
String getEncryptFormat();
|
||||
|
||||
public void setEncryptFormat(String encryptFormat);
|
||||
void setEncryptFormat(String encryptFormat);
|
||||
|
||||
boolean isDeleteProtection();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,9 @@ public interface VolumeApiService {
|
|||
|
||||
Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, List<Long> zoneIds) throws ResourceAllocationException;
|
||||
|
||||
Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, String customId, long owner, String chainInfo, String name);
|
||||
Volume updateVolume(long volumeId, String path, String state, Long storageId,
|
||||
Boolean displayVolume, Boolean deleteProtection,
|
||||
String customId, long owner, String chainInfo, String name);
|
||||
|
||||
/**
|
||||
* Extracts the volume to a particular location.
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ public class ApiConstants {
|
|||
public static final String DATACENTER_NAME = "datacentername";
|
||||
public static final String DATADISK_OFFERING_LIST = "datadiskofferinglist";
|
||||
public static final String DEFAULT_VALUE = "defaultvalue";
|
||||
public static final String DELETE_PROTECTION = "deleteprotection";
|
||||
public static final String DESCRIPTION = "description";
|
||||
public static final String DESTINATION = "destination";
|
||||
public static final String DESTINATION_ZONE_ID = "destzoneid";
|
||||
|
|
|
|||
|
|
@ -146,6 +146,14 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction,
|
|||
@Parameter(name = ApiConstants.EXTRA_CONFIG, type = CommandType.STRING, since = "4.12", description = "an optional URL encoded string that can be passed to the virtual machine upon successful deployment", length = 5120)
|
||||
private String extraConfig;
|
||||
|
||||
@Parameter(name = ApiConstants.DELETE_PROTECTION,
|
||||
type = CommandType.BOOLEAN, since = "4.20.0",
|
||||
description = "Set delete protection for the virtual machine. If " +
|
||||
"true, the instance will be protected from deletion. " +
|
||||
"Note: If the instance is managed by another service like" +
|
||||
" autoscaling groups or CKS, delete protection will be ignored.")
|
||||
private Boolean deleteProtection;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -215,6 +223,10 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction,
|
|||
return cleanupDetails == null ? false : cleanupDetails.booleanValue();
|
||||
}
|
||||
|
||||
public Boolean getDeleteProtection() {
|
||||
return deleteProtection;
|
||||
}
|
||||
|
||||
public Map<String, Map<Integer, String>> getDhcpOptionsMap() {
|
||||
Map<String, Map<Integer, String>> dhcpOptionsMap = new HashMap<>();
|
||||
if (dhcpOptionsNetworkList != null && !dhcpOptionsNetworkList.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -77,6 +77,14 @@ public class UpdateVolumeCmd extends BaseAsyncCustomIdCmd implements UserCmd {
|
|||
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "new name of the volume", since = "4.16")
|
||||
private String name;
|
||||
|
||||
@Parameter(name = ApiConstants.DELETE_PROTECTION,
|
||||
type = CommandType.BOOLEAN, since = "4.20.0",
|
||||
description = "Set delete protection for the volume. If true, The volume " +
|
||||
"will be protected from deletion. Note: If the volume is managed by " +
|
||||
"another service like autoscaling groups or CKS, delete protection will be " +
|
||||
"ignored.")
|
||||
private Boolean deleteProtection;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -109,6 +117,10 @@ public class UpdateVolumeCmd extends BaseAsyncCustomIdCmd implements UserCmd {
|
|||
return name;
|
||||
}
|
||||
|
||||
public Boolean getDeleteProtection() {
|
||||
return deleteProtection;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -168,7 +180,7 @@ public class UpdateVolumeCmd extends BaseAsyncCustomIdCmd implements UserCmd {
|
|||
public void execute() {
|
||||
CallContext.current().setEventDetails("Volume Id: " + this._uuidMgr.getUuid(Volume.class, getId()));
|
||||
Volume result = _volumeService.updateVolume(getId(), getPath(), getState(), getStorageId(), getDisplayVolume(),
|
||||
getCustomId(), getEntityOwnerId(), getChainInfo(), getName());
|
||||
getDeleteProtection(), getCustomId(), getEntityOwnerId(), getChainInfo(), getName());
|
||||
if (result != null) {
|
||||
VolumeResponse response = _responseGenerator.createVolumeResponse(getResponseView(), result);
|
||||
response.setResponseName(getCommandName());
|
||||
|
|
|
|||
|
|
@ -320,6 +320,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
|
|||
@Param(description = "true if vm contains XS/VMWare tools inorder to support dynamic scaling of VM cpu/memory.")
|
||||
private Boolean isDynamicallyScalable;
|
||||
|
||||
@SerializedName(ApiConstants.DELETE_PROTECTION)
|
||||
@Param(description = "true if vm has delete protection.", since = "4.20.0")
|
||||
private boolean deleteProtection;
|
||||
|
||||
@SerializedName(ApiConstants.SERVICE_STATE)
|
||||
@Param(description = "State of the Service from LB rule")
|
||||
private String serviceState;
|
||||
|
|
@ -995,6 +999,14 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
|
|||
isDynamicallyScalable = dynamicallyScalable;
|
||||
}
|
||||
|
||||
public boolean isDeleteProtection() {
|
||||
return deleteProtection;
|
||||
}
|
||||
|
||||
public void setDeleteProtection(boolean deleteProtection) {
|
||||
this.deleteProtection = deleteProtection;
|
||||
}
|
||||
|
||||
public String getOsTypeId() {
|
||||
return osTypeId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -261,6 +261,10 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
|
|||
@Param(description = "true if storage snapshot is supported for the volume, false otherwise", since = "4.16")
|
||||
private boolean supportsStorageSnapshot;
|
||||
|
||||
@SerializedName(ApiConstants.DELETE_PROTECTION)
|
||||
@Param(description = "true if volume has delete protection.", since = "4.20.0")
|
||||
private boolean deleteProtection;
|
||||
|
||||
@SerializedName(ApiConstants.PHYSICAL_SIZE)
|
||||
@Param(description = "the bytes actually consumed on disk")
|
||||
private Long physicalsize;
|
||||
|
|
@ -584,6 +588,14 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
|
|||
return this.supportsStorageSnapshot;
|
||||
}
|
||||
|
||||
public boolean isDeleteProtection() {
|
||||
return deleteProtection;
|
||||
}
|
||||
|
||||
public void setDeleteProtection(boolean deleteProtection) {
|
||||
this.deleteProtection = deleteProtection;
|
||||
}
|
||||
|
||||
public String getIsoId() {
|
||||
return isoId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,6 +182,9 @@ public class VolumeVO implements Volume {
|
|||
@Column(name = "encrypt_format")
|
||||
private String encryptFormat;
|
||||
|
||||
@Column(name = "delete_protection")
|
||||
private boolean deleteProtection;
|
||||
|
||||
|
||||
// Real Constructor
|
||||
public VolumeVO(Type type, String name, long dcId, long domainId,
|
||||
|
|
@ -678,4 +681,13 @@ public class VolumeVO implements Volume {
|
|||
public String getEncryptFormat() { return encryptFormat; }
|
||||
|
||||
public void setEncryptFormat(String encryptFormat) { this.encryptFormat = encryptFormat; }
|
||||
|
||||
@Override
|
||||
public boolean isDeleteProtection() {
|
||||
return deleteProtection;
|
||||
}
|
||||
|
||||
public void setDeleteProtection(boolean deleteProtection) {
|
||||
this.deleteProtection = deleteProtection;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,10 +167,8 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject<State, Vi
|
|||
@Column(name = "dynamically_scalable")
|
||||
protected boolean dynamicallyScalable;
|
||||
|
||||
/*
|
||||
@Column(name="tags")
|
||||
protected String tags;
|
||||
*/
|
||||
@Column(name = "delete_protection")
|
||||
protected boolean deleteProtection;
|
||||
|
||||
@Transient
|
||||
Map<String, String> details;
|
||||
|
|
@ -542,6 +540,14 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject<State, Vi
|
|||
return dynamicallyScalable;
|
||||
}
|
||||
|
||||
public boolean isDeleteProtection() {
|
||||
return deleteProtection;
|
||||
}
|
||||
|
||||
public void setDeleteProtection(boolean deleteProtection) {
|
||||
this.deleteProtection = deleteProtection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getEntityType() {
|
||||
return VirtualMachine.class;
|
||||
|
|
|
|||
|
|
@ -53,7 +53,11 @@ public interface UserVmDao extends GenericDao<UserVmVO, Long> {
|
|||
* @param hostName TODO
|
||||
* @param instanceName
|
||||
*/
|
||||
void updateVM(long id, String displayName, boolean enable, Long osTypeId, String userData, Long userDataId, String userDataDetails, boolean displayVm, boolean isDynamicallyScalable, String customId, String hostName, String instanceName);
|
||||
void updateVM(long id, String displayName, boolean enable, Long osTypeId,
|
||||
String userData, Long userDataId, String userDataDetails,
|
||||
boolean displayVm, boolean isDynamicallyScalable,
|
||||
boolean deleteProtection, String customId, String hostName,
|
||||
String instanceName);
|
||||
|
||||
List<UserVmVO> findDestroyedVms(Date date);
|
||||
|
||||
|
|
|
|||
|
|
@ -274,8 +274,11 @@ public class UserVmDaoImpl extends GenericDaoBase<UserVmVO, Long> implements Use
|
|||
}
|
||||
|
||||
@Override
|
||||
public void updateVM(long id, String displayName, boolean enable, Long osTypeId, String userData, Long userDataId, String userDataDetails, boolean displayVm,
|
||||
boolean isDynamicallyScalable, String customId, String hostName, String instanceName) {
|
||||
public void updateVM(long id, String displayName, boolean enable, Long osTypeId,
|
||||
String userData, Long userDataId, String userDataDetails,
|
||||
boolean displayVm, boolean isDynamicallyScalable,
|
||||
boolean deleteProtection, String customId, String hostName,
|
||||
String instanceName) {
|
||||
UserVmVO vo = createForUpdate();
|
||||
vo.setDisplayName(displayName);
|
||||
vo.setHaEnabled(enable);
|
||||
|
|
@ -285,6 +288,7 @@ public class UserVmDaoImpl extends GenericDaoBase<UserVmVO, Long> implements Use
|
|||
vo.setUserDataDetails(userDataDetails);
|
||||
vo.setDisplayVm(displayVm);
|
||||
vo.setDynamicallyScalable(isDynamicallyScalable);
|
||||
vo.setDeleteProtection(deleteProtection);
|
||||
if (hostName != null) {
|
||||
vo.setHostName(hostName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -620,3 +620,6 @@ INSERT IGNORE INTO `cloud`.`hypervisor_capabilities` (uuid, hypervisor_type, hyp
|
|||
INSERT IGNORE INTO `cloud`.`guest_os_hypervisor` (uuid, hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) SELECT UUID(),'VMware', '8.0.2', guest_os_name, guest_os_id, utc_timestamp(), 0 FROM `cloud`.`guest_os_hypervisor` WHERE hypervisor_type='VMware' AND hypervisor_version='8.0';
|
||||
INSERT IGNORE INTO `cloud`.`hypervisor_capabilities` (uuid, hypervisor_type, hypervisor_version, max_guests_limit, security_group_enabled, max_data_volumes_limit, max_hosts_per_cluster, storage_motion_supported, vm_snapshot_enabled) values (UUID(), 'VMware', '8.0.3', 1024, 0, 59, 64, 1, 1);
|
||||
INSERT IGNORE INTO `cloud`.`guest_os_hypervisor` (uuid, hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) SELECT UUID(),'VMware', '8.0.3', guest_os_name, guest_os_id, utc_timestamp(), 0 FROM `cloud`.`guest_os_hypervisor` WHERE hypervisor_type='VMware' AND hypervisor_version='8.0';
|
||||
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vm_instance', 'delete_protection', 'boolean DEFAULT FALSE COMMENT "delete protection for vm" ');
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.volumes', 'delete_protection', 'boolean DEFAULT FALSE COMMENT "delete protection for volumes" ');
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ SELECT
|
|||
`vm_instance`.`instance_name` AS `instance_name`,
|
||||
`vm_instance`.`guest_os_id` AS `guest_os_id`,
|
||||
`vm_instance`.`display_vm` AS `display_vm`,
|
||||
`vm_instance`.`delete_protection` AS `delete_protection`,
|
||||
`guest_os`.`uuid` AS `guest_os_uuid`,
|
||||
`vm_instance`.`pod_id` AS `pod_id`,
|
||||
`host_pod_ref`.`uuid` AS `pod_uuid`,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ SELECT
|
|||
`volumes`.`chain_info` AS `chain_info`,
|
||||
`volumes`.`external_uuid` AS `external_uuid`,
|
||||
`volumes`.`encrypt_format` AS `encrypt_format`,
|
||||
`volumes`.`delete_protection` AS `delete_protection`,
|
||||
`account`.`id` AS `account_id`,
|
||||
`account`.`uuid` AS `account_uuid`,
|
||||
`account`.`account_name` AS `account_name`,
|
||||
|
|
|
|||
|
|
@ -935,6 +935,11 @@ public class VolumeObject implements VolumeInfo {
|
|||
volumeVO.setEncryptFormat(encryptFormat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeleteProtection() {
|
||||
return volumeVO.isDeleteProtection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFollowRedirects() {
|
||||
return followRedirects;
|
||||
|
|
|
|||
|
|
@ -426,6 +426,12 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
|
|||
userVmResponse.setDynamicallyScalable(userVm.isDynamicallyScalable());
|
||||
}
|
||||
|
||||
if (userVm.getDeleteProtection() == null) {
|
||||
userVmResponse.setDeleteProtection(false);
|
||||
} else {
|
||||
userVmResponse.setDeleteProtection(userVm.getDeleteProtection());
|
||||
}
|
||||
|
||||
if (userVm.getAutoScaleVmGroupName() != null) {
|
||||
userVmResponse.setAutoScaleVmGroupName(userVm.getAutoScaleVmGroupName());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,6 +138,12 @@ public class VolumeJoinDaoImpl extends GenericDaoBaseWithTagInformation<VolumeJo
|
|||
volResponse.setMinIops(volume.getMinIops());
|
||||
volResponse.setMaxIops(volume.getMaxIops());
|
||||
|
||||
if (volume.getDeleteProtection() == null) {
|
||||
volResponse.setDeleteProtection(false);
|
||||
} else {
|
||||
volResponse.setDeleteProtection(volume.getDeleteProtection());
|
||||
}
|
||||
|
||||
volResponse.setCreated(volume.getCreated());
|
||||
if (volume.getState() != null) {
|
||||
volResponse.setState(volume.getState().toString());
|
||||
|
|
|
|||
|
|
@ -436,6 +436,9 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro
|
|||
@Column(name = "dynamically_scalable")
|
||||
private boolean isDynamicallyScalable;
|
||||
|
||||
@Column(name = "delete_protection")
|
||||
protected Boolean deleteProtection;
|
||||
|
||||
|
||||
public UserVmJoinVO() {
|
||||
// Empty constructor
|
||||
|
|
@ -946,6 +949,9 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro
|
|||
return isDynamicallyScalable;
|
||||
}
|
||||
|
||||
public Boolean getDeleteProtection() {
|
||||
return deleteProtection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getEntityType() {
|
||||
|
|
|
|||
|
|
@ -280,6 +280,9 @@ public class VolumeJoinVO extends BaseViewWithTagInformationVO implements Contro
|
|||
@Column(name = "encrypt_format")
|
||||
private String encryptionFormat = null;
|
||||
|
||||
@Column(name = "delete_protection")
|
||||
protected Boolean deleteProtection;
|
||||
|
||||
public VolumeJoinVO() {
|
||||
}
|
||||
|
||||
|
|
@ -619,6 +622,10 @@ public class VolumeJoinVO extends BaseViewWithTagInformationVO implements Contro
|
|||
return encryptionFormat;
|
||||
}
|
||||
|
||||
public Boolean getDeleteProtection() {
|
||||
return deleteProtection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getEntityType() {
|
||||
return Volume.class;
|
||||
|
|
|
|||
|
|
@ -1699,6 +1699,12 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||
}
|
||||
|
||||
public void validateDestroyVolume(Volume volume, Account caller, boolean expunge, boolean forceExpunge) {
|
||||
if (volume.isDeleteProtection()) {
|
||||
throw new InvalidParameterValueException(String.format(
|
||||
"Volume [id = %s, name = %s] has delete protection enabled and cannot be deleted.",
|
||||
volume.getUuid(), volume.getName()));
|
||||
}
|
||||
|
||||
if (expunge) {
|
||||
// When trying to expunge, permission is denied when the caller is not an admin and the AllowUserExpungeRecoverVolume is false for the caller.
|
||||
final Long userId = caller.getAccountId();
|
||||
|
|
@ -2757,13 +2763,15 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_UPDATE, eventDescription = "updating volume", async = true)
|
||||
public Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume,
|
||||
public Volume updateVolume(long volumeId, String path, String state, Long storageId,
|
||||
Boolean displayVolume, Boolean deleteProtection,
|
||||
String customId, long entityOwnerId, String chainInfo, String name) {
|
||||
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
if (!_accountMgr.isRootAdmin(caller.getId())) {
|
||||
if (path != null || state != null || storageId != null || displayVolume != null || customId != null || chainInfo != null) {
|
||||
throw new InvalidParameterValueException("The domain admin and normal user are not allowed to update volume except volume name");
|
||||
throw new InvalidParameterValueException("The domain admin and normal user are " +
|
||||
"not allowed to update volume except volume name & delete protection");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2815,6 +2823,10 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||
volume.setName(name);
|
||||
}
|
||||
|
||||
if (deleteProtection != null) {
|
||||
volume.setDeleteProtection(deleteProtection);
|
||||
}
|
||||
|
||||
updateDisplay(volume, displayVolume);
|
||||
|
||||
_volsDao.update(volumeId, volume);
|
||||
|
|
|
|||
|
|
@ -141,8 +141,14 @@ public interface UserVmManager extends UserVmService {
|
|||
|
||||
boolean setupVmForPvlan(boolean add, Long hostId, NicProfile nic);
|
||||
|
||||
UserVm updateVirtualMachine(long id, String displayName, String group, Boolean ha, Boolean isDisplayVmEnabled, Long osTypeId, String userData,
|
||||
Long userDataId, String userDataDetails, Boolean isDynamicallyScalable, HTTPMethod httpMethod, String customId, String hostName, String instanceName, List<Long> securityGroupIdList, Map<String, Map<Integer, String>> extraDhcpOptionsMap) throws ResourceUnavailableException, InsufficientCapacityException;
|
||||
UserVm updateVirtualMachine(long id, String displayName, String group, Boolean ha,
|
||||
Boolean isDisplayVmEnabled, Boolean deleteProtection,
|
||||
Long osTypeId, String userData, Long userDataId,
|
||||
String userDataDetails, Boolean isDynamicallyScalable,
|
||||
HTTPMethod httpMethod, String customId, String hostName,
|
||||
String instanceName, List<Long> securityGroupIdList,
|
||||
Map<String, Map<Integer, String>> extraDhcpOptionsMap
|
||||
) throws ResourceUnavailableException, InsufficientCapacityException;
|
||||
|
||||
//the validateCustomParameters, save and remove CustomOfferingDetils functions can be removed from the interface once we can
|
||||
//find a common place for all the scaling and upgrading code of both user and systemvms.
|
||||
|
|
|
|||
|
|
@ -2921,8 +2921,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
}
|
||||
}
|
||||
return updateVirtualMachine(id, displayName, group, ha, isDisplayVm, osTypeId, userData, userDataId, userDataDetails, isDynamicallyScalable,
|
||||
cmd.getHttpMethod(), cmd.getCustomId(), hostName, cmd.getInstanceName(), securityGroupIdList, cmd.getDhcpOptionsMap());
|
||||
return updateVirtualMachine(id, displayName, group, ha, isDisplayVm,
|
||||
cmd.getDeleteProtection(), osTypeId, userData,
|
||||
userDataId, userDataDetails, isDynamicallyScalable, cmd.getHttpMethod(),
|
||||
cmd.getCustomId(), hostName, cmd.getInstanceName(), securityGroupIdList,
|
||||
cmd.getDhcpOptionsMap());
|
||||
}
|
||||
|
||||
private boolean isExtraConfig(String detailName) {
|
||||
|
|
@ -3023,9 +3026,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
|
||||
@Override
|
||||
public UserVm updateVirtualMachine(long id, String displayName, String group, Boolean ha, Boolean isDisplayVmEnabled, Long osTypeId, String userData,
|
||||
Long userDataId, String userDataDetails, Boolean isDynamicallyScalable, HTTPMethod httpMethod, String customId, String hostName, String instanceName, List<Long> securityGroupIdList, Map<String, Map<Integer, String>> extraDhcpOptionsMap)
|
||||
throws ResourceUnavailableException, InsufficientCapacityException {
|
||||
public UserVm updateVirtualMachine(long id, String displayName, String group, Boolean ha,
|
||||
Boolean isDisplayVmEnabled, Boolean deleteProtection,
|
||||
Long osTypeId, String userData, Long userDataId,
|
||||
String userDataDetails, Boolean isDynamicallyScalable,
|
||||
HTTPMethod httpMethod, String customId, String hostName,
|
||||
String instanceName, List<Long> securityGroupIdList,
|
||||
Map<String, Map<Integer, String>> extraDhcpOptionsMap
|
||||
) throws ResourceUnavailableException, InsufficientCapacityException {
|
||||
UserVmVO vm = _vmDao.findById(id);
|
||||
if (vm == null) {
|
||||
throw new CloudRuntimeException("Unable to find virtual machine with id " + id);
|
||||
|
|
@ -3060,6 +3068,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
isDisplayVmEnabled = vm.isDisplayVm();
|
||||
}
|
||||
|
||||
if (deleteProtection == null) {
|
||||
deleteProtection = vm.isDeleteProtection();
|
||||
}
|
||||
|
||||
boolean updateUserdata = false;
|
||||
if (userData != null) {
|
||||
// check and replace newlines
|
||||
|
|
@ -3174,7 +3186,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
.getUuid(), nic.getId(), extraDhcpOptionsMap);
|
||||
}
|
||||
|
||||
_vmDao.updateVM(id, displayName, ha, osTypeId, userData, userDataId, userDataDetails, isDisplayVmEnabled, isDynamicallyScalable, customId, hostName, instanceName);
|
||||
_vmDao.updateVM(id, displayName, ha, osTypeId, userData, userDataId,
|
||||
userDataDetails, isDisplayVmEnabled, isDynamicallyScalable,
|
||||
deleteProtection, customId, hostName, instanceName);
|
||||
|
||||
if (updateUserdata) {
|
||||
updateUserData(vm);
|
||||
|
|
@ -3411,6 +3425,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
return vm;
|
||||
}
|
||||
|
||||
if (vm.isDeleteProtection()) {
|
||||
throw new InvalidParameterValueException(String.format(
|
||||
"Instance [id = %s, name = %s] has delete protection enabled and cannot be deleted.",
|
||||
vm.getUuid(), vm.getName()));
|
||||
}
|
||||
|
||||
// check if vm belongs to AutoScale vm group in Disabled state
|
||||
autoScaleManager.checkIfVmActionAllowed(vmId);
|
||||
|
||||
|
|
@ -8586,6 +8606,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
if (!(volume.getVolumeType() == Volume.Type.ROOT || volume.getVolumeType() == Volume.Type.DATADISK)) {
|
||||
throw new InvalidParameterValueException("Please specify volume of type " + Volume.Type.DATADISK.toString() + " or " + Volume.Type.ROOT.toString());
|
||||
}
|
||||
if (volume.isDeleteProtection()) {
|
||||
throw new InvalidParameterValueException(String.format(
|
||||
"Volume [id = %s, name = %s] has delete protection enabled and cannot be deleted",
|
||||
volume.getUuid(), volume.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -487,7 +487,7 @@ public class UserVmManagerImplTest {
|
|||
Mockito.verify(userVmManagerImpl).getSecurityGroupIdList(updateVmCommand);
|
||||
|
||||
Mockito.verify(userVmManagerImpl).updateVirtualMachine(nullable(Long.class), nullable(String.class), nullable(String.class), nullable(Boolean.class),
|
||||
nullable(Boolean.class), nullable(Long.class),
|
||||
nullable(Boolean.class), nullable(Boolean.class), nullable(Long.class),
|
||||
nullable(String.class), nullable(Long.class), nullable(String.class), nullable(Boolean.class), nullable(HTTPMethod.class), nullable(String.class), nullable(String.class), nullable(String.class), nullable(List.class),
|
||||
nullable(Map.class));
|
||||
|
||||
|
|
@ -498,7 +498,7 @@ public class UserVmManagerImplTest {
|
|||
Mockito.doNothing().when(userVmManagerImpl).validateInputsAndPermissionForUpdateVirtualMachineCommand(updateVmCommand);
|
||||
Mockito.doReturn(new ArrayList<Long>()).when(userVmManagerImpl).getSecurityGroupIdList(updateVmCommand);
|
||||
Mockito.lenient().doReturn(Mockito.mock(UserVm.class)).when(userVmManagerImpl).updateVirtualMachine(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean(),
|
||||
Mockito.anyBoolean(), Mockito.anyLong(),
|
||||
Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyLong(),
|
||||
Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyBoolean(), Mockito.any(HTTPMethod.class), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyList(),
|
||||
Mockito.anyMap());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1035,6 +1035,34 @@ class TestVMLifeCycle(cloudstackTestCase):
|
|||
|
||||
return
|
||||
|
||||
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
|
||||
def test_14_destroy_vm_delete_protection(self):
|
||||
"""Test destroy Virtual Machine with delete protection
|
||||
"""
|
||||
|
||||
# Validate the following
|
||||
# 1. Should not be able to delete the VM when delete protection is enabled
|
||||
# 2. Should be able to delete the VM after disabling delete protection
|
||||
|
||||
vm = VirtualMachine.create(
|
||||
self.apiclient,
|
||||
self.services["small"],
|
||||
serviceofferingid=self.small_offering.id,
|
||||
mode=self.services["mode"],
|
||||
startvm=False
|
||||
)
|
||||
|
||||
vm.update(self.apiclient, deleteprotection=True)
|
||||
try:
|
||||
vm.delete(self.apiclient)
|
||||
self.fail("VM shouldn't get deleted with delete protection enabled")
|
||||
except Exception as e:
|
||||
self.debug("Expected exception: %s" % e)
|
||||
|
||||
vm.update(self.apiclient, deleteprotection=False)
|
||||
vm.delete(self.apiclient)
|
||||
|
||||
return
|
||||
|
||||
class TestSecuredVmMigration(cloudstackTestCase):
|
||||
|
||||
|
|
|
|||
|
|
@ -1038,6 +1038,33 @@ class TestVolumes(cloudstackTestCase):
|
|||
)
|
||||
return
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
|
||||
def test_14_delete_volume_delete_protection(self):
|
||||
"""Delete a Volume with delete protection
|
||||
|
||||
# Validate the following
|
||||
# 1. delete volume will fail when delete protection is enabled
|
||||
# 2. delete volume is successful when delete protection is disabled
|
||||
"""
|
||||
|
||||
volume = Volume.create(
|
||||
self.apiclient,
|
||||
self.services,
|
||||
zoneid=self.zone.id,
|
||||
account=self.account.name,
|
||||
domainid=self.account.domainid,
|
||||
diskofferingid=self.disk_offering.id
|
||||
)
|
||||
volume.update(self.apiclient, deleteprotection=True)
|
||||
try:
|
||||
volume.delete(self.apiclient)
|
||||
self.fail("Volume delete should have failed with delete protection enabled")
|
||||
except Exception as e:
|
||||
self.debug("Volume delete failed as expected with error: %s" % e)
|
||||
|
||||
volume.update(self.apiclient, deleteprotection=False)
|
||||
volume.destroy(self.apiclient, expunge=True)
|
||||
|
||||
|
||||
class TestVolumeEncryption(cloudstackTestCase):
|
||||
|
||||
|
|
|
|||
|
|
@ -1160,6 +1160,14 @@ class Volume:
|
|||
|
||||
return Volume(apiclient.createVolume(cmd).__dict__)
|
||||
|
||||
def update(self, apiclient, **kwargs):
|
||||
"""Updates the volume"""
|
||||
|
||||
cmd = updateVolume.updateVolumeCmd()
|
||||
cmd.id = self.id
|
||||
[setattr(cmd, k, v) for k, v in list(kwargs.items())]
|
||||
return (apiclient.updateVolume(cmd))
|
||||
|
||||
@classmethod
|
||||
def create_custom_disk(cls, apiclient, services, account=None,
|
||||
domainid=None, diskofferingid=None, projectid=None):
|
||||
|
|
|
|||
|
|
@ -740,6 +740,7 @@
|
|||
"label.deleting.iso": "Deleting ISO",
|
||||
"label.deleting.snapshot": "Deleting Snapshot",
|
||||
"label.deleting.template": "Deleting Template",
|
||||
"label.deleteprotection": "Delete protection",
|
||||
"label.demote.project.owner": "Demote Account to regular role",
|
||||
"label.demote.project.owner.user": "Demote User to regular role",
|
||||
"label.deny": "Deny",
|
||||
|
|
|
|||
|
|
@ -81,7 +81,8 @@ export default {
|
|||
details: () => {
|
||||
var fields = ['name', 'displayname', 'id', 'state', 'ipaddress', 'ip6address', 'templatename', 'ostypename',
|
||||
'serviceofferingname', 'isdynamicallyscalable', 'haenable', 'hypervisor', 'boottype', 'bootmode', 'account',
|
||||
'domain', 'zonename', 'userdataid', 'userdataname', 'userdataparams', 'userdatadetails', 'userdatapolicy', 'hostcontrolstate']
|
||||
'domain', 'zonename', 'userdataid', 'userdataname', 'userdataparams', 'userdatadetails', 'userdatapolicy',
|
||||
'hostcontrolstate', 'deleteprotection']
|
||||
const listZoneHaveSGEnabled = store.getters.zones.filter(zone => zone.securitygroupsenabled === true)
|
||||
if (!listZoneHaveSGEnabled || listZoneHaveSGEnabled.length === 0) {
|
||||
return fields
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export default {
|
|||
|
||||
return fields
|
||||
},
|
||||
details: ['name', 'id', 'type', 'storagetype', 'diskofferingdisplaytext', 'deviceid', 'sizegb', 'physicalsize', 'provisioningtype', 'utilization', 'diskkbsread', 'diskkbswrite', 'diskioread', 'diskiowrite', 'diskiopstotal', 'miniops', 'maxiops', 'path'],
|
||||
details: ['name', 'id', 'type', 'storagetype', 'diskofferingdisplaytext', 'deviceid', 'sizegb', 'physicalsize', 'provisioningtype', 'utilization', 'diskkbsread', 'diskkbswrite', 'diskioread', 'diskiowrite', 'diskiopstotal', 'miniops', 'maxiops', 'path', 'deleteprotection'],
|
||||
related: [{
|
||||
name: 'snapshot',
|
||||
title: 'label.snapshots',
|
||||
|
|
@ -148,7 +148,7 @@ export default {
|
|||
icon: 'edit-outlined',
|
||||
label: 'label.edit',
|
||||
dataView: true,
|
||||
args: ['name'],
|
||||
args: ['name', 'deleteprotection'],
|
||||
mapping: {
|
||||
account: {
|
||||
value: (record) => { return record.account }
|
||||
|
|
|
|||
|
|
@ -111,6 +111,13 @@
|
|||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="deleteprotection" ref="deleteprotection">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.deleteprotection')" :tooltip="apiParams.deleteprotection.description"/>
|
||||
</template>
|
||||
<a-switch v-model:checked="form.deleteprotection" />
|
||||
</a-form-item>
|
||||
|
||||
<div :span="24" class="action-button">
|
||||
<a-button :loading="loading" @click="onCloseAction">{{ $t('label.cancel') }}</a-button>
|
||||
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
|
||||
|
|
@ -175,6 +182,7 @@ export default {
|
|||
displayname: this.resource.displayname,
|
||||
ostypeid: this.resource.ostypeid,
|
||||
isdynamicallyscalable: this.resource.isdynamicallyscalable,
|
||||
deleteprotection: this.resource.deleteprotection,
|
||||
group: this.resource.group,
|
||||
securitygroupids: this.resource.securitygroup.map(x => x.id),
|
||||
userdata: '',
|
||||
|
|
@ -314,6 +322,9 @@ export default {
|
|||
if (values.isdynamicallyscalable !== undefined) {
|
||||
params.isdynamicallyscalable = values.isdynamicallyscalable
|
||||
}
|
||||
if (values.deleteprotection !== undefined) {
|
||||
params.deleteprotection = values.deleteprotection
|
||||
}
|
||||
if (values.haenable !== undefined) {
|
||||
params.haenable = values.haenable
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue