Skip to content

Commit

Permalink
Feature: Allow adding delete protection for VMs & volumes (#9633)
Browse files Browse the repository at this point in the history
Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com>
  • Loading branch information
vishesh92 and sureshanaparti authored Sep 9, 2024
1 parent f8d8a9c commit 1303a4f
Show file tree
Hide file tree
Showing 30 changed files with 261 additions and 28 deletions.
10 changes: 6 additions & 4 deletions api/src/main/java/com/cloud/storage/Volume.java
Original file line number Diff line number Diff line change
Expand Up @@ -271,11 +271,13 @@ enum Event {

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();
}
4 changes: 3 additions & 1 deletion api/src/main/java/com/cloud/storage/VolumeApiService.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account acc

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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ///////////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -215,6 +223,10 @@ public boolean isCleanupDetails(){
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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ///////////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -109,6 +117,10 @@ public String getName() {
return name;
}

public Boolean getDeleteProtection() {
return deleteProtection;
}

/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -168,7 +180,7 @@ public String getEventDescription() {
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -995,6 +999,14 @@ public void setDynamicallyScalable(Boolean dynamicallyScalable) {
isDynamicallyScalable = dynamicallyScalable;
}

public boolean isDeleteProtection() {
return deleteProtection;
}

public void setDeleteProtection(boolean deleteProtection) {
this.deleteProtection = deleteProtection;
}

public String getOsTypeId() {
return osTypeId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -584,6 +588,14 @@ public boolean getSupportsStorageSnapshot() {
return this.supportsStorageSnapshot;
}

public boolean isDeleteProtection() {
return deleteProtection;
}

public void setDeleteProtection(boolean deleteProtection) {
this.deleteProtection = deleteProtection;
}

public String getIsoId() {
return isoId;
}
Expand Down
12 changes: 12 additions & 0 deletions engine/schema/src/main/java/com/cloud/storage/VolumeVO.java
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -678,4 +681,13 @@ public void setExternalUuid(String externalUuid) {
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;
}
}
14 changes: 10 additions & 4 deletions engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -542,6 +540,14 @@ public boolean isDynamicallyScalable() {
return dynamicallyScalable;
}

public boolean isDeleteProtection() {
return deleteProtection;
}

public void setDeleteProtection(boolean deleteProtection) {
this.deleteProtection = deleteProtection;
}

@Override
public Class<?> getEntityType() {
return VirtualMachine.class;
Expand Down
6 changes: 5 additions & 1 deletion engine/schema/src/main/java/com/cloud/vm/dao/UserVmDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,11 @@ public List<UserVmVO> listByAccountAndDataCenter(long accountId, long dcId) {
}

@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);
Expand All @@ -285,6 +288,7 @@ public void updateVM(long id, String displayName, boolean enable, Long osTypeId,
vo.setUserDataDetails(userDataDetails);
vo.setDisplayVm(displayVm);
vo.setDynamicallyScalable(isDynamicallyScalable);
vo.setDeleteProtection(deleteProtection);
if (hostName != null) {
vo.setHostName(hostName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" ');
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,11 @@ public void setEncryptFormat(String encryptFormat) {
volumeVO.setEncryptFormat(encryptFormat);
}

@Override
public boolean isDeleteProtection() {
return volumeVO.isDeleteProtection();
}

@Override
public boolean isFollowRedirects() {
return followRedirects;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,12 @@ public UserVmResponse newUserVmResponse(ResponseView view, String objectName, Us
userVmResponse.setDynamicallyScalable(userVm.isDynamicallyScalable());
}

if (userVm.getDeleteProtection() == null) {
userVmResponse.setDeleteProtection(false);
} else {
userVmResponse.setDeleteProtection(userVm.getDeleteProtection());
}

if (userVm.getAutoScaleVmGroupName() != null) {
userVmResponse.setAutoScaleVmGroupName(userVm.getAutoScaleVmGroupName());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ public VolumeResponse newVolumeResponse(ResponseView view, VolumeJoinVO volume)
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());
Expand Down
6 changes: 6 additions & 0 deletions server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -946,6 +949,9 @@ public Boolean isDynamicallyScalable() {
return isDynamicallyScalable;
}

public Boolean getDeleteProtection() {
return deleteProtection;
}

@Override
public Class<?> getEntityType() {
Expand Down
7 changes: 7 additions & 0 deletions server/src/main/java/com/cloud/api/query/vo/VolumeJoinVO.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
}

Expand Down Expand Up @@ -619,6 +622,10 @@ public String getEncryptionFormat() {
return encryptionFormat;
}

public Boolean getDeleteProtection() {
return deleteProtection;
}

@Override
public Class<?> getEntityType() {
return Volume.class;
Expand Down
16 changes: 14 additions & 2 deletions server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -1699,6 +1699,12 @@ public boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitio
}

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();
Expand Down Expand Up @@ -2757,13 +2763,15 @@ protected String createVolumeInfoFromVolumes(List<VolumeVO> vmVolumes) {

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

Expand Down Expand Up @@ -2815,6 +2823,10 @@ public Volume updateVolume(long volumeId, String path, String state, Long storag
volume.setName(name);
}

if (deleteProtection != null) {
volume.setDeleteProtection(deleteProtection);
}

updateDisplay(volume, displayVolume);

_volsDao.update(volumeId, volume);
Expand Down
10 changes: 8 additions & 2 deletions server/src/main/java/com/cloud/vm/UserVmManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,14 @@ boolean upgradeVirtualMachine(Long id, Long serviceOfferingId, Map<String, Strin

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.
Expand Down
Loading

0 comments on commit 1303a4f

Please sign in to comment.