diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 81ed185dae5a..62d1c777a11c 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -606,6 +606,7 @@ public class EventTypes { public static final String EVENT_VM_BACKUP_OFFERING_REMOVE = "BACKUP.OFFERING.REMOVE"; public static final String EVENT_VM_BACKUP_CREATE = "BACKUP.CREATE"; public static final String EVENT_VM_BACKUP_RESTORE = "BACKUP.RESTORE"; + public static final String EVENT_VM_BACKUP_RESTORE_TO_VM = "BACKUP.RESTORE.TO.VM"; public static final String EVENT_VM_BACKUP_DELETE = "BACKUP.DELETE"; public static final String EVENT_VM_BACKUP_RESTORE_VOLUME_TO_VM = "BACKUP.RESTORE.VOLUME.TO.VM"; public static final String EVENT_VM_BACKUP_SCHEDULE_CONFIGURE = "BACKUP.SCHEDULE.CONFIGURE"; diff --git a/api/src/main/java/com/cloud/offering/DiskOfferingInfo.java b/api/src/main/java/com/cloud/offering/DiskOfferingInfo.java index d83039e15c2b..af83a41882b4 100644 --- a/api/src/main/java/com/cloud/offering/DiskOfferingInfo.java +++ b/api/src/main/java/com/cloud/offering/DiskOfferingInfo.java @@ -23,6 +23,7 @@ public class DiskOfferingInfo { private Long _size; private Long _minIops; private Long _maxIops; + private Long _deviceId; public DiskOfferingInfo() { } @@ -31,6 +32,18 @@ public DiskOfferingInfo(DiskOffering diskOffering) { _diskOffering = diskOffering; } + public DiskOfferingInfo(DiskOffering diskOffering, Long size, Long minIops, Long maxIops) { + _diskOffering = diskOffering; + _size = size; + _minIops = minIops; + _maxIops = maxIops; + } + + public DiskOfferingInfo(DiskOffering diskOffering, Long size, Long minIops, Long maxIops, Long deviceId) { + this(diskOffering, size, minIops, maxIops); + _deviceId = deviceId; + } + public void setDiskOffering(DiskOffering diskOffering) { _diskOffering = diskOffering; } @@ -62,4 +75,12 @@ public void setMaxIops(Long maxIops) { public Long getMaxIops() { return _maxIops; } + + public void setDeviceId(Long deviceId) { + _deviceId = deviceId; + } + + public Long getDeviceId() { + return _deviceId; + } } diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java b/api/src/main/java/com/cloud/vm/UserVmService.java index 72b18b70e186..14f1be415eab 100644 --- a/api/src/main/java/com/cloud/vm/UserVmService.java +++ b/api/src/main/java/com/cloud/vm/UserVmService.java @@ -24,6 +24,7 @@ import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd; import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd; import org.apache.cloudstack.api.command.user.vm.AddNicToVMCmd; +import org.apache.cloudstack.api.command.user.vm.CreateVMFromBackupCmd; import org.apache.cloudstack.api.command.user.vm.DeployVMCmd; import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd; import org.apache.cloudstack.api.command.user.vm.RebootVMCmd; @@ -55,6 +56,7 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.Network.IpAddresses; import com.cloud.offering.DiskOffering; +import com.cloud.offering.DiskOfferingInfo; import com.cloud.offering.ServiceOffering; import com.cloud.storage.StoragePool; import com.cloud.template.VirtualMachineTemplate; @@ -217,7 +219,7 @@ void startVirtualMachineForHA(VirtualMachine vm, Map securityGroupIdList, - Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, + Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, List dataDiskOfferingsInfo, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIp, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameter, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, @@ -294,7 +296,7 @@ UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering s * available. */ UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, - List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, + List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, List dataDiskOfferingsInfo, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, String vmType) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; @@ -366,7 +368,7 @@ UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOfferin * available. */ UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, Account owner, - String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, + String hostName, String displayName, Long diskOfferingId, Long diskSize, List dataDiskOfferingsInfo, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Map templateOvfPropertiesMap, boolean dynamicScalingEnabled, String vmType, Long overrideDiskOfferingId) @@ -412,8 +414,7 @@ UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffe void deletePrivateTemplateRecord(Long templateId); HypervisorType getHypervisorTypeOfUserVM(long vmid); - - UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, +UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, StorageUnavailableException, ResourceAllocationException; /** @@ -513,4 +514,8 @@ UserVm importVM(final DataCenter zone, final Host host, final VirtualMachineTemp * @return true if the VM is successfully unmanaged, false if not. */ boolean unmanageUserVM(Long vmId); + + UserVm allocateVMFromBackup(CreateVMFromBackupCmd cmd) throws InsufficientCapacityException, ResourceAllocationException, ResourceUnavailableException; + + UserVm restoreVMFromBackup(CreateVMFromBackupCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException; } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 03de07c37da0..2f937e326f83 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -137,6 +137,7 @@ public class ApiConstants { public static final String MAX_IOPS = "maxiops"; public static final String HYPERVISOR_SNAPSHOT_RESERVE = "hypervisorsnapshotreserve"; public static final String DATACENTER_NAME = "datacentername"; + public static final String DATADISKS_DETAILS = "datadisksdetails"; public static final String DATADISK_OFFERING_LIST = "datadiskofferinglist"; public static final String DEFAULT_VALUE = "defaultvalue"; public static final String DELETE_PROTECTION = "deleteprotection"; @@ -145,6 +146,7 @@ public class ApiConstants { public static final String DESTINATION_ZONE_ID = "destzoneid"; public static final String DETAILS = "details"; public static final String DEVICE_ID = "deviceid"; + public static final String DEVICE_IDS = "deviceids"; public static final String DIRECT_DOWNLOAD = "directdownload"; public static final String DISK = "disk"; public static final String DISK_OFFERING_ID = "diskofferingid"; @@ -156,7 +158,9 @@ public class ApiConstants { public static final String DISK_IO_WRITE = "diskiowrite"; public static final String DISK_IO_PSTOTAL = "diskiopstotal"; public static final String DISK_SIZE = "disksize"; + public static final String DISK_SIZES = "disksizes"; public static final String DISK_SIZE_STRICTNESS = "disksizestrictness"; + public static final String DISK_OFFERING_IDS = "diskofferingids"; public static final String DISK_OFFERING_STRICTNESS = "diskofferingstrictness"; public static final String DOWNLOAD_DETAILS = "downloaddetails"; public static final String UTILIZATION = "utilization"; @@ -552,6 +556,7 @@ public class ApiConstants { public static final String IS_DEDICATED = "isdedicated"; public static final String TAKEN = "taken"; public static final String VM_AVAILABLE = "vmavailable"; + public static final String VM_DETAILS = "vmdetails"; public static final String VM_LIMIT = "vmlimit"; public static final String VM_TOTAL = "vmtotal"; public static final String VM_TYPE = "vmtype"; @@ -735,6 +740,7 @@ public class ApiConstants { public static final String LIST_ALL = "listall"; public static final String LIST_ONLY_REMOVED = "listonlyremoved"; public static final String LIST_SYSTEM_VMS = "listsystemvms"; + public static final String LIST_VM_DETAILS = "listvmdetails"; public static final String IP_RANGES = "ipranges"; public static final String IPV4_ROUTING = "ip4routing"; public static final String IPV4_ROUTES = "ip4routes"; diff --git a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java index ea0d946ee417..d63becf5a446 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -523,7 +523,7 @@ List createTemplateResponses(ResponseView view, VirtualMachine UserDataResponse createUserDataResponse(UserData userData); - BackupResponse createBackupResponse(Backup backup); + BackupResponse createBackupResponse(Backup backup, Boolean listVmDetails); BackupScheduleResponse createBackupScheduleResponse(BackupSchedule backup); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java index 7d87cc37e6cd..ec0f5e28f433 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java @@ -75,6 +75,12 @@ public class ListBackupsCmd extends BaseListProjectAndAccountResourcesCmd { description = "list backups by zone id") private Long zoneId; + @Parameter(name = ApiConstants.LIST_VM_DETAILS, + type = CommandType.BOOLEAN, + since = "4.21.0", + description = "list backups with VM details") + private Boolean listVmDetails; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -91,6 +97,10 @@ public Long getZoneId() { return zoneId; } + public Boolean getListVmDetails() { + return listVmDetails; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -101,7 +111,7 @@ protected void setupResponseBackupList(final List backups, final Integer if (backup == null) { continue; } - BackupResponse backupResponse = _responseGenerator.createBackupResponse(backup); + BackupResponse backupResponse = _responseGenerator.createBackupResponse(backup, this.getListVmDetails()); responses.add(backupResponse); } final ListResponse response = new ListResponse<>(); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMFromBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMFromBackupCmd.java new file mode 100644 index 000000000000..ca5893297cf6 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMFromBackupCmd.java @@ -0,0 +1,127 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.vm; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.user.UserCmd; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.BackupManager; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.uservm.UserVm; +import com.cloud.vm.VirtualMachine; + +@APICommand(name = "createVMFromBackup", + description = "Creates and automatically starts a VM from a backup.", + responseObject = UserVmResponse.class, + responseView = ResponseObject.ResponseView.Restricted, + entityType = {VirtualMachine.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = true, + since = "4.21.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) + +public class CreateVMFromBackupCmd extends DeployVMCmd implements UserCmd { + + @Inject + BackupManager backupManager; + + @Parameter(name = ApiConstants.BACKUP_ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + required = true, + description = "backup ID to create the VM from") + private Long backupId; + + public Long getBackupId() { + return backupId; + } + + @Override + public void create() { + UserVm vm; + try { + vm = _userVmService.allocateVMFromBackup(this); + if (vm != null) { + setEntityId(vm.getId()); + setEntityUuid(vm.getUuid()); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to deploy vm"); + } + } catch (InsufficientCapacityException ex) { + logger.info(ex); + logger.trace(ex.getMessage(), ex); + throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, ex.getMessage()); + } catch (ResourceUnavailableException ex) { + logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage()); + } catch (ConcurrentOperationException ex) { + logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } catch (ResourceAllocationException ex) { + logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage()); + } + } + + @Override + public void execute () { + UserVm vm = null; + try { + vm = _userVmService.restoreVMFromBackup(this); + } catch (ResourceUnavailableException ex) { + logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage()); + } catch (ResourceAllocationException ex) { + logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage()); + } catch (ConcurrentOperationException ex) { + logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } catch (InsufficientCapacityException ex) { + StringBuilder message = new StringBuilder(ex.getMessage()); + if (ex instanceof InsufficientServerCapacityException) { + if (((InsufficientServerCapacityException)ex).isAffinityApplied()) { + message.append(", Please check the affinity groups provided, there may not be sufficient capacity to follow them"); + } + } + logger.info(String.format("%s: %s", message.toString(), ex.getLocalizedMessage())); + logger.debug(message.toString(), ex); + throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, message.toString()); + } + + if (vm != null) { + UserVmResponse response = _responseGenerator.createUserVmResponse(getResponseView(), "virtualmachine", vm).get(0); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to deploy vm uuid:"+getEntityUuid()); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index 52d42a95d981..1d2850783f92 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -70,6 +70,7 @@ import com.cloud.network.Network; import com.cloud.network.Network.IpAddresses; import com.cloud.offering.DiskOffering; +import com.cloud.offering.DiskOfferingInfo; import com.cloud.template.VirtualMachineTemplate; import com.cloud.uservm.UserVm; import com.cloud.utils.net.Dhcp; @@ -91,11 +92,11 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG private Long zoneId; @ACL - @Parameter(name = ApiConstants.SERVICE_OFFERING_ID, type = CommandType.UUID, entityType = ServiceOfferingResponse.class, required = true, description = "the ID of the service offering for the virtual machine") + @Parameter(name = ApiConstants.SERVICE_OFFERING_ID, type = CommandType.UUID, entityType = ServiceOfferingResponse.class, description = "the ID of the service offering for the virtual machine") private Long serviceOfferingId; @ACL - @Parameter(name = ApiConstants.TEMPLATE_ID, type = CommandType.UUID, entityType = TemplateResponse.class, required = true, description = "the ID of the template for the virtual machine") + @Parameter(name = ApiConstants.TEMPLATE_ID, type = CommandType.UUID, entityType = TemplateResponse.class, description = "the ID of the template for the virtual machine") private Long templateId; @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "host name for the virtual machine", validations = {ApiArgValidator.RFCComplianceDomainName}) @@ -147,6 +148,13 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG since = "4.4") private Long rootdisksize; + @Parameter(name = ApiConstants.DATADISKS_DETAILS, + type = CommandType.MAP, + since = "4.21.0", + description = "Disk offering details for creating multiple data volumes. Mutually exclusibe with diskOfferingId." + + " Example: datadisksdetails[0].diskofferingid=1&datadisksdetails[0].size=10&datadisksdetails[0].miniops=100&datadisksdetails[0].maxiops=200") + private Map dataDisksDetails; + @Parameter(name = ApiConstants.GROUP, type = CommandType.STRING, description = "an optional group for the virtual machine") private String group; @@ -278,6 +286,8 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG description = "Enable packed virtqueues or not.") private Boolean nicPackedVirtQueues; + private List dataDiskOfferingsInfo; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -510,6 +520,59 @@ public List getSSHKeyPairNames() { return sshKeyPairs; } + public List getDataDiskOfferingsInfo() { + if (this.dataDiskOfferingsInfo != null) { + return this.dataDiskOfferingsInfo; + } + if (dataDisksDetails == null || dataDisksDetails.isEmpty()) { + return null; + } + List diskOfferingInfoList = new ArrayList<>(); + Collection dataDisksCollection = dataDisksDetails.values(); + Iterator iter = dataDisksCollection.iterator(); + while (iter.hasNext()) { + HashMap dataDisk = (HashMap)iter.next(); + String diskOfferingUuid = dataDisk.get(ApiConstants.DISK_OFFERING_ID); + if (diskOfferingUuid == null) { + throw new InvalidParameterValueException("Disk offering id is required for data disk"); + } + DiskOffering diskOffering = _entityMgr.findByUuid(DiskOffering.class, diskOfferingUuid); + if (diskOffering == null) { + throw new InvalidParameterValueException("Unable to find disk offering " + diskOfferingUuid); + } + if (diskOffering.isComputeOnly()) { + throw new InvalidParameterValueException(String.format("The disk offering id %d provided is directly mapped to a service offering, please provide an individual disk offering", diskOffering.getUuid())); + } + + Long size = null; + Long minIops = null; + Long maxIops = null; + Long deviceId = Long.parseLong(dataDisk.get(ApiConstants.DEVICE_ID)); + if (diskOffering.isCustomized()) { + if (dataDisk.get(ApiConstants.SIZE) == null) { + throw new InvalidParameterValueException("Size is required for custom disk offering"); + } + size = Long.parseLong(dataDisk.get(ApiConstants.SIZE)); + } else { + size = diskOffering.getDiskSize() / (1024 * 1024 * 1024); + } + if (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops()) { + if (dataDisk.get(ApiConstants.MIN_IOPS) == null) { + throw new InvalidParameterValueException("Min IOPS is required for custom disk offering"); + } + if (dataDisk.get(ApiConstants.MAX_IOPS) == null) { + throw new InvalidParameterValueException("Max IOPS is required for custom disk offering"); + } + minIops = Long.parseLong(dataDisk.get(ApiConstants.MIN_IOPS)); + maxIops = Long.parseLong(dataDisk.get(ApiConstants.MAX_IOPS)); + } + DiskOfferingInfo diskOfferingInfo = new DiskOfferingInfo(diskOffering, size, minIops, maxIops, deviceId); + diskOfferingInfoList.add(diskOfferingInfo); + } + this.dataDiskOfferingsInfo = diskOfferingInfoList; + return dataDiskOfferingsInfo; + } + public Long getHostId() { return hostId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java index 63419680fea3..55b3ea502da4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java @@ -26,6 +26,7 @@ import com.google.gson.annotations.SerializedName; import java.util.Date; +import java.util.Map; @EntityReference(value = Backup.class) public class BackupResponse extends BaseResponse { @@ -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", since = "4.21.0") + private Map vmDetails; + public String getId() { return id; } @@ -245,4 +250,12 @@ public String getZone() { public void setZone(String zone) { this.zone = zone; } + + public Map getVmDetails() { + return vmDetails; + } + + public void setVmDetails(Map vmDetails) { + this.vmDetails = vmDetails; + } } diff --git a/api/src/main/java/org/apache/cloudstack/backup/Backup.java b/api/src/main/java/org/apache/cloudstack/backup/Backup.java index f21f20adb33e..1e00e096ebbc 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -19,6 +19,7 @@ import java.util.Date; import java.util.List; +import java.util.Map; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.api.Identity; @@ -144,4 +145,6 @@ public String toString() { Long getProtectedSize(); List getBackedUpVolumes(); long getZoneId(); + Map getDetails(); + String getDetail(String name); } diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java index 8b45bb4ee5ef..f2ab8c40b8f4 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.backup; import java.util.List; +import java.util.Map; import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd; import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd; @@ -27,9 +28,12 @@ import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.offering.DiskOfferingInfo; import com.cloud.utils.Pair; import com.cloud.utils.component.Manager; import com.cloud.utils.component.PluggableService; +import com.cloud.vm.VirtualMachine; /** * Backup and Recover Manager Interface @@ -133,6 +137,11 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer */ boolean restoreBackup(final Long backupId); + /** + * Restore a backup to a new Instance + */ + boolean restoreBackupToVM(Long backupId, Long vmId) throws ResourceUnavailableException; + /** * Restore a backed up volume and attach it to a VM */ @@ -146,5 +155,17 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer */ boolean deleteBackup(final Long backupId, final Boolean forced); + void validateBackupForZone(Long zoneId); + BackupOffering updateBackupOffering(UpdateBackupOfferingCmd updateBackupOfferingCmd); + + DiskOfferingInfo getRootDiskOfferingInfoFromBackup(Backup backup); + + List getDataDiskOfferingListFromBackup(Backup backup); + + void updateDiskOfferingSizeFromBackup(List dataDiskOfferingsInfo, Backup backup); + + Map getVmDetailsForBackup(VirtualMachine vm); + + Map getDiskOfferingDetailsForBackup(Long vmId); } diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java index d36dfb7360f6..3f0fcd0541d2 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -85,6 +85,8 @@ public interface BackupProvider { */ boolean deleteBackup(Backup backup, boolean forced); + boolean restoreBackupToVM(VirtualMachine vm, Backup backup, String hostIp, String dataStoreUuid); + /** * Restore VM from backup */ @@ -109,4 +111,9 @@ public interface BackupProvider { * @param metric */ void syncBackups(VirtualMachine vm, Backup.Metric metric); + + /** + * Returns if the backup provider supports creating new instance from backup + */ + boolean supportsInstanceFromBackup(); } diff --git a/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java index 7228e35147af..f447fbe3d008 100644 --- a/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java +++ b/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java @@ -30,7 +30,8 @@ public class RestoreBackupCommand extends Command { private String backupPath; private String backupRepoType; private String backupRepoAddress; - private List volumePaths; + private List backupVolumesUUIDs; + private List restoreVolumePaths; private String diskType; private Boolean vmExists; private String restoreVolumeUUID; @@ -72,12 +73,12 @@ public void setBackupRepoAddress(String backupRepoAddress) { this.backupRepoAddress = backupRepoAddress; } - public List getVolumePaths() { - return volumePaths; + public List getRestoreVolumePaths() { + return restoreVolumePaths; } - public void setVolumePaths(List volumePaths) { - this.volumePaths = volumePaths; + public void setRestoreVolumePaths(List restoreVolumePaths) { + this.restoreVolumePaths = restoreVolumePaths; } public Boolean isVmExists() { @@ -127,4 +128,12 @@ public void setVmState(VirtualMachine.State vmState) { public boolean executeInSequence() { return true; } + + public List getBackupVolumesUUIDs() { + return backupVolumesUUIDs; + } + + public void setBackupVolumesUUIDs(List backupVolumesUUIDs) { + this.backupVolumesUUIDs = backupVolumesUUIDs; + } } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java index 3ffa496b5445..cdd1001f0232 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java @@ -37,6 +37,7 @@ import com.cloud.exception.InsufficientCapacityException; import com.cloud.hypervisor.Hypervisor; import com.cloud.offering.DiskOffering; +import com.cloud.offering.DiskOfferingInfo; import com.cloud.vm.NicProfile; @Path("orchestration") @@ -67,7 +68,8 @@ VirtualMachineEntity createVirtualMachine(@QueryParam("id") String id, @QueryPar @QueryParam("compute-tags") List computeTags, @QueryParam("root-disk-tags") List rootDiskTags, @QueryParam("network-nic-map") Map> networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan, @QueryParam("root-disk-size") Long rootDiskSize, @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap, - @QueryParam("datadisktemplate-diskoffering-map") Map datadiskTemplateToDiskOfferingMap, @QueryParam("disk-offering-id") Long diskOfferingId, @QueryParam("root-disk-offering-id") Long rootDiskOfferingId) throws InsufficientCapacityException; + @QueryParam("datadisktemplate-diskoffering-map") Map datadiskTemplateToDiskOfferingMap, + @QueryParam("disk-offering-id") Long diskOfferingId, @QueryParam("root-disk-offering-id") Long rootDiskOfferingId, @QueryParam("data-disks-offering-info") List dataDiskOfferingsInfo) throws InsufficientCapacityException; @POST VirtualMachineEntity createVirtualMachineFromScratch(@QueryParam("id") String id, @QueryParam("owner") String owner, @QueryParam("iso-id") String isoId, @@ -75,7 +77,8 @@ VirtualMachineEntity createVirtualMachineFromScratch(@QueryParam("id") String id @QueryParam("os") String os, @QueryParam("cpu") int cpu, @QueryParam("speed") int speed, @QueryParam("ram") long memory, @QueryParam("disk-size") Long diskSize, @QueryParam("compute-tags") List computeTags, @QueryParam("root-disk-tags") List rootDiskTags, @QueryParam("network-nic-map") Map> networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan, - @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap, @QueryParam("disk-offering-id") Long diskOfferingId) throws InsufficientCapacityException; + @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap, @QueryParam("disk-offering-id") Long diskOfferingId, + @QueryParam("data-disks-offering-info") List dataDiskOfferingsInfo) throws InsufficientCapacityException; @POST NetworkEntity createNetwork(String id, String name, String domainName, String cidr, String gateway); diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index 6d27b0efed31..5632499092c7 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -527,21 +527,25 @@ public void allocate(final String vmInstanceName, final VirtualMachineTemplate t // Create new Volume context and inject event resource type, id and details to generate VOLUME.CREATE event for the ROOT disk. CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume); try { + Long nextDiskNumber = 1L; if (dataDiskOfferings != null) { for (final DiskOfferingInfo dataDiskOfferingInfo : dataDiskOfferings) { - volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId(), dataDiskOfferingInfo.getDiskOffering(), dataDiskOfferingInfo.getSize(), - dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), persistedVm, template, owner, null); + Long deviceId = dataDiskOfferingInfo.getDeviceId(); + volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId() + "-" + String.valueOf(deviceId), dataDiskOfferingInfo.getDiskOffering(), dataDiskOfferingInfo.getSize(), + dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), persistedVm, template, owner, deviceId); + if (nextDiskNumber <= deviceId) { + nextDiskNumber = deviceId + 1; + } } } if (datadiskTemplateToDiskOfferingMap != null && !datadiskTemplateToDiskOfferingMap.isEmpty()) { - int diskNumber = 1; for (Entry dataDiskTemplateToDiskOfferingMap : datadiskTemplateToDiskOfferingMap.entrySet()) { DiskOffering diskOffering = dataDiskTemplateToDiskOfferingMap.getValue(); long diskOfferingSize = diskOffering.getDiskSize() / (1024 * 1024 * 1024); VMTemplateVO dataDiskTemplate = _templateDao.findById(dataDiskTemplateToDiskOfferingMap.getKey()); - volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId() + "-" + String.valueOf(diskNumber), diskOffering, diskOfferingSize, null, null, - persistedVm, dataDiskTemplate, owner, Long.valueOf(diskNumber)); - diskNumber++; + volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId() + "-" + String.valueOf(nextDiskNumber), diskOffering, diskOfferingSize, null, null, + persistedVm, dataDiskTemplate, owner, nextDiskNumber); + nextDiskNumber++; } } } finally { diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java index 6763a13aed63..0ad7438f3242 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java @@ -159,7 +159,8 @@ public void destroyVolume(String volumeEntity) { @Override public VirtualMachineEntity createVirtualMachine(String id, String owner, String templateId, String hostName, String displayName, String hypervisor, int cpu, int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map> networkNicMap, DeploymentPlan plan, - Long rootDiskSize, Map> extraDhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Long dataDiskOfferingId, Long rootDiskOfferingId) throws InsufficientCapacityException { + Long rootDiskSize, Map> extraDhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, + Long dataDiskOfferingId, Long rootDiskOfferingId, List dataDiskOfferingsInfo) throws InsufficientCapacityException { // VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks, // vmEntityManager); @@ -225,6 +226,7 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String DiskOfferingInfo dataDiskOfferingInfo = new DiskOfferingInfo(); dataDiskOfferingInfo.setDiskOffering(diskOffering); + dataDiskOfferingInfo.setDeviceId(1L); dataDiskOfferingInfo.setSize(size); if (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops()) { @@ -241,6 +243,8 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String dataDiskOfferings.add(dataDiskOfferingInfo); } + } else if (dataDiskOfferingsInfo != null){ + dataDiskOfferings.addAll(dataDiskOfferingsInfo); } if (dataDiskTemplateToDiskOfferingMap != null && !dataDiskTemplateToDiskOfferingMap.isEmpty()) { @@ -264,7 +268,7 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String @Override public VirtualMachineEntity createVirtualMachineFromScratch(String id, String owner, String isoId, String hostName, String displayName, String hypervisor, String os, int cpu, int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map> networkNicMap, DeploymentPlan plan, - Map> extraDhcpOptionMap, Long diskOfferingId) + Map> extraDhcpOptionMap, Long diskOfferingId, List dataDiskOfferingsInfo) throws InsufficientCapacityException { // VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks, vmEntityManager); @@ -321,7 +325,7 @@ public VirtualMachineEntity createVirtualMachineFromScratch(String id, String ow HypervisorType hypervisorType = HypervisorType.valueOf(hypervisor); - _itMgr.allocate(vm.getInstanceName(), _templateDao.findById(new Long(isoId)), computeOffering, rootDiskOfferingInfo, new ArrayList(), networkIpMap, plan, hypervisorType, extraDhcpOptionMap, null); + _itMgr.allocate(vm.getInstanceName(), _templateDao.findById(new Long(isoId)), computeOffering, rootDiskOfferingInfo, dataDiskOfferingsInfo, networkIpMap, plan, hypervisorType, extraDhcpOptionMap, null); return vmEntity; } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupDetailVO.java new file mode 100644 index 000000000000..52baca11282e --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupDetailVO.java @@ -0,0 +1,98 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.ResourceDetail; + +@Entity +@Table(name = "backup_details") +public class BackupDetailVO implements ResourceDetail { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "backup_id") + private long resourceId; + + @Column(name = "name") + private String name; + + @Column(name = "value", length = 1024) + private String value; + + @Column(name = "display") + private boolean display = true; + + public BackupDetailVO() { + } + + public BackupDetailVO(long templateId, String name, String value, boolean display) { + this.resourceId = templateId; + this.name = name; + this.value = value; + this.display = display; + } + + @Override + public long getId() { + return id; + } + + @Override + public long getResourceId() { + return resourceId; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + + @Override + public boolean isDisplay() { + return display; + } + + public void setId(long id) { + this.id = id; + } + + public void setResourceId(long resourceId) { + this.resourceId = resourceId; + } + + public void setName(String name) { + this.name = name; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java index b4cd2f7badae..891d789f97ba 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java @@ -19,6 +19,7 @@ import com.cloud.utils.db.GenericDao; import com.google.gson.Gson; + import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.commons.lang3.StringUtils; @@ -26,6 +27,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Map; import java.util.UUID; import javax.persistence.Column; @@ -38,6 +40,7 @@ import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import javax.persistence.Transient; @Entity @Table(name = "backups") @@ -91,6 +94,9 @@ public class BackupVO implements Backup { @Column(name = "backed_volumes", length = 65535) protected String backedUpVolumes; + @Transient + Map details; + public BackupVO() { this.uuid = UUID.randomUUID().toString(); } @@ -229,10 +235,32 @@ public void setBackedUpVolumes(String backedUpVolumes) { this.backedUpVolumes = backedUpVolumes; } + @Override + public Map getDetails() { + return details; + } + + public void setDetail(String name, String value) { + assert (details != null) : "Did you forget to load the details?"; + this.details.put(name, value); + } + + @Override + public String getDetail(String name) { + return this.details.get(name); + } + + public void setDetails(Map details) { + this.details = details; + } + + public void addDetails(Map details) { + this.details.putAll(details); + } + public Date getRemoved() { return removed; } - public void setRemoved(Date removed) { this.removed = removed; } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java index 89a13245b0a0..07a438143058 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java @@ -35,5 +35,7 @@ public interface BackupDao extends GenericDao { List syncBackups(Long zoneId, Long vmId, List externalBackups); BackupVO getBackupVO(Backup backup); List listByOfferingId(Long backupOfferingId); - BackupResponse newBackupResponse(Backup backup); + void loadDetails(BackupVO backup); + void saveDetails(BackupVO backup); + BackupResponse newBackupResponse(Backup backup, Boolean listVmDetails); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java index 5a9cd0620374..bbb92ba6c4d6 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java @@ -18,14 +18,18 @@ package org.apache.cloudstack.backup.dao; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import javax.annotation.PostConstruct; import javax.inject.Inject; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupDetailVO; import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.BackupVO; @@ -38,6 +42,8 @@ import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.VMInstanceDao; import com.google.gson.Gson; @@ -59,6 +65,9 @@ public class BackupDaoImpl extends GenericDaoBase implements Bac @Inject BackupOfferingDao backupOfferingDao; + @Inject + BackupDetailsDao backupDetailsDao; + private SearchBuilder backupSearch; public BackupDaoImpl() { @@ -133,6 +142,27 @@ public void removeExistingBackups(Long zoneId, Long vmId) { expunge(sc); } + @Override + public BackupVO persist(BackupVO backup) { + return Transaction.execute((TransactionCallback) status -> { + BackupVO backupDb = super.persist(backup); + saveDetails(backup); + loadDetails(backupDb); + return backupDb; + }); + } + + @Override + public boolean update(Long id, BackupVO backup) { + return Transaction.execute((TransactionCallback) status -> { + boolean result = super.update(id, backup); + if (result == true) { + saveDetails(backup); + } + return result; + }); + } + @Override public List syncBackups(Long zoneId, Long vmId, List externalBackups) { for (Backup backup : externalBackups) { @@ -143,7 +173,27 @@ public List syncBackups(Long zoneId, Long vmId, List externalBac } @Override - public BackupResponse newBackupResponse(Backup backup) { + public void loadDetails(BackupVO backup) { + Map details = backupDetailsDao.listDetailsKeyPairs(backup.getId()); + backup.setDetails(details); + } + + @Override + public void saveDetails(BackupVO backup) { + Map detailsStr = backup.getDetails(); + if (detailsStr == null) { + return; + } + List details = new ArrayList(); + for (String key : detailsStr.keySet()) { + BackupDetailVO detail = new BackupDetailVO(backup.getId(), key, detailsStr.get(key), true); + details.add(detail); + } + backupDetailsDao.saveDetails(details); + } + + @Override + public BackupResponse newBackupResponse(Backup backup, Boolean listVmDetails) { VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); AccountVO account = accountDao.findByIdIncludingRemoved(vm.getAccountId()); DomainVO domain = domainDao.findByIdIncludingRemoved(vm.getDomainId()); @@ -180,6 +230,24 @@ public BackupResponse newBackupResponse(Backup backup) { response.setDomain(domain.getName()); response.setZoneId(zone.getUuid()); response.setZone(zone.getName()); + + if (Boolean.TRUE.equals(listVmDetails)) { + Map details = backupDetailsDao.listDetailsKeyPairs(backup.getId(), true); + if (details != null) { + HashMap vmDetails = new HashMap<>(); + vmDetails.put(ApiConstants.HYPERVISOR, details.get(ApiConstants.HYPERVISOR)); + vmDetails.put(ApiConstants.TEMPLATE_ID, details.get(ApiConstants.TEMPLATE_ID)); + vmDetails.put(ApiConstants.SERVICE_OFFERING_ID, details.get(ApiConstants.SERVICE_OFFERING_ID)); + vmDetails.put(ApiConstants.NETWORK_IDS, details.get(ApiConstants.NETWORK_IDS)); + vmDetails.put(ApiConstants.DISK_OFFERING_IDS, details.get(ApiConstants.DISK_OFFERING_IDS)); + vmDetails.put(ApiConstants.DEVICE_IDS, details.get(ApiConstants.DEVICE_IDS)); + vmDetails.put(ApiConstants.DISK_SIZES, details.get(ApiConstants.DISK_SIZES)); + vmDetails.put(ApiConstants.MIN_IOPS, details.get(ApiConstants.MIN_IOPS)); + vmDetails.put(ApiConstants.MAX_IOPS, details.get(ApiConstants.MAX_IOPS)); + response.setVmDetails(vmDetails); + } + } + response.setObjectName("backup"); return response; } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDetailsDao.java new file mode 100644 index 000000000000..664650074bce --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDetailsDao.java @@ -0,0 +1,26 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup.dao; + +import org.apache.cloudstack.backup.BackupDetailVO; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDao; + +import com.cloud.utils.db.GenericDao; + +public interface BackupDetailsDao extends GenericDao, ResourceDetailsDao { + +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDetailsDaoImpl.java new file mode 100644 index 000000000000..08c7192af909 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDetailsDaoImpl.java @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup.dao; + + +import org.apache.cloudstack.backup.BackupDetailVO; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; +import org.springframework.stereotype.Component; + +@Component +public class BackupDetailsDaoImpl extends ResourceDetailsDaoBase implements BackupDetailsDao { + + @Override + public void addDetail(long resourceId, String key, String value, boolean display) { + super.addDetail(new BackupDetailVO(resourceId, key, value, display)); + } +} diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index 4f22234d7bf4..23734b1001a0 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -270,6 +270,7 @@ + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql index 4a5a0203a15a..edff83123d47 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql @@ -25,6 +25,17 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.console_session', 'console_endpoint_ -- Add client_address column to cloud.console_session table CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.console_session', 'client_address', 'VARCHAR(45)'); +-- Create backup details table +CREATE TABLE `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` varchar(1024) 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; + -- Allow default roles to use quotaCreditsList INSERT INTO `cloud`.`role_permissions` (uuid, role_id, rule, permission, sort_order) SELECT uuid(), role_id, 'quotaCreditsList', permission, sort_order @@ -33,4 +44,3 @@ WHERE rp.rule = 'quotaStatement' AND NOT EXISTS(SELECT 1 FROM cloud.role_permissions rp_ WHERE rp.role_id = rp_.role_id AND rp_.rule = 'quotaCreditsList'); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host', 'last_mgmt_server_id', 'bigint unsigned DEFAULT NULL COMMENT "last management server this host is connected to" AFTER `mgmt_server_id`'); - diff --git a/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java index d4b3cff0f5c1..f75605ac9af9 100644 --- a/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java +++ b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java @@ -25,6 +25,7 @@ import javax.inject.Inject; import com.cloud.storage.dao.VolumeDao; + import org.apache.cloudstack.backup.dao.BackupDao; import com.cloud.utils.Pair; @@ -40,6 +41,8 @@ public class DummyBackupProvider extends AdapterBase implements BackupProvider { private BackupDao backupDao; @Inject private VolumeDao volumeDao; + @Inject + private BackupManager backupManager; @Override public String getName() { @@ -107,7 +110,7 @@ public boolean removeVMFromBackupOffering(VirtualMachine vm) { @Override public boolean willDeleteBackupsOnOfferingRemoval() { - return true; + return false; } @Override @@ -127,6 +130,11 @@ public boolean takeBackup(VirtualMachine vm) { backup.setDomainId(vm.getDomainId()); backup.setZoneId(vm.getDataCenterId()); backup.setBackedUpVolumes(BackupManagerImpl.createVolumeInfoFromVolumes(volumeDao.findByInstance(vm.getId()))); + Map details = backupManager.getVmDetailsForBackup(vm); + backup.setDetails(details); + Map diskOfferingDetails = backupManager.getDiskOfferingDetailsForBackup(vm.getId()); + backup.addDetails(diskOfferingDetails); + return backupDao.persist(backup) != null; } @@ -138,4 +146,14 @@ public boolean deleteBackup(Backup backup, boolean forced) { @Override public void syncBackups(VirtualMachine vm, Backup.Metric metric) { } + + @Override + public boolean supportsInstanceFromBackup() { + return true; + } + + @Override + public boolean restoreBackupToVM(VirtualMachine vm, Backup backup, String hostIp, String dataStoreUuid) { + return true; + } } diff --git a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index 5d3d1a919330..253ee62b715d 100644 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@ -36,6 +36,7 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachine; import com.cloud.vm.dao.VMInstanceDao; + import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupRepositoryDao; @@ -93,6 +94,9 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co @Inject private AgentManager agentManager; + @Inject + BackupManager backupManager; + protected Host getLastVMHypervisorHost(VirtualMachine vm) { Long hostId = vm.getLastHostId(); if (hostId == null) { @@ -179,6 +183,8 @@ public boolean takeBackup(final VirtualMachine vm) { backupVO.setSize(answer.getSize()); backupVO.setStatus(Backup.Status.BackedUp); backupVO.setBackedUpVolumes(BackupManagerImpl.createVolumeInfoFromVolumes(volumeDao.findByInstance(vm.getId()))); + Map details = backupManager.getDiskOfferingDetailsForBackup(vm.getId()); + backupVO.addDetails(details); return backupDao.update(backupVO.getId(), backupVO); } else { backupVO.setStatus(Backup.Status.Failed); @@ -205,13 +211,26 @@ private BackupVO createBackupObject(VirtualMachine vm, String backupPath) { backup.setAccountId(vm.getAccountId()); backup.setDomainId(vm.getDomainId()); backup.setZoneId(vm.getDataCenterId()); + Map details = backupManager.getVmDetailsForBackup(vm); + backup.setDetails(details); + return backupDao.persist(backup); } + @Override + public boolean restoreBackupToVM(VirtualMachine vm, Backup backup, String hostIp, String dataStoreUuid) { + return restoreVMBackup(vm, backup); + } + @Override public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { + return restoreVMBackup(vm, backup); + } + + private boolean restoreVMBackup(VirtualMachine vm, Backup backup) { List backedVolumes = backup.getBackedUpVolumes(); - List volumes = backedVolumes.stream().map(volume -> volumeDao.findByUuid(volume.getUuid())).collect(Collectors.toList()); + List backedVolumesUUIDs = backedVolumes.stream().map(volume -> volume.getUuid()).collect(Collectors.toList()); + List restoreVolumes = volumeDao.findByInstance(vm.getId()); LOG.debug("Restoring vm {} from backup {} on the NAS Backup Provider", vm, backup); BackupRepository backupRepository = getBackupRepository(vm, backup); @@ -222,7 +241,8 @@ public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { restoreCommand.setBackupRepoType(backupRepository.getType()); restoreCommand.setBackupRepoAddress(backupRepository.getAddress()); restoreCommand.setVmName(vm.getName()); - restoreCommand.setVolumePaths(getVolumePaths(volumes)); + restoreCommand.setBackupVolumesUUIDs(backedVolumesUUIDs); + restoreCommand.setRestoreVolumePaths(getVolumePaths(restoreVolumes)); restoreCommand.setVmExists(vm.getRemoved() == null); restoreCommand.setVmState(vm.getState()); @@ -287,7 +307,7 @@ public Pair restoreBackedUpVolume(Backup backup, String volumeU restoreCommand.setBackupRepoType(backupRepository.getType()); restoreCommand.setBackupRepoAddress(backupRepository.getAddress()); restoreCommand.setVmName(vmNameAndState.first()); - restoreCommand.setVolumePaths(Collections.singletonList(String.format("%s/%s", dataStore.getLocalPath(), volumeUUID))); + restoreCommand.setRestoreVolumePaths(Collections.singletonList(String.format("%s/%s", dataStore.getLocalPath(), volumeUUID))); restoreCommand.setDiskType(volume.getVolumeType().name().toLowerCase(Locale.ROOT)); restoreCommand.setVmExists(null); restoreCommand.setVmState(vmNameAndState.second()); @@ -403,6 +423,11 @@ public void syncBackups(VirtualMachine vm, Backup.Metric metric) { // TODO: check and sum/return backups metrics on per VM basis } + @Override + public boolean supportsInstanceFromBackup() { + return true; + } + @Override public List listBackupOfferings(Long zoneId) { final List repositories = backupRepositoryDao.listByZoneAndProvider(zoneId, getName()); diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java index 393e2911ac38..3f751bcba288 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java @@ -21,14 +21,17 @@ import com.cloud.host.Status; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; +import com.cloud.offering.ServiceOffering; import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.storage.dao.VolumeDao; +import com.cloud.template.VirtualMachineTemplate; import com.cloud.utils.Pair; import com.cloud.utils.Ternary; import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.db.EntityManager; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallbackNoReturn; import com.cloud.utils.db.TransactionStatus; @@ -37,6 +40,8 @@ import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.dao.VMInstanceDao; + +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.InternalIdentity; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDaoImpl; @@ -117,6 +122,12 @@ public class NetworkerBackupProvider extends AdapterBase implements BackupProvid @Inject private VMInstanceDao vmInstanceDao; + @Inject + private EntityManager entityManager; + + @Inject + BackupManager backupManager; + private static String getUrlDomain(String url) throws URISyntaxException { URI uri; try { @@ -513,6 +524,10 @@ public boolean takeBackup(VirtualMachine vm) { BackupVO backup = getClient(vm.getDataCenterId()).registerBackupForVm(vm, backupJobStart, saveTime); if (backup != null) { backup.setBackedUpVolumes(BackupManagerImpl.createVolumeInfoFromVolumes(volumeDao.findByInstance(vm.getId()))); + Map details = backupManager.getVmDetailsForBackup(vm); + backup.setDetails(details); + details = backupManager.getDiskOfferingDetailsForBackup(vm.getId()); + backup.addDetails(details); backupDao.persist(backup); return true; } else { @@ -626,6 +641,15 @@ public void doInTransactionWithoutResult(TransactionStatus status) { strayBackup.setAccountId(vm.getAccountId()); strayBackup.setDomainId(vm.getDomainId()); strayBackup.setZoneId(vm.getDataCenterId()); + + HashMap details = new HashMap<>(); + details.put(ApiConstants.HYPERVISOR, vm.getHypervisorType().toString()); + ServiceOffering serviceOffering = entityManager.findById(ServiceOffering.class, vm.getServiceOfferingId()); + details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOffering.getUuid()); + VirtualMachineTemplate template = entityManager.findById(VirtualMachineTemplate.class, vm.getTemplateId()); + details.put(ApiConstants.TEMPLATE_ID, template.getUuid()); + strayBackup.setDetails(details); + LOG.debug(String.format("Creating a new entry in backups: [id: %s, uuid: %s, vm_id: %s, external_id: %s, type: %s, date: %s, backup_offering_id: %s, account_id: %s, " + "domain_id: %s, zone_id: %s].", strayBackup.getId(), strayBackup.getUuid(), strayBackup.getVmId(), strayBackup.getExternalId(), strayBackup.getType(), strayBackup.getDate(), strayBackup.getBackupOfferingId(), strayBackup.getAccountId(), @@ -644,6 +668,16 @@ public void doInTransactionWithoutResult(TransactionStatus status) { }); } + @Override + public boolean supportsInstanceFromBackup() { + return false; + } + @Override public boolean willDeleteBackupsOnOfferingRemoval() { return false; } + + @Override + public boolean restoreBackupToVM(VirtualMachine vm, Backup backup, String hostIp, String dataStoreUuid) { + return true; + } } diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java index 36bfd4564752..9d6b779c403f 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java @@ -267,6 +267,7 @@ public BackupVO registerBackupForVm(VirtualMachine vm, Date backupJobStart, Stri backup.setAccountId(vm.getAccountId()); backup.setDomainId(vm.getDomainId()); backup.setZoneId(vm.getDataCenterId()); + return backup; } catch (final IOException e) { LOG.error("Failed to register backup from EMC Networker due to:", e); diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java index c120d8bd5999..274746db1ee4 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java @@ -50,6 +50,7 @@ import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMap; import com.cloud.dc.dao.VmwareDatacenterDao; import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao; +import com.cloud.storage.dao.VolumeDao; import com.cloud.user.User; import com.cloud.utils.Pair; import com.cloud.utils.component.AdapterBase; @@ -109,6 +110,10 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, private AgentManager agentMgr; @Inject private VirtualMachineManager virtualMachineManager; + @Inject + private BackupManager backupManager; + @Inject + private VolumeDao volumeDao; protected VeeamClient getClient(final Long zoneId) { try { @@ -216,7 +221,7 @@ public boolean removeVMFromBackupOffering(final VirtualMachine vm) { @Override public boolean willDeleteBackupsOnOfferingRemoval() { - return true; + return false; } @Override @@ -294,7 +299,7 @@ private void prepareForBackupRestoration(VirtualMachine vm) { public Pair restoreBackedUpVolume(Backup backup, String volumeUuid, String hostIp, String dataStoreUuid, Pair vmNameAndState) { final Long zoneId = backup.getZoneId(); final String restorePointId = backup.getExternalId(); - return getClient(zoneId).restoreVMToDifferentLocation(restorePointId, hostIp, dataStoreUuid); + return getClient(zoneId).restoreVMToDifferentLocation(restorePointId, null, hostIp, dataStoreUuid); } @Override @@ -378,6 +383,11 @@ public void doInTransactionWithoutResult(TransactionStatus status) { backup.setAccountId(vm.getAccountId()); backup.setDomainId(vm.getDomainId()); backup.setZoneId(vm.getDataCenterId()); + backup.setBackedUpVolumes(BackupManagerImpl.createVolumeInfoFromVolumes(volumeDao.findByInstance(vm.getId()))); + Map details = backupManager.getVmDetailsForBackup(vm); + backup.setDetails(details); + details = backupManager.getDiskOfferingDetailsForBackup(vm.getId()); + backup.addDetails(details); logger.debug("Creating a new entry in backups: [id: {}, uuid: {}, name: {}, vm_id: {}, external_id: {}, type: {}, date: {}, backup_offering_id: {}, account_id: {}, " + "domain_id: {}, zone_id: {}].", backup.getId(), backup.getUuid(), backup.getName(), backup.getVmId(), backup.getExternalId(), backup.getType(), backup.getDate(), backup.getBackupOfferingId(), backup.getAccountId(), backup.getDomainId(), backup.getZoneId()); @@ -396,6 +406,19 @@ public void doInTransactionWithoutResult(TransactionStatus status) { }); } + @Override + public boolean restoreBackupToVM(VirtualMachine vm, Backup backup, String hostIp, String dataStoreUuid) { + final Long zoneId = backup.getZoneId(); + final String restorePointId = backup.getExternalId(); + final String restoreLocation = vm.getInstanceName(); + return getClient(zoneId).restoreVMToDifferentLocation(restorePointId, restoreLocation, hostIp, dataStoreUuid).first(); + } + + @Override + public boolean supportsInstanceFromBackup() { + return true; + } + @Override public String getConfigComponentName() { return BackupService.class.getSimpleName(); diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java index d911736090cb..2b41199b1a16 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java @@ -925,8 +925,10 @@ private Date formatDate(String date) throws ParseException { return dateFormat.parse(StringUtils.substring(date, 0, 19)); } - public Pair restoreVMToDifferentLocation(String restorePointId, String hostIp, String dataStoreUuid) { - final String restoreLocation = RESTORE_VM_SUFFIX + UUID.randomUUID().toString(); + public Pair restoreVMToDifferentLocation(String restorePointId, String restoreLocation, String hostIp, String dataStoreUuid) { + if (restoreLocation == null) { + restoreLocation = RESTORE_VM_SUFFIX + UUID.randomUUID().toString(); + } final String datastoreId = dataStoreUuid.replace("-",""); final List cmds = Arrays.asList( "$points = Get-VBRRestorePoint", diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java index 23ead355096d..30e45a70dafe 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java @@ -58,36 +58,38 @@ public Answer execute(RestoreBackupCommand command, LibvirtComputingResource ser String mountOptions = command.getMountOptions(); Boolean vmExists = command.isVmExists(); String diskType = command.getDiskType(); - List volumePaths = command.getVolumePaths(); + List backedVolumeUUIDs = command.getBackupVolumesUUIDs(); + List restoreVolumePaths = command.getRestoreVolumePaths(); String restoreVolumeUuid = command.getRestoreVolumeUUID(); String newVolumeId = null; if (Objects.isNull(vmExists)) { - String volumePath = volumePaths.get(0); + String volumePath = restoreVolumePaths.get(0); int lastIndex = volumePath.lastIndexOf("/"); newVolumeId = volumePath.substring(lastIndex + 1); restoreVolume(backupPath, backupRepoType, backupRepoAddress, volumePath, diskType, restoreVolumeUuid, new Pair<>(vmName, command.getVmState())); } else if (Boolean.TRUE.equals(vmExists)) { - restoreVolumesOfExistingVM(volumePaths, backupPath, backupRepoType, backupRepoAddress, mountOptions); + restoreVolumesOfExistingVM(restoreVolumePaths, backedVolumeUUIDs, backupPath, backupRepoType, backupRepoAddress, mountOptions); } else { - restoreVolumesOfDestroyedVMs(volumePaths, vmName, backupPath, backupRepoType, backupRepoAddress, mountOptions); + restoreVolumesOfDestroyedVMs(restoreVolumePaths, vmName, backupPath, backupRepoType, backupRepoAddress, mountOptions); } return new BackupAnswer(command, true, newVolumeId); } - private void restoreVolumesOfExistingVM(List volumePaths, String backupPath, - String backupRepoType, String backupRepoAddress, String mountOptions) { + private void restoreVolumesOfExistingVM(List restoreVolumePaths, List backedVolumesUUIDs, String backupPath, + String backupRepoType, String backupRepoAddress, String mountOptions) { String diskType = "root"; String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType); try { - for (int idx = 0; idx < volumePaths.size(); idx++) { - String volumePath = volumePaths.get(idx); - Pair bkpPathAndVolUuid = getBackupPath(mountDirectory, volumePath, backupPath, diskType, null); + for (int idx = 0; idx < restoreVolumePaths.size(); idx++) { + String restoreVolumePath = restoreVolumePaths.get(idx); + String backupVolumeUuid = backedVolumesUUIDs.get(idx); + Pair bkpPathAndVolUuid = getBackupPath(mountDirectory, null, backupPath, diskType, backupVolumeUuid); diskType = "datadisk"; try { - replaceVolumeWithBackup(volumePath, bkpPathAndVolUuid.first()); + replaceVolumeWithBackup(restoreVolumePath, bkpPathAndVolUuid.first()); } catch (IOException e) { throw new CloudRuntimeException(String.format("Unable to revert backup for volume [%s] due to [%s].", bkpPathAndVolUuid.second(), e.getMessage()), e); } @@ -96,7 +98,6 @@ private void restoreVolumesOfExistingVM(List volumePaths, String backupP unmountBackupDirectory(mountDirectory); deleteTemporaryDirectory(mountDirectory); } - } private void restoreVolumesOfDestroyedVMs(List volumePaths, String vmName, String backupPath, @@ -177,8 +178,7 @@ private void deleteTemporaryDirectory(String backupDirectory) { private Pair getBackupPath(String mountDirectory, String volumePath, String backupPath, String diskType, String volumeUuid) { String bkpPath = String.format(FILE_PATH_PLACEHOLDER, mountDirectory, backupPath); - int lastIndex = volumePath.lastIndexOf(File.separator); - String volUuid = Objects.isNull(volumeUuid) ? volumePath.substring(lastIndex + 1) : volumeUuid; + String volUuid = Objects.isNull(volumeUuid) ? volumePath.substring(volumePath.lastIndexOf(File.separator) + 1) : volumeUuid; String backupFileName = String.format("%s.%s.qcow2", diskType.toLowerCase(Locale.ROOT), volUuid); bkpPath = String.format(FILE_PATH_PLACEHOLDER, bkpPath, backupFileName); return new Pair<>(bkpPath, volUuid); diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java index 8c983149d02d..91495ba9738b 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java @@ -410,12 +410,12 @@ protected UserVm createKubernetesNode(String joinIp) throws ManagementServerExce List securityGroupIds = new ArrayList<>(); securityGroupIds.add(kubernetesCluster.getSecurityGroupId()); nodeVm = userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, clusterTemplate, networkIds, securityGroupIds, owner, - hostName, hostName, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, null, null, keypairs, + hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, null, null, keypairs, null, addrs, null, null, null, customParameterMap, null, null, null, null, true, null, UserVmManager.CKS_NODE); } else { nodeVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, clusterTemplate, networkIds, owner, - hostName, hostName, null, null, null, + hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, null, null, keypairs, null, addrs, null, null, null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null); } diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java index a2384a2e0feb..0f79e2be14ac 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java @@ -222,12 +222,12 @@ private UserVm createKubernetesControlNode(final Network network, String serverI List securityGroupIds = new ArrayList<>(); securityGroupIds.add(kubernetesCluster.getSecurityGroupId()); controlVm = userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, clusterTemplate, networkIds, securityGroupIds, owner, - hostName, hostName, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, null, null, keypairs, + hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, null, null, keypairs, requestedIps, addrs, null, null, null, customParameterMap, null, null, null, null, true, null, UserVmManager.CKS_NODE); } else { controlVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, clusterTemplate, networkIds, owner, - hostName, hostName, null, null, null, + hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, null, null, keypairs, requestedIps, addrs, null, null, null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null); } @@ -299,12 +299,12 @@ private UserVm createKubernetesAdditionalControlNode(final String joinIp, final List securityGroupIds = new ArrayList<>(); securityGroupIds.add(kubernetesCluster.getSecurityGroupId()); additionalControlVm = userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, clusterTemplate, networkIds, securityGroupIds, owner, - hostName, hostName, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, null, null, keypairs, + hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, null, null, keypairs, null, addrs, null, null, null, customParameterMap, null, null, null, null, true, null, UserVmManager.CKS_NODE); } else { additionalControlVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, clusterTemplate, networkIds, owner, - hostName, hostName, null, null, null, + hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, null, null, keypairs, null, addrs, null, null, null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null); } diff --git a/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java b/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java index 31159e7d3d95..d7af62fb0651 100644 --- a/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java +++ b/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java @@ -194,7 +194,7 @@ private UserVm deploySharedFSVM(Long zoneId, Account owner, List networkId CallContext vmContext = CallContext.register(CallContext.current(), ApiCommandResourceType.VirtualMachine); try { vm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, hostName, hostName, - diskOfferingId, size, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, + diskOfferingId, size, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, null, null, keypairs, null, addrs, null, null, null, customParameterMap, null, null, null, null, true, UserVmManager.SHAREDFSVM, null); diff --git a/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java b/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java index 4393b0565f89..9e50c1680344 100644 --- a/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java +++ b/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java @@ -255,7 +255,7 @@ public void testDeploySharedFS() throws ResourceUnavailableException, Insufficie when(vm.getId()).thenReturn(s_vmId); when(userVmService.createAdvancedVirtualMachine( any(DataCenter.class), any(ServiceOffering.class), any(VirtualMachineTemplate.class), anyList(), any(Account.class), anyString(), - anyString(), anyLong(), anyLong(), isNull(), any(Hypervisor.HypervisorType.class), any(BaseCmd.HTTPMethod.class), anyString(), + anyString(), anyLong(), anyLong(), any(), isNull(), any(Hypervisor.HypervisorType.class), any(BaseCmd.HTTPMethod.class), anyString(), isNull(), isNull(), anyList(), isNull(), any(Network.IpAddresses.class), isNull(), isNull(), isNull(), anyMap(), isNull(), isNull(), isNull(), isNull(), anyBoolean(), anyString(), isNull())).thenReturn(vm); diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index 944f60d292ca..2162303bfb57 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -1102,7 +1102,7 @@ public static DiskOfferingVO findComputeOnlyDiskOfferingById(Long diskOfferingId return null; } - public static DiskOfferingVO findDiskOfferingById(Long diskOfferingId) { + public static DiskOfferingVO findNonComputeDiskOfferingById(Long diskOfferingId) { if (diskOfferingId == null) { return null; } @@ -1113,6 +1113,14 @@ public static DiskOfferingVO findDiskOfferingById(Long diskOfferingId) { return null; } + public static DiskOfferingVO findDiskOfferingById(Long diskOfferingId) { + if (diskOfferingId == null) { + return null; + } + DiskOfferingVO off = s_diskOfferingDao.findByIdIncludingRemoved(diskOfferingId); + return off; + } + public static ServiceOfferingVO findServiceOfferingByComputeOnlyDiskOffering(Long diskOfferingId, boolean includingRemoved) { ServiceOfferingVO off = s_serviceOfferingDao.findServiceOfferingByComputeOnlyDiskOffering(diskOfferingId, includingRemoved); return off; @@ -2252,8 +2260,8 @@ public static ResourceIconVO getResourceIconByResourceUUID(String resourceUUID, return s_resourceIconDao.findByResourceUuid(resourceUUID, resourceType); } - public static BackupResponse newBackupResponse(Backup backup) { - return s_backupDao.newBackupResponse(backup); + public static BackupResponse newBackupResponse(Backup backup, Boolean listVmDetails) { + return s_backupDao.newBackupResponse(backup, listVmDetails); } public static BackupScheduleResponse newBackupScheduleResponse(BackupSchedule schedule) { diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index fcc4444670cf..fbe4a40f1e47 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -4963,8 +4963,8 @@ public UserDataResponse createUserDataResponse(UserData userData) { } @Override - public BackupResponse createBackupResponse(Backup backup) { - return ApiDBUtils.newBackupResponse(backup); + public BackupResponse createBackupResponse(Backup backup, Boolean listVmDetails) { + return ApiDBUtils.newBackupResponse(backup, listVmDetails); } @Override diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index 7e10df24e1b5..78e38d0847be 100644 --- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java @@ -211,7 +211,7 @@ public UserVmResponse newUserVmResponse(ResponseView view, String objectName, Us userVmResponse.setServiceOfferingName(userVm.getServiceOfferingName()); } if (details.contains(VMDetails.all) || details.contains(VMDetails.diskoff)) { - DiskOfferingVO diskOfferingVO = ApiDBUtils.findDiskOfferingById(userVm.getDiskOfferingId()); + DiskOfferingVO diskOfferingVO = ApiDBUtils.findNonComputeDiskOfferingById(userVm.getDiskOfferingId()); if (diskOfferingVO != null) { userVmResponse.setDiskOfferingId(userVm.getDiskOfferingUuid()); userVmResponse.setDiskOfferingName(userVm.getDiskOfferingName()); diff --git a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java index bda84f09fe61..24f68c8ccba9 100644 --- a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java +++ b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java @@ -1807,7 +1807,7 @@ protected UserVm createNewVM(AutoScaleVmGroupVO asGroup) { if (zone.getNetworkType() == NetworkType.Basic) { vm = userVmService.createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, null, owner, vmHostName, - vmHostName, diskOfferingId, dataDiskSize, null, + vmHostName, diskOfferingId, dataDiskSize, null, null, hypervisorType, HTTPMethod.GET, userData, userDataId, userDataDetails, sshKeyPairs, null, null, true, null, affinityGroupIdList, customParameters, null, null, null, null, true, overrideDiskOfferingId); @@ -1815,13 +1815,13 @@ protected UserVm createNewVM(AutoScaleVmGroupVO asGroup) { if (networkModel.checkSecurityGroupSupportForNetwork(owner, zone, networkIds, Collections.emptyList())) { vm = userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, networkIds, null, - owner, vmHostName,vmHostName, diskOfferingId, dataDiskSize, null, + owner, vmHostName,vmHostName, diskOfferingId, dataDiskSize, null, null, hypervisorType, HTTPMethod.GET, userData, userDataId, userDataDetails, sshKeyPairs, null, null, true, null, affinityGroupIdList, customParameters, null, null, null, null, true, overrideDiskOfferingId, null); } else { vm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, vmHostName, vmHostName, - diskOfferingId, dataDiskSize, null, + diskOfferingId, dataDiskSize, null, null, hypervisorType, HTTPMethod.GET, userData, userDataId, userDataDetails, sshKeyPairs, null, addrs, true, null, affinityGroupIdList, customParameters, null, null, null, null, true, null, overrideDiskOfferingId); diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index a371a0647019..4b11216a1f9b 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -940,7 +940,7 @@ public VolumeVO allocVolume(CreateVolumeCmd cmd) throws ResourceAllocationExcept String userSpecifiedName = getVolumeNameFromCommand(cmd); - return commitVolume(cmd, caller, owner, displayVolume, zoneId, diskOfferingId, provisioningType, size, minIops, maxIops, parentVolume, userSpecifiedName, + return commitVolume(cmd.getSnapshotId(), caller, owner, displayVolume, zoneId, diskOfferingId, provisioningType, size, minIops, maxIops, parentVolume, userSpecifiedName, _uuidMgr.generateUuid(Volume.class, cmd.getCustomId()), details); } @@ -954,7 +954,7 @@ public void validateCustomDiskOfferingSizeRange(Long sizeInGB) { } } - private VolumeVO commitVolume(final CreateVolumeCmd cmd, final Account caller, final Account owner, final Boolean displayVolume, final Long zoneId, final Long diskOfferingId, + private VolumeVO commitVolume(final Long snapshotId, final Account caller, final Account owner, final Boolean displayVolume, final Long zoneId, final Long diskOfferingId, final Storage.ProvisioningType provisioningType, final Long size, final Long minIops, final Long maxIops, final VolumeVO parentVolume, final String userSpecifiedName, final String uuid, final Map details) { return Transaction.execute(new TransactionCallback() { @Override @@ -982,7 +982,7 @@ public VolumeVO doInTransaction(TransactionStatus status) { volume = _volsDao.persist(volume); - if (cmd.getSnapshotId() == null && displayVolume) { + if (snapshotId == null && displayVolume) { // for volume created from snapshot, create usage event after volume creation UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), diskOfferingId, null, size, Volume.class.getName(), volume.getUuid(), displayVolume); diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index f497c8810153..1fbffb8fefc2 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -74,6 +74,7 @@ import org.apache.cloudstack.api.command.admin.vm.ExpungeVMCmd; import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd; import org.apache.cloudstack.api.command.user.vm.AddNicToVMCmd; +import org.apache.cloudstack.api.command.user.vm.CreateVMFromBackupCmd; import org.apache.cloudstack.api.command.user.vm.DeployVMCmd; import org.apache.cloudstack.api.command.user.vm.DeployVnfApplianceCmd; import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd; @@ -96,6 +97,7 @@ import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupVO; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.cloud.entity.api.VirtualMachineEntity; @@ -284,6 +286,7 @@ import com.cloud.network.security.dao.SecurityGroupDao; import com.cloud.network.vpc.VpcManager; import com.cloud.offering.DiskOffering; +import com.cloud.offering.DiskOfferingInfo; import com.cloud.offering.NetworkOffering; import com.cloud.offering.NetworkOffering.Availability; import com.cloud.offering.ServiceOffering; @@ -983,23 +986,18 @@ public UserVm resetVMUserData(ResetVMUserDataCmd cmd) throws ResourceUnavailable @Override @ActionEvent(eventType = EventTypes.EVENT_VM_RESETSSHKEY, eventDescription = "resetting Vm SSHKey", async = true) public UserVm resetVMSSHKey(ResetVMSSHKeyCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException { - Account caller = CallContext.current().getCallingAccount(); Account owner = _accountMgr.finalizeOwner(caller, cmd.getAccountName(), cmd.getDomainId(), cmd.getProjectId()); - Long vmId = cmd.getId(); UserVmVO userVm = _vmDao.findById(cmd.getId()); if (userVm == null) { - throw new InvalidParameterValueException("unable to find a virtual machine by id" + cmd.getId()); + throw new InvalidParameterValueException("unable to find a virtual machine by id " + cmd.getId()); } if (UserVmManager.SHAREDFSVM.equals(userVm.getUserVmType())) { throw new InvalidParameterValueException("Operation not supported on Shared FileSystem Instance"); } - VMTemplateVO template = _templateDao.findByIdIncludingRemoved(userVm.getTemplateId()); - // Do parameters input validation - if (userVm.getState() == State.Error || userVm.getState() == State.Expunging) { logger.error("vm ({}) is not in the right state: {}", userVm, userVm.getState()); throw new InvalidParameterValueException("Vm with specified id is not in the right state"); @@ -1009,32 +1007,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 names = cmd.getNames(); + if (CollectionUtils.isEmpty(names)) { throw new InvalidParameterValueException("'keypair' or 'keypairs' must be specified"); } + userVm = resetVMSSHKeyInternal(userVm, owner, names); + return userVm; + } + + private UserVmVO resetVMSSHKeyInternal(UserVmVO userVm, Account owner, List names) throws ResourceUnavailableException, InsufficientCapacityException { + Account caller = CallContext.current().getCallingAccount(); + String keypairnames = ""; String sshPublicKeys = ""; List pairs = new ArrayList<>(); - pairs = _sshKeyPairDao.findByNames(owner.getAccountId(), owner.getDomainId(), cmd.getNames()); - if (pairs == null || pairs.size() != cmd.getNames().size()) { + pairs = _sshKeyPairDao.findByNames(owner.getAccountId(), owner.getDomainId(), names); + if (pairs == null || pairs.size() != names.size()) { throw new InvalidParameterValueException("Not all specified keypairs exist"); } sshPublicKeys = pairs.stream().map(p -> p.getPublicKey()).collect(Collectors.joining("\n")); - keypairnames = String.join(",", cmd.getNames()); + keypairnames = String.join(",", names); _accountMgr.checkAccess(caller, null, true, userVm); - boolean result = resetVMSSHKeyInternal(vmId, sshPublicKeys, keypairnames); + boolean result = resetVMSSHKeyInternal(userVm.getId(), sshPublicKeys, keypairnames); - UserVmVO vm = _vmDao.findById(vmId); + UserVmVO vm = _vmDao.findById(userVm.getId()); _vmDao.loadDetails(vm); if (!result) { throw new CloudRuntimeException("Failed to reset SSH Key for the virtual machine "); } - removeEncryptedPasswordFromUserVmVoDetails(vmId); + removeEncryptedPasswordFromUserVmVoDetails(userVm.getId()); _vmDao.loadDetails(userVm); return userVm; @@ -3667,7 +3673,7 @@ private boolean validPassword(String password) { @Override @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = "deploying Vm", create = true) public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List securityGroupIdList, - Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, + Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, List dataDiskOfferingsInfo, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParametes, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, @@ -3717,7 +3723,7 @@ public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOff } } - return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, + return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, null, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParametes, customId, dhcpOptionMap, dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId); @@ -3726,7 +3732,7 @@ public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOff @Override @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = "deploying Vm", create = true) public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, - List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, + List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, List dataDiskOfferingsInfo, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, String vmType) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { @@ -3830,7 +3836,7 @@ public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, Service } } - return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, + return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, null, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, vmType, overrideDiskOfferingId); } @@ -3838,7 +3844,7 @@ public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, Service @Override @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = "deploying Vm", create = true) public UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, Account owner, - String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, + String hostName, String displayName, Long diskOfferingId, Long diskSize, List dataDiskOfferingsInfo, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayvm, String keyboard, List affinityGroupIdList, Map customParametrs, String customId, Map> dhcpOptionsMap, Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String vmType, Long overrideDiskOfferingId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, @@ -3892,7 +3898,7 @@ public UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serv } } verifyExtraDhcpOptionsNetwork(dhcpOptionsMap, networkList); - return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, null, group, httpmethod, userData, + return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, dataDiskOfferingsInfo, networkList, null, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, hypervisor, caller, requestedIps, defaultIps, displayvm, keyboard, affinityGroupIdList, customParametrs, customId, dhcpOptionsMap, dataDiskTemplateToDiskOfferingMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, overrideDiskOfferingId); } @@ -4022,7 +4028,7 @@ public void checkNameForRFCCompliance(String name) { @DB private UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate tmplt, String hostName, String displayName, Account owner, - Long diskOfferingId, Long diskSize, List networkList, List securityGroupIdList, String group, HTTPMethod httpmethod, String userData, + Long diskOfferingId, Long diskSize, List dataDiskOfferingsInfo, List networkList, List securityGroupIdList, String group, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, HypervisorType hypervisor, Account caller, Map requestedIps, IpAddresses defaultIps, Boolean isDisplayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map datadiskTemplateToDiskOfferringMap, @@ -4113,12 +4119,7 @@ private UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffe throw new InvalidParameterValueException("Root volume encryption is not supported for hypervisor type " + hypervisorType); } - long additionalDiskSize = 0L; - if (!isIso && diskOfferingId != null) { - DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); - additionalDiskSize = verifyAndGetDiskSize(diskOffering, diskSize); - } - UserVm vm = getCheckedUserVmResource(zone, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, caller, requestedIps, defaultIps, isDisplayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, template, hypervisorType, accountId, offering, isIso, rootDiskOfferingId, volumesSize, additionalDiskSize); + UserVm vm = getCheckedUserVmResource(zone, hostName, displayName, owner, diskOfferingId, diskSize, dataDiskOfferingsInfo, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, caller, requestedIps, defaultIps, isDisplayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, template, hypervisorType, accountId, offering, isIso, rootDiskOfferingId, volumesSize); _securityGroupMgr.addInstanceToGroups(vm, securityGroupIdList); @@ -4131,21 +4132,21 @@ private UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffe } private UserVm getCheckedUserVmResource(DataCenter zone, String hostName, String displayName, Account owner, - Long diskOfferingId, Long diskSize, List networkList, List securityGroupIdList, String group, + Long diskOfferingId, Long diskSize, List dataDiskOfferingsInfo, List networkList, List securityGroupIdList, String group, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Account caller, Map requestedIps, IpAddresses defaultIps, Boolean isDisplayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map datadiskTemplateToDiskOfferringMap, Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String vmType, VMTemplateVO template, HypervisorType hypervisorType, long accountId, ServiceOfferingVO offering, boolean isIso, - Long rootDiskOfferingId, long volumesSize, long additionalDiskSize) throws ResourceAllocationException { + Long rootDiskOfferingId, long volumesSize) throws ResourceAllocationException { if (!VirtualMachineManager.ResourceCountRunningVMsonly.value()) { List resourceLimitHostTags = resourceLimitService.getResourceLimitHostTags(offering, template); try (CheckedReservation vmReservation = new CheckedReservation(owner, ResourceType.user_vm, resourceLimitHostTags, 1l, reservationDao, resourceLimitService); CheckedReservation cpuReservation = new CheckedReservation(owner, ResourceType.cpu, resourceLimitHostTags, Long.valueOf(offering.getCpu()), reservationDao, resourceLimitService); CheckedReservation memReservation = new CheckedReservation(owner, ResourceType.memory, resourceLimitHostTags, Long.valueOf(offering.getRamSize()), reservationDao, resourceLimitService); ) { - return getUncheckedUserVmResource(zone, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, caller, requestedIps, defaultIps, isDisplayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, template, hypervisorType, accountId, offering, isIso, rootDiskOfferingId, volumesSize, additionalDiskSize); + return getUncheckedUserVmResource(zone, hostName, displayName, owner, diskOfferingId, diskSize, dataDiskOfferingsInfo, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, caller, requestedIps, defaultIps, isDisplayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, template, hypervisorType, accountId, offering, isIso, rootDiskOfferingId, volumesSize); } catch (ResourceAllocationException | CloudRuntimeException e) { throw e; } catch (Exception e) { @@ -4154,7 +4155,7 @@ private UserVm getCheckedUserVmResource(DataCenter zone, String hostName, String } } else { - return getUncheckedUserVmResource(zone, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, caller, requestedIps, defaultIps, isDisplayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, template, hypervisorType, accountId, offering, isIso, rootDiskOfferingId, volumesSize, additionalDiskSize); + return getUncheckedUserVmResource(zone, hostName, displayName, owner, diskOfferingId, diskSize, dataDiskOfferingsInfo, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, caller, requestedIps, defaultIps, isDisplayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, template, hypervisorType, accountId, offering, isIso, rootDiskOfferingId, volumesSize); } } @@ -4163,24 +4164,51 @@ protected List getResourceLimitStorageTags(long diskOfferingId) { return resourceLimitService.getResourceLimitStorageTags(diskOfferingVO); } + private List reserveStorageResourcesForVm(Account owner, Long diskOfferingId, Long diskSize, List dataDiskOfferingsInfo, Long rootDiskOfferingId, ServiceOfferingVO offering, Long rootDiskSize) throws ResourceAllocationException { + List checkedReservations = new ArrayList<>(); + + List rootResourceLimitStorageTags = getResourceLimitStorageTags(rootDiskOfferingId != null ? rootDiskOfferingId : offering.getDiskOfferingId()); + CheckedReservation rootVolumeReservation = new CheckedReservation(owner, ResourceType.volume, rootResourceLimitStorageTags, 1L, reservationDao, resourceLimitService); + checkedReservations.add(rootVolumeReservation); + CheckedReservation rootPrimaryStorageReservation = new CheckedReservation(owner, ResourceType.primary_storage, rootResourceLimitStorageTags, rootDiskSize, reservationDao, resourceLimitService); + checkedReservations.add(rootPrimaryStorageReservation); + + if (diskOfferingId != null) { + List additionalResourceLimitStorageTags = diskOfferingId != null ? getResourceLimitStorageTags(diskOfferingId) : null; + DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); + Long size = verifyAndGetDiskSize(diskOffering, diskSize); + CheckedReservation additionalVolumeReservation = diskOfferingId != null ? new CheckedReservation(owner, ResourceType.volume, additionalResourceLimitStorageTags, 1L, reservationDao, resourceLimitService) : null; + checkedReservations.add(additionalVolumeReservation); + CheckedReservation additionalPrimaryStorageReservation = diskOfferingId != null ? new CheckedReservation(owner, ResourceType.primary_storage, additionalResourceLimitStorageTags, size, reservationDao, resourceLimitService) : null; + checkedReservations.add(additionalPrimaryStorageReservation); + + } else if (dataDiskOfferingsInfo != null) { + for (DiskOfferingInfo diskOfferingInfo : dataDiskOfferingsInfo) { + DiskOffering diskOffering = diskOfferingInfo.getDiskOffering(); + List additionalResourceLimitStorageTagsForDataDisk = getResourceLimitStorageTags(diskOfferingInfo.getDiskOffering().getId()); + Long size = verifyAndGetDiskSize(diskOffering, diskOfferingInfo.getSize()); + CheckedReservation additionalVolumeReservation = new CheckedReservation(owner, ResourceType.volume, additionalResourceLimitStorageTagsForDataDisk, 1L, reservationDao, resourceLimitService); + checkedReservations.add(additionalVolumeReservation); + CheckedReservation additionalPrimaryStorageReservation = new CheckedReservation(owner, ResourceType.primary_storage, additionalResourceLimitStorageTagsForDataDisk, size, reservationDao, resourceLimitService); + checkedReservations.add(additionalPrimaryStorageReservation); + } + } + return checkedReservations; + } + private UserVm getUncheckedUserVmResource(DataCenter zone, String hostName, String displayName, Account owner, - Long diskOfferingId, Long diskSize, List networkList, List securityGroupIdList, String group, + Long diskOfferingId, Long diskSize, List dataDiskOfferingsInfo, List networkList, List securityGroupIdList, String group, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Account caller, Map requestedIps, IpAddresses defaultIps, Boolean isDisplayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map datadiskTemplateToDiskOfferringMap, Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String vmType, VMTemplateVO template, HypervisorType hypervisorType, long accountId, ServiceOfferingVO offering, boolean isIso, - Long rootDiskOfferingId, long volumesSize, long additionalDiskSize) throws ResourceAllocationException - { - List rootResourceLimitStorageTags = getResourceLimitStorageTags(rootDiskOfferingId != null ? rootDiskOfferingId : offering.getDiskOfferingId()); - List additionalResourceLimitStorageTags = diskOfferingId != null ? getResourceLimitStorageTags(diskOfferingId) : null; + Long rootDiskOfferingId, long volumesSize) throws ResourceAllocationException { + List checkedReservations = new ArrayList<>(); - try (CheckedReservation rootVolumeReservation = new CheckedReservation(owner, ResourceType.volume, rootResourceLimitStorageTags, 1L, reservationDao, resourceLimitService); - CheckedReservation additionalVolumeReservation = diskOfferingId != null ? new CheckedReservation(owner, ResourceType.volume, additionalResourceLimitStorageTags, 1L, reservationDao, resourceLimitService) : null; - CheckedReservation rootPrimaryStorageReservation = new CheckedReservation(owner, ResourceType.primary_storage, rootResourceLimitStorageTags, volumesSize, reservationDao, resourceLimitService); - CheckedReservation additionalPrimaryStorageReservation = diskOfferingId != null ? new CheckedReservation(owner, ResourceType.primary_storage, additionalResourceLimitStorageTags, additionalDiskSize, reservationDao, resourceLimitService) : null; - ) { + try { + checkedReservations = reserveStorageResourcesForVm(owner, diskOfferingId, diskSize, dataDiskOfferingsInfo, rootDiskOfferingId, offering, volumesSize); // verify security group ids if (securityGroupIdList != null) { @@ -4450,15 +4478,24 @@ private UserVm getUncheckedUserVmResource(DataCenter zone, String hostName, Stri UserVmVO vm = commitUserVm(zone, template, hostName, displayName, owner, diskOfferingId, diskSize, userData, userDataId, userDataDetails, caller, isDisplayVm, keyboard, accountId, userId, offering, isIso, sshPublicKeys, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, dhcpOptionMap, - datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, rootDiskOfferingId, keypairnames); + datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, rootDiskOfferingId, keypairnames, dataDiskOfferingsInfo); assignInstanceToGroup(group, id); return vm; - } catch (ResourceAllocationException | CloudRuntimeException e) { + } catch (ResourceAllocationException | CloudRuntimeException e) { throw e; } catch (Exception e) { logger.error("error during resource reservation and allocation", e); throw new CloudRuntimeException(e); + } finally { + for (CheckedReservation checkedReservation : checkedReservations) { + try { + checkedReservation.close(); + } catch (Exception e) { + logger.error("error during resource reservation and allocation", e); + throw new CloudRuntimeException(e); + } + } } } @@ -4476,7 +4513,7 @@ private void assignInstanceToGroup(String group, long id) { } } - private long verifyAndGetDiskSize(DiskOfferingVO diskOffering, Long diskSize) { + private long verifyAndGetDiskSize(DiskOffering diskOffering, Long diskSize) { long size = 0l; if (diskOffering == null) { throw new InvalidParameterValueException("Specified disk offering cannot be found"); @@ -4587,7 +4624,7 @@ private UserVmVO commitUserVm(final boolean isImport, final DataCenter zone, fin final long accountId, final long userId, final ServiceOffering offering, final boolean isIso, final String sshPublicKeys, final LinkedHashMap> networkNicMap, final long id, final String instanceName, final String uuidName, final HypervisorType hypervisorType, final Map customParameters, final Map> extraDhcpOptionMap, final Map dataDiskTemplateToDiskOfferingMap, - final Map userVmOVFPropertiesMap, final VirtualMachine.PowerState powerState, final boolean dynamicScalingEnabled, String vmType, final Long rootDiskOfferingId, String sshkeypairs) throws InsufficientCapacityException { + final Map userVmOVFPropertiesMap, final VirtualMachine.PowerState powerState, final boolean dynamicScalingEnabled, String vmType, final Long rootDiskOfferingId, String sshkeypairs, List dataDiskOfferingsInfo) throws InsufficientCapacityException { UserVmVO vm = new UserVmVO(id, instanceName, displayName, template.getId(), hypervisorType, template.getGuestOSId(), offering.isOfferHA(), offering.getLimitCpuUse(), owner.getDomainId(), owner.getId(), userId, offering.getId(), userData, userDataId, userDataDetails, hostName); vm.setUuid(uuidName); @@ -4705,7 +4742,7 @@ private UserVmVO commitUserVm(final boolean isImport, final DataCenter zone, fin orchestrateVirtualMachineCreate(vm, guestOSCategory, computeTags, rootDiskTags, plan, rootDiskSize, template, hostName, displayName, owner, diskOfferingId, diskSize, offering, isIso,networkNicMap, hypervisorType, extraDhcpOptionMap, dataDiskTemplateToDiskOfferingMap, - rootDiskOfferingId); + rootDiskOfferingId, dataDiskOfferingsInfo); } CallContext.current().setEventDetails("Vm Id: " + vm.getUuid()); @@ -4738,16 +4775,16 @@ private void orchestrateVirtualMachineCreate(UserVmVO vm, GuestOSCategoryVO gues ServiceOffering offering, boolean isIso, LinkedHashMap> networkNicMap, HypervisorType hypervisorType, Map> extraDhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, - Long rootDiskOfferingId) throws InsufficientCapacityException{ + Long rootDiskOfferingId, List dataDiskOfferingsInfo) throws InsufficientCapacityException{ try { if (isIso) { _orchSrvc.createVirtualMachineFromScratch(vm.getUuid(), Long.toString(owner.getAccountId()), vm.getIsoId().toString(), hostName, displayName, hypervisorType.name(), guestOSCategory.getName(), offering.getCpu(), offering.getSpeed(), offering.getRamSize(), diskSize, computeTags, rootDiskTags, - networkNicMap, plan, extraDhcpOptionMap, rootDiskOfferingId); + networkNicMap, plan, extraDhcpOptionMap, rootDiskOfferingId, dataDiskOfferingsInfo); } else { _orchSrvc.createVirtualMachine(vm.getUuid(), Long.toString(owner.getAccountId()), Long.toString(template.getId()), hostName, displayName, hypervisorType.name(), offering.getCpu(), offering.getSpeed(), offering.getRamSize(), diskSize, computeTags, rootDiskTags, networkNicMap, plan, rootDiskSize, extraDhcpOptionMap, - dataDiskTemplateToDiskOfferingMap, diskOfferingId, rootDiskOfferingId); + dataDiskTemplateToDiskOfferingMap, diskOfferingId, rootDiskOfferingId, dataDiskOfferingsInfo); } if (logger.isDebugEnabled()) { @@ -4869,13 +4906,13 @@ private UserVmVO commitUserVm(final DataCenter zone, final VirtualMachineTemplat final long accountId, final long userId, final ServiceOfferingVO offering, final boolean isIso, final String sshPublicKeys, final LinkedHashMap> networkNicMap, final long id, final String instanceName, final String uuidName, final HypervisorType hypervisorType, final Map customParameters, final Map> extraDhcpOptionMap, final Map dataDiskTemplateToDiskOfferingMap, - Map userVmOVFPropertiesMap, final boolean dynamicScalingEnabled, String vmType, final Long rootDiskOfferingId, String sshkeypairs) throws InsufficientCapacityException { + Map userVmOVFPropertiesMap, final boolean dynamicScalingEnabled, String vmType, final Long rootDiskOfferingId, String sshkeypairs, List dataDiskOfferingsInfo) throws InsufficientCapacityException { return commitUserVm(false, zone, null, null, template, hostName, displayName, owner, diskOfferingId, diskSize, userData, userDataId, userDataDetails, isDisplayVm, keyboard, accountId, userId, offering, isIso, sshPublicKeys, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, extraDhcpOptionMap, dataDiskTemplateToDiskOfferingMap, - userVmOVFPropertiesMap, null, dynamicScalingEnabled, vmType, rootDiskOfferingId, sshkeypairs); + userVmOVFPropertiesMap, null, dynamicScalingEnabled, vmType, rootDiskOfferingId, sshkeypairs, dataDiskOfferingsInfo); } public void validateRootDiskResize(final HypervisorType hypervisorType, Long rootDiskSize, VMTemplateVO templateVO, UserVmVO vm, final Map customParameters) throws InvalidParameterValueException @@ -6072,6 +6109,9 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE } Long serviceOfferingId = cmd.getServiceOfferingId(); + if (serviceOfferingId == null) { + throw new InvalidParameterValueException("Unable to execute API command deployvirtualmachine due to missing parameter serviceofferingid"); + } Long overrideDiskOfferingId = cmd.getOverrideDiskOfferingId(); ServiceOffering serviceOffering = _entityMgr.findById(ServiceOffering.class, serviceOfferingId); @@ -6096,6 +6136,9 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE } Long templateId = cmd.getTemplateId(); + if (templateId == null) { + throw new InvalidParameterValueException("Unable to execute API command deployvirtualmachine due to missing parameter templateid"); + } boolean dynamicScalingEnabled = cmd.isDynamicScalingEnabled(); @@ -6139,6 +6182,11 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE } } + List dataDiskOfferingsInfo = cmd.getDataDiskOfferingsInfo(); + if (dataDiskOfferingsInfo != null && diskOfferingId != null) { + new InvalidParameterValueException("Cannot specify both disk offering id and data disk offering details"); + } + if (!zone.isLocalStorageEnabled()) { DiskOffering diskOfferingMappedInServiceOffering = _entityMgr.findById(DiskOffering.class, serviceOffering.getDiskOfferingId()); if (diskOfferingMappedInServiceOffering.isUseLocalStorage()) { @@ -6191,7 +6239,7 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE throw new InvalidParameterValueException("Can't specify network Ids in Basic zone"); } else { vm = createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, getSecurityGroupIdList(cmd, zone, template, owner), owner, name, displayName, diskOfferingId, - size , group , cmd.getHypervisor(), cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm , keyboard , cmd.getAffinityGroupIdList(), + size , dataDiskOfferingsInfo, group , cmd.getHypervisor(), cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm , keyboard , cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId); } @@ -6199,7 +6247,7 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE if (_networkModel.checkSecurityGroupSupportForNetwork(owner, zone, networkIds, cmd.getSecurityGroupIdList())) { vm = createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, networkIds, getSecurityGroupIdList(cmd, zone, template, owner), owner, name, - displayName, diskOfferingId, size, group, cmd.getHypervisor(), cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, + displayName, diskOfferingId, size, dataDiskOfferingsInfo, group, cmd.getHypervisor(), cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId, null); @@ -6207,7 +6255,7 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE if (cmd.getSecurityGroupIdList() != null && !cmd.getSecurityGroupIdList().isEmpty()) { throw new InvalidParameterValueException("Can't create vm with security groups; security group feature is not enabled per zone"); } - vm = createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, name, displayName, diskOfferingId, size, group, + vm = createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, name, displayName, diskOfferingId, size, dataDiskOfferingsInfo, group, cmd.getHypervisor(), cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId); if (cmd instanceof DeployVnfApplianceCmd) { @@ -8938,7 +8986,7 @@ public UserVm importVM(final DataCenter zone, final Host host, final VirtualMach null, null, userData, null, null, isDisplayVm, keyboard, accountId, userId, serviceOffering, template.getFormat().equals(ImageFormat.ISO), sshPublicKeys, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, - null, null, null, powerState, dynamicScalingEnabled, null, serviceOffering.getDiskOfferingId(), null); + null, null, null, powerState, dynamicScalingEnabled, null, serviceOffering.getDiskOfferingId(), null, null); } @Override @@ -8983,6 +9031,215 @@ public boolean unmanageUserVM(Long vmId) { return true; } + @Override + public UserVm allocateVMFromBackup(CreateVMFromBackupCmd cmd) throws InsufficientCapacityException, ResourceAllocationException, ResourceUnavailableException { + //Verify that all objects exist before passing them to the service + Account owner = _accountService.getActiveAccountById(cmd.getEntityOwnerId()); + Long zoneId = cmd.getZoneId(); + DataCenter zone = _dcDao.findById(zoneId); + if (zone == null) { + throw new InvalidParameterValueException("Unable to find zone by id=" + zoneId); + } + + 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()); + + Long serviceOfferingId = cmd.getServiceOfferingId(); + ServiceOffering serviceOffering; + if (serviceOfferingId != null) { + serviceOffering = serviceOfferingDao.findById(serviceOfferingId); + if (serviceOffering == null) { + throw new InvalidParameterValueException("Unable to find service offering: " + serviceOfferingId); + } + } 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"); + } + } + + Long overrideDiskOfferingId = cmd.getOverrideDiskOfferingId(); + if (overrideDiskOfferingId == null) { + DiskOfferingInfo rootDiskOfferingInfo = backupManager.getRootDiskOfferingInfoFromBackup(backup); + if (rootDiskOfferingInfo != null && + serviceOffering.getDiskOfferingId() != rootDiskOfferingInfo.getDiskOffering().getId()) { + overrideDiskOfferingId = rootDiskOfferingInfo.getDiskOffering().getId(); + Map details = cmd.getDetails(); + details.put(VmDetailConstants.ROOT_DISK_SIZE, rootDiskOfferingInfo.getSize().toString()); + details.put(MIN_IOPS, rootDiskOfferingInfo.getSize().toString()); + details.put(MAX_IOPS, rootDiskOfferingInfo.getSize().toString()); + } + } + + if (ServiceOffering.State.Inactive.equals(serviceOffering.getState())) { + throw new InvalidParameterValueException(String.format("Service offering is inactive: [%s].", serviceOffering.getUuid())); + } + + if (serviceOffering.getDiskOfferingStrictness() && overrideDiskOfferingId != null) { + throw new InvalidParameterValueException(String.format("Cannot override disk offering id %d since provided service offering is strictly mapped to its disk offering", overrideDiskOfferingId)); + } + + if (!serviceOffering.isDynamic()) { + for(String detail: cmd.getDetails().keySet()) { + if(detail.equalsIgnoreCase(VmDetailConstants.CPU_NUMBER) || detail.equalsIgnoreCase(VmDetailConstants.CPU_SPEED) || detail.equalsIgnoreCase(VmDetailConstants.MEMORY)) { + throw new InvalidParameterValueException("cpuNumber or cpuSpeed or memory should not be specified for static service offering"); + } + } + } + + Long templateId = cmd.getTemplateId(); + VirtualMachineTemplate template; + if (templateId != null) { + 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 id while creating instance"); + } + template = _templateDao.findByUuid(templateUuid); + if (template == null) { + throw new CloudRuntimeException("Unable to find template with the uuid stored in backup. Please specify a valid template id while creating the instance"); + } + } + + if (template.isDeployAsIs()) { + throw new InvalidParameterValueException("Deploy as is template not supported"); + } + + if (cmd.getDiskOfferingId() != null) { + throw new InvalidParameterValueException(ApiConstants.DISK_OFFERING_ID + " parameter is not supported for creating instance from backup. Please use the parameter " + ApiConstants.DATADISKS_DETAILS); + } + + List dataDiskOfferingsInfo = cmd.getDataDiskOfferingsInfo(); + if (dataDiskOfferingsInfo != null) { + backupManager.updateDiskOfferingSizeFromBackup(dataDiskOfferingsInfo, backup); + } else { + dataDiskOfferingsInfo = backupManager.getDataDiskOfferingListFromBackup(backup); + } + + DiskOffering diskOfferingMappedInServiceOffering = _diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); + if (diskOfferingMappedInServiceOffering.isUseLocalStorage()) { + throw new InvalidParameterValueException("Local storage disk offering not supported for instance created from backup"); + } + + List networkIds = cmd.getNetworkIds(); + LinkedHashMap userVmNetworkMap = getVmOvfNetworkMapping(zone, owner, template, cmd.getVmNetworkMap()); + if (MapUtils.isNotEmpty(userVmNetworkMap)) { + networkIds = new ArrayList<>(userVmNetworkMap.values()); + } + + Map ipToNetworkMap = cmd.getIpToNetworkMap(); + if (networkIds == null && ipToNetworkMap == null) { + String networkIdsString = backup.getDetail(ApiConstants.NETWORK_IDS); + if (networkIdsString == null) { + throw new CloudRuntimeException("Backup doesn't contain network information. Please specify atleast one valid network while creating instance"); + } + List networkUuids = List.of(networkIdsString.split(",")); + ipToNetworkMap = new LinkedHashMap(); + networkIds = new ArrayList(); + for (String networkUuid: networkUuids) { + 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(); + ipToNetworkMap.put(networkId, null); + networkIds.add(networkId); + } + } + + if (cmd.getUserData() != null) { + throw new InvalidParameterValueException("User data not supported for instance created from backup"); + } + + String name = cmd.getName(); + String displayName = cmd.getDisplayName(); + Long size = cmd.getSize(); + Map dataDiskTemplateToDiskOfferingMap = cmd.getDataDiskTemplateToDiskOfferingMap(); + List sshKeyPairs = new ArrayList(); + String ipAddress = cmd.getIpAddress(); + String ip6Address = cmd.getIp6Address(); + String macAddress = cmd.getMacAddress(); + IpAddresses addrs = new IpAddresses(ipAddress, ip6Address, macAddress); + Map userVmOVFProperties = new HashMap<>(); + + UserVm vm = null; + if (zone.getNetworkType() == NetworkType.Basic) { + if (networkIds != null) { + throw new InvalidParameterValueException("Can't specify network Ids in Basic zone"); + } else { + vm = createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, getSecurityGroupIdList(cmd, zone, template, owner), owner, name, displayName, null, + size , dataDiskOfferingsInfo, null , cmd.getHypervisor(), cmd.getHttpMethod(), null, null, null, sshKeyPairs, ipToNetworkMap, addrs, null , null , cmd.getAffinityGroupIdList(), + cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), + dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, false, overrideDiskOfferingId); + } + } else { + if (_networkModel.checkSecurityGroupSupportForNetwork(owner, zone, networkIds, + cmd.getSecurityGroupIdList())) { + vm = createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, networkIds, getSecurityGroupIdList(cmd, zone, template, owner), owner, name, + displayName, null, size, dataDiskOfferingsInfo, null, cmd.getHypervisor(), cmd.getHttpMethod(), null, null, null, sshKeyPairs, ipToNetworkMap, addrs, null, null, + cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), + dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, false, overrideDiskOfferingId, null); + + } else { + if (cmd.getSecurityGroupIdList() != null && !cmd.getSecurityGroupIdList().isEmpty()) { + throw new InvalidParameterValueException("Can't create vm with security groups; security group feature is not enabled per zone"); + } + vm = createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, name, displayName, null, size, dataDiskOfferingsInfo, null, + cmd.getHypervisor(), cmd.getHttpMethod(), null, null, null, sshKeyPairs, ipToNetworkMap, addrs, null, null, cmd.getAffinityGroupIdList(), cmd.getDetails(), + cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, false, null, overrideDiskOfferingId); + } + } + + return vm; + } + + @Override + public UserVm restoreVMFromBackup(CreateVMFromBackupCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + long vmId = cmd.getEntityId(); + Map diskOfferingMap = cmd.getDataDiskTemplateToDiskOfferingMap(); + Map additonalParams = new HashMap<>(); + UserVm vm = startVirtualMachine(vmId, null, null, null, diskOfferingMap, additonalParams, null); + + boolean status = false; + status = stopVirtualMachine(CallContext.current().getCallingUserId(), vm.getId()) ; + if (!status) { + expungeVm(vm.getId()); + throw new CloudRuntimeException("Unable to stop the instance before restore "); + } + + backupManager.restoreBackupToVM(cmd.getBackupId(), vmId); + + Account owner = _accountService.getActiveAccountById(cmd.getEntityOwnerId()); + UserVmVO userVm = _vmDao.findById(vmId); + + List sshKeyPairNames = cmd.getSSHKeyPairNames(); + if (sshKeyPairNames != null && !sshKeyPairNames.isEmpty()) { + vm = resetVMSSHKeyInternal(userVm, owner, sshKeyPairNames); + } + + if (cmd.getStartVm()) { + vm = startVirtualMachine(vmId, null, null, null, diskOfferingMap, additonalParams, null); + } + return vm; + } + /* Generate usage events related to unmanaging a VM */ diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index 2e52d1ccc446..f8f6ec7a80a5 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -21,16 +21,29 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TimeZone; import java.util.Timer; import java.util.TimerTask; import java.util.stream.Collectors; import com.amazonaws.util.CollectionUtils; +import com.cloud.api.query.dao.UserVmJoinDao; +import com.cloud.api.query.vo.UserVmJoinVO; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.offering.DiskOffering; +import com.cloud.offering.DiskOfferingInfo; +import com.cloud.offering.ServiceOffering; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.VolumeApiService; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.template.VirtualMachineTemplate; import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.UserVmService; import com.cloud.vm.VirtualMachineManager; import javax.inject.Inject; import javax.naming.ConfigurationException; @@ -57,6 +70,7 @@ import org.apache.cloudstack.api.command.user.backup.repository.AddBackupRepositoryCmd; import org.apache.cloudstack.api.command.user.backup.repository.DeleteBackupRepositoryCmd; import org.apache.cloudstack.api.command.user.backup.repository.ListBackupRepositoriesCmd; +import org.apache.cloudstack.api.command.user.vm.CreateVMFromBackupCmd; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao; @@ -155,6 +169,12 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { @Inject private UserVmDao userVmDao; @Inject + private ServiceOfferingDao serviceOfferingDao; + @Inject + private VMTemplateDao vmTemplateDao; + @Inject + private UserVmJoinDao userVmJoinDao; + @Inject private ApiDispatcher apiDispatcher; @Inject private AsyncJobManager asyncJobManager; @@ -164,6 +184,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { private VolumeApiService volumeApiService; @Inject private VolumeOrchestrationService volumeOrchestrationService; + @Inject + public UserVmService userVmService; private AsyncJobDispatcher asyncJobDispatcher; private Timer backupTimer; @@ -185,7 +207,7 @@ public List listBackupProviderOfferings(final Long zoneId) { if (zoneId == null || zoneId < 1) { throw new CloudRuntimeException("Invalid zone ID passed"); } - validateForZone(zoneId); + validateBackupForZone(zoneId); final Account account = CallContext.current().getCallingAccount(); if (!accountService.isRootAdmin(account.getId())) { throw new PermissionDeniedException("Parameter external can only be specified by a Root Admin, permission denied"); @@ -198,7 +220,7 @@ public List listBackupProviderOfferings(final Long zoneId) { @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_IMPORT_OFFERING, eventDescription = "importing backup offering", async = true) public BackupOffering importBackupOffering(final ImportBackupOfferingCmd cmd) { - validateForZone(cmd.getZoneId()); + validateBackupForZone(cmd.getZoneId()); final BackupOffering existingOffering = backupOfferingDao.findByExternalId(cmd.getExternalId(), cmd.getZoneId()); if (existingOffering != null) { throw new CloudRuntimeException("A backup offering with external ID " + cmd.getExternalId() + " already exists"); @@ -273,10 +295,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 getVmDetailsForBackup(VirtualMachine vm) { + HashMap details = new HashMap<>(); + details.put(ApiConstants.HYPERVISOR, vm.getHypervisorType().toString()); + 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 userVmJoinVOs = userVmJoinDao.searchByIds(vm.getId()); + if (userVmJoinVOs != null && !userVmJoinVOs.isEmpty()) { + Set networkIds = new HashSet<>(); + for (UserVmJoinVO userVmJoinVO : userVmJoinVOs) { + networkIds.add(userVmJoinVO.getNetworkUuid()); + } + if (!networkIds.isEmpty()) { + details.put(ApiConstants.NETWORK_IDS, String.join(",", networkIds)); + } + } + return details; + } + + @Override + public Map getDiskOfferingDetailsForBackup(Long vmId) { + List volumes = volumeDao.findByInstance(vmId); + List diskOfferingIds = new ArrayList<>(); + List diskSizes = new ArrayList<>(); + List minIops = new ArrayList<>(); + List maxIops = new ArrayList<>(); + List deviceIds = new ArrayList<>(); + Map details = new HashMap<>(); + + for (Volume vol : volumes) { + if (vol.getVolumeType() != Volume.Type.ROOT && vol.getVolumeType() != Volume.Type.DATADISK) { + continue; + } + DiskOffering diskOffering = diskOfferingDao.findById(vol.getDiskOfferingId()); + diskOfferingIds.add(diskOffering.getUuid()); + diskSizes.add(vol.getSize()); + minIops.add(vol.getMinIops()); + maxIops.add(vol.getMaxIops()); + deviceIds.add(vol.getDeviceId()); + } + if (!diskOfferingIds.isEmpty()) { + details.put(ApiConstants.DISK_OFFERING_IDS, String.join(",", diskOfferingIds)); + details.put(ApiConstants.DISK_SIZES, String.join(",", diskSizes.stream().map(String::valueOf).collect(Collectors.toList()))); + details.put(ApiConstants.MIN_IOPS, String.join(",", minIops.stream().map(String::valueOf).collect(Collectors.toList()))); + details.put(ApiConstants.MAX_IOPS, String.join(",", maxIops.stream().map(String::valueOf).collect(Collectors.toList()))); + details.put(ApiConstants.DEVICE_IDS, String.join(",", deviceIds.stream().map(String::valueOf).collect(Collectors.toList()))); + } + return details; + } + public static String createVolumeInfoFromVolumes(List vmVolumes) { List list = new ArrayList<>(); for (VolumeVO vol : vmVolumes) { @@ -294,7 +368,7 @@ public boolean assignVMToBackupOffering(Long vmId, Long offeringId) { throw new CloudRuntimeException("VM is not in running or stopped state"); } - validateForZone(vm.getDataCenterId()); + validateBackupForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); @@ -360,7 +434,7 @@ public boolean removeVMFromBackupOffering(final Long vmId, final boolean forced) throw new CloudRuntimeException(String.format("Can't find any VM with ID: [%s].", vmId)); } - validateForZone(vm.getDataCenterId()); + validateBackupForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); final BackupOfferingVO offering = backupOfferingDao.findById(vm.getBackupOfferingId()); @@ -421,7 +495,7 @@ public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) { } final VMInstanceVO vm = findVmById(vmId); - validateForZone(vm.getDataCenterId()); + validateBackupForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); if (vm.getBackupOfferingId() == null) { @@ -461,7 +535,7 @@ public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) { @Override public List listBackupSchedule(final Long vmId) { final VMInstanceVO vm = findVmById(vmId); - validateForZone(vm.getDataCenterId()); + validateBackupForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); return backupScheduleDao.listByVM(vmId).stream().map(BackupSchedule.class::cast).collect(Collectors.toList()); @@ -471,7 +545,7 @@ public List listBackupSchedule(final Long vmId) { @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE, eventDescription = "deleting VM backup schedule") public boolean deleteBackupSchedule(final Long vmId) { final VMInstanceVO vm = findVmById(vmId); - validateForZone(vm.getDataCenterId()); + validateBackupForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); final BackupSchedule schedule = backupScheduleDao.findByVM(vmId); @@ -485,7 +559,7 @@ public boolean deleteBackupSchedule(final Long vmId) { @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_CREATE, eventDescription = "creating VM backup", async = true) public boolean createBackup(final Long vmId) { final VMInstanceVO vm = findVmById(vmId); - validateForZone(vm.getDataCenterId()); + validateBackupForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); if (vm.getBackupOfferingId() == null) { @@ -610,7 +684,7 @@ public boolean restoreBackup(final Long backupId) { if (backup == null) { throw new CloudRuntimeException("Backup " + backupId + " does not exist"); } - validateForZone(backup.getZoneId()); + validateBackupForZone(backup.getZoneId()); final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); if (vm == null) { @@ -748,6 +822,152 @@ private Backup.VolumeInfo getVolumeInfo(List backedUpVolumes, return null; } + @Override + public void updateDiskOfferingSizeFromBackup(List dataDiskOfferingsInfo, Backup backup) { + List 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 ("0".equals(deviceIds[i])) { + 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 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 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 || diskOffering.getState().equals(DiskOffering.State.Inactive)) { + 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 = (Boolean.TRUE.equals(diskOffering.isCustomizedIops()) && !minIopsList[i].equals("null")) ? + Long.parseLong(minIopsList[i]) : null; + Long maxIops = (Boolean.TRUE.equals(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("The VM could not be found"); + } + if (!vm.getState().equals(VirtualMachine.State.Stopped)) { + throw new CloudRuntimeException("The VM should be in stopped state"); + } + + List backupVolumes = backup.getBackedUpVolumes(); + if (backupVolumes == null) { + throw new CloudRuntimeException("Backed up volumes info not found in the backup"); + } + + List 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) { + throw new CloudRuntimeException("Failed to find backup offering of the VM backup."); + } + final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); + if (!backupProvider.supportsInstanceFromBackup()) { + throw new CloudRuntimeException("Create instance from VM is not supported by the " + offering.getProvider() + " plugin."); + } + + 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_BACKUP_RESTORE_TO_VM, + String.format("Restoring backup %s to VM %s", backup.getUuid(), vm.getInstanceName()), + vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), + true, 0); + + String host = null; + String dataStore = null; + if (!"nas".equals(offering.getProvider())) { + Pair restoreInfo = getRestoreVolumeHostAndDatastore(vm); + host = restoreInfo.first().getPrivateIpAddress(); + dataStore = restoreInfo.second().getUuid(); + } + if (!backupProvider.restoreBackupToVM(vm, backup, host, dataStore)) { + ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE_TO_VM, + String.format("Failed to restore backup %s to VM %s", backup.getUuid(), vm.getInstanceName()), + vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), eventId); + throw new CloudRuntimeException(String.format("Error restoring backup [%s] to VM %s.", backupDetailsInMessage)); + } + } catch (CloudRuntimeException e) { + updateVolumeState(vm, Volume.Event.RestoreFailed, Volume.State.Ready); + updateVmState(vm, VirtualMachine.Event.RestoringFailed, VirtualMachine.State.Stopped); + logger.error(String.format("Failed to restore backup [%s] to VM %s due to: [%s].", backupDetailsInMessage, vm.getInstanceName(), e.getMessage()), e); + ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE_TO_VM, + String.format("Failed to restore backup %s to VM %s", backup.getUuid(), vm.getInstanceName()), + vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), eventId); + throw new CloudRuntimeException(String.format("Error restoring backup [%s] to VM %s.", backupDetailsInMessage)); + } + updateVolumeState(vm, Volume.Event.RestoreSucceeded, Volume.State.Ready); + updateVmState(vm, VirtualMachine.Event.RestoringSuccess, VirtualMachine.State.Stopped); + ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_INFO, EventTypes.EVENT_VM_BACKUP_RESTORE_TO_VM, + String.format("Successfully completed restoring backup %s to VM %s", backup.getUuid(), vm.getInstanceName()), + vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),eventId); + return true; + } + @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true) public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long backupId, final Long vmId) throws Exception { @@ -758,7 +978,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, if (backup == null) { throw new CloudRuntimeException("Provided backup not found"); } - validateForZone(backup.getZoneId()); + validateBackupForZone(backup.getZoneId()); final VMInstanceVO vm = findVmById(vmId); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); @@ -849,7 +1069,7 @@ public boolean deleteBackup(final Long backupId, final Boolean forced) { if (vm == null) { throw new CloudRuntimeException("VM " + vmId + " does not exist"); } - validateForZone(vm.getDataCenterId()); + validateBackupForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(backup.getBackupOfferingId()); if (offering == null) { @@ -932,7 +1152,8 @@ public boolean isDisabled(final Long zoneId) { return !(BackupFrameworkEnabled.value() && BackupFrameworkEnabled.valueIn(zoneId)); } - private void validateForZone(final Long zoneId) { + @Override + public void validateBackupForZone(final Long zoneId) { if (zoneId == null || isDisabled(zoneId)) { throw new CloudRuntimeException("Backup and Recovery feature is disabled for the zone"); } @@ -990,6 +1211,7 @@ public List> getCommands() { cmdList.add(AddBackupRepositoryCmd.class); cmdList.add(DeleteBackupRepositoryCmd.class); cmdList.add(ListBackupRepositoriesCmd.class); + cmdList.add(CreateVMFromBackupCmd.class); return cmdList; } diff --git a/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java b/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java index 7d2b35361bca..3bcd563f7cd7 100644 --- a/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java +++ b/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java @@ -1272,7 +1272,7 @@ public void testCreateNewVM1() throws ResourceUnavailableException, Insufficient when(zoneMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic); when(userVmService.createBasicSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), any(), - any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true), any(), any(), any(), + any(), any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true), any(), any(), any(), any(), any(), any(), any(), eq(true), any())).thenReturn(userVmMock); UserVm result = autoScaleManagerImplSpy.createNewVM(asVmGroupMock); @@ -1283,7 +1283,7 @@ public void testCreateNewVM1() throws ResourceUnavailableException, Insufficient "-" + asVmGroupMock.getNextVmSeq() + "-[a-z]{6}"; Mockito.verify(userVmService).createBasicSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), matches(vmHostNamePattern), matches(vmHostNamePattern), - any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true), any(), any(), any(), + any(), any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true), any(), any(), any(), any(), any(), any(), any(), eq(true), any()); Mockito.verify(asVmGroupMock).setNextVmSeq(nextVmSeq + 1); } @@ -1319,7 +1319,7 @@ public void testCreateNewVM2() throws ResourceUnavailableException, Insufficient when(zoneMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced); when(userVmService.createAdvancedSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), any(), - any(), any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), eq(true), any(), any())).thenReturn(userVmMock); when(networkModel.checkSecurityGroupSupportForNetwork(account, zoneMock, List.of(networkId), Collections.emptyList())).thenReturn(true); @@ -1332,7 +1332,7 @@ public void testCreateNewVM2() throws ResourceUnavailableException, Insufficient "-" + asVmGroupMock.getNextVmSeq() + "-[a-z]{6}"; Mockito.verify(userVmService).createAdvancedSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), matches(vmHostNamePattern), matches(vmHostNamePattern), - any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), eq(true), any(), any()); Mockito.verify(asVmGroupMock).setNextVmSeq(nextVmSeq + 2); } @@ -1368,7 +1368,7 @@ public void testCreateNewVM3() throws ResourceUnavailableException, Insufficient when(zoneMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced); when(userVmService.createAdvancedVirtualMachine(any(), any(), any(), any(), any(), any(), any(), - any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true), any(), any(), any(), + any(), any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true), any(), any(), any(), any(), any(), any(), any(), eq(true), any(), any())).thenReturn(userVmMock); when(networkModel.checkSecurityGroupSupportForNetwork(account, zoneMock, List.of(networkId), Collections.emptyList())).thenReturn(false); @@ -1381,7 +1381,7 @@ public void testCreateNewVM3() throws ResourceUnavailableException, Insufficient "-" + asVmGroupMock.getNextVmSeq() + "-[a-z]{6}"; Mockito.verify(userVmService).createAdvancedVirtualMachine(any(), any(), any(), any(), any(), matches(vmHostNamePattern), matches(vmHostNamePattern), - any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true), any(), any(), any(), + any(), any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true), any(), any(), any(), any(), any(), any(), any(), eq(true), any(), any()); Mockito.verify(asVmGroupMock).setNextVmSeq(nextVmSeq + 3); } diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index f07d2af21af2..7a533212fb2f 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; @@ -36,20 +37,27 @@ import static org.mockito.Mockito.when; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseCmd.HTTPMethod; import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd; +import org.apache.cloudstack.api.command.user.vm.CreateVMFromBackupCmd; import org.apache.cloudstack.api.command.user.vm.DeployVMCmd; import org.apache.cloudstack.api.command.user.vm.DeployVnfApplianceCmd; +import org.apache.cloudstack.api.command.user.vm.ResetVMSSHKeyCmd; import org.apache.cloudstack.api.command.user.vm.ResetVMUserDataCmd; import org.apache.cloudstack.api.command.user.vm.RestoreVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupVO; +import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; @@ -92,8 +100,10 @@ import com.cloud.network.NetworkModel; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; +import com.cloud.network.element.UserDataServiceProvider; import com.cloud.network.security.SecurityGroupVO; import com.cloud.offering.DiskOffering; +import com.cloud.offering.DiskOfferingInfo; import com.cloud.offering.ServiceOffering; import com.cloud.server.ManagementService; import com.cloud.service.ServiceOfferingVO; @@ -118,10 +128,12 @@ import com.cloud.user.AccountService; import com.cloud.user.AccountVO; import com.cloud.user.ResourceLimitService; +import com.cloud.user.SSHKeyPairVO; import com.cloud.user.UserData; import com.cloud.user.UserDataVO; import com.cloud.user.UserVO; import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.SSHKeyPairDao; import com.cloud.user.dao.UserDao; import com.cloud.user.dao.UserDataDao; import com.cloud.uservm.UserVm; @@ -248,6 +260,12 @@ public class UserVmManagerImplTest { @Mock PrimaryDataStoreDao primaryDataStoreDao; + @Mock + BackupDao backupDao; + + @Mock + BackupManager backupManager; + @Mock VirtualMachineManager virtualMachineManager; @@ -362,6 +380,9 @@ public class UserVmManagerImplTest { @Mock ServiceOfferingJoinDao serviceOfferingJoinDao; + @Mock + SSHKeyPairDao sshKeyPairDao; + @Mock private VMInstanceVO vmInstanceMock; @@ -598,13 +619,13 @@ private void configureDoNothingForMethodsThatWeDoNotWantToTest() throws Resource Mockito.lenient().doReturn(Mockito.mock(UserVm.class)).when(userVmManagerImpl).updateVirtualMachine(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyBoolean(), Mockito.any(HTTPMethod.class), Mockito.anyString(), Mockito.anyString(), - Mockito.anyString(), Mockito.anyList(), Mockito.any()); + Mockito.anyString(), anyList(), Mockito.any()); Mockito.doNothing().when(userVmManagerImpl).validateIfVmSupportsMigration(Mockito.any(), Mockito.anyLong()); Mockito.doNothing().when(userVmManagerImpl).validateOldAndNewAccounts(Mockito.nullable(Account.class), Mockito.nullable(Account.class), Mockito.anyLong(), Mockito.nullable(String.class), Mockito.nullable(Long.class)); Mockito.doNothing().when(userVmManagerImpl).validateIfVmHasNoRules(Mockito.any(), Mockito.anyLong()); Mockito.doNothing().when(userVmManagerImpl).removeInstanceFromInstanceGroup(Mockito.anyLong()); - Mockito.doNothing().when(userVmManagerImpl).verifyResourceLimitsForAccountAndStorage(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyList(), Mockito.any()); + Mockito.doNothing().when(userVmManagerImpl).verifyResourceLimitsForAccountAndStorage(Mockito.any(), Mockito.any(), Mockito.any(), anyList(), Mockito.any()); Mockito.doNothing().when(userVmManagerImpl).validateIfNewOwnerHasAccessToTemplate(Mockito.any(), Mockito.any(), Mockito.any()); Mockito.doNothing().when(userVmManagerImpl).updateVmOwner(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); @@ -1082,14 +1103,14 @@ public void createVirtualMachine() throws ResourceUnavailableException, Insuffic when(_dcMock.isLocalStorageEnabled()).thenReturn(true); when(_dcMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic); Mockito.doReturn(userVmVoMock).when(userVmManagerImpl).createBasicSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), any(), - any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(), any(), any(), any(), any(), eq(true), any()); UserVm result = userVmManagerImpl.createVirtualMachine(deployVMCmd); assertEquals(userVmVoMock, result); Mockito.verify(vnfTemplateManager).validateVnfApplianceNics(templateMock, null); Mockito.verify(userVmManagerImpl).createBasicSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), any(), - any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(), any(), any(), any(), any(), eq(true), any()); } @@ -1342,7 +1363,7 @@ public void createVirtualMachineWithCloudRuntimeException() throws ResourceUnava cre.addProxyObject(vmId, "vmId"); Mockito.doThrow(cre).when(userVmManagerImpl).createBasicSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), any(), - any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(), any(), any(), any(), any(), eq(true), any()); CloudRuntimeException creThrown = assertThrows(CloudRuntimeException.class, () -> userVmManagerImpl.createVirtualMachine(deployVMCmd)); @@ -2693,7 +2714,7 @@ public void updateBasicTypeNetworkForVmTestNetworkIdListIsNullCallsCleanupOfOldO securityGroupIdList); Mockito.verify(userVmManagerImpl).cleanupOfOldOwnerNicsForNetwork(virtualMachineProfileMock); - Mockito.verify(userVmManagerImpl).addDefaultNetworkToNetworkList(Mockito.anyList(), Mockito.any()); + Mockito.verify(userVmManagerImpl).addDefaultNetworkToNetworkList(anyList(), Mockito.any()); Mockito.verify(userVmManagerImpl).allocateNetworksForVm(Mockito.any(), Mockito.any()); Mockito.verify(userVmManagerImpl).addSecurityGroupsToVm(accountMock, userVmVoMock,virtualMachineTemplateMock, securityGroupIdList, networkMock); } @@ -2711,7 +2732,7 @@ public void updateBasicTypeNetworkForVmTestNetworkIdListIsEmptyCallsCleanupOfOld securityGroupIdList); Mockito.verify(userVmManagerImpl).cleanupOfOldOwnerNicsForNetwork(virtualMachineProfileMock); - Mockito.verify(userVmManagerImpl).addDefaultNetworkToNetworkList(Mockito.anyList(), Mockito.any()); + Mockito.verify(userVmManagerImpl).addDefaultNetworkToNetworkList(anyList(), Mockito.any()); Mockito.verify(userVmManagerImpl).allocateNetworksForVm(Mockito.any(), Mockito.any()); Mockito.verify(userVmManagerImpl).addSecurityGroupsToVm(accountMock, userVmVoMock,virtualMachineTemplateMock, securityGroupIdList, networkMock); } @@ -3125,4 +3146,174 @@ public void executeStepsToChangeOwnershipOfVmTestResourceCountRunningVmsOnlyEnab Mockito.verify(userVmManagerImpl, Mockito.never()).resourceCountIncrement(Mockito.anyLong(), Mockito.any(), Mockito.any(), Mockito.any()); } } + @Test + public void testAllocateVMFromBackupUsingCmdValues() throws InsufficientCapacityException, ResourceAllocationException, ResourceUnavailableException { + Long backupId = 4L; + Long rootDiskOfferingId = 5L; + Long networkId = 6L; + + CreateVMFromBackupCmd cmd = new CreateVMFromBackupCmd(); + cmd._accountService = accountService; + cmd._entityMgr = entityManager; + when(accountService.finalyzeAccountId(nullable(String.class), nullable(Long.class), nullable(Long.class), eq(true))).thenReturn(accountId); + when(accountService.getActiveAccountById(accountId)).thenReturn(account); + + ReflectionTestUtils.setField(cmd, "serviceOfferingId", serviceOfferingId); + ReflectionTestUtils.setField(cmd, "templateId", templateId); + ReflectionTestUtils.setField(cmd, "backupId", backupId); + ReflectionTestUtils.setField(cmd, "zoneId", zoneId); + + ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class); + when(serviceOffering.getDiskOfferingId()).thenReturn(rootDiskOfferingId); + DiskOfferingVO rootDiskOffering = mock(DiskOfferingVO.class); + when(_serviceOfferingDao.findById(serviceOfferingId)).thenReturn(serviceOffering); + when(diskOfferingDao.findById(rootDiskOfferingId)).thenReturn(rootDiskOffering); + + Map diskDetails = new HashMap<>(); + diskDetails.put(ApiConstants.DISK_OFFERING_ID, "disk-offering-uuid"); + diskDetails.put(ApiConstants.DEVICE_ID, "1"); + diskDetails.put(ApiConstants.SIZE, "5"); + diskDetails.put(ApiConstants.MIN_IOPS, "1000"); + diskDetails.put(ApiConstants.MAX_IOPS, "5000"); + Map> disksDetails = new HashMap<>(); + disksDetails.put(0, diskDetails); + ReflectionTestUtils.setField(cmd, "dataDisksDetails", disksDetails); + DiskOffering diskOffering = mock(DiskOffering.class); + when(diskOffering.isCustomized()).thenReturn(true); + when(diskOffering.isCustomizedIops()).thenReturn(true); + when(entityManager.findByUuid(DiskOffering.class, "disk-offering-uuid")).thenReturn(diskOffering); + + VMTemplateVO template = mock(VMTemplateVO.class); + when(templateDao.findById(templateId)).thenReturn(template); + + BackupVO backup = mock(BackupVO.class); + when(backup.getZoneId()).thenReturn(zoneId); + when(backupDao.findById(backupId)).thenReturn(backup); + when(backup.getDetail(ApiConstants.NETWORK_IDS)).thenReturn("network-uuid"); + NetworkVO network = mock(NetworkVO.class); + when(network.getId()).thenReturn(networkId); + when(_networkDao.findByUuid("network-uuid")).thenReturn(network); + + Mockito.doReturn(userVmVoMock).when(userVmManagerImpl).createAdvancedVirtualMachine(any(), any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(), + any(), any(), any(), any(), eq(false), any(), any()); + + UserVm result = userVmManagerImpl.allocateVMFromBackup(cmd); + + assertNotNull(result); + Mockito.verify(backupDao).findById(backupId); + Mockito.verify(userVmManagerImpl).createAdvancedVirtualMachine(any(), any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(), + any(), any(), any(), any(), eq(false), any(), any()); + } + + @Test + public void testAllocateVMFromBackupUsingBackupValues() throws InsufficientCapacityException, ResourceAllocationException, ResourceUnavailableException { + Long backupId = 5L; + Long rootDiskOfferingId = 6L; + Long network1Id = 7L; + Long network2Id = 8L; + + CreateVMFromBackupCmd cmd = mock(CreateVMFromBackupCmd.class); + when(cmd.getZoneId()).thenReturn(zoneId); + when(cmd.getBackupId()).thenReturn(backupId); + when(cmd.getEntityOwnerId()).thenReturn(accountId); + when(cmd.getServiceOfferingId()).thenReturn(null); + when(cmd.getDiskOfferingId()).thenReturn(null); + when(cmd.getTemplateId()).thenReturn(null); + when(cmd.getNetworkIds()).thenReturn(null); + when(cmd.getIpToNetworkMap()).thenReturn(null); + when(cmd.getDataDiskOfferingsInfo()).thenReturn(null); + + Account owner = mock(Account.class); + when(accountService.getActiveAccountById(accountId)).thenReturn(owner); + + DataCenterVO zone = mock(DataCenterVO.class); + when(_dcDao.findById(zoneId)).thenReturn(zone); + + BackupVO backup = mock(BackupVO.class); + when(backup.getZoneId()).thenReturn(zoneId); + when(backupDao.findById(backupId)).thenReturn(backup); + + DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); + when(backup.getDetail(ApiConstants.SERVICE_OFFERING_ID)).thenReturn("service-offering-uuid"); + when(_serviceOfferingDao.findByUuid("service-offering-uuid")).thenReturn(serviceOffering); + when(serviceOffering.getDiskOfferingId()).thenReturn(rootDiskOfferingId); + when(diskOfferingDao.findById(rootDiskOfferingId)).thenReturn(diskOffering); + + when(backup.getDetail(ApiConstants.TEMPLATE_ID)).thenReturn("template-uuid"); + VMTemplateVO template = mock(VMTemplateVO.class); + when(templateDao.findByUuid("template-uuid")).thenReturn(template); + + when(backup.getDetail(ApiConstants.NETWORK_IDS)).thenReturn("net1-uuid,net2-uuid"); + NetworkVO network1 = mock(NetworkVO.class); + NetworkVO network2 = mock(NetworkVO.class); + when(_networkDao.findByUuid("net1-uuid")).thenReturn(network1); + when(_networkDao.findByUuid("net2-uuid")).thenReturn(network2); + when(network1.getId()).thenReturn(network1Id); + when(network2.getId()).thenReturn(network2Id); + when(backupManager.getDataDiskOfferingListFromBackup(backup)).thenReturn(List.of(new DiskOfferingInfo(diskOffering, 10L, 1000L, 2000L))); + + Mockito.doReturn(userVmVoMock).when(userVmManagerImpl).createAdvancedVirtualMachine(any(), any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(), + any(), any(), any(), any(), eq(false), any(), any()); + + UserVm result = userVmManagerImpl.allocateVMFromBackup(cmd); + + assertNotNull(result); + Mockito.verify(userVmManagerImpl).createAdvancedVirtualMachine(any(), any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(), + any(), any(), any(), any(), eq(false), any(), any()); + } + + @Test + public void testResetVMSSHKey() throws ResourceUnavailableException, InsufficientCapacityException { + Long domainId = 4L; + Long projectId = 5L; + Long networkId = 6L; + + List names = List.of("keypair1", "keypair2"); + ResetVMSSHKeyCmd cmd = mock(ResetVMSSHKeyCmd.class); + when(cmd.getId()).thenReturn(vmId); + when(cmd.getAccountName()).thenReturn("testAccount"); + when(cmd.getDomainId()).thenReturn(domainId); + when(cmd.getProjectId()).thenReturn(projectId); + when(cmd.getNames()).thenReturn(names); + + Account owner = mock(Account.class); + when(owner.getAccountId()).thenReturn(accountId); + when(owner.getDomainId()).thenReturn(domainId); + when(accountManager.finalizeOwner(callerAccount, "testAccount", domainId, projectId)).thenReturn(owner); + + UserVmVO userVm = new UserVmVO(vmId, null, null, templateId, Hypervisor.HypervisorType.KVM, 0, + true, false, domainId, accountId, 0L, 0L, null, null, null, null); + ReflectionTestUtils.setField(userVm, "state", VirtualMachine.State.Stopped); + userVm.setUserVmType("User"); + when(userVmDao.findById(vmId)).thenReturn(userVm); + VMTemplateVO template = mock(VMTemplateVO.class); + when(templateDao.findByIdIncludingRemoved(templateId)).thenReturn(template); + + Nic nic = mock(Nic.class); + when(nic.getNetworkId()).thenReturn(networkId); + when(networkModel.getDefaultNic(vmId)).thenReturn(nic); + NetworkVO network = mock(NetworkVO.class); + when(_networkDao.findById(networkId)).thenReturn(network); + UserDataServiceProvider element = mock(UserDataServiceProvider.class); + when(element.saveSSHKey(any(), any(), any(), any())).thenReturn(true); + when(_networkMgr.getSSHKeyResetProvider(network)).thenReturn(element); + + SSHKeyPairVO keyPair1 = mock(SSHKeyPairVO.class); + SSHKeyPairVO keyPair2 = mock(SSHKeyPairVO.class); + when(keyPair1.getPublicKey()).thenReturn("ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAr..."); + when(keyPair2.getPublicKey()).thenReturn("ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAr..."); + when(sshKeyPairDao.findByNames(accountId, domainId, names)).thenReturn(Arrays.asList(keyPair1, keyPair2)); + + UserVm result = userVmManagerImpl.resetVMSSHKey(cmd); + + assertNotNull(result); + Map details = result.getDetails(); + Assert.assertEquals(details.get(VmDetailConstants.SSH_PUBLIC_KEY), "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAr...\n" + + "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAr..."); + Assert.assertEquals(details.get(VmDetailConstants.SSH_KEY_PAIR_NAMES), "keypair1,keypair2"); + } } diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java index 3bf1fb97e4d0..0255fd0604ca 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -16,21 +16,50 @@ // under the License. package org.apache.cloudstack.backup; +import com.cloud.api.query.dao.UserVmJoinDao; +import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.event.ActionEventUtils; +import com.cloud.event.UsageEventUtils; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.offering.DiskOffering; +import com.cloud.offering.DiskOfferingInfo; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeApiService; import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.DomainManager; +import com.cloud.user.User; import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.fsm.NoTransitionException; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.dao.VMInstanceDao; + +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd; import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd; +import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -42,13 +71,22 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; +import org.springframework.test.util.ReflectionTestUtils; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) @@ -72,9 +110,41 @@ public class BackupManagerTest { @Mock VolumeDao volumeDao; + @Mock + DiskOfferingDao diskOfferingDao; + + @Mock + ServiceOfferingDao serviceOfferingDao; + + @Mock + VMTemplateDao vmTemplateDao; + + @Mock + UserVmJoinDao userVmJoinDao; + + @Mock + VMInstanceDao vmInstanceDao; + + @Mock + BackupDao backupDao; + + @Mock + AccountManager accountManager; + + @Mock + DomainManager domainManager; + + @Mock + PrimaryDataStoreDao primaryDataStoreDao; + + @Mock + HostDao hostDao; + private String[] hostPossibleValues = {"127.0.0.1", "hostname"}; private String[] datastoresPossibleValues = {"e9804933-8609-4de3-bccc-6278072a496c", "datastore-name"}; private AutoCloseable closeable; + private ConfigDepotImpl configDepotImpl; + private boolean updatedConfigKeyDepot = false; @Before public void setup() throws Exception { @@ -97,11 +167,39 @@ public void setup() throws Exception { offering.setUserDrivenBackupAllowed(true); return true; }); + + backupProvider = mock(BackupProvider.class); + when(backupProvider.getName()).thenReturn("testbackupprovider"); + Map backupProvidersMap = new HashMap<>(); + backupProvidersMap.put(backupProvider.getName().toLowerCase(), backupProvider); + ReflectionTestUtils.setField(backupManager, "backupProvidersMap", backupProvidersMap); + + Account account = mock(Account.class); + User user = mock(User.class); + CallContext.register(user, account); } @After public void tearDown() throws Exception { closeable.close(); + if (updatedConfigKeyDepot) { + ReflectionTestUtils.setField(BackupManager.BackupFrameworkEnabled, "s_depot", configDepotImpl); + } + CallContext.unregister(); + } + + private void overrideBackupFrameworkConfigValue() { + ConfigKey configKey = BackupManager.BackupFrameworkEnabled; + this.configDepotImpl = (ConfigDepotImpl) ReflectionTestUtils.getField(configKey, "s_depot"); + ConfigDepotImpl configDepot = Mockito.mock(ConfigDepotImpl.class); + Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupFrameworkEnabled.key()), + Mockito.eq(ConfigKey.Scope.Global), Mockito.isNull())).thenReturn("true"); + Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupFrameworkEnabled.key()), + Mockito.eq(ConfigKey.Scope.Zone), Mockito.anyLong())).thenReturn("true"); + Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupProviderPlugin.key()), + Mockito.eq(ConfigKey.Scope.Zone), Mockito.anyLong())).thenReturn("testbackupprovider"); + ReflectionTestUtils.setField(configKey, "s_depot", configDepot); + updatedConfigKeyDepot = true; } @Test @@ -118,7 +216,7 @@ public void testExceptionWhenUpdateWithNullId() { } } - @Test (expected = InvalidParameterValueException.class) + @Test(expected = InvalidParameterValueException.class) public void testExceptionWhenUpdateWithNonExistentId() { Long id = 123l; @@ -128,7 +226,7 @@ public void testExceptionWhenUpdateWithNonExistentId() { backupManager.updateBackupOffering(cmd); } - @Test (expected = ServerApiException.class) + @Test(expected = ServerApiException.class) public void testExceptionWhenUpdateWithoutChanges() { UpdateBackupOfferingCmd cmd = Mockito.spy(UpdateBackupOfferingCmd.class); when(cmd.getName()).thenReturn(null); @@ -159,7 +257,7 @@ public void testUpdateBackupOfferingSuccess() { @Test public void restoreBackedUpVolumeTestHostIpAndDatastoreUuid() { BackupVO backupVO = new BackupVO(); - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f"; String vmName = "i-2-3-VM"; VirtualMachine.State vmState = VirtualMachine.State.Running; @@ -169,19 +267,19 @@ public void restoreBackedUpVolumeTestHostIpAndDatastoreUuid() { Mockito.when(backupProvider.restoreBackedUpVolume(Mockito.any(), Mockito.eq(volumeUuid), Mockito.eq("127.0.0.1"), Mockito.eq("e9804933-8609-4de3-bccc-6278072a496c"), Mockito.eq(vmNameAndState))).thenReturn(new Pair(Boolean.TRUE, "Success")); - Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); + Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); assertEquals(Boolean.TRUE, restoreBackedUpVolume.first()); assertEquals("Success", restoreBackedUpVolume.second()); - Mockito.verify(backupProvider, times(1)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), + verify(backupProvider, times(1)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), any(Pair.class)); } @Test public void restoreBackedUpVolumeTestHostIpAndDatastoreName() { BackupVO backupVO = new BackupVO(); - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f"; String vmName = "i-2-3-VM"; VirtualMachine.State vmState = VirtualMachine.State.Running; @@ -190,19 +288,19 @@ public void restoreBackedUpVolumeTestHostIpAndDatastoreName() { Pair vmNameAndState = new Pair<>("i-2-3-VM", VirtualMachine.State.Running); Mockito.when(backupProvider.restoreBackedUpVolume(Mockito.any(), Mockito.eq(volumeUuid), Mockito.eq("127.0.0.1"), Mockito.eq("datastore-name"), Mockito.eq(vmNameAndState))).thenReturn(new Pair(Boolean.TRUE, "Success2")); - Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); + Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); assertEquals(Boolean.TRUE, restoreBackedUpVolume.first()); assertEquals("Success2", restoreBackedUpVolume.second()); - Mockito.verify(backupProvider, times(2)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), + verify(backupProvider, times(2)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), any(Pair.class)); } @Test public void restoreBackedUpVolumeTestHostNameAndDatastoreUuid() { BackupVO backupVO = new BackupVO(); - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f"; String vmName = "i-2-3-VM"; VirtualMachine.State vmState = VirtualMachine.State.Running; @@ -211,20 +309,20 @@ public void restoreBackedUpVolumeTestHostNameAndDatastoreUuid() { Pair vmNameAndState = new Pair<>("i-2-3-VM", VirtualMachine.State.Running); Mockito.when(backupProvider.restoreBackedUpVolume(Mockito.any(), Mockito.eq(volumeUuid), - Mockito.eq("hostname"), Mockito.eq("e9804933-8609-4de3-bccc-6278072a496c"), Mockito.eq(vmNameAndState) )).thenReturn(new Pair(Boolean.TRUE, "Success3")); - Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); + Mockito.eq("hostname"), Mockito.eq("e9804933-8609-4de3-bccc-6278072a496c"), Mockito.eq(vmNameAndState))).thenReturn(new Pair(Boolean.TRUE, "Success3")); + Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); assertEquals(Boolean.TRUE, restoreBackedUpVolume.first()); assertEquals("Success3", restoreBackedUpVolume.second()); - Mockito.verify(backupProvider, times(3)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), + verify(backupProvider, times(3)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), any(Pair.class)); } @Test public void restoreBackedUpVolumeTestHostAndDatastoreName() { BackupVO backupVO = new BackupVO(); - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f"; String vmName = "i-2-3-VM"; VirtualMachine.State vmState = VirtualMachine.State.Running; @@ -233,22 +331,22 @@ public void restoreBackedUpVolumeTestHostAndDatastoreName() { Pair vmNameAndState = new Pair<>("i-2-3-VM", VirtualMachine.State.Running); Mockito.when(backupProvider.restoreBackedUpVolume(Mockito.any(), Mockito.eq(volumeUuid), - Mockito.eq("hostname"), Mockito.eq("datastore-name"), Mockito.eq(vmNameAndState))).thenReturn(new Pair(Boolean.TRUE, "Success4")); - Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); + Mockito.eq("hostname"), Mockito.eq("datastore-name"), Mockito.eq(vmNameAndState))).thenReturn(new Pair(Boolean.TRUE, "Success4")); + Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); assertEquals(Boolean.TRUE, restoreBackedUpVolume.first()); assertEquals("Success4", restoreBackedUpVolume.second()); - Mockito.verify(backupProvider, times(4)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), + verify(backupProvider, times(4)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), any(Pair.class)); } @Test public void tryRestoreVMTestRestoreSucceeded() throws NoTransitionException { - BackupOffering offering = Mockito.mock(BackupOffering.class); - VolumeVO volumeVO = Mockito.mock(VolumeVO.class); - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); - BackupVO backup = Mockito.mock(BackupVO.class); + BackupOffering offering = mock(BackupOffering.class); + VolumeVO volumeVO = mock(VolumeVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + BackupVO backup = mock(BackupVO.class); try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(), @@ -273,10 +371,10 @@ public void tryRestoreVMTestRestoreSucceeded() throws NoTransitionException { @Test public void tryRestoreVMTestRestoreFails() throws NoTransitionException { - BackupOffering offering = Mockito.mock(BackupOffering.class); - VolumeVO volumeVO = Mockito.mock(VolumeVO.class); - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); - BackupVO backup = Mockito.mock(BackupVO.class); + BackupOffering offering = mock(BackupOffering.class); + VolumeVO volumeVO = mock(VolumeVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + BackupVO backup = mock(BackupVO.class); try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(), @@ -304,4 +402,331 @@ public void tryRestoreVMTestRestoreFails() throws NoTransitionException { } } } + + @Test + public void testGetVmDetailsForBackup() { + Long vmId = 1L; + VirtualMachine vm = mock(VirtualMachine.class); + when(vm.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(vm.getServiceOfferingId()).thenReturn(1L); + when(vm.getTemplateId()).thenReturn(1L); + when(vm.getId()).thenReturn(vmId); + + ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class); + when(serviceOffering.getUuid()).thenReturn("service-offering-uuid"); + when(serviceOfferingDao.findById(1L)).thenReturn(serviceOffering); + + VMTemplateVO template = mock(VMTemplateVO.class); + when(template.getUuid()).thenReturn("template-uuid"); + when(vmTemplateDao.findById(1L)).thenReturn(template); + + UserVmJoinVO userVmJoinVO = mock(UserVmJoinVO.class); + when(userVmJoinVO.getNetworkUuid()).thenReturn("mocked-network-uuid"); + List userVmJoinVOs = Collections.singletonList(userVmJoinVO); + when(userVmJoinDao.searchByIds(vmId)).thenReturn(userVmJoinVOs); + + Map details = backupManager.getVmDetailsForBackup(vm); + + assertEquals("KVM", details.get(ApiConstants.HYPERVISOR)); + assertEquals("service-offering-uuid", details.get(ApiConstants.SERVICE_OFFERING_ID)); + assertEquals("template-uuid", details.get(ApiConstants.TEMPLATE_ID)); + assertEquals("mocked-network-uuid", details.get(ApiConstants.NETWORK_IDS)); + } + + @Test + public void testGetDiskOfferingDetailsForBackup() { + Long vmId = 1L; + VolumeVO volume = new VolumeVO(Volume.Type.DATADISK, null, 0, 0, 0, 0, null, 1024L, 100L, 1000L, null); + volume.setDiskOfferingId(1L); + volume.setSize(1024L); + volume.setDeviceId(0L); + volume.setMinIops(100L); + volume.setMaxIops(200L); + when(volumeDao.findByInstance(vmId)).thenReturn(Collections.singletonList(volume)); + + DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); + when(diskOffering.getUuid()).thenReturn("disk-offering-uuid"); + when(diskOfferingDao.findById(1L)).thenReturn(diskOffering); + + Map details = backupManager.getDiskOfferingDetailsForBackup(vmId); + + assertEquals("disk-offering-uuid", details.get(ApiConstants.DISK_OFFERING_IDS)); + assertEquals("1024", details.get(ApiConstants.DISK_SIZES)); + assertEquals("100", details.get(ApiConstants.MIN_IOPS)); + assertEquals("200", details.get(ApiConstants.MAX_IOPS)); + assertEquals("0", details.get(ApiConstants.DEVICE_IDS)); + } + + @Test + public void getDataDiskOfferingListFromBackup() { + Long size1 = 5L * 1024 * 1024 * 1024; + Long size2 = 10L * 1024 * 1024 * 1024; + Backup backup = mock(Backup.class); + when(backup.getDetail(ApiConstants.DISK_OFFERING_IDS)).thenReturn("root-disk-offering-uuid,disk-offering-uuid-1,disk-offering-uuid-2"); + when(backup.getDetail(ApiConstants.DEVICE_IDS)).thenReturn("0,1,2"); + when(backup.getDetail(ApiConstants.DISK_SIZES)).thenReturn("0," + size1 + "," + size2); + when(backup.getDetail(ApiConstants.MIN_IOPS)).thenReturn("0,100,200"); + when(backup.getDetail(ApiConstants.MAX_IOPS)).thenReturn("0,300,400"); + + DiskOfferingVO rootDiskOffering = mock(DiskOfferingVO.class); + + DiskOfferingVO diskOffering1 = mock(DiskOfferingVO.class); + when(diskOffering1.getUuid()).thenReturn("disk-offering-uuid-1"); + when(diskOffering1.getState()).thenReturn(DiskOffering.State.Active); + when(diskOffering1.isCustomizedIops()).thenReturn(true); + + DiskOfferingVO diskOffering2 = mock(DiskOfferingVO.class); + when(diskOffering2.getUuid()).thenReturn("disk-offering-uuid-2"); + when(diskOffering2.getState()).thenReturn(DiskOffering.State.Active); + when(diskOffering2.isCustomizedIops()).thenReturn(true); + + when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering1); + when(diskOfferingDao.findByUuid("disk-offering-uuid-2")).thenReturn(diskOffering2); + + List diskOfferingInfoList = backupManager.getDataDiskOfferingListFromBackup(backup); + + assertEquals(2, diskOfferingInfoList.size()); + assertEquals("disk-offering-uuid-1", diskOfferingInfoList.get(0).getDiskOffering().getUuid()); + assertEquals(Long.valueOf(5), diskOfferingInfoList.get(0).getSize()); + assertEquals(Long.valueOf(1), diskOfferingInfoList.get(0).getDeviceId()); + assertEquals(Long.valueOf(100), diskOfferingInfoList.get(0).getMinIops()); + assertEquals(Long.valueOf(300), diskOfferingInfoList.get(0).getMaxIops()); + + assertEquals("disk-offering-uuid-2", diskOfferingInfoList.get(1).getDiskOffering().getUuid()); + assertEquals(Long.valueOf(10), diskOfferingInfoList.get(1).getSize()); + assertEquals(Long.valueOf(2), diskOfferingInfoList.get(1).getDeviceId()); + assertEquals(Long.valueOf(200), diskOfferingInfoList.get(1).getMinIops()); + assertEquals(Long.valueOf(400), diskOfferingInfoList.get(1).getMaxIops()); + } + + @Test + public void getDataDiskOfferingListFromBackupNullIops() { + Long size = 5L * 1024 * 1024 * 1024; + Backup backup = mock(Backup.class); + when(backup.getDetail(ApiConstants.DISK_OFFERING_IDS)).thenReturn("disk-offering-uuid-1"); + when(backup.getDetail(ApiConstants.DEVICE_IDS)).thenReturn("1"); + when(backup.getDetail(ApiConstants.DISK_SIZES)).thenReturn("" + size); + when(backup.getDetail(ApiConstants.MIN_IOPS)).thenReturn("null"); + when(backup.getDetail(ApiConstants.MAX_IOPS)).thenReturn("null"); + + DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); + when(diskOffering.getUuid()).thenReturn("disk-offering-uuid-1"); + when(diskOffering.isCustomizedIops()).thenReturn(true); + when(diskOffering.getState()).thenReturn(DiskOffering.State.Active); + + when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering); + + List diskOfferingInfoList = backupManager.getDataDiskOfferingListFromBackup(backup); + + assertEquals(1, diskOfferingInfoList.size()); + assertEquals("disk-offering-uuid-1", diskOfferingInfoList.get(0).getDiskOffering().getUuid()); + assertEquals(Long.valueOf(5), diskOfferingInfoList.get(0).getSize()); + assertEquals(Long.valueOf(1), diskOfferingInfoList.get(0).getDeviceId()); + assertNull(diskOfferingInfoList.get(0).getMinIops()); + assertNull(diskOfferingInfoList.get(0).getMaxIops()); + } + + @Test + public void testUpdateDiskOfferingSizeFromBackup() { + Long sizeInBackup = 5L; + Long sizeInCmd = 2L; + Backup backup = mock(Backup.class); + when(backup.getDetail(ApiConstants.DISK_OFFERING_IDS)).thenReturn("disk-offering-uuid-1"); + when(backup.getDetail(ApiConstants.DEVICE_IDS)).thenReturn("1"); + when(backup.getDetail(ApiConstants.DISK_SIZES)).thenReturn("" + sizeInBackup * 1024 * 1024 * 1024); + when(backup.getDetail(ApiConstants.MIN_IOPS)).thenReturn("null"); + when(backup.getDetail(ApiConstants.MAX_IOPS)).thenReturn("null"); + + DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); + when(diskOffering.isCustomizedIops()).thenReturn(true); + when(diskOffering.getState()).thenReturn(DiskOffering.State.Active); + + when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering); + List diskOfferingInfoList = List.of(new DiskOfferingInfo(diskOffering, sizeInCmd, 1L, null, null)); + + backupManager.updateDiskOfferingSizeFromBackup(diskOfferingInfoList, backup); + + assertEquals(sizeInBackup, diskOfferingInfoList.get(0).getSize()); + } + + @Test + public void testGetRootDiskOfferingInfoFromBackup() { + Long size = 5L * 1024 * 1024 * 1024; + Backup backup = mock(Backup.class); + when(backup.getDetail(ApiConstants.DISK_OFFERING_IDS)).thenReturn("root-disk-offering-uuid"); + when(backup.getDetail(ApiConstants.DEVICE_IDS)).thenReturn("0"); + when(backup.getDetail(ApiConstants.DISK_SIZES)).thenReturn("" + size); + + DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); + when(diskOffering.getUuid()).thenReturn("root-disk-offering-uuid"); + when(diskOfferingDao.findByUuid("root-disk-offering-uuid")).thenReturn(diskOffering); + + DiskOfferingInfo diskOfferingInfo = backupManager.getRootDiskOfferingInfoFromBackup(backup); + + assertEquals("root-disk-offering-uuid", diskOfferingInfo.getDiskOffering().getUuid()); + assertEquals(Long.valueOf(5), diskOfferingInfo.getSize()); + assertEquals(Long.valueOf(0), diskOfferingInfo.getDeviceId()); + } + + @Test + public void testImportBackupOffering() { + ImportBackupOfferingCmd cmd = Mockito.mock(ImportBackupOfferingCmd.class); + when(cmd.getZoneId()).thenReturn(1L); + when(cmd.getExternalId()).thenReturn("external-id"); + when(cmd.getName()).thenReturn("Test Offering"); + when(cmd.getDescription()).thenReturn("Test Description"); + when(cmd.getUserDrivenBackups()).thenReturn(true); + + overrideBackupFrameworkConfigValue(); + + when(backupOfferingDao.findByExternalId("external-id", 1L)).thenReturn(null); + when(backupOfferingDao.findByName("Test Offering", 1L)).thenReturn(null); + + BackupOfferingVO offering = new BackupOfferingVO(1L, "external-id", "testbackupprovider", "Test Offering", "Test Description", true); + when(backupOfferingDao.persist(any(BackupOfferingVO.class))).thenReturn(offering); + when(backupProvider.isValidProviderOffering(cmd.getZoneId(), cmd.getExternalId())).thenReturn(true); + + BackupOffering result = backupManager.importBackupOffering(cmd); + + assertEquals("Test Offering", result.getName()); + assertEquals("Test Description", result.getDescription()); + assertEquals(true, result.isUserDrivenBackupAllowed()); + assertEquals("external-id", result.getExternalId()); + assertEquals("testbackupprovider", result.getProvider()); + } + + @Test + public void testCreateVolumeInfoFromVolumes() { + List volumes = new ArrayList<>(); + VolumeVO volume1 = new VolumeVO(Volume.Type.ROOT, "vol1", 1L, 2L, 3L, + 4L, null, 1024L, 0L, 0L, null); + volume1.setUuid("uuid1"); + volume1.setPath("path1"); + volume1.setVolumeType(Volume.Type.ROOT); + volumes.add(volume1); + + VolumeVO volume2 = new VolumeVO(Volume.Type.ROOT, "vol2", 1L, 2L, 3L, + 4L, null, 2048L, 0L, 0L, null); + volume2.setUuid("uuid2"); + volume2.setPath("path2"); + volume2.setVolumeType(Volume.Type.DATADISK); + volumes.add(volume2); + + String expectedJson = "[{\"uuid\":\"uuid1\",\"type\":\"ROOT\",\"size\":1024,\"path\":\"path1\"},{\"uuid\":\"uuid2\",\"type\":\"DATADISK\",\"size\":2048,\"path\":\"path2\"}]"; + String actualJson = BackupManagerImpl.createVolumeInfoFromVolumes(volumes); + + assertEquals(expectedJson, actualJson); + } + + @Test + public void testAssignVMToBackupOffering() { + Long vmId = 1L; + Long offeringId = 2L; + + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getId()).thenReturn(vmId); + BackupOfferingVO offering = mock(BackupOfferingVO.class); + + overrideBackupFrameworkConfigValue(); + + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + when(backupOfferingDao.findById(offeringId)).thenReturn(offering); + when(vm.getState()).thenReturn(VirtualMachine.State.Running); + when(vm.getDataCenterId()).thenReturn(1L); + when(vm.getBackupOfferingId()).thenReturn(null); + when(offering.getProvider()).thenReturn("testbackupprovider"); + when(backupProvider.assignVMToBackupOffering(vm, offering)).thenReturn(true); + when(vmInstanceDao.update(1L, vm)).thenReturn(true); + + try (MockedStatic ignored2 = Mockito.mockStatic(UsageEventUtils.class)) { + boolean result = backupManager.assignVMToBackupOffering(vmId, offeringId); + + assertTrue(result); + verify(vmInstanceDao, times(1)).findById(vmId); + verify(backupOfferingDao, times(1)).findById(offeringId); + verify(backupManager, times(1)).getBackupProvider("testbackupprovider"); + } + } + + @Test + public void testRemoveVMFromBackupOffering() { + Long vmId = 1L; + boolean forced = true; + + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); + when(vm.getDataCenterId()).thenReturn(1L); + when(vm.getBackupOfferingId()).thenReturn(2L); + + BackupOfferingVO offering = mock(BackupOfferingVO.class); + when(backupOfferingDao.findById(vm.getBackupOfferingId())).thenReturn(offering); + when(offering.getProvider()).thenReturn("testbackupprovider"); + when(backupProvider.removeVMFromBackupOffering(vm)).thenReturn(true); + + overrideBackupFrameworkConfigValue(); + + try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class)) { + boolean result = backupManager.removeVMFromBackupOffering(vmId, forced); + + assertTrue(result); + verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId); + verify(backupOfferingDao, times(1)).findById(vm.getBackupOfferingId()); + verify(backupManager, times(1)).getBackupProvider("testbackupprovider"); + } + } + + @Test + public void testRestoreBackupToVM() throws NoTransitionException { + Long backupId = 1L; + Long vmId = 2L; + Long hostId = 3L; + Long offeringId = 4L; + Long poolId = 5L; + + BackupVO backup = mock(BackupVO.class); + when(backup.getBackupOfferingId()).thenReturn(offeringId); + + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); + when(vm.getId()).thenReturn(vmId); + when(vm.getState()).thenReturn(VirtualMachine.State.Stopped); + when(vm.getHostId()).thenReturn(hostId); + + BackupOfferingVO offering = mock(BackupOfferingVO.class); + BackupProvider backupProvider = mock(BackupProvider.class); + when(backupProvider.supportsInstanceFromBackup()).thenReturn(true); + + overrideBackupFrameworkConfigValue(); + + when(backupDao.findById(backupId)).thenReturn(backup); + when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); + when(backupOfferingDao.findByIdIncludingRemoved(offeringId)).thenReturn(offering); + when(offering.getProvider()).thenReturn("testbackupprovider"); + when(backupManager.getBackupProvider("testbackupprovider")).thenReturn(backupProvider); + when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.RestoringRequested, hostId)).thenReturn(true); + when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.RestoringSuccess, hostId)).thenReturn(true); + + VolumeVO rootVolume = mock(VolumeVO.class); + when(rootVolume.getPoolId()).thenReturn(poolId); + HostVO host = mock(HostVO.class); + when(hostDao.findById(hostId)).thenReturn(host); + StoragePoolVO pool = mock(StoragePoolVO.class); + when(volumeDao.findIncludingRemovedByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(List.of(rootVolume)); + when(primaryDataStoreDao.findById(poolId)).thenReturn(pool); + when(rootVolume.getPoolId()).thenReturn(poolId); + when(volumeDao.findIncludingRemovedByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(List.of(rootVolume)); + when(primaryDataStoreDao.findById(poolId)).thenReturn(pool); + when(backupProvider.restoreBackupToVM(vm, backup, null, null)).thenReturn(true); + + try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { + boolean result = backupManager.restoreBackupToVM(backupId, vmId); + + assertTrue(result); + verify(backupProvider, times(1)).restoreBackupToVM(vm, backup, null, null); + verify(virtualMachineManager, times(1)).stateTransitTo(vm, VirtualMachine.Event.RestoringRequested, hostId); + verify(virtualMachineManager, times(1)).stateTransitTo(vm, VirtualMachine.Event.RestoringSuccess, hostId); + } catch (ResourceUnavailableException e) { + fail("Test failed due to exception" + e); + } + } } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index e07962d63d0a..ed6c8b030597 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -540,6 +540,7 @@ "label.conditions": "Conditions", "label.configuration": "Configuration", "label.configure": "Configure", + "label.configure.instance": "Configure Instance", "label.configure.health.monitor": "Configure Health Monitor", "label.configure.app": "Configure the App", "label.configure.ldap": "Configure LDAP", @@ -2633,11 +2634,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", "message.acquire.ip.failed": "Failed to acquire IP.", "message.action.acquire.ip": "Please confirm that you want to acquire new IP.", "message.action.cancel.maintenance": "Your host has been successfully canceled for maintenance. This process can take up to several minutes.", "message.action.cancel.maintenance.mode": "Please confirm that you want to cancel this maintenance.", "message.action.create.snapshot.from.vmsnapshot": "Please confirm that you want to create Snapshot from Instance Snapshot", +"message.action.create.instance.from.backup": "Please confirm that you want to create a new Instance from the given Backup.
Click on configure to edit the parameters for the new Instance.", "message.action.delete.asnrange": "Please confirm the AS range that you want to delete", "message.action.delete.autoscale.vmgroup": "Please confirm that you want to delete this autoscaling group.", "message.action.delete.backup.offering": "Please confirm that you want to delete this backup offering?", @@ -2918,6 +2921,7 @@ "message.create.sharedfs.failed": "Failed to create Shared FileSystem.", "message.create.sharedfs.processing": "Shared FileSystem creation in progress.", "message.create.tungsten.public.network": "Create Tungsten-Fabric public Network", +"message.create.instance.from.backup.prefill": "Data is prefilled using the configurations stored in the backup. Edit to change individual fields.", "message.create.internallb": "Creating internal LB", "message.create.internallb.failed": "Failed to create internal LB.", "message.create.internallb.processing": "Creation of internal LB is in progress", diff --git a/ui/src/components/view/DeployVMFromBackup.vue b/ui/src/components/view/DeployVMFromBackup.vue new file mode 100644 index 000000000000..57bdb12f7a58 --- /dev/null +++ b/ui/src/components/view/DeployVMFromBackup.vue @@ -0,0 +1,2196 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + + + + + + + diff --git a/ui/src/config/section/storage.js b/ui/src/config/section/storage.js index e0fa72dff8a0..6a62e05f542a 100644 --- a/ui/src/config/section/storage.js +++ b/ui/src/config/section/storage.js @@ -452,6 +452,17 @@ export default { popup: true, component: shallowRef(defineAsyncComponent(() => import('@/views/storage/RestoreAttachBackupVolume.vue'))) }, + { + api: 'createVMFromBackup', + icon: 'caret-right-outlined', + docHelp: 'adminguide/virtual_machines.html#restoring-vm-backups', + label: 'label.create.instance.from.backup', + message: 'message.backup.restore', + dataView: true, + popup: true, + show: (record) => { return record.state !== 'Destroyed' }, + component: shallowRef(defineAsyncComponent(() => import('@/views/storage/CreateVMFromBackup.vue'))) + }, { api: 'removeVirtualMachineFromBackupOffering', icon: 'scissor-outlined', diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue index e2ac07fbf7bd..bffe0dd9e4a1 100644 --- a/ui/src/views/AutogenView.vue +++ b/ui/src/views/AutogenView.vue @@ -556,6 +556,10 @@ export default { if (this.$route.path === '/vm' || this.$route.path.includes('/vm/')) { this.fetchData() } + if (this.$route.path === '/backup') { + this.$router.push('/vm') + this.fetchData() + } }) eventBus.on('refresh-icon', () => { if (this.$showIcon()) { diff --git a/ui/src/views/compute/wizard/VolumeDiskOfferingSelectView.vue b/ui/src/views/compute/wizard/VolumeDiskOfferingSelectView.vue new file mode 100644 index 000000000000..245a8b761970 --- /dev/null +++ b/ui/src/views/compute/wizard/VolumeDiskOfferingSelectView.vue @@ -0,0 +1,269 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + + + + + diff --git a/ui/src/views/storage/CreateVMFromBackup.vue b/ui/src/views/storage/CreateVMFromBackup.vue new file mode 100644 index 000000000000..6e6051ed34c4 --- /dev/null +++ b/ui/src/views/storage/CreateVMFromBackup.vue @@ -0,0 +1,230 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + + + + +