diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java index 2052fd287c8..3ac023e2dfd 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java @@ -158,4 +158,5 @@ public interface VMInstanceDao extends GenericDao, StateDao< boolean isPowerStateUpToDate(long instanceId); List listNonMigratingVmsByHostEqualsLastHost(long hostId); + } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java index 97a505d7c8f..22a197a13d3 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -300,6 +300,7 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem LastHostAndStatesSearch.and("lastHost", LastHostAndStatesSearch.entity().getLastHostId(), Op.EQ); LastHostAndStatesSearch.and("states", LastHostAndStatesSearch.entity().getState(), Op.IN); LastHostAndStatesSearch.done(); + } @Override diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index fc12e0ad8d1..579a33d6ee8 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -81,6 +81,10 @@ import org.apache.cloudstack.api.command.user.vm.UpgradeVMCmd; import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; import org.apache.cloudstack.api.command.user.vmgroup.DeleteVMGroupCmd; import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; +import com.cloud.agent.api.to.deployasis.OVFNetworkTO; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.cloud.entity.api.VirtualMachineEntity; import org.apache.cloudstack.engine.cloud.entity.api.db.dao.VMNetworkMapDao; @@ -141,7 +145,6 @@ import com.cloud.agent.api.VolumeStatsEntry; import com.cloud.agent.api.to.DiskTO; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; -import com.cloud.agent.api.to.deployasis.OVFNetworkTO; import com.cloud.agent.api.to.deployasis.OVFPropertyTO; import com.cloud.agent.manager.Commands; import com.cloud.alert.AlertManager; @@ -518,6 +521,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private StorageManager storageManager; @Inject private ServiceOfferingJoinDao serviceOfferingJoinDao; + @Inject + private BackupDao backupDao; + @Inject + private BackupManager backupManager; private ScheduledExecutorService _executor = null; private ScheduledExecutorService _vmIpFetchExecutor = null; @@ -2294,6 +2301,13 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } try { + if (vm.getBackupOfferingId() != null) { + List backupsForVm = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + if (CollectionUtils.isEmpty(backupsForVm)) { + backupManager.removeVMFromBackupOffering(vm.getId(), true); + } + } + releaseNetworkResourcesOnExpunge(vm.getId()); List rootVol = _volsDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT); diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index b11d1bca150..e582765f5ca 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.TimeZone; import java.util.Timer; import java.util.TimerTask; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; @@ -61,6 +62,7 @@ import org.apache.cloudstack.poll.BackgroundPollManager; import org.apache.cloudstack.poll.BackgroundPollTask; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; import com.cloud.api.ApiDispatcher; @@ -333,24 +335,22 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { } boolean result = false; - VMInstanceVO vmInstance = null; try { - vmInstance = vmInstanceDao.acquireInLockTable(vm.getId()); - vmInstance.setBackupOfferingId(null); - vmInstance.setBackupExternalId(null); - vmInstance.setBackupVolumes(null); - result = backupProvider.removeVMFromBackupOffering(vmInstance); + vm.setBackupOfferingId(null); + vm.setBackupExternalId(null); + vm.setBackupVolumes(null); + result = backupProvider.removeVMFromBackupOffering(vm); if (result && backupProvider.willDeleteBackupsOnOfferingRemoval()) { final List backups = backupDao.listByVmId(null, vm.getId()); for (final Backup backup : backups) { backupDao.remove(backup.getId()); } } - if ((result || forced) && vmInstanceDao.update(vmInstance.getId(), vmInstance)) { + if ((result || forced) && vmInstanceDao.update(vm.getId(), vm)) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), "Backup-" + vm.getHostName() + "-" + vm.getUuid(), vm.getBackupOfferingId(), null, null, Backup.class.getSimpleName(), vm.getUuid()); - final BackupSchedule backupSchedule = backupScheduleDao.findByVM(vmInstance.getId()); + final BackupSchedule backupSchedule = backupScheduleDao.findByVM(vm.getId()); if (backupSchedule != null) { backupScheduleDao.remove(backupSchedule.getId()); } @@ -358,10 +358,6 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { } } catch (final Exception e) { LOG.warn("Exception caught when trying to remove VM from the backup offering: ", e); - } finally { - if (vmInstance != null) { - vmInstanceDao.releaseFromLockTable(vmInstance.getId()); - } } return result; } @@ -671,6 +667,14 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { if (offering == null) { throw new CloudRuntimeException("VM backup offering ID " + vm.getBackupOfferingId() + " does not exist"); } + List backupsForVm = backupDao.listByVmId(vm.getDataCenterId(), vmId); + if (CollectionUtils.isNotEmpty(backupsForVm)) { + backupsForVm = backupsForVm.stream().filter(vmBackup -> vmBackup.getId() != backupId).collect(Collectors.toList()); + if (backupsForVm.size() <= 0 && vm.getRemoved() != null) { + removeVMFromBackupOffering(vmId, true); + } + } + final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); boolean result = backupProvider.deleteBackup(backup); if (result) { diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 190398287e6..95f17273faa 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -2413,6 +2413,7 @@ "message.action.delete.vpn.user": "Please confirm that you want to delete the VPN user.", "message.action.delete.zone": "Please confirm that you want to delete this zone.", "message.action.destroy.instance": "Please confirm that you want to destroy the instance.", +"message.action.destroy.instance.with.backups": "Please confirm that you want to destroy the instance. There may be backups associated with the instance which will not be deleted.", "message.action.destroy.systemvm": "Please confirm that you want to destroy the System VM.", "message.action.destroy.volume": "Please confirm that you want to destroy the volume.", "message.action.disable.cluster": "Please confirm that you want to disable this cluster.", @@ -2431,6 +2432,7 @@ "message.action.enable.pod": "Please confirm that you want to enable this pod.", "message.action.enable.zone": "Please confirm that you want to enable this zone.", "message.action.expunge.instance": "Please confirm that you want to expunge this instance.", +"message.action.expunge.instance.with.backups": "Please confirm that you want to expunge this instance. There may be backups associated with the instance which will not be deleted.", "message.action.force.reconnect": "Your host has been successfully forced to reconnect. This process can take up to several minutes.", "message.action.host.enable.maintenance.mode": "Enabling maintenance mode will cause a live migration of all running instances on this host to any available host.", "message.action.instance.reset.password": "Please confirm that you want to change the ROOT password for this virtual machine.", diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js index 3f97995cd16..2a65246c06f 100644 --- a/ui/src/config/section/compute.js +++ b/ui/src/config/section/compute.js @@ -371,7 +371,7 @@ export default { api: 'expungeVirtualMachine', icon: 'delete', label: 'label.action.expunge.instance', - message: 'message.action.expunge.instance', + message: (record) => { return record.backupofferingid ? 'message.action.expunge.instance.with.backups' : 'message.action.expunge.instance' }, docHelp: 'adminguide/virtual_machines.html#deleting-vms', dataView: true, show: (record, store) => { return ['Destroyed', 'Expunging'].includes(record.state) && store.features.allowuserexpungerecovervm } diff --git a/ui/src/views/compute/DestroyVM.vue b/ui/src/views/compute/DestroyVM.vue index 4c09b1282f7..46fb3c32e73 100644 --- a/ui/src/views/compute/DestroyVM.vue +++ b/ui/src/views/compute/DestroyVM.vue @@ -17,7 +17,7 @@