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

Reply via email to