JoaoJandre commented on code in PR #10140:
URL: https://github.com/apache/cloudstack/pull/10140#discussion_r2228507025


##########
server/src/main/java/com/cloud/server/ManagementServerImpl.java:
##########
@@ -1036,6 +1037,8 @@ public class ManagementServerImpl extends ManagerBase 
implements ManagementServe
     @Inject
     StoragePoolTagsDao storagePoolTagsDao;
     @Inject
+    BackupManager backupManager;

Review Comment:
   ```suggestion
       private BackupManager backupManager;
   ```



##########
server/src/main/java/com/cloud/vm/UserVmManagerImpl.java:
##########
@@ -9317,6 +9391,234 @@ public boolean unmanageUserVM(Long vmId) {
         return true;
     }
 
+    private void updateDetailsWithRootDiskAttributes(Map<String, String> 
details, VmDiskInfo rootVmDiskInfo) {
+        details.put(VmDetailConstants.ROOT_DISK_SIZE, 
rootVmDiskInfo.getSize().toString());
+        if (rootVmDiskInfo.getMinIops() != null) {
+            details.put(MIN_IOPS, rootVmDiskInfo.getMinIops().toString());
+        }
+        if (rootVmDiskInfo.getMaxIops() != null) {
+            details.put(MAX_IOPS, rootVmDiskInfo.getMaxIops().toString());
+        }
+    }
+
+    private void checkRootDiskSizeAgainstBackup(Long 
instanceVolumeSize,DiskOffering rootDiskOffering, Long backupVolumeSize) {
+        Long instanceRootDiskSize = rootDiskOffering.isCustomized() ? 
instanceVolumeSize : rootDiskOffering.getDiskSize() / GiB_TO_BYTES;
+        if (instanceRootDiskSize < backupVolumeSize) {
+            throw new InvalidParameterValueException(
+                    String.format("Instance volume root disk size %d[GiB] 
cannot be less than the backed-up volume size %d[GiB].",
+                            instanceVolumeSize, backupVolumeSize));
+        }
+    }
+
+    @Override
+    public UserVm allocateVMFromBackup(CreateVMFromBackupCmd cmd) throws 
InsufficientCapacityException, ResourceAllocationException, 
ResourceUnavailableException {
+        if (!backupManager.canCreateInstanceFromBackup(cmd.getBackupId())) {
+            throw new CloudRuntimeException("Create instance from backup is 
not supported for this provider.");
+        }
+        DataCenter zone = _dcDao.findById(cmd.getZoneId());
+        if (zone == null) {
+            throw new InvalidParameterValueException("Unable to find zone by 
id=" + cmd.getZoneId());
+        }
+
+        BackupVO backup = backupDao.findById(cmd.getBackupId());
+        if (backup == null) {
+            throw new InvalidParameterValueException("Backup " + 
cmd.getBackupId() + " does not exist");
+        }
+        if (backup.getZoneId() != cmd.getZoneId()) {
+            throw new InvalidParameterValueException("Instance should be 
created in the same zone as the backup");
+        }
+        backupManager.validateBackupForZone(backup.getZoneId());
+        backupDao.loadDetails(backup);
+
+        verifyDetails(cmd.getDetails());
+
+        UserVmVO backupVm = _vmDao.findByIdIncludingRemoved(backup.getVmId());
+        HypervisorType hypervisorType = backupVm.getHypervisorType();
+
+        Long serviceOfferingId = cmd.getServiceOfferingId();
+        ServiceOffering serviceOffering;
+        if (serviceOfferingId != null) {
+            serviceOffering = serviceOfferingDao.findById(serviceOfferingId);
+            if (serviceOffering == null) {
+                throw new InvalidParameterValueException("Unable to find 
service offering: " + serviceOffering.getId());
+            }
+        } else {
+            String serviceOfferingUuid = 
backup.getDetail(ApiConstants.SERVICE_OFFERING_ID);
+            if (serviceOfferingUuid == null) {
+                throw new CloudRuntimeException("Backup doesn't contain 
service offering uuid. Please specify a valid service offering id while 
creating the instance");
+            }
+            serviceOffering = 
serviceOfferingDao.findByUuid(serviceOfferingUuid);
+            if (serviceOffering == null) {
+                throw new CloudRuntimeException("Unable to find service 
offering with the uuid stored in backup. Please specify a valid service 
offering id while creating instance");
+            }
+        }
+        verifyServiceOffering(cmd, serviceOffering);
+
+        VirtualMachineTemplate template;
+        if (cmd.getTemplateId() != null) {
+            Long templateId = cmd.getTemplateId();
+            template = _templateDao.findById(templateId);
+            if (template == null) {
+                throw new InvalidParameterValueException("Unable to use 
template " + templateId);
+            }
+        } else {
+            String templateUuid = backup.getDetail(ApiConstants.TEMPLATE_ID);
+            if (templateUuid == null) {
+                throw new CloudRuntimeException("Backup doesn't contain 
Template uuid. Please specify a valid Template/ISO while creating the 
instance");
+            }
+            template = _templateDao.findByUuid(templateUuid);
+            if (template == null) {
+                throw new CloudRuntimeException("Unable to find template 
associated with the backup. Please specify a valid Template/ISO while creating 
instance");
+            }
+        }
+        verifyTemplate(cmd, template, serviceOffering.getId());
+
+        Long size = cmd.getSize();
+
+        Long diskOfferingId = cmd.getDiskOfferingId();
+        Boolean isIso = template.getFormat().equals(ImageFormat.ISO);
+        if (diskOfferingId != null) {
+            if (!isIso) {
+                throw new 
InvalidParameterValueException(ApiConstants.DISK_OFFERING_ID + " parameter is 
supported for creating instance from backup only for ISO. For creating VMs with 
templates, please use the parameter " + ApiConstants.DATADISKS_DETAILS);
+            }
+            DiskOffering diskOffering = 
_diskOfferingDao.findById(diskOfferingId);
+            if (diskOffering == null) {
+                throw new InvalidParameterValueException("Unable to find disk 
offering " + diskOfferingId);
+            }
+            if (diskOffering.isComputeOnly()) {
+                throw new InvalidParameterValueException(String.format("The 
disk offering %s provided is directly mapped to a service offering, please 
provide an individual disk offering", diskOffering));
+            }
+        }
+
+        Long overrideDiskOfferingId = cmd.getOverrideDiskOfferingId();
+
+        VmDiskInfo rootVmDiskInfoFromBackup = 
backupManager.getRootDiskInfoFromBackup(backup);
+
+        if (isIso) {
+            if (diskOfferingId == null) {
+                diskOfferingId = 
rootVmDiskInfoFromBackup.getDiskOffering().getId();
+                updateDetailsWithRootDiskAttributes(cmd.getDetails(), 
rootVmDiskInfoFromBackup);
+                size = rootVmDiskInfoFromBackup.getSize();
+            } else {
+                DiskOffering rootDiskOffering = 
_diskOfferingDao.findById(diskOfferingId);
+                checkRootDiskSizeAgainstBackup(size, rootDiskOffering, 
rootVmDiskInfoFromBackup.getSize());
+            }
+        } else {
+            if (overrideDiskOfferingId == null) {
+                overrideDiskOfferingId = serviceOffering.getDiskOfferingId();
+                updateDetailsWithRootDiskAttributes(cmd.getDetails(), 
rootVmDiskInfoFromBackup);
+            } else {
+                DiskOffering overrideDiskOffering = 
_diskOfferingDao.findById(overrideDiskOfferingId);
+                if (overrideDiskOffering.isComputeOnly()) {
+                    updateDetailsWithRootDiskAttributes(cmd.getDetails(), 
rootVmDiskInfoFromBackup);
+                } else {
+                    String diskSizeFromDetails = 
cmd.getDetails().get(VmDetailConstants.ROOT_DISK_SIZE);
+                    Long rootDiskSize = diskSizeFromDetails == null ? null : 
Long.parseLong(diskSizeFromDetails);
+                    checkRootDiskSizeAgainstBackup(rootDiskSize, 
overrideDiskOffering, rootVmDiskInfoFromBackup.getSize());
+                }
+            }
+        }
+
+        List<VmDiskInfo> dataDiskInfoList = cmd.getDataDiskInfoList();
+        if (dataDiskInfoList != null) {
+            backupManager.checkVmDisksSizeAgainstBackup(dataDiskInfoList, 
backup);
+        } else {
+            dataDiskInfoList = 
backupManager.getDataDiskInfoListFromBackup(backup);
+        }
+
+        List<Long> networkIds = cmd.getNetworkIds();
+        Account owner = 
_accountService.getActiveAccountById(cmd.getEntityOwnerId());
+        LinkedHashMap<Integer, Long> userVmNetworkMap = 
getVmOvfNetworkMapping(zone, owner, template, cmd.getVmNetworkMap());
+        if (MapUtils.isNotEmpty(userVmNetworkMap)) {
+            networkIds = new ArrayList<>(userVmNetworkMap.values());
+        }
+
+        Map<Long, IpAddresses> ipToNetworkMap = cmd.getIpToNetworkMap();
+        if (networkIds == null && ipToNetworkMap == null) {
+            networkIds = new ArrayList<Long>();
+            ipToNetworkMap = backupManager.getIpToNetworkMapFromBackup(backup, 
cmd.getPreserveIp(), networkIds);
+        }
+
+        UserVm vm = createVirtualMachine(cmd, zone, owner, serviceOffering, 
template, hypervisorType, diskOfferingId, size, overrideDiskOfferingId, 
dataDiskInfoList, networkIds, ipToNetworkMap, null, null);
+
+        String vmSettingsFromBackup = 
backup.getDetail(ApiConstants.VM_SETTINGS);
+        if (vm != null && vmSettingsFromBackup != null) {
+            UserVmVO vmVO = _vmDao.findById(vm.getId());
+            Map<String, String> details = 
vmInstanceDetailsDao.listDetailsKeyPairs(vm.getId());
+            vmVO.setDetails(details);
+
+            Type type = new TypeToken<Map<String, String>>(){}.getType();
+            Map<String, String> vmDetailsFromBackup = new 
Gson().fromJson(vmSettingsFromBackup, type);
+            for (Entry<String, String> entry : vmDetailsFromBackup.entrySet()) 
{
+                if (!details.containsKey(entry.getKey())) {
+                    vmVO.setDetail(entry.getKey(), entry.getValue());
+                }
+            }
+            _vmDao.saveDetails(vmVO);
+        }
+
+        return vm;
+    }
+
+    @Override
+    public UserVm restoreVMFromBackup(CreateVMFromBackupCmd cmd) throws 
ResourceUnavailableException, InsufficientCapacityException, 
ResourceAllocationException {
+        long vmId = cmd.getEntityId();
+        Map<Long, DiskOffering> diskOfferingMap = 
cmd.getDataDiskTemplateToDiskOfferingMap();
+        Map<VirtualMachineProfile.Param, Object> additonalParams = new 
HashMap<>();
+        UserVm vm;
+
+        try {
+            vm = startVirtualMachine(vmId, null, null, null, diskOfferingMap, 
additonalParams, null);
+
+            boolean status =  
stopVirtualMachine(CallContext.current().getCallingUserId(), vm.getId()) ;

Review Comment:
   Why start then stop the VM?



##########
engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java:
##########
@@ -193,27 +272,97 @@ public List<BackupVO> listBackupsByVMandIntervalType(Long 
vmId, Backup.Type back
     }
 
     @Override
-    public BackupResponse newBackupResponse(Backup backup) {
+    public void loadDetails(BackupVO backup) {
+        Map<String, String> details = 
backupDetailsDao.listDetailsKeyPairs(backup.getId());
+        backup.setDetails(details);
+    }
+
+    @Override
+    public void saveDetails(BackupVO backup) {
+        Map<String, String> detailsStr = backup.getDetails();
+        if (detailsStr == null) {
+            return;
+        }
+        List<BackupDetailVO> details = new ArrayList<BackupDetailVO>();
+        for (String key : detailsStr.keySet()) {
+            BackupDetailVO detail = new BackupDetailVO(backup.getId(), key, 
detailsStr.get(key), true);
+            details.add(detail);
+        }
+        backupDetailsDao.saveDetails(details);
+    }
+
+    @Override
+    public List<Long> listVmIdsWithBackupsInZone(Long zoneId) {
+        SearchCriteria<Long> sc = backupVmSearchInZone.create();
+        sc.setParameters("zone_id", zoneId);
+        return customSearchIncludingRemoved(sc, null);
+    }
+
+    Map<String, String> getDetailsFromBackupDetails(Long backupId) {

Review Comment:
   Considering that this method does not do a single query in the `backups` 
table (instead querying multiple separate tables), don't you think it should 
not be somewhere else? I think that it should not be on the BackupDaoImpl.



##########
server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java:
##########
@@ -406,9 +501,12 @@ public boolean removeVMFromBackupOffering(final Long vmId, 
final boolean forced)
                 }
             }
             if ((result || forced) && vmInstanceDao.update(vm.getId(), vm)) {
-                
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, 
vm.getAccountId(), vm.getDataCenterId(), vm.getId(),
-                        "Backup-" + vm.getHostName() + "-" + vm.getUuid(), 
vm.getBackupOfferingId(), null, null,
-                        Backup.class.getSimpleName(), vm.getUuid());
+                final List<Backup> backups = backupDao.listByVmId(null, 
vm.getId());
+                if (backups.size() == 0) {
+                    
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, 
vm.getAccountId(), vm.getDataCenterId(), vm.getId(),
+                            "Backup-" + vm.getHostName() + "-" + vm.getUuid(), 
backupOfferingId, null, null,
+                            Backup.class.getSimpleName(), vm.getUuid());
+                }

Review Comment:
   If this event is not published here, when will it be published?



##########
server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java:
##########
@@ -926,6 +1058,224 @@ private Backup.VolumeInfo 
getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes,
         return null;
     }
 
+    @Override
+    public void checkVmDisksSizeAgainstBackup(List<VmDiskInfo> vmDiskInfoList, 
Backup backup) {
+        List<VmDiskInfo> vmDiskInfoListFromBackup = 
getDataDiskInfoListFromBackup(backup);
+        int index = 0;
+        if (vmDiskInfoList.size() != vmDiskInfoListFromBackup.size()) {
+            throw new InvalidParameterValueException("Unable to create 
Instance from Backup " +
+                    "as the backup has a different number of disks than the 
Instance.");
+        }
+        for (VmDiskInfo vmDiskInfo : vmDiskInfoList) {
+            if (index < vmDiskInfoListFromBackup.size()) {
+                if (vmDiskInfo.getSize() < 
vmDiskInfoListFromBackup.get(index).getSize()) {
+                    throw new InvalidParameterValueException(
+                            String.format("Instance volume size %d[GiB] cannot 
be less than the backed-up volume size %d[GiB].",
+                            vmDiskInfo.getSize(), 
vmDiskInfoListFromBackup.get(index).getSize()));
+                }
+            }
+            index++;
+        }
+    }
+
+    @Override
+    public VmDiskInfo getRootDiskInfoFromBackup(Backup backup) {
+        List<Backup.VolumeInfo> volumes = backup.getBackedUpVolumes();
+        VmDiskInfo rootDiskOffering = null;
+        if (volumes == null || volumes.isEmpty()) {
+            throw new CloudRuntimeException("Failed to get backed-up volumes 
info from backup");
+        }
+        for (Backup.VolumeInfo volume : volumes) {
+            if (volume.getType() == Volume.Type.ROOT) {
+                DiskOfferingVO diskOffering = 
diskOfferingDao.findByUuid(volume.getDiskOfferingId());
+                if (diskOffering == null) {
+                    throw new CloudRuntimeException(String.format("Unable to 
find the root disk offering with uuid (%s) " +
+                            "stored in backup. Please specify a valid root 
disk offering id while creating the instance",
+                            volume.getDiskOfferingId()));
+                }
+                Long size = volume.getSize() / (1024 * 1024 * 1024);
+                rootDiskOffering = new VmDiskInfo(diskOffering, size, 
volume.getMinIops(), volume.getMaxIops());
+            }
+        }
+        if (rootDiskOffering == null) {
+            throw new CloudRuntimeException("Failed to get the root disk in 
backed-up volumes info from backup");
+        }
+        return rootDiskOffering;
+    }
+
+    @Override
+    public List<VmDiskInfo> getDataDiskInfoListFromBackup(Backup backup) {
+        List<VmDiskInfo> vmDiskInfoList = new ArrayList<>();
+        List<Backup.VolumeInfo> volumes = backup.getBackedUpVolumes();
+        if (volumes == null || volumes.isEmpty()) {
+            throw new CloudRuntimeException("Failed to get backed-up Volumes 
info from backup");
+        }
+        for (Backup.VolumeInfo volume : volumes) {
+            if (volume.getType() == Volume.Type.DATADISK) {
+                DiskOfferingVO diskOffering = 
diskOfferingDao.findByUuid(volume.getDiskOfferingId());
+                if (diskOffering == null || 
diskOffering.getState().equals(DiskOffering.State.Inactive)) {
+                    throw new CloudRuntimeException("Unable to find the disk 
offering with uuid (" + volume.getDiskOfferingId() + ") stored in backup. " +
+                            "Please specify a valid disk offering id while 
creating the instance");
+                }
+                Long size = volume.getSize() / (1024 * 1024 * 1024);
+                vmDiskInfoList.add(new VmDiskInfo(diskOffering, size, 
volume.getMinIops(), volume.getMaxIops(), volume.getDeviceId()));
+            }
+        }
+        return vmDiskInfoList;
+    }
+
+    private List<String> parseAddressString(String input) {
+        if (input == null) return null;
+        return Arrays.stream(input.split(","))
+                .map(s -> "null".equalsIgnoreCase(s.trim()) ? null : s.trim())
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public Map<Long, Network.IpAddresses> getIpToNetworkMapFromBackup(Backup 
backup, boolean preserveIps, List<Long> networkIds)
+    {
+        Map<Long, Network.IpAddresses> ipToNetworkMap = new 
LinkedHashMap<Long, Network.IpAddresses>();
+
+        String nicsJson = backup.getDetail(ApiConstants.NICS);
+        if (nicsJson == null) {
+            throw new CloudRuntimeException("Backup doesn't contain network 
information. " +
+                    "Please specify at least one valid network while creating 
instance");
+        }
+
+        Type type = new TypeToken<List<Map<String, String>>>(){}.getType();
+        List<Map<String, String>> nics = new Gson().fromJson(nicsJson, type);
+
+        for (Map<String, String> nic : nics) {
+            String networkUuid = nic.get(ApiConstants.NETWORK_ID);
+            if (networkUuid == null) {
+                throw new CloudRuntimeException("Backup doesn't contain 
network information. " +
+                        "Please specify at least one valid network while 
creating instance");
+            }
+
+            Network network = networkDao.findByUuid(networkUuid);
+            if (network == null) {
+                throw new CloudRuntimeException("Unable to find network with 
the uuid " + networkUuid + " stored in backup. " +
+                        "Please specify a valid network id while creating the 
instance");
+            }
+
+            Long networkId = network.getId();
+            Network.IpAddresses ipAddresses = null;
+
+            if (preserveIps) {
+                String ip = nic.get(ApiConstants.IP_ADDRESS);
+                String ipv6 = nic.get(ApiConstants.IP6_ADDRESS);
+                String mac = nic.get(ApiConstants.MAC_ADDRESS);
+                ipAddresses = networkService.getIpAddressesFromIps(ip, ipv6, 
mac);
+            }
+
+            ipToNetworkMap.put(networkId, ipAddresses);
+            networkIds.add(networkId);
+        }
+        return ipToNetworkMap;
+    }
+
+    @Override
+    public Boolean canCreateInstanceFromBackup(final Long backupId) {
+        final BackupVO backup = backupDao.findById(backupId);
+        BackupOffering offering = 
backupOfferingDao.findByIdIncludingRemoved(backup.getBackupOfferingId());
+        if (offering == null) {
+            throw new CloudRuntimeException("Failed to find backup offering");
+        }
+        final BackupProvider backupProvider = 
getBackupProvider(offering.getProvider());
+        return backupProvider.supportsInstanceFromBackup();
+    }
+
+    @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");
+        }
+        if (backup.getStatus() != Backup.Status.BackedUp) {
+            throw new CloudRuntimeException("Backup should be in BackedUp 
state");
+        }
+        validateBackupForZone(backup.getZoneId());
+
+        VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId);
+        if (vm == null) {
+            throw new CloudRuntimeException("Instance with ID " + 
backup.getVmId() + " couldn't be found.");
+        }
+        accountManager.checkAccess(CallContext.current().getCallingAccount(), 
null, true, vm);
+
+        if (vm.getRemoved() != null) {
+            throw new CloudRuntimeException("Instance with ID " + 
backup.getVmId() + " couldn't be found.");
+        }
+        if (!vm.getState().equals(VirtualMachine.State.Stopped)) {
+            throw new CloudRuntimeException("The VM should be in stopped 
state");
+        }
+
+        List<Backup.VolumeInfo> backupVolumes = backup.getBackedUpVolumes();
+        if (backupVolumes == null) {
+            throw new CloudRuntimeException("Backed up volumes info not found 
in the backup");
+        }
+
+        List<VolumeVO> vmVolumes = volumeDao.findByInstance(vmId);
+        if (vmVolumes.size() != backupVolumes.size()) {
+            throw new CloudRuntimeException("Unable to create Instance from 
backup as the backup has a different number of disks than the Instance");
+        }
+
+        int index = 0;
+        for (VolumeVO vmVolume: vmVolumes) {
+            Backup.VolumeInfo backupVolume = backupVolumes.get(index);
+            if (vmVolume.getSize() < backupVolume.getSize()) {
+                throw new CloudRuntimeException(String.format(
+                    "Instance volume size %d[GiB] for volume (%s) is less than 
the backed-up volume size %d[GiB] for backed-up volume (%s).",
+                    vmVolume.getSize(), vmVolume.getUuid(), 
backupVolume.getSize(), backupVolume.getUuid()));
+            }
+            index++;
+        }
+
+        BackupOffering offering = 
backupOfferingDao.findByIdIncludingRemoved(backup.getBackupOfferingId());
+        if (offering == null) {
+            throw new CloudRuntimeException("Failed to find backup offering");
+        }
+        final BackupProvider backupProvider = 
getBackupProvider(offering.getProvider());
+        if (!backupProvider.supportsInstanceFromBackup()) {
+            throw new CloudRuntimeException("Create instance from backup is 
not supported by the " + offering.getProvider() + " provider.");
+        }
+
+        String backupDetailsInMessage = 
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(backup, "uuid", 
"externalId", "newVMId", "type", "status", "date");
+        Long eventId = null;
+        try {
+            updateVmState(vm, VirtualMachine.Event.RestoringRequested, 
VirtualMachine.State.Restoring);
+            updateVolumeState(vm, Volume.Event.RestoreRequested, 
Volume.State.Restoring);
+            eventId = ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, 
vm.getAccountId(), EventTypes.EVENT_VM_CREATE_FROM_BACKUP,
+                    String.format("Creating Instance %s from backup %s", 
vm.getInstanceName(), 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();
+            }
+            if (!backupProvider.restoreBackupToVM(vm, backup, host, 
dataStore)) {
+                throw new CloudRuntimeException(String.format("Error restoring 
backup [%s] to VM %s.", backupDetailsInMessage, vm.getUuid()));
+            }
+        } catch (Exception e) {
+            updateVolumeState(vm, Volume.Event.RestoreFailed, 
Volume.State.Ready);
+            updateVmState(vm, VirtualMachine.Event.RestoringFailed, 
VirtualMachine.State.Stopped);

Review Comment:
   If we are creating a VM from a backup, this is a new VM correct?
   If the process failed, shouldn't the VM and the volumes go into an error 
state? If not, we might have a bunch of volumes that are marked as `Ready` but 
are inconsistent.



##########
server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java:
##########
@@ -282,18 +319,75 @@ public boolean deleteBackupOffering(final Long 
offeringId) {
             throw new CloudRuntimeException("Could not find a backup offering 
with id: " + offeringId);
         }
 
-        if (vmInstanceDao.listByZoneWithBackups(offering.getZoneId(), 
offering.getId()).size() > 0) {
+        if (backupDao.listByOfferingId(offering.getId()).size() > 0) {
+            throw new CloudRuntimeException("Backup Offering cannot be removed 
as it has backups associated with it.");
+        }
+
+        if (vmInstanceDao.listByZoneAndBackupOffering(offering.getZoneId(), 
offering.getId()).size() > 0) {
             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());
     }
 
-    public static String createVolumeInfoFromVolumes(List<VolumeVO> vmVolumes) 
{
+    @Override
+    public Map<String, String> getBackupDetailsFromVM(VirtualMachine vm) {
+        HashMap<String, String> details = new HashMap<>();
+
+        ServiceOffering serviceOffering = 
serviceOfferingDao.findById(vm.getServiceOfferingId());
+        details.put(ApiConstants.SERVICE_OFFERING_ID, 
serviceOffering.getUuid());
+        VirtualMachineTemplate template = 
vmTemplateDao.findById(vm.getTemplateId());
+        details.put(ApiConstants.TEMPLATE_ID, template.getUuid());
+
+        List<VMInstanceDetailVO> vmDetails = 
vmInstanceDetailsDao.listDetails(vm.getId());
+        HashMap<String, String> settings = new HashMap<>();
+        for (VMInstanceDetailVO detail : vmDetails) {
+            settings.put(detail.getName(), detail.getValue());
+        }
+        if (!settings.isEmpty()) {
+            details.put(ApiConstants.VM_SETTINGS, new Gson().toJson(settings));
+        }
+
+        List<UserVmJoinVO> userVmJoinVOs = 
userVmJoinDao.searchByIds(vm.getId());
+        if (userVmJoinVOs != null && !userVmJoinVOs.isEmpty()) {
+            List<Map<String, String>> nics = new ArrayList<>();
+            Set<String> seen = new HashSet<>();
+
+            for (UserVmJoinVO userVmJoinVO : userVmJoinVOs) {
+                Map<String, String> nicInfo = new HashMap<>();
+                String key = userVmJoinVO.getNetworkUuid();
+                if (seen.add(key)) {
+                    nicInfo.put(ApiConstants.NETWORK_ID, 
userVmJoinVO.getNetworkUuid());
+                    nicInfo.put(ApiConstants.IP_ADDRESS, 
userVmJoinVO.getIpAddress());
+                    nicInfo.put(ApiConstants.IP6_ADDRESS, 
userVmJoinVO.getIp6Address());
+                    nicInfo.put(ApiConstants.MAC_ADDRESS, 
userVmJoinVO.getMacAddress());
+                    nics.add(nicInfo);
+                }
+            }
+
+            if (!nics.isEmpty()) {
+                String nicJson = new Gson().toJson(nics);
+                details.put("nics", nicJson);
+            }

Review Comment:
   We could extract this to a method



##########
engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java:
##########
@@ -240,10 +268,27 @@ public void setBackedUpVolumes(String backedUpVolumes) {
         this.backedUpVolumes = backedUpVolumes;
     }
 
+    @Override
+    public Map<String, String> getDetails() {
+        return details;
+    }
+
+    @Override
+    public String getDetail(String name) {
+        return this.details.get(name);
+    }
+
+    public void setDetails(Map<String, String> details) {
+        this.details = details;
+    }
+
+    public void addDetails(Map<String, String> details) {
+        this.details.putAll(details);
+    }

Review Comment:
   It seems like this is never used in the code



##########
server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java:
##########
@@ -599,10 +705,19 @@ public boolean createBackup(final Long vmId, final Long 
scheduleId) throws Resou
             throw new CloudRuntimeException("VM backup offering not found");
         }
 
+        final BackupProvider backupProvider = 
getBackupProvider(offering.getProvider());
+        if (offering == null) {
+            throw new CloudRuntimeException("VM backup provider not found for 
the offering");
+        }

Review Comment:
   If offering is null, a NPE will be thrown in the previous line.



##########
plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java:
##########
@@ -116,6 +121,15 @@ public class NetworkerBackupProvider extends AdapterBase 
implements BackupProvid
     @Inject
     private VMInstanceDao vmInstanceDao;
 
+    @Inject
+    private EntityManager entityManager;
+
+    @Inject
+    BackupManager backupManager;

Review Comment:
   ```suggestion
       private BackupManager backupManager;
   ```



##########
engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql:
##########
@@ -241,3 +237,88 @@ ALTER TABLE `cloud`.`vm_instance_details` ADD CONSTRAINT 
`fk_vm_instance_details
 
 CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_schedule', 'uuid', 
'VARCHAR(40) NOT NULL');
 UPDATE `cloud`.`backup_schedule` SET uuid = UUID();
+
+-- Add column max_backup to backup_schedule table
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_schedule', 'max_backups', 
'int(8) default NULL COMMENT "maximum number of backups to maintain"');
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_schedule', 'quiescevm', 
'tinyint(1) default NULL COMMENT "Quiesce VM before taking backup"');
+
+-- Add columns name, description and backup_interval_type to backup table
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'name', 'VARCHAR(255) 
NULL COMMENT "name of the backup"');
+UPDATE `cloud`.`backups` backup INNER JOIN `cloud`.`vm_instance` vm ON 
backup.vm_id = vm.id SET backup.name = vm.name;
+ALTER TABLE `cloud`.`backups` MODIFY COLUMN `name` VARCHAR(255) NOT NULL;
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'description', 
'VARCHAR(1024) COMMENT "description for the backup"');
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'backup_interval_type', 
'int(5) COMMENT "type of backup, e.g. manual, recurring - hourly, daily, weekly 
or monthly"');
+
+-- Create backup details table
+CREATE TABLE IF NOT EXISTS `cloud`.`backup_details` (
+  `id` bigint unsigned NOT NULL auto_increment,
+  `backup_id` bigint unsigned NOT NULL COMMENT 'backup id',
+  `name` varchar(255) NOT NULL,
+  `value` TEXT NOT NULL,
+  `display` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Should detail be displayed 
to the end user',
+  PRIMARY KEY (`id`),
+  CONSTRAINT `fk_backup_details__backup_id` FOREIGN KEY 
`fk_backup_details__backup_id`(`backup_id`) REFERENCES `backups`(`id`) ON 
DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Add diskOfferingId, deviceId, minIops and maxIops to backed_volumes in 
backups table
+UPDATE `cloud`.`backups` b
+INNER JOIN `cloud`.`vm_instance` vm ON b.vm_id = vm.id
+SET b.backed_volumes = (
+    SELECT CONCAT("[",
+        GROUP_CONCAT(
+            CONCAT(
+                "{\"uuid\":\"", v.uuid, "\",",
+                "\"type\":\"", v.volume_type, "\",",
+                "\"size\":", v.`size`, ",",
+                "\"path\":\"", IFNULL(v.path, 'null'), "\",",
+                "\"deviceId\":", IFNULL(v.device_id, 'null'), ",",
+                "\"diskOfferingId\":\"", doff.uuid, "\",",
+                "\"minIops\":", IFNULL(v.min_iops, 'null'), ",",
+                "\"maxIops\":", IFNULL(v.max_iops, 'null'),
+                "}"
+            )
+            SEPARATOR ","
+        ),
+    "]")
+    FROM `cloud`.`volumes` v
+    LEFT JOIN `cloud`.`disk_offering` doff ON v.disk_offering_id = doff.id
+    WHERE v.instance_id = vm.id
+);
+
+-- Add diskOfferingId, deviceId, minIops and maxIops to backup_volumes in 
vm_instance table
+UPDATE `cloud`.`vm_instance` vm
+SET vm.backup_volumes = (
+    SELECT CONCAT("[",

Review Comment:
   Why do we have the same info in two tables?



##########
server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java:
##########
@@ -1470,7 +1910,22 @@ private Backup 
checkAndUpdateIfBackupEntryExistsForRestorePoint(Backup.RestorePo
             return null;
         }
 
-        private void syncBackups(BackupProvider backupProvider, VirtualMachine 
vm, Backup.Metric metric) {
+        private void processRemoveList(List<Long> removeList, VirtualMachine 
vm) {
+            for (final Long backupIdToRemove : removeList) {
+                logger.warn(String.format("Removing backup with ID: [%s].", 
backupIdToRemove));
+                Backup backup = backupDao.findById(backupIdToRemove);
+                resourceLimitMgr.decrementResourceCount(backup.getAccountId(), 
Resource.ResourceType.backup);
+                resourceLimitMgr.decrementResourceCount(backup.getAccountId(), 
Resource.ResourceType.backup_storage, backup.getSize());
+                boolean result = backupDao.remove(backupIdToRemove);
+                if (result) {
+                    
checkAndGenerateUsageForLastBackupDeletedAfterOfferingRemove(vm, backup);
+                } else {
+                    logger.error("Failed to remove backup db wentry ith ID: {} 
during sync backups", backupIdToRemove);

Review Comment:
   ```suggestion
                       logger.error("Failed to remove backup db entry with ID: 
{} during sync backups", backupIdToRemove);
   ```



##########
server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java:
##########
@@ -926,6 +1058,224 @@ private Backup.VolumeInfo 
getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes,
         return null;
     }
 
+    @Override
+    public void checkVmDisksSizeAgainstBackup(List<VmDiskInfo> vmDiskInfoList, 
Backup backup) {
+        List<VmDiskInfo> vmDiskInfoListFromBackup = 
getDataDiskInfoListFromBackup(backup);
+        int index = 0;
+        if (vmDiskInfoList.size() != vmDiskInfoListFromBackup.size()) {
+            throw new InvalidParameterValueException("Unable to create 
Instance from Backup " +
+                    "as the backup has a different number of disks than the 
Instance.");
+        }
+        for (VmDiskInfo vmDiskInfo : vmDiskInfoList) {
+            if (index < vmDiskInfoListFromBackup.size()) {
+                if (vmDiskInfo.getSize() < 
vmDiskInfoListFromBackup.get(index).getSize()) {
+                    throw new InvalidParameterValueException(
+                            String.format("Instance volume size %d[GiB] cannot 
be less than the backed-up volume size %d[GiB].",
+                            vmDiskInfo.getSize(), 
vmDiskInfoListFromBackup.get(index).getSize()));
+                }
+            }
+            index++;
+        }
+    }
+
+    @Override
+    public VmDiskInfo getRootDiskInfoFromBackup(Backup backup) {
+        List<Backup.VolumeInfo> volumes = backup.getBackedUpVolumes();
+        VmDiskInfo rootDiskOffering = null;
+        if (volumes == null || volumes.isEmpty()) {
+            throw new CloudRuntimeException("Failed to get backed-up volumes 
info from backup");
+        }
+        for (Backup.VolumeInfo volume : volumes) {
+            if (volume.getType() == Volume.Type.ROOT) {
+                DiskOfferingVO diskOffering = 
diskOfferingDao.findByUuid(volume.getDiskOfferingId());
+                if (diskOffering == null) {
+                    throw new CloudRuntimeException(String.format("Unable to 
find the root disk offering with uuid (%s) " +
+                            "stored in backup. Please specify a valid root 
disk offering id while creating the instance",
+                            volume.getDiskOfferingId()));
+                }
+                Long size = volume.getSize() / (1024 * 1024 * 1024);
+                rootDiskOffering = new VmDiskInfo(diskOffering, size, 
volume.getMinIops(), volume.getMaxIops());
+            }
+        }
+        if (rootDiskOffering == null) {
+            throw new CloudRuntimeException("Failed to get the root disk in 
backed-up volumes info from backup");
+        }
+        return rootDiskOffering;
+    }
+
+    @Override
+    public List<VmDiskInfo> getDataDiskInfoListFromBackup(Backup backup) {
+        List<VmDiskInfo> vmDiskInfoList = new ArrayList<>();
+        List<Backup.VolumeInfo> volumes = backup.getBackedUpVolumes();
+        if (volumes == null || volumes.isEmpty()) {
+            throw new CloudRuntimeException("Failed to get backed-up Volumes 
info from backup");
+        }
+        for (Backup.VolumeInfo volume : volumes) {
+            if (volume.getType() == Volume.Type.DATADISK) {
+                DiskOfferingVO diskOffering = 
diskOfferingDao.findByUuid(volume.getDiskOfferingId());
+                if (diskOffering == null || 
diskOffering.getState().equals(DiskOffering.State.Inactive)) {
+                    throw new CloudRuntimeException("Unable to find the disk 
offering with uuid (" + volume.getDiskOfferingId() + ") stored in backup. " +
+                            "Please specify a valid disk offering id while 
creating the instance");
+                }
+                Long size = volume.getSize() / (1024 * 1024 * 1024);
+                vmDiskInfoList.add(new VmDiskInfo(diskOffering, size, 
volume.getMinIops(), volume.getMaxIops(), volume.getDeviceId()));
+            }
+        }
+        return vmDiskInfoList;
+    }
+
+    private List<String> parseAddressString(String input) {
+        if (input == null) return null;
+        return Arrays.stream(input.split(","))
+                .map(s -> "null".equalsIgnoreCase(s.trim()) ? null : s.trim())
+                .collect(Collectors.toList());
+    }

Review Comment:
   Is this used anywhere? Also, what is its purpose?



##########
server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java:
##########
@@ -1959,8 +1959,7 @@ private Pair<String, String> 
handleCheckAndRepairVolumeJob(Long vmId, Long volum
                 } else if (jobResult instanceof ResourceAllocationException) {
                     throw (ResourceAllocationException)jobResult;
                 } else if (jobResult instanceof Throwable) {
-                    Throwable throwable = (Throwable) jobResult;
-                    throw new RuntimeException(String.format("Unexpected 
exception: %s", throwable.getMessage()), throwable);
+                    throw new RuntimeException("Unexpected exception", 
(Throwable) jobResult);

Review Comment:
   CloudRuntimeException



-- 
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