shwstppr commented on code in PR #10140: URL: https://github.com/apache/cloudstack/pull/10140#discussion_r1917875143
########## server/src/main/java/com/cloud/vm/UserVmManagerImpl.java: ########## @@ -6139,6 +6186,11 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE } } + List<DiskOfferingInfo> dataDiskOfferingsInfo = cmd.getDataDiskOfferingsInfo(); + if (dataDiskOfferingsInfo != null && diskOfferingId != null) { Review Comment: If diskOfferingId is the ID of just another data disk, is it better to generate a diskOfferingInfo using it and use a single variable dataDiskOfferingsInfo in the subsequent method calls? ########## api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java: ########## @@ -147,6 +148,13 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG since = "4.4") private Long rootdisksize; + @Parameter(name = ApiConstants.DATADISKS_DETAILS, Review Comment: @abh1sar is it possible to use the existing details param here? ########## server/src/main/java/com/cloud/vm/UserVmManagerImpl.java: ########## @@ -1009,32 +1011,40 @@ public UserVm resetVMSSHKey(ResetVMSSHKeyCmd cmd) throws ResourceUnavailableExce throw new InvalidParameterValueException("Vm " + userVm + " should be stopped to do SSH Key reset"); } - if (cmd.getNames() == null || cmd.getNames().isEmpty()) { + List<String> names = cmd.getNames(); + if (names == null || names.isEmpty()) { Review Comment: use StringUtils? ########## api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java: ########## @@ -102,6 +103,10 @@ public class BackupResponse extends BaseResponse { @Param(description = "zone name") private String zone; + @SerializedName(ApiConstants.VM_DETAILS) + @Param(description = "Lists the vm specific details for the backup") Review Comment: since attribute here ########## server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java: ########## @@ -273,10 +292,62 @@ public boolean deleteBackupOffering(final Long offeringId) { throw new CloudRuntimeException("Backup offering is assigned to VMs, remove the assignment(s) in order to remove the offering."); } - validateForZone(offering.getZoneId()); + validateBackupForZone(offering.getZoneId()); return backupOfferingDao.remove(offering.getId()); } + @Override + public Map<String, String> getVmDetailsForBackup(VirtualMachine vm) { + HashMap<String, String> details = new HashMap<>(); + details.put(ApiConstants.HYPERVISOR, vm.getHypervisorType().toString()); + ServiceOffering serviceOffering = entityManager.findById(ServiceOffering.class, vm.getServiceOfferingId()); Review Comment: Any reason ServiceOfferingDao, VmTemplateDao can't be used here? ########## plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java: ########## @@ -117,6 +122,12 @@ public class NetworkerBackupProvider extends AdapterBase implements BackupProvid @Inject private VMInstanceDao vmInstanceDao; + @Inject + private EntityManager entityManager; Review Comment: Can we use `*Daos` instead? ########## server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java: ########## @@ -748,6 +819,151 @@ private Backup.VolumeInfo getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, return null; } + @Override + public void updateDiskOfferingSizeFromBackup(List<DiskOfferingInfo> dataDiskOfferingsInfo, Backup backup) { + List<DiskOfferingInfo> dataDiskOfferingsInfoFromBackup = getDataDiskOfferingListFromBackup(backup); + int index = 0; + for(DiskOfferingInfo diskOfferingInfo : dataDiskOfferingsInfo) { + diskOfferingInfo.setSize(Math.max(diskOfferingInfo.getSize(), dataDiskOfferingsInfoFromBackup.get(index).getSize())); + index++; + } + } + + @Override + public DiskOfferingInfo getRootDiskOfferingInfoFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); Review Comment: These details will be stored only for new backups, what happens if an older backup is used? Does the user need to provide inputs in that case? ########## plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java: ########## @@ -22,6 +22,7 @@ import com.cloud.vm.VirtualMachine; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; + Review Comment: Changes in this file can be reverted maybe ########## server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java: ########## @@ -748,6 +819,151 @@ private Backup.VolumeInfo getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, return null; } + @Override + public void updateDiskOfferingSizeFromBackup(List<DiskOfferingInfo> dataDiskOfferingsInfo, Backup backup) { + List<DiskOfferingInfo> dataDiskOfferingsInfoFromBackup = getDataDiskOfferingListFromBackup(backup); + int index = 0; + for(DiskOfferingInfo diskOfferingInfo : dataDiskOfferingsInfo) { + diskOfferingInfo.setSize(Math.max(diskOfferingInfo.getSize(), dataDiskOfferingsInfoFromBackup.get(index).getSize())); Review Comment: It this used for fixed size offerings as well? Could there be a case with offering of size 5GB having volume of size 7GB? ########## server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java: ########## @@ -748,6 +819,151 @@ private Backup.VolumeInfo getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, return null; } + @Override + public void updateDiskOfferingSizeFromBackup(List<DiskOfferingInfo> dataDiskOfferingsInfo, Backup backup) { + List<DiskOfferingInfo> dataDiskOfferingsInfoFromBackup = getDataDiskOfferingListFromBackup(backup); + int index = 0; + for(DiskOfferingInfo diskOfferingInfo : dataDiskOfferingsInfo) { + diskOfferingInfo.setSize(Math.max(diskOfferingInfo.getSize(), dataDiskOfferingsInfoFromBackup.get(index).getSize())); + index++; + } + } + + @Override + public DiskOfferingInfo getRootDiskOfferingInfoFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + + for (int i = 0; i < diskOfferingIds.length; i++) { + if (deviceIds[i].equals("0")) { + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the root disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid root disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + return new DiskOfferingInfo(diskOffering, size, null, null, 0L); + } + } + return null; + } + + @Override + public List<DiskOfferingInfo> getDataDiskOfferingListFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + String [] minIopsList = backup.getDetail(ApiConstants.MIN_IOPS).split(","); + String [] maxIopsList = backup.getDetail(ApiConstants.MAX_IOPS).split(","); + + List<DiskOfferingInfo> diskOfferingInfoList = new ArrayList<>(); + for (int i = 0; i < diskOfferingIds.length; i++) { + Long deviceId = Long.parseLong(deviceIds[i]); + if (deviceId == 0) { + continue; + } + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + Long minIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !minIopsList[i].equals("null")) ? + Long.parseLong(minIopsList[i]) : null; + Long maxIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !maxIopsList[i].equals("null")) ? + Long.parseLong(maxIopsList[i]) : null; + diskOfferingInfoList.add(new DiskOfferingInfo(diskOffering, size, minIops, maxIops, deviceId)); + } + return diskOfferingInfoList; + } + + @Override + public boolean restoreBackupToVM(final Long backupId, final Long vmId) throws ResourceUnavailableException { + final BackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + } + validateBackupForZone(backup.getZoneId()); + + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM ID " + backup.getVmId() + " couldn't be found on existing or removed VMs"); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + if (vm.getRemoved() != null) { + throw new CloudRuntimeException("VM is removed from CloudStack"); Review Comment: Maybe better to use more generic message..."VM can not found"? ########## server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java: ########## @@ -748,6 +819,151 @@ private Backup.VolumeInfo getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, return null; } + @Override + public void updateDiskOfferingSizeFromBackup(List<DiskOfferingInfo> dataDiskOfferingsInfo, Backup backup) { + List<DiskOfferingInfo> dataDiskOfferingsInfoFromBackup = getDataDiskOfferingListFromBackup(backup); + int index = 0; + for(DiskOfferingInfo diskOfferingInfo : dataDiskOfferingsInfo) { + diskOfferingInfo.setSize(Math.max(diskOfferingInfo.getSize(), dataDiskOfferingsInfoFromBackup.get(index).getSize())); + index++; + } + } + + @Override + public DiskOfferingInfo getRootDiskOfferingInfoFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + + for (int i = 0; i < diskOfferingIds.length; i++) { + if (deviceIds[i].equals("0")) { + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the root disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid root disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + return new DiskOfferingInfo(diskOffering, size, null, null, 0L); + } + } + return null; + } + + @Override + public List<DiskOfferingInfo> getDataDiskOfferingListFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + String [] minIopsList = backup.getDetail(ApiConstants.MIN_IOPS).split(","); + String [] maxIopsList = backup.getDetail(ApiConstants.MAX_IOPS).split(","); + + List<DiskOfferingInfo> diskOfferingInfoList = new ArrayList<>(); + for (int i = 0; i < diskOfferingIds.length; i++) { + Long deviceId = Long.parseLong(deviceIds[i]); + if (deviceId == 0) { + continue; + } + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + Long minIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !minIopsList[i].equals("null")) ? + Long.parseLong(minIopsList[i]) : null; + Long maxIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !maxIopsList[i].equals("null")) ? + Long.parseLong(maxIopsList[i]) : null; + diskOfferingInfoList.add(new DiskOfferingInfo(diskOffering, size, minIops, maxIops, deviceId)); + } + return diskOfferingInfoList; + } + + @Override + public boolean restoreBackupToVM(final Long backupId, final Long vmId) throws ResourceUnavailableException { + final BackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + } + validateBackupForZone(backup.getZoneId()); + + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM ID " + backup.getVmId() + " couldn't be found on existing or removed VMs"); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + if (vm.getRemoved() != null) { + throw new CloudRuntimeException("VM is removed from CloudStack"); + } + if (vm.getRemoved() != null) { + throw new CloudRuntimeException("VM should be in the stopped state"); + } + if (!vm.getState().equals(VirtualMachine.State.Stopped)) { + throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup"); + } + + List<Backup.VolumeInfo> backupVolumes = backup.getBackedUpVolumes(); + if (backupVolumes == null) { + throw new CloudRuntimeException("Backed-up volumes not found"); + } + + List<VolumeVO> vmVolumes = volumeDao.findByInstance(vmId); + if (vmVolumes.size() != backupVolumes.size()) { + throw new CloudRuntimeException("Unable to restore VM with the current backup as the backup has different number of disks as the VM"); + } + + BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(backup.getBackupOfferingId()); + if (offering == null) { + String errorMessage = "Failed to find backup offering of the VM backup."; + throw new CloudRuntimeException("Failed to find backup offering of the VM backup."); + } + if ("networker".equals(offering.getProvider())) { Review Comment: maybe better to define in the provider itself - `offering.getProvider().supportsInstanceFromBackup()` ########## server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java: ########## @@ -748,6 +819,151 @@ private Backup.VolumeInfo getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, return null; } + @Override + public void updateDiskOfferingSizeFromBackup(List<DiskOfferingInfo> dataDiskOfferingsInfo, Backup backup) { + List<DiskOfferingInfo> dataDiskOfferingsInfoFromBackup = getDataDiskOfferingListFromBackup(backup); + int index = 0; + for(DiskOfferingInfo diskOfferingInfo : dataDiskOfferingsInfo) { + diskOfferingInfo.setSize(Math.max(diskOfferingInfo.getSize(), dataDiskOfferingsInfoFromBackup.get(index).getSize())); + index++; + } + } + + @Override + public DiskOfferingInfo getRootDiskOfferingInfoFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + + for (int i = 0; i < diskOfferingIds.length; i++) { + if (deviceIds[i].equals("0")) { + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the root disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid root disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + return new DiskOfferingInfo(diskOffering, size, null, null, 0L); + } + } + return null; + } + + @Override + public List<DiskOfferingInfo> getDataDiskOfferingListFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + String [] minIopsList = backup.getDetail(ApiConstants.MIN_IOPS).split(","); + String [] maxIopsList = backup.getDetail(ApiConstants.MAX_IOPS).split(","); + + List<DiskOfferingInfo> diskOfferingInfoList = new ArrayList<>(); + for (int i = 0; i < diskOfferingIds.length; i++) { + Long deviceId = Long.parseLong(deviceIds[i]); + if (deviceId == 0) { + continue; + } + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + Long minIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !minIopsList[i].equals("null")) ? + Long.parseLong(minIopsList[i]) : null; + Long maxIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !maxIopsList[i].equals("null")) ? + Long.parseLong(maxIopsList[i]) : null; + diskOfferingInfoList.add(new DiskOfferingInfo(diskOffering, size, minIops, maxIops, deviceId)); + } + return diskOfferingInfoList; + } + + @Override + public boolean restoreBackupToVM(final Long backupId, final Long vmId) throws ResourceUnavailableException { + final BackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + } + validateBackupForZone(backup.getZoneId()); + + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM ID " + backup.getVmId() + " couldn't be found on existing or removed VMs"); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + if (vm.getRemoved() != null) { + throw new CloudRuntimeException("VM is removed from CloudStack"); + } + if (vm.getRemoved() != null) { + throw new CloudRuntimeException("VM should be in the stopped state"); + } + if (!vm.getState().equals(VirtualMachine.State.Stopped)) { + throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup"); + } + + List<Backup.VolumeInfo> backupVolumes = backup.getBackedUpVolumes(); + if (backupVolumes == null) { + throw new CloudRuntimeException("Backed-up volumes not found"); + } + + List<VolumeVO> vmVolumes = volumeDao.findByInstance(vmId); + if (vmVolumes.size() != backupVolumes.size()) { + throw new CloudRuntimeException("Unable to restore VM with the current backup as the backup has different number of disks as the VM"); + } + + BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(backup.getBackupOfferingId()); + if (offering == null) { + String errorMessage = "Failed to find backup offering of the VM backup."; + throw new CloudRuntimeException("Failed to find backup offering of the VM backup."); + } + if ("networker".equals(offering.getProvider())) { + throw new CloudRuntimeException("Create instance from VM is not supported for Networker Backup plugin."); + } + + String backupDetailsInMessage = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(backup, "uuid", "externalId", "newVMId", "type", "status", "date"); + try { + updateVmState(vm, VirtualMachine.Event.RestoringRequested, VirtualMachine.State.Restoring); + updateVolumeState(vm, Volume.Event.RestoreRequested, Volume.State.Restoring); + ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventTypes.EVENT_VM_BACKUP_RESTORE, + String.format("Restoring VM %s from backup %s", vm.getUuid(), backup.getUuid()), + vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), + true, 0); + + String host = null; + String dataStore = null; + if (!"nas".equals(offering.getProvider())) { + Pair<HostVO, StoragePoolVO> restoreInfo = getRestoreVolumeHostAndDatastore(vm); + host = restoreInfo.first().getPrivateIpAddress(); + dataStore = restoreInfo.second().getUuid(); + } + final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); + if (!backupProvider.restoreBackupToVM(vm, backup, host, dataStore)) { + ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE, + String.format("Failed to restore VM %s from backup %s", vm.getInstanceName(), backup.getUuid()), + vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0); + throw new CloudRuntimeException("Error restoring VM from backup with uuid " + backup.getUuid()); + } + // The restore process is executed by a backup provider outside of ACS, I am using the catch-all (Exception) to + // ensure that no provider-side exception is missed. Therefore, we have a proper handling of exceptions, and rollbacks if needed. + } catch (Exception e) { + logger.error(String.format("Failed to restore backup [%s] due to: [%s].", backupDetailsInMessage, e.getMessage()), e); Review Comment: ActionEventUtils.onFailedActionEvent? ########## ui/public/locales/en.json: ########## @@ -2626,11 +2628,13 @@ "label.bucket.policy": "Bucket Policy", "label.usersecretkey": "Secret Key", "label.create.bucket": "Create Bucket", +"label.create.instance.from.backup": "Create new instance from backup", Review Comment: why two different keys? `label.create.new.instance.from.backup` and `label.create.instance.from.backup` ########## ui/src/views/compute/DeployVMFromBackup.vue: ########## @@ -0,0 +1,2211 @@ +// Licensed to the Apache Software Foundation (ASF) under one Review Comment: should this be moved to components? ########## ui/src/views/compute/wizard/VolumeDiskOfferingMap.vue: ########## @@ -0,0 +1,265 @@ +// Licensed to the Apache Software Foundation (ASF) under one Review Comment: VolumeDiskofferingSelectView? ########## server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java: ########## @@ -748,6 +819,151 @@ private Backup.VolumeInfo getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, return null; } + @Override + public void updateDiskOfferingSizeFromBackup(List<DiskOfferingInfo> dataDiskOfferingsInfo, Backup backup) { + List<DiskOfferingInfo> dataDiskOfferingsInfoFromBackup = getDataDiskOfferingListFromBackup(backup); + int index = 0; + for(DiskOfferingInfo diskOfferingInfo : dataDiskOfferingsInfo) { + diskOfferingInfo.setSize(Math.max(diskOfferingInfo.getSize(), dataDiskOfferingsInfoFromBackup.get(index).getSize())); + index++; + } + } + + @Override + public DiskOfferingInfo getRootDiskOfferingInfoFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + + for (int i = 0; i < diskOfferingIds.length; i++) { + if (deviceIds[i].equals("0")) { Review Comment: better to use - "0".equals(deviceIds[i]) ########## server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java: ########## @@ -748,6 +819,151 @@ private Backup.VolumeInfo getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, return null; } + @Override + public void updateDiskOfferingSizeFromBackup(List<DiskOfferingInfo> dataDiskOfferingsInfo, Backup backup) { + List<DiskOfferingInfo> dataDiskOfferingsInfoFromBackup = getDataDiskOfferingListFromBackup(backup); + int index = 0; + for(DiskOfferingInfo diskOfferingInfo : dataDiskOfferingsInfo) { + diskOfferingInfo.setSize(Math.max(diskOfferingInfo.getSize(), dataDiskOfferingsInfoFromBackup.get(index).getSize())); + index++; + } + } + + @Override + public DiskOfferingInfo getRootDiskOfferingInfoFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + + for (int i = 0; i < diskOfferingIds.length; i++) { + if (deviceIds[i].equals("0")) { + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the root disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid root disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + return new DiskOfferingInfo(diskOffering, size, null, null, 0L); + } + } + return null; + } + + @Override + public List<DiskOfferingInfo> getDataDiskOfferingListFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + String [] minIopsList = backup.getDetail(ApiConstants.MIN_IOPS).split(","); + String [] maxIopsList = backup.getDetail(ApiConstants.MAX_IOPS).split(","); + + List<DiskOfferingInfo> diskOfferingInfoList = new ArrayList<>(); + for (int i = 0; i < diskOfferingIds.length; i++) { + Long deviceId = Long.parseLong(deviceIds[i]); + if (deviceId == 0) { + continue; + } + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); Review Comment: edge case - what if the disk offering access for zone/domain is removed. (Not sure of UI but can be done using API) ########## server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java: ########## @@ -748,6 +819,151 @@ private Backup.VolumeInfo getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, return null; } + @Override + public void updateDiskOfferingSizeFromBackup(List<DiskOfferingInfo> dataDiskOfferingsInfo, Backup backup) { + List<DiskOfferingInfo> dataDiskOfferingsInfoFromBackup = getDataDiskOfferingListFromBackup(backup); + int index = 0; + for(DiskOfferingInfo diskOfferingInfo : dataDiskOfferingsInfo) { + diskOfferingInfo.setSize(Math.max(diskOfferingInfo.getSize(), dataDiskOfferingsInfoFromBackup.get(index).getSize())); + index++; + } + } + + @Override + public DiskOfferingInfo getRootDiskOfferingInfoFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + + for (int i = 0; i < diskOfferingIds.length; i++) { + if (deviceIds[i].equals("0")) { + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the root disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid root disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + return new DiskOfferingInfo(diskOffering, size, null, null, 0L); + } + } + return null; + } + + @Override + public List<DiskOfferingInfo> getDataDiskOfferingListFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + String [] minIopsList = backup.getDetail(ApiConstants.MIN_IOPS).split(","); + String [] maxIopsList = backup.getDetail(ApiConstants.MAX_IOPS).split(","); + + List<DiskOfferingInfo> diskOfferingInfoList = new ArrayList<>(); + for (int i = 0; i < diskOfferingIds.length; i++) { + Long deviceId = Long.parseLong(deviceIds[i]); + if (deviceId == 0) { + continue; + } + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + Long minIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !minIopsList[i].equals("null")) ? + Long.parseLong(minIopsList[i]) : null; + Long maxIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !maxIopsList[i].equals("null")) ? + Long.parseLong(maxIopsList[i]) : null; + diskOfferingInfoList.add(new DiskOfferingInfo(diskOffering, size, minIops, maxIops, deviceId)); + } + return diskOfferingInfoList; + } + + @Override + public boolean restoreBackupToVM(final Long backupId, final Long vmId) throws ResourceUnavailableException { + final BackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + } + validateBackupForZone(backup.getZoneId()); + + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM ID " + backup.getVmId() + " couldn't be found on existing or removed VMs"); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + if (vm.getRemoved() != null) { + throw new CloudRuntimeException("VM is removed from CloudStack"); + } + if (vm.getRemoved() != null) { Review Comment: wrong check ########## server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java: ########## @@ -748,6 +819,151 @@ private Backup.VolumeInfo getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, return null; } + @Override + public void updateDiskOfferingSizeFromBackup(List<DiskOfferingInfo> dataDiskOfferingsInfo, Backup backup) { + List<DiskOfferingInfo> dataDiskOfferingsInfoFromBackup = getDataDiskOfferingListFromBackup(backup); + int index = 0; + for(DiskOfferingInfo diskOfferingInfo : dataDiskOfferingsInfo) { + diskOfferingInfo.setSize(Math.max(diskOfferingInfo.getSize(), dataDiskOfferingsInfoFromBackup.get(index).getSize())); + index++; + } + } + + @Override + public DiskOfferingInfo getRootDiskOfferingInfoFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + + for (int i = 0; i < diskOfferingIds.length; i++) { + if (deviceIds[i].equals("0")) { + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the root disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid root disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + return new DiskOfferingInfo(diskOffering, size, null, null, 0L); + } + } + return null; + } + + @Override + public List<DiskOfferingInfo> getDataDiskOfferingListFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + String [] minIopsList = backup.getDetail(ApiConstants.MIN_IOPS).split(","); + String [] maxIopsList = backup.getDetail(ApiConstants.MAX_IOPS).split(","); + + List<DiskOfferingInfo> diskOfferingInfoList = new ArrayList<>(); + for (int i = 0; i < diskOfferingIds.length; i++) { + Long deviceId = Long.parseLong(deviceIds[i]); + if (deviceId == 0) { + continue; + } + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + Long minIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !minIopsList[i].equals("null")) ? + Long.parseLong(minIopsList[i]) : null; + Long maxIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !maxIopsList[i].equals("null")) ? + Long.parseLong(maxIopsList[i]) : null; + diskOfferingInfoList.add(new DiskOfferingInfo(diskOffering, size, minIops, maxIops, deviceId)); + } + return diskOfferingInfoList; + } + + @Override + public boolean restoreBackupToVM(final Long backupId, final Long vmId) throws ResourceUnavailableException { + final BackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + } + validateBackupForZone(backup.getZoneId()); + + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM ID " + backup.getVmId() + " couldn't be found on existing or removed VMs"); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + if (vm.getRemoved() != null) { + throw new CloudRuntimeException("VM is removed from CloudStack"); + } + if (vm.getRemoved() != null) { + throw new CloudRuntimeException("VM should be in the stopped state"); + } + if (!vm.getState().equals(VirtualMachine.State.Stopped)) { + throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup"); + } + + List<Backup.VolumeInfo> backupVolumes = backup.getBackedUpVolumes(); + if (backupVolumes == null) { + throw new CloudRuntimeException("Backed-up volumes not found"); Review Comment: Better message can be used ########## server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java: ########## @@ -748,6 +819,151 @@ private Backup.VolumeInfo getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, return null; } + @Override + public void updateDiskOfferingSizeFromBackup(List<DiskOfferingInfo> dataDiskOfferingsInfo, Backup backup) { + List<DiskOfferingInfo> dataDiskOfferingsInfoFromBackup = getDataDiskOfferingListFromBackup(backup); + int index = 0; + for(DiskOfferingInfo diskOfferingInfo : dataDiskOfferingsInfo) { + diskOfferingInfo.setSize(Math.max(diskOfferingInfo.getSize(), dataDiskOfferingsInfoFromBackup.get(index).getSize())); + index++; + } + } + + @Override + public DiskOfferingInfo getRootDiskOfferingInfoFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + + for (int i = 0; i < diskOfferingIds.length; i++) { + if (deviceIds[i].equals("0")) { + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the root disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid root disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + return new DiskOfferingInfo(diskOffering, size, null, null, 0L); + } + } + return null; + } + + @Override + public List<DiskOfferingInfo> getDataDiskOfferingListFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + String [] minIopsList = backup.getDetail(ApiConstants.MIN_IOPS).split(","); + String [] maxIopsList = backup.getDetail(ApiConstants.MAX_IOPS).split(","); + + List<DiskOfferingInfo> diskOfferingInfoList = new ArrayList<>(); + for (int i = 0; i < diskOfferingIds.length; i++) { + Long deviceId = Long.parseLong(deviceIds[i]); + if (deviceId == 0) { + continue; + } + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + Long minIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !minIopsList[i].equals("null")) ? Review Comment: ```suggestion Long minIops = (Boolean.TRUE.equals(diskOffering.isCustomizedIops()) && !minIopsList[i].equals("null")) ? ``` ########## server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java: ########## @@ -748,6 +819,151 @@ private Backup.VolumeInfo getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, return null; } + @Override + public void updateDiskOfferingSizeFromBackup(List<DiskOfferingInfo> dataDiskOfferingsInfo, Backup backup) { + List<DiskOfferingInfo> dataDiskOfferingsInfoFromBackup = getDataDiskOfferingListFromBackup(backup); + int index = 0; + for(DiskOfferingInfo diskOfferingInfo : dataDiskOfferingsInfo) { + diskOfferingInfo.setSize(Math.max(diskOfferingInfo.getSize(), dataDiskOfferingsInfoFromBackup.get(index).getSize())); + index++; + } + } + + @Override + public DiskOfferingInfo getRootDiskOfferingInfoFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + + for (int i = 0; i < diskOfferingIds.length; i++) { + if (deviceIds[i].equals("0")) { + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the root disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid root disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + return new DiskOfferingInfo(diskOffering, size, null, null, 0L); + } + } + return null; + } + + @Override + public List<DiskOfferingInfo> getDataDiskOfferingListFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + String [] minIopsList = backup.getDetail(ApiConstants.MIN_IOPS).split(","); + String [] maxIopsList = backup.getDetail(ApiConstants.MAX_IOPS).split(","); + + List<DiskOfferingInfo> diskOfferingInfoList = new ArrayList<>(); + for (int i = 0; i < diskOfferingIds.length; i++) { + Long deviceId = Long.parseLong(deviceIds[i]); + if (deviceId == 0) { + continue; + } + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + Long minIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !minIopsList[i].equals("null")) ? + Long.parseLong(minIopsList[i]) : null; + Long maxIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !maxIopsList[i].equals("null")) ? + Long.parseLong(maxIopsList[i]) : null; + diskOfferingInfoList.add(new DiskOfferingInfo(diskOffering, size, minIops, maxIops, deviceId)); + } + return diskOfferingInfoList; + } + + @Override + public boolean restoreBackupToVM(final Long backupId, final Long vmId) throws ResourceUnavailableException { + final BackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + } + validateBackupForZone(backup.getZoneId()); + + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM ID " + backup.getVmId() + " couldn't be found on existing or removed VMs"); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + if (vm.getRemoved() != null) { + throw new CloudRuntimeException("VM is removed from CloudStack"); + } + if (vm.getRemoved() != null) { + throw new CloudRuntimeException("VM should be in the stopped state"); + } + if (!vm.getState().equals(VirtualMachine.State.Stopped)) { + throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup"); + } + + List<Backup.VolumeInfo> backupVolumes = backup.getBackedUpVolumes(); + if (backupVolumes == null) { + throw new CloudRuntimeException("Backed-up volumes not found"); + } + + List<VolumeVO> vmVolumes = volumeDao.findByInstance(vmId); + if (vmVolumes.size() != backupVolumes.size()) { + throw new CloudRuntimeException("Unable to restore VM with the current backup as the backup has different number of disks as the VM"); + } + + BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(backup.getBackupOfferingId()); + if (offering == null) { + String errorMessage = "Failed to find backup offering of the VM backup."; Review Comment: not used ########## server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java: ########## @@ -748,6 +819,151 @@ private Backup.VolumeInfo getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, return null; } + @Override + public void updateDiskOfferingSizeFromBackup(List<DiskOfferingInfo> dataDiskOfferingsInfo, Backup backup) { + List<DiskOfferingInfo> dataDiskOfferingsInfoFromBackup = getDataDiskOfferingListFromBackup(backup); + int index = 0; + for(DiskOfferingInfo diskOfferingInfo : dataDiskOfferingsInfo) { + diskOfferingInfo.setSize(Math.max(diskOfferingInfo.getSize(), dataDiskOfferingsInfoFromBackup.get(index).getSize())); + index++; + } + } + + @Override + public DiskOfferingInfo getRootDiskOfferingInfoFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + + for (int i = 0; i < diskOfferingIds.length; i++) { + if (deviceIds[i].equals("0")) { + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the root disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid root disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + return new DiskOfferingInfo(diskOffering, size, null, null, 0L); + } + } + return null; + } + + @Override + public List<DiskOfferingInfo> getDataDiskOfferingListFromBackup(Backup backup) { + String diskOfferingIdsDetail = backup.getDetail(ApiConstants.DISK_OFFERING_IDS); + if (diskOfferingIdsDetail == null) { + return null; + } + + String [] diskOfferingIds = diskOfferingIdsDetail.split(","); + String [] deviceIds = backup.getDetail(ApiConstants.DEVICE_IDS).split(","); + String [] diskSizes = backup.getDetail(ApiConstants.DISK_SIZES).split(","); + String [] minIopsList = backup.getDetail(ApiConstants.MIN_IOPS).split(","); + String [] maxIopsList = backup.getDetail(ApiConstants.MAX_IOPS).split(","); + + List<DiskOfferingInfo> diskOfferingInfoList = new ArrayList<>(); + for (int i = 0; i < diskOfferingIds.length; i++) { + Long deviceId = Long.parseLong(deviceIds[i]); + if (deviceId == 0) { + continue; + } + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(diskOfferingIds[i]); + if (diskOffering == null) { + throw new CloudRuntimeException("Unable to find the disk offering with uuid (" + diskOfferingIds[i] + ") stored in backup. Please specify a valid disk offering id while creating the instance"); + } + Long size = Long.parseLong(diskSizes[i]) / (1024 * 1024 * 1024); + Long minIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !minIopsList[i].equals("null")) ? + Long.parseLong(minIopsList[i]) : null; + Long maxIops = (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops() && !maxIopsList[i].equals("null")) ? + Long.parseLong(maxIopsList[i]) : null; + diskOfferingInfoList.add(new DiskOfferingInfo(diskOffering, size, minIops, maxIops, deviceId)); + } + return diskOfferingInfoList; + } + + @Override + public boolean restoreBackupToVM(final Long backupId, final Long vmId) throws ResourceUnavailableException { + final BackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + } + validateBackupForZone(backup.getZoneId()); + + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM ID " + backup.getVmId() + " couldn't be found on existing or removed VMs"); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + if (vm.getRemoved() != null) { + throw new CloudRuntimeException("VM is removed from CloudStack"); + } + if (vm.getRemoved() != null) { + throw new CloudRuntimeException("VM should be in the stopped state"); + } + if (!vm.getState().equals(VirtualMachine.State.Stopped)) { + throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup"); + } + + List<Backup.VolumeInfo> backupVolumes = backup.getBackedUpVolumes(); + if (backupVolumes == null) { + throw new CloudRuntimeException("Backed-up volumes not found"); + } + + List<VolumeVO> vmVolumes = volumeDao.findByInstance(vmId); + if (vmVolumes.size() != backupVolumes.size()) { + throw new CloudRuntimeException("Unable to restore VM with the current backup as the backup has different number of disks as the VM"); + } + + BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(backup.getBackupOfferingId()); + if (offering == null) { + String errorMessage = "Failed to find backup offering of the VM backup."; + throw new CloudRuntimeException("Failed to find backup offering of the VM backup."); + } + if ("networker".equals(offering.getProvider())) { + throw new CloudRuntimeException("Create instance from VM is not supported for Networker Backup plugin."); + } + + String backupDetailsInMessage = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(backup, "uuid", "externalId", "newVMId", "type", "status", "date"); + try { + updateVmState(vm, VirtualMachine.Event.RestoringRequested, VirtualMachine.State.Restoring); + updateVolumeState(vm, Volume.Event.RestoreRequested, Volume.State.Restoring); + ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventTypes.EVENT_VM_BACKUP_RESTORE, + String.format("Restoring VM %s from backup %s", vm.getUuid(), backup.getUuid()), + vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), + true, 0); + + String host = null; + String dataStore = null; + if (!"nas".equals(offering.getProvider())) { + Pair<HostVO, StoragePoolVO> restoreInfo = getRestoreVolumeHostAndDatastore(vm); + host = restoreInfo.first().getPrivateIpAddress(); + dataStore = restoreInfo.second().getUuid(); + } + final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); + if (!backupProvider.restoreBackupToVM(vm, backup, host, dataStore)) { + ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE, + String.format("Failed to restore VM %s from backup %s", vm.getInstanceName(), backup.getUuid()), + vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0); + throw new CloudRuntimeException("Error restoring VM from backup with uuid " + backup.getUuid()); + } + // The restore process is executed by a backup provider outside of ACS, I am using the catch-all (Exception) to + // ensure that no provider-side exception is missed. Therefore, we have a proper handling of exceptions, and rollbacks if needed. + } catch (Exception e) { Review Comment: possible to avoid catch all? -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: commits-unsubscr...@cloudstack.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org