From ccec1bc6aa4b90a001e04af70165c3d38f9b09fb Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Tue, 19 Nov 2019 15:01:30 +0530 Subject: [PATCH 01/81] feature: VM Backup and Recovery This introduces a new framework that provides CloudStack users the ability to backup their guest VMs for recovery purposes, in case they suffer a hardware or software issue with their instances or the underlying infrastructure. This framework allows CloudStack to be integrated with Backup and Recovery providers, as pluggable solutions. Each provider must implement the backup and recovery methods provided by the framework in their own way. Two B&R plugins ship with this change: - Veeam B&R: for VMware - Dummy: for testing purposes FS: https://cwiki.apache.org/confluence/display/CLOUDSTACK/Backup+and+Recovery+Framework Signed-off-by: Rohit Yadav --- .../main/java/com/cloud/event/EventTypes.java | 21 +- .../com/cloud/hypervisor/HypervisorGuru.java | 8 +- .../java/com/cloud/server/ResourceTag.java | 1 + .../apache/cloudstack/api/ApiConstants.java | 7 + .../cloudstack/api/BaseBackupListCmd.java | 81 ++ .../cloudstack/api/ResponseGenerator.java | 10 +- .../admin/backup/DeleteBackupOfferingCmd.java | 92 +++ .../admin/backup/ImportBackupOfferingCmd.java | 137 ++++ .../ListBackupProviderOfferingsCmd.java | 94 +++ .../admin/backup/ListBackupProvidersCmd.java | 99 +++ .../command/admin/vm/ImportVMCmdByAdmin.java | 103 +++ .../command/user/backup/CreateBackupCmd.java | 124 +++ .../user/backup/CreateBackupScheduleCmd.java | 107 +++ .../command/user/backup/DeleteBackupCmd.java | 112 +++ .../user/backup/ListBackupOfferingsCmd.java | 102 +++ .../command/user/backup/ListBackupsCmd.java | 113 +++ .../command/user/backup/RestoreBackupCmd.java | 121 +++ ...storeVolumeFromBackupAndAttachToVMCmd.java | 143 ++++ .../api/response/BackupOfferingResponse.java | 69 ++ .../api/response/BackupProviderResponse.java | 53 ++ .../api/response/BackupResponse.java | 177 ++++ .../response/BackupRestorePointResponse.java | 66 ++ .../org/apache/cloudstack/backup/Backup.java | 148 ++++ .../cloudstack/backup/BackupManager.java | 113 +++ .../cloudstack/backup/BackupOffering.java | 30 + .../cloudstack/backup/BackupProvider.java | 93 +++ .../cloudstack/backup/BackupService.java | 37 + .../apache/cloudstack/usage/UsageTypes.java | 2 + client/pom.xml | 53 +- .../cloudstack/backup/module.properties | 21 + ...e-lifecycle-backup-context-inheritable.xml | 32 + .../spring-core-registry-core-context.xml | 4 + .../com/cloud/network/dao/NetworkDao.java | 2 + .../com/cloud/network/dao/NetworkDaoImpl.java | 8 + .../cloud/service/dao/ServiceOfferingDao.java | 2 + .../service/dao/ServiceOfferingDaoImpl.java | 16 + .../cloud/storage/dao/DiskOfferingDao.java | 3 + .../storage/dao/DiskOfferingDaoImpl.java | 43 + .../cloud/storage/dao/VMTemplatePoolDao.java | 4 + .../storage/dao/VMTemplatePoolDaoImpl.java | 24 + .../java/com/cloud/storage/dao/VolumeDao.java | 2 + .../com/cloud/storage/dao/VolumeDaoImpl.java | 8 + .../java/com/cloud/usage/UsageBackupVO.java | 172 ++++ .../com/cloud/usage/dao/UsageBackupDao.java | 32 + .../cloud/usage/dao/UsageBackupDaoImpl.java | 140 ++++ .../main/java/com/cloud/vm/VMInstanceVO.java | 4 + .../main/java/com/cloud/vm/dao/NicDao.java | 2 + .../java/com/cloud/vm/dao/NicDaoImpl.java | 8 + .../cloudstack/backup/BackupOfferingVO.java | 112 +++ .../apache/cloudstack/backup/BackupVO.java | 218 +++++ .../cloudstack/backup/dao/BackupDao.java | 41 + .../cloudstack/backup/dao/BackupDaoImpl.java | 173 ++++ .../backup/dao/BackupOfferingDao.java | 32 + .../backup/dao/BackupOfferingDaoImpl.java | 87 ++ ...spring-engine-schema-core-daos-context.xml | 3 + .../META-INF/db/schema-41300to41400.sql | 51 ++ .../storage/dao/DiskOfferingDaoImplTest.java | 56 ++ .../cloudstack/quota/QuotaManagerImpl.java | 3 +- .../cloudstack/quota/constant/QuotaTypes.java | 5 +- plugins/backup/dummy/pom.xml | 41 + .../backup/DummyBackupProvider.java | 130 +++ .../cloudstack/dummy-backup/module.properties | 18 + .../spring-backup-dummy-context.xml | 27 + plugins/backup/veeam/pom.xml | 54 ++ .../backup/VeeamBackupProvider.java | 259 ++++++ .../cloudstack/backup/veeam/VeeamBackup.java | 104 +++ .../backup/veeam/VeeamBackupOffering.java | 66 ++ .../cloudstack/backup/veeam/VeeamClient.java | 663 +++++++++++++++ .../cloudstack/backup/veeam/VeeamObject.java | 28 + .../backup/veeam/api/BackupJobCloneInfo.java | 58 ++ .../veeam/api/CreateObjectInJobSpec.java | 46 ++ .../backup/veeam/api/EntityReferences.java | 39 + .../backup/veeam/api/HierarchyItem.java | 68 ++ .../backup/veeam/api/HierarchyItems.java | 39 + .../cloudstack/backup/veeam/api/Job.java | 163 ++++ .../backup/veeam/api/JobCloneSpec.java | 41 + .../cloudstack/backup/veeam/api/Link.java | 69 ++ .../backup/veeam/api/ObjectInJob.java | 94 +++ .../backup/veeam/api/ObjectsInJob.java | 39 + .../cloudstack/backup/veeam/api/Ref.java | 83 ++ .../backup/veeam/api/RestoreSession.java | 120 +++ .../cloudstack/backup/veeam/api/Result.java | 47 ++ .../cloudstack/backup/veeam/api/Task.java | 106 +++ .../cloudstack/veeam/module.properties | 18 + .../veeam/spring-backup-veeam-context.xml | 27 + .../backup/veeam/VeeamClientTest.java | 113 +++ .../com/cloud/hypervisor/guru/VMwareGuru.java | 774 +++++++++++++++++- .../vmware/manager/VmwareManagerImpl.java | 4 +- .../vmware/resource/VmwareResource.java | 25 +- plugins/pom.xml | 15 +- pom.xml | 5 +- .../main/java/com/cloud/api/ApiDBUtils.java | 22 + .../java/com/cloud/api/ApiResponseHelper.java | 48 +- .../cloud/hypervisor/HypervisorGuruBase.java | 12 + .../com/cloud/usage/UsageServiceImpl.java | 43 +- .../cloudstack/backup/BackupManagerImpl.java | 582 +++++++++++++ .../spring-server-core-managers-context.xml | 5 + .../com/cloud/vpc/dao/MockNetworkDaoImpl.java | 5 + .../integration/smoke/test_backup_recovery.py | 202 +++++ tools/apidoc/gen_toc.py | 2 + tools/marvin/marvin/lib/base.py | 124 +++ ui/css/cloudstack3.css | 6 + ui/l10n/en.js | 7 + ui/scripts/configuration.js | 143 +++- ui/scripts/instances.js | 74 ++ ui/scripts/storage.js | 269 ++++++ .../com/cloud/usage/UsageManagerImpl.java | 36 +- .../cloud/usage/parser/BackupUsageParser.java | 124 +++ .../main/java/com/cloud/utils/UuidUtils.java | 22 + .../hypervisor/vmware/mo/DatastoreMO.java | 2 +- .../cloud/hypervisor/vmware/mo/NetworkMO.java | 9 + .../hypervisor/vmware/util/VmwareClient.java | 2 +- 112 files changed, 8654 insertions(+), 92 deletions(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteBackupOfferingCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProvidersCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVMCmdByAdmin.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/BackupProviderResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/BackupRestorePointResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/backup/Backup.java create mode 100644 api/src/main/java/org/apache/cloudstack/backup/BackupManager.java create mode 100644 api/src/main/java/org/apache/cloudstack/backup/BackupOffering.java create mode 100644 api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java create mode 100644 api/src/main/java/org/apache/cloudstack/backup/BackupService.java create mode 100644 core/src/main/resources/META-INF/cloudstack/backup/module.properties create mode 100644 core/src/main/resources/META-INF/cloudstack/backup/spring-core-lifecycle-backup-context-inheritable.xml create mode 100644 engine/schema/src/main/java/com/cloud/usage/UsageBackupVO.java create mode 100644 engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDao.java create mode 100644 engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java create mode 100644 engine/schema/src/test/java/com/cloud/storage/dao/DiskOfferingDaoImplTest.java create mode 100644 plugins/backup/dummy/pom.xml create mode 100644 plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java create mode 100644 plugins/backup/dummy/src/main/resources/META-INF/cloudstack/dummy-backup/module.properties create mode 100644 plugins/backup/dummy/src/main/resources/META-INF/cloudstack/dummy-backup/spring-backup-dummy-context.xml create mode 100644 plugins/backup/veeam/pom.xml create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackupOffering.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObject.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/BackupJobCloneInfo.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/CreateObjectInJobSpec.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/EntityReferences.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/HierarchyItem.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/HierarchyItems.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Job.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/JobCloneSpec.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Link.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/ObjectInJob.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/ObjectsInJob.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Ref.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/RestoreSession.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Result.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Task.java create mode 100644 plugins/backup/veeam/src/main/resources/META-INF/cloudstack/veeam/module.properties create mode 100644 plugins/backup/veeam/src/main/resources/META-INF/cloudstack/veeam/spring-backup-veeam-context.xml create mode 100644 plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java create mode 100644 server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java create mode 100644 test/integration/smoke/test_backup_recovery.py create mode 100644 usage/src/main/java/com/cloud/usage/parser/BackupUsageParser.java diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index a30518aaf176..1b6a688e859b 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -19,6 +19,13 @@ import java.util.HashMap; import java.util.Map; +import org.apache.cloudstack.acl.Role; +import org.apache.cloudstack.acl.RolePermission; +import org.apache.cloudstack.annotation.Annotation; +import org.apache.cloudstack.config.Configuration; +import org.apache.cloudstack.ha.HAConfig; +import org.apache.cloudstack.usage.Usage; + import com.cloud.dc.DataCenter; import com.cloud.dc.Pod; import com.cloud.dc.StorageNetworkIpRange; @@ -69,12 +76,6 @@ import com.cloud.vm.Nic; import com.cloud.vm.NicSecondaryIp; import com.cloud.vm.VirtualMachine; -import org.apache.cloudstack.acl.Role; -import org.apache.cloudstack.acl.RolePermission; -import org.apache.cloudstack.annotation.Annotation; -import org.apache.cloudstack.config.Configuration; -import org.apache.cloudstack.ha.HAConfig; -import org.apache.cloudstack.usage.Usage; public class EventTypes { @@ -472,6 +473,14 @@ public class EventTypes { public static final String EVENT_VM_SNAPSHOT_OFF_PRIMARY = "VMSNAPSHOT.OFF_PRIMARY"; public static final String EVENT_VM_SNAPSHOT_REVERT = "VMSNAPSHOT.REVERTTO"; + // Backup and Recovery events + public static final String EVENT_VM_BACKUP_IMPORT_OFFERING = "BACKUP.IMPORT.OFFERING"; + 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_DELETE = "BACKUP.DELETE"; + public static final String EVENT_VM_BACKUP_CUSTOM_SCHEDULE = "BACKUP.CUSTOM.SCHEDULE"; + public static final String EVENT_VM_BACKUP_RESTORE_VOLUME_TO_VM = "BACKUP.RESTORE.VOLUME.TO.VM"; + // external network device events public static final String EVENT_EXTERNAL_NVP_CONTROLLER_ADD = "PHYSICAL.NVPCONTROLLER.ADD"; public static final String EVENT_EXTERNAL_NVP_CONTROLLER_DELETE = "PHYSICAL.NVPCONTROLLER.DELETE"; diff --git a/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java b/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java index da2c7d04eb33..97294da229b9 100644 --- a/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java +++ b/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java @@ -19,13 +19,14 @@ import java.util.List; import java.util.Map; -import com.cloud.storage.StoragePool; +import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.framework.config.ConfigKey; import com.cloud.agent.api.Command; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.StoragePool; import com.cloud.utils.Pair; import com.cloud.utils.component.Adapter; import com.cloud.vm.NicProfile; @@ -86,6 +87,11 @@ public interface HypervisorGuru extends Adapter { Map getClusterSettings(long vmId); + VirtualMachine importVirtualMachine(long zoneId, long domainId, long accountId, long userId, + String vmInternalName, Backup backup) throws Exception; + + boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location, Backup.VolumeInfo volumeInfo, + VirtualMachine vm, long poolId, Backup backup) throws Exception; /** * Will generate commands to migrate a vm to a pool. For now this will only work for stopped VMs on Vmware. * diff --git a/api/src/main/java/com/cloud/server/ResourceTag.java b/api/src/main/java/com/cloud/server/ResourceTag.java index 99eb8603d1c3..fb07762639a4 100644 --- a/api/src/main/java/com/cloud/server/ResourceTag.java +++ b/api/src/main/java/com/cloud/server/ResourceTag.java @@ -29,6 +29,7 @@ public enum ResourceObjectType { ISO(true, false), Volume(true, true), Snapshot(true, false), + Backup(true, false), Network(true, true), Nic(false, true), LoadBalancer(true, true), 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 fb44a8a11f48..fde6079ed00a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -32,6 +32,7 @@ public class ApiConstants { public static final String APPLIED = "applied"; public static final String LIST_LB_VMIPS = "lbvmips"; public static final String AVAILABLE = "available"; + public static final String BACKUP_ID = "backupid"; public static final String BITS = "bits"; public static final String BOOTABLE = "bootable"; public static final String BIND_DN = "binddn"; @@ -133,6 +134,7 @@ public class ApiConstants { public static final String EXTRA_DHCP_OPTION_NAME = "extradhcpoptionname"; public static final String EXTRA_DHCP_OPTION_CODE = "extradhcpoptioncode"; public static final String EXTRA_DHCP_OPTION_VALUE = "extradhcpvalue"; + public static final String EXTERNAL = "external"; public static final String FENCE = "fence"; public static final String FETCH_LATEST = "fetchlatest"; public static final String FIRSTNAME = "firstname"; @@ -209,6 +211,7 @@ public class ApiConstants { public static final String LBID = "lbruleid"; public static final String MAX = "max"; public static final String MAC_ADDRESS = "macaddress"; + public static final String MAX_BACKUPS = "maxbackups"; public static final String MAX_SNAPS = "maxsnaps"; public static final String MAX_CPU_NUMBER = "maxcpunumber"; public static final String MAX_MEMORY = "maxmemory"; @@ -277,6 +280,8 @@ public class ApiConstants { public static final String RESOURCE_TYPE = "resourcetype"; public static final String RESOURCE_TYPE_NAME = "resourcetypename"; public static final String RESPONSE = "response"; + public static final String RESTORE_POINTS = "restorepoints"; + public static final String RESTORE_POINT_ID = "restorepointid"; public static final String REVERTABLE = "revertable"; public static final String REGISTERED = "registered"; public static final String QUALIFIERS = "qualifiers"; @@ -344,6 +349,7 @@ public class ApiConstants { public static final String VALUE = "value"; public static final String VIRTUAL_MACHINE_ID = "virtualmachineid"; public static final String VIRTUAL_MACHINE_IDS = "virtualmachineids"; + public static final String VIRTUAL_MACHINE_NAME = "virtualmachinename"; public static final String VIRTUAL_MACHINE_ID_IP = "vmidipmap"; public static final String VIRTUAL_MACHINE_COUNT = "virtualmachinecount"; public static final String USAGE_ID = "usageid"; @@ -362,6 +368,7 @@ public class ApiConstants { public static final String VNET = "vnet"; public static final String IS_VOLATILE = "isvolatile"; public static final String VOLUME_ID = "volumeid"; + public static final String VOLUMES = "volumes"; public static final String ZONE = "zone"; public static final String ZONE_ID = "zoneid"; public static final String ZONE_NAME = "zonename"; diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java new file mode 100644 index 000000000000..b5c5d4779e5a --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java @@ -0,0 +1,81 @@ +// 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; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.BackupRestorePointResponse; +import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.context.CallContext; + +public abstract class BaseBackupListCmd extends BaseListCmd { + + protected void setupResponseBackupOfferingsList(final List offerings) { + final ListResponse response = new ListResponse<>(); + final List responses = new ArrayList<>(); + for (final BackupOffering offering : offerings) { + if (offering == null) { + continue; + } + BackupOfferingResponse backupOfferingResponse = _responseGenerator.createBackupOfferingResponse(offering); + responses.add(backupOfferingResponse); + } + response.setResponses(responses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + protected void setupResponseBackupList(final List backups, final List restorePoints) { + final ListResponse response = new ListResponse<>(); + final List responses = new ArrayList<>(); + for (Backup backup : backups) { + if (backup == null) { + continue; + } + BackupResponse backupResponse = _responseGenerator.createBackupResponse(backup); + if (restorePoints != null && !restorePoints.isEmpty()) { + final List restorePointResponses = new ArrayList<>(); + for (Backup.RestorePoint rp : restorePoints) { + if (rp == null) { + continue; + } + BackupRestorePointResponse rpResponse = new BackupRestorePointResponse(); + rpResponse.setId(rp.getId()); + rpResponse.setCreated(rp.getCreated()); + rpResponse.setType(rp.getType()); + restorePointResponses.add(rpResponse); + } + backupResponse.setRestorePoints(restorePointResponses); + } + responses.add(backupResponse); + } + response.setResponses(responses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} 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 740ee468702f..61d5a8735323 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -22,7 +22,6 @@ import java.util.Map; import java.util.Set; -import org.apache.cloudstack.management.ManagementServerHost; import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ApiConstants.HostDetails; @@ -35,6 +34,7 @@ import org.apache.cloudstack.api.response.AutoScalePolicyResponse; import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse; import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse; +import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.api.response.CapacityResponse; import org.apache.cloudstack.api.response.ClusterResponse; import org.apache.cloudstack.api.response.ConditionResponse; @@ -110,6 +110,7 @@ import org.apache.cloudstack.api.response.UsageRecordResponse; import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.VMSnapshotResponse; import org.apache.cloudstack.api.response.VirtualRouterProviderResponse; import org.apache.cloudstack.api.response.VlanIpRangeResponse; @@ -118,7 +119,10 @@ import org.apache.cloudstack.api.response.VpcResponse; import org.apache.cloudstack.api.response.VpnUsersResponse; import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.config.Configuration; +import org.apache.cloudstack.management.ManagementServerHost; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; import org.apache.cloudstack.region.PortableIp; import org.apache.cloudstack.region.PortableIpRange; @@ -465,5 +469,9 @@ List createTemplateResponses(ResponseView view, VirtualMachine SSHKeyPairResponse createSSHKeyPairResponse(SSHKeyPair sshkeyPair, boolean privatekey); + BackupResponse createBackupResponse(Backup backup); + + BackupOfferingResponse createBackupOfferingResponse(BackupOffering policy); + ManagementServerResponse createManagementResponse(ManagementServerHost mgmt); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteBackupOfferingCmd.java new file mode 100644 index 000000000000..a405fd6d4a7f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteBackupOfferingCmd.java @@ -0,0 +1,92 @@ +// 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.admin.backup; + +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.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +@APICommand(name = DeleteBackupOfferingCmd.APINAME, + description = "Deletes a backup offering", + responseObject = SuccessResponse.class, since = "4.14.0", + authorized = {RoleType.Admin}) +public class DeleteBackupOfferingCmd extends BaseCmd { + public static final String APINAME = "deleteBackupOffering"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + //////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = BackupOfferingResponse.class, + required = true, + description = "ID of the backup offering") + private Long id; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + if (backupManager.deleteBackupOffering(getId())) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable to remove backup offering: " + getId()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java new file mode 100644 index 000000000000..3ac340dbdf9d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java @@ -0,0 +1,137 @@ +// 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.admin.backup; + +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.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.exception.CloudRuntimeException; + +@APICommand(name = ImportBackupOfferingCmd.APINAME, + description = "Imports a backup offering using a backup provider", + responseObject = BackupOfferingResponse.class, since = "4.14.0", + authorized = {RoleType.Admin}) +public class ImportBackupOfferingCmd extends BaseAsyncCmd { + public static final String APINAME = "importBackupOffering"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + //////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, + description = "the name of the backup offering") + private String policyName; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = true, + description = "the description of the backup offering") + private String description; + + @Parameter(name = ApiConstants.EXTERNAL_ID, + type = CommandType.STRING, + required = true, + description = "The backup offering ID (from backup provider side)") + private String policyExternalId; + + @Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class, + description = "The zone ID", required = true) + private Long zoneId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getPolicyName() { + return policyName; + } + + public String getPolicyExternalId() { + return policyExternalId; + } + + public Long getZoneId() { + return zoneId; + } + + public String getDescription() { + return description; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + BackupOffering policy = backupManager.importBackupOffering(getZoneId(), getPolicyExternalId(), getPolicyName(), getDescription()); + if (policy != null) { + BackupOfferingResponse response = _responseGenerator.createBackupOfferingResponse(policy); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add a backup offering"); + } + } catch (InvalidParameterValueException e) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage()); + } catch (CloudRuntimeException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_IMPORT_OFFERING; + } + + @Override + public String getEventDescription() { + return "Importing backup offering: " + policyName + " (externalId=" + policyExternalId + ") on zone " + zoneId ; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java new file mode 100644 index 000000000000..a452f756a513 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java @@ -0,0 +1,94 @@ +// 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.admin.backup; + +import java.util.List; + +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.BaseBackupListCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupOffering; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.exception.CloudRuntimeException; + +@APICommand(name = ListBackupProviderOfferingsCmd.APINAME, + description = "Lists external backup offerings of the provider", + responseObject = BackupOfferingResponse.class, since = "4.14.0", + authorized = {RoleType.Admin}) +public class ListBackupProviderOfferingsCmd extends BaseBackupListCmd { + public static final String APINAME = "listBackupProviderOfferings"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class, + required = true, description = "The zone ID") + private Long zoneId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getZoneId() { + return zoneId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, ServerApiException, ConcurrentOperationException { + validateParameters(); + try { + final List backupOfferings = backupManager.listBackupProviderOfferings(getZoneId()); + setupResponseBackupOfferingsList(backupOfferings); + } catch (InvalidParameterValueException e) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage()); + } catch (CloudRuntimeException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + private void validateParameters() { + if (getZoneId() == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please provide a valid zone ID "); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } +} \ No newline at end of file diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProvidersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProvidersCmd.java new file mode 100644 index 000000000000..44afb1bf7dbd --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProvidersCmd.java @@ -0,0 +1,99 @@ +// 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.admin.backup; + +import java.util.ArrayList; +import java.util.List; + +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.BaseCmd; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.BackupProviderResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupProvider; + +import com.cloud.user.Account; + +@APICommand(name = ListBackupProvidersCmd.APINAME, + description = "Lists Backup and Recovery providers", + responseObject = BackupProviderResponse.class, since = "4.14.0", + authorized = {RoleType.Admin}) +public class ListBackupProvidersCmd extends BaseListCmd { + public static final String APINAME = "listBackupProviders"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "List Backup and Recovery provider by name") + private String name; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getName() { + return name; + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + private void setupResponse(final List providers) { + final ListResponse response = new ListResponse<>(); + final List responses = new ArrayList<>(); + for (final BackupProvider provider : providers) { + if (provider == null || (getName() != null && !provider.getName().equals(getName()))) { + continue; + } + final BackupProviderResponse backupProviderResponse = new BackupProviderResponse(); + backupProviderResponse.setName(provider.getName()); + backupProviderResponse.setDescription(provider.getDescription()); + backupProviderResponse.setObjectName("providers"); + responses.add(backupProviderResponse); + } + response.setResponses(responses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public void execute() { + List providers = backupManager.listBackupProviders(); + setupResponse(providers); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVMCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVMCmdByAdmin.java new file mode 100644 index 000000000000..54c0cbd25d7e --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVMCmdByAdmin.java @@ -0,0 +1,103 @@ +// 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.admin.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.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.hypervisor.Hypervisor; + +@APICommand(name = ImportVMCmdByAdmin.APINAME, + description = "Imports an un-managed external VM to CloudStack", + responseObject = SuccessResponse.class, + since = "4.14.0", + authorized = {RoleType.Admin}) +public class ImportVMCmdByAdmin extends BaseAsyncCmd { + public static final String APINAME = "importVirtualMachine"; + + @Inject + BackupManager backupManager; + + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + required = true, + description = "The internal VM name as on the hypervisor") + private String vmInternalName; + + @Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class, + description = "The zone ID", required = true) + private Long zoneId; + + @Override + public String getEventType() { + return "IMPORT.VM"; + } + + @Override + public String getEventDescription() { + return "Importing VM"; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + long accountId = CallContext.current().getCallingAccountId(); + long userId = CallContext.current().getCallingUserId(); + long domainId = CallContext.current().getCallingAccount().getDomainId(); + boolean result = backupManager.importVM(zoneId, domainId, accountId, userId, vmInternalName, Hypervisor.HypervisorType.VMware, null); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable to import VM"); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + public String getVmInternalName() { + return vmInternalName; + } + + public Long getZoneId() { + return zoneId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java new file mode 100644 index 000000000000..427cc5e3570d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java @@ -0,0 +1,124 @@ +// 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.backup; + +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.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.exception.CloudRuntimeException; + +@APICommand(name = CreateBackupCmd.APINAME, + description = "Create VM backup", + responseObject = BackupResponse.class, since = "4.14.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class CreateBackupCmd extends BaseAsyncCmd { + public static final String APINAME = "createBackup"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + required = true, + description = "id of the VM") + private Long vmId; + + @Parameter(name = ApiConstants.POLICY_ID, + type = CommandType.UUID, + entityType = BackupOfferingResponse.class, + required = true, + description = "id of the backup offering") + private Long policyId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getVmId() { + return vmId; + } + + public Long getPolicyId() { + return policyId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + Backup backup = backupManager.createBackup(vmId, policyId); + if (backup != null) { + BackupResponse response = _responseGenerator.createBackupResponse(backup); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new CloudRuntimeException("Error while creating backup of VM"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_CREATE; + } + + @Override + public String getEventDescription() { + return "Creating backup for VM " + vmId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java new file mode 100644 index 000000000000..ad7befb7f9ca --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java @@ -0,0 +1,107 @@ +// 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.backup; + +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.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; + +@APICommand(name = CreateBackupScheduleCmd.APINAME, + description = "Create a user-defined VM backup schedule", + responseObject = BackupResponse.class, since = "4.14.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class CreateBackupScheduleCmd extends BaseAsyncCmd { + public static final String APINAME = "createBackupSchedule"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.BACKUP_ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + required = true, + description = "id of the backup for which custom schedule is to be defined") + private Long backupId; + + @Parameter(name = ApiConstants.INTERVAL_TYPE, type = CommandType.STRING, required = true, description = "valid values are HOURLY, DAILY, WEEKLY, and MONTHLY") + private String intervalType; + + @Parameter(name = ApiConstants.MAX_BACKUPS, type = CommandType.INTEGER, required = true, description = "maximum number of backup restore points to keep") + private Integer maxBackups; + + @Parameter(name = ApiConstants.SCHEDULE, type = CommandType.STRING, required = true, description = "custom backup schedule, the format is:" + + "for HOURLY MM*, for DAILY MM:HH*, for WEEKLY MM:HH:DD (1-7)*, for MONTHLY MM:HH:DD (1-28)") + private String schedule; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getBackupId() { + return backupId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ServerApiException { + try { + // TODO: ask service layer to do the magic + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_CUSTOM_SCHEDULE; + } + + @Override + public String getEventDescription() { + return "Creating user-defined backup schedule for backup " + backupId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java new file mode 100644 index 000000000000..7488d082a302 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java @@ -0,0 +1,112 @@ +// 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.backup; + +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.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.exception.CloudRuntimeException; + +@APICommand(name = DeleteBackupCmd.APINAME, + description = "Delete VM backup", + responseObject = SuccessResponse.class, since = "4.14.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class DeleteBackupCmd extends BaseAsyncCmd { + public static final String APINAME = "deleteBackup"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + required = true, + description = "id of the VM backup") + private Long backupId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return backupId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + boolean result = backupManager.deleteBackup(backupId); + // FIXME: the response type? + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new CloudRuntimeException("Error while deleting backup of VM"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_DELETE; + } + + @Override + public String getEventDescription() { + return "Deleting backup ID " + backupId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java new file mode 100644 index 000000000000..0857b1555441 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java @@ -0,0 +1,102 @@ +// 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.backup; + +import java.util.List; + +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.BaseBackupListCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupOffering; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.exception.CloudRuntimeException; + +@APICommand(name = ListBackupOfferingsCmd.APINAME, + description = "Lists backup offerings", + responseObject = BackupOfferingResponse.class, since = "4.14.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListBackupOfferingsCmd extends BaseBackupListCmd { + public static final String APINAME = "listBackupOfferings"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = BaseCmd.CommandType.UUID, entityType = BackupOfferingResponse.class, + description = "The backup offering ID") + private Long policyId; + + @Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class, + description = "The zone ID") + private Long zoneId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getZoneId() { + return zoneId; + } + + public Long getPolicyId() { + return policyId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, ServerApiException, ConcurrentOperationException { + validateParameters(); + try { + final List backupOfferings = backupManager.listBackupOfferings(getZoneId(), getPolicyId()); + setupResponseBackupOfferingsList(backupOfferings); + } catch (InvalidParameterValueException e) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage()); + } catch (CloudRuntimeException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + private void validateParameters() { + if (getZoneId() == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please provide a valid zone ID"); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } +} 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 new file mode 100644 index 000000000000..f04f5672bbd8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java @@ -0,0 +1,113 @@ +// 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.backup; + +import java.util.ArrayList; +import java.util.List; + +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.BaseBackupListCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +@APICommand(name = ListBackupsCmd.APINAME, + description = "Lists VM backups", + responseObject = BackupResponse.class, since = "4.14.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListBackupsCmd extends BaseBackupListCmd { + public static final String APINAME = "listBackups"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + description = "id of the VM") + private Long id; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + description = "id of the VM") + private Long vmId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + + public Long getId() { + return id; + } + + public Long getVmId() { + return vmId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try{ + List restorePoints = new ArrayList<>(); + List backups = backupManager.listBackups(getId(), getVmId()); + if (backups.size() > 0 && getVmId() != null) { + Backup backup = backups.get(0); + restorePoints = backupManager.listBackupRestorePoints(backup.getId()); + } + setupResponseBackupList(backups, restorePoints); + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java new file mode 100644 index 000000000000..4a52bac6acaf --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java @@ -0,0 +1,121 @@ +// 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.backup; + +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.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.exception.CloudRuntimeException; + +@APICommand(name = RestoreBackupCmd.APINAME, + description = "Restore a VM from a VM backup", + responseObject = SuccessResponse.class, since = "4.14.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class RestoreBackupCmd extends BaseAsyncCmd { + public static final String APINAME = "restoreBackup"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.BACKUP_ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + required = true, + description = "id of the backup") + private Long backupId; + + @Parameter(name = ApiConstants.RESTORE_POINT_ID, + type = CommandType.STRING, + required = true, + description = "external id of the restore point") + private String restorePointId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getBackupId() { + return backupId; + } + + public String getRestorePointId() { + return restorePointId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + boolean result = backupManager.restoreBackup(backupId, restorePointId); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new CloudRuntimeException("Error while restoring VM from backup"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_RESTORE; + } + + @Override + public String getEventDescription() { + return "Restoring VM from restore point: " + restorePointId + " on backup: " + backupId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java new file mode 100644 index 000000000000..c98e884675e3 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java @@ -0,0 +1,143 @@ +// 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.backup; + +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.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.exception.CloudRuntimeException; + +@APICommand(name = RestoreVolumeFromBackupAndAttachToVMCmd.APINAME, + description = "Restore and attach a backed up volume to VM", + responseObject = SuccessResponse.class, since = "4.14.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class RestoreVolumeFromBackupAndAttachToVMCmd extends BaseAsyncCmd { + public static final String APINAME = "restoreVolumeFromBackupAndAttachToVM"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.BACKUP_ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + required = true, + description = "id of the backup") + private Long backupId; + + @Parameter(name = ApiConstants.RESTORE_POINT_ID, + type = CommandType.STRING, + required = true, + description = "id of the backup restore point") + private String restorePointId; + + @Parameter(name = ApiConstants.VOLUME_ID, + type = CommandType.STRING, + required = true, + description = "id of the volume backed up") + private String volumeUuid; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + required = true, + description = "id of the VM where to attach the restored volume") + private Long vmId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getVolumeUuid() { + return volumeUuid; + } + + public Long getVmId() { + return vmId; + } + + public Long getBackupId() { + return backupId; + } + + public String getRestorePointId() { + return restorePointId; + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + boolean result = backupManager.restoreBackupVolumeAndAttachToVM(volumeUuid, vmId, backupId, restorePointId); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new CloudRuntimeException("Error restoring volume and attaching to VM"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_RESTORE_VOLUME_TO_VM; + } + + @Override + public String getEventDescription() { + return "Restoring volume "+ volumeUuid + " from backup " + backupId + " and attaching it to VM " + vmId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java new file mode 100644 index 000000000000..a702f24fdd07 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java @@ -0,0 +1,69 @@ +// 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.response; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.backup.BackupOffering; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = BackupOffering.class) +public class BackupOfferingResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the backup offering") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "name for the backup offering") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "description for the backup offering") + private String description; + + @SerializedName(ApiConstants.EXTERNAL_ID) + @Param(description = "external ID on the provider side") + private String externalId; + + @SerializedName(ApiConstants.ZONE_ID) + @Param(description = "zone ID") + private String zoneId; + + public void setId(String id) { + this.id = id; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + public void setName(String name) { + this.name = name; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupProviderResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupProviderResponse.java new file mode 100644 index 000000000000..5227d850887c --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupProviderResponse.java @@ -0,0 +1,53 @@ +// 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.response; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.backup.BackupProvider; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(BackupProvider.class) +public class BackupProviderResponse extends BaseResponse { + @SerializedName(ApiConstants.NAME) + @Param(description = "the CA service provider name") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "the description of the CA service provider") + private String description; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} 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 new file mode 100644 index 000000000000..0a7e08b8e74b --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java @@ -0,0 +1,177 @@ +// 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.response; + +import java.util.Date; +import java.util.List; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.backup.Backup; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = Backup.class) +public class BackupResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "internal id of the backup") + private String id; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_NAME) + @Param(description = "name of the VM") + private String vmName; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) + @Param(description = "ID of the vm") + private String vmId; + + @SerializedName(ApiConstants.ZONE_ID) + @Param(description = "zone id") + private String zoneId; + + @SerializedName(ApiConstants.ACCOUNT_ID) + @Param(description = "account id") + private String accountId; + + @SerializedName(ApiConstants.EXTERNAL_ID) + @Param(description = "external backup id") + private String externalId; + + @SerializedName(ApiConstants.VOLUMES) + @Param(description = "backup volumes") + private String volumes; + + @SerializedName(ApiConstants.STATUS) + @Param(description = "backup volume ids") + private Backup.Status status; + + @SerializedName(ApiConstants.SIZE) + @Param(description = "backup size in bytes") + private Long size; + + @SerializedName(ApiConstants.VIRTUAL_SIZE) + @Param(description = "backup protected (virtual) size in bytes") + private Long protectedSize; + + @SerializedName(ApiConstants.RESTORE_POINTS) + @Param(description = "list of backup restore points") + private List restorePoints; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "backup creation date") + private Date createdDate; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + public String getVmId() { + return vmId; + } + + public void setVmId(String vmId) { + this.vmId = vmId; + } + + public Backup.Status getStatus() { + return status; + } + + public void setStatus(Backup.Status status) { + this.status = status; + } + + public Long getSize() { + return size; + } + + public void setSize(Long size) { + this.size = size; + } + + public Long getProtectedSize() { + return protectedSize; + } + + public void setProtectedSize(Long protectedSize) { + this.protectedSize = protectedSize; + } + + public String getVolumes() { + return volumes; + } + + public void setVolumes(String volumes) { + this.volumes = volumes; + } + + public List getRestorePoints() { + return restorePoints; + } + + public void setRestorePoints(List restorePoints) { + this.restorePoints = restorePoints; + } + + public Date getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Date createdDate) { + this.createdDate = createdDate; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupRestorePointResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupRestorePointResponse.java new file mode 100644 index 000000000000..afb3e9ffc5fd --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupRestorePointResponse.java @@ -0,0 +1,66 @@ +// 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.response; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.backup.Backup; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = Backup.RestorePoint.class) +public class BackupRestorePointResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "external id of the restore point") + private String id; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "created time") + private String created; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "restore point type") + private String type; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getCreated() { + return created; + } + + public void setCreated(String created) { + this.created = created; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/Backup.java b/api/src/main/java/org/apache/cloudstack/backup/Backup.java new file mode 100644 index 000000000000..d64e31c07fb7 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -0,0 +1,148 @@ +//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 +//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 java.util.Date; +import java.util.List; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import com.cloud.storage.Volume; +import com.cloud.utils.StringUtils; + +public interface Backup extends InternalIdentity, Identity { + + enum Status { + Allocated, Queued, BackingUp, BackedUp, Error, Failed, Restoring, Removed, Expunged + } + + class Metric { + private Long backupSize = 0L; + private Long dataSize = 0L; + + public Metric(final Long backupSize, final Long dataSize) { + this.backupSize = backupSize; + this.dataSize = dataSize; + } + + public Long getBackupSize() { + return backupSize; + } + + public Long getDataSize() { + return dataSize; + } + + public void setBackupSize(Long backupSize) { + this.backupSize = backupSize; + } + + public void setDataSize(Long dataSize) { + this.dataSize = dataSize; + } + } + + class RestorePoint { + private String id; + private String created; + private String type; + + public RestorePoint(String id, String created, String type) { + this.id = id; + this.created = created; + this.type = type; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getCreated() { + return created; + } + + public void setCreated(String created) { + this.created = created; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + } + + class VolumeInfo { + private String uuid; + private Volume.Type type; + private Long size; + private String path; + + public VolumeInfo(String uuid, String path, Volume.Type type, Long size) { + this.uuid = uuid; + this.type = type; + this.size = size; + this.path = path; + } + + public String getUuid() { + return uuid; + } + + public Volume.Type getType() { + return type; + } + + public void setType(Volume.Type type) { + this.type = type; + } + + public String getPath() { + return path; + } + + public Long getSize() { + return size; + } + + @Override + public String toString() { + return StringUtils.join(":", uuid, path, type, size); + } + } + + Long getVmId(); + Long getOfferingId(); + List getBackedUpVolumes(); + String getVolumes(); + Status getStatus(); + Long getSize(); + Long getProtectedSize(); + String getExternalId(); + Long getAccountId(); + Long getZoneId(); + Date getCreated(); + Date getRemoved(); +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java new file mode 100644 index 000000000000..911cedb1cd83 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -0,0 +1,113 @@ +// 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 java.util.List; + +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; + +import com.cloud.hypervisor.Hypervisor; +import com.cloud.utils.component.Manager; +import com.cloud.utils.component.PluggableService; + +/** + * Backup and Recover Manager Interface + */ +public interface BackupManager extends BackupService, Configurable, PluggableService, Manager { + + ConfigKey BackupFrameworkEnabled = new ConfigKey<>("Advanced", Boolean.class, + "backup.framework.enabled", + "false", + "Is backup and recovery framework enabled.", true, ConfigKey.Scope.Zone); + + ConfigKey BackupProviderPlugin = new ConfigKey<>("Advanced", String.class, + "backup.framework.provider.plugin", + "dummy", + "The backup and recovery provider plugin.", true, ConfigKey.Scope.Zone); + + ConfigKey BackupSyncPollingInterval = new ConfigKey<>("Advanced", Long.class, + "backup.framework.sync.interval", + "300", + "The backup and recovery background sync task polling interval in seconds.", true); + /** + * Add a new Backup and Recovery policy to CloudStack by mapping an existing external backup offering to a name and description + * @param zoneId zone id + * @param offeringExternalId backup offering external id + * @param offeringName name for the backup offering + * @param offeringDescription description for the backup offering + */ + BackupOffering importBackupOffering(final Long zoneId, final String offeringExternalId, + final String offeringName, final String offeringDescription); + + /** + * List backup offerings + * @param zoneId zone id + * @param policyId if not null, only the policy with this id is listed + */ + List listBackupOfferings(final Long zoneId, final Long policyId); + + /** + * List backup provider offerings + * @param zoneId zone id + */ + List listBackupProviderOfferings(final Long zoneId); + + /** + * Deletes a backup offering + */ + boolean deleteBackupOffering(final Long policyId); + + /** + * List existing backups for a VM + */ + List listBackups(final Long id, final Long vmId); + + /** + * Lists restore points for a VM backup + * @param backupId + * @return + */ + List listBackupRestorePoints(final Long backupId); + + /** + * Creates backup of a VM + * @param vmId Virtual Machine ID + * @return returns operation success + */ + Backup createBackup(final Long vmId, final Long policyId); + + /** + * Deletes a backup + * @return returns operation success + */ + boolean deleteBackup(final Long backupId); + + /** + * Restore a full VM from backup + */ + boolean restoreBackup(final Long backupId, final String restorePointId); + + /** + * Restore a backed up volume and attach it to a VM + */ + boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long vmId, final Long backupId, final String restorePointId) throws Exception; + + boolean importVM(long zoneId, long domainId, long accountId, long userId, + String vmInternalName, Hypervisor.HypervisorType hypervisorType, Backup backup); +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupOffering.java b/api/src/main/java/org/apache/cloudstack/backup/BackupOffering.java new file mode 100644 index 000000000000..92f336db6298 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupOffering.java @@ -0,0 +1,30 @@ +//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 +//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 org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface BackupOffering extends InternalIdentity, Identity { + + String getExternalId(); + String getName(); + String getDescription(); + boolean isImported(); + long getZoneId(); + +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java new file mode 100644 index 000000000000..3361a28ce355 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -0,0 +1,93 @@ +//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 +//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 java.util.List; +import java.util.Map; + +import com.cloud.utils.Pair; +import com.cloud.vm.VirtualMachine; + +public interface BackupProvider { + + /** + * Returns the unique name of the provider + * @return returns provider name + */ + String getName(); + + /** + * Returns description about the backup and recovery provider plugin + * @return returns description + */ + String getDescription(); + + /** + * Returns the list of existing backup policies on the provider + * @return backup policies list + */ + List listBackupOfferings(Long zoneId); + + /** + * True if policy with id uuid exists on the backup provider + */ + boolean isBackupOffering(Long zoneId, String uuid); + + /** + * Creates backup of a VM assigned to a policy + * @param policy + * @param vm + * @return true if backup successfully starts + */ + Backup createBackup(BackupOffering policy, VirtualMachine vm, Backup backup); + + /** + * Removes a VM backup + * @param vm + * @param backup + * @return + */ + boolean removeBackup(VirtualMachine vm, Backup backup); + + /** + * Starts and creates an adhoc backup process + * for a previously registered VM backup + * @param backup + * @return + */ + boolean takeBackup(Backup backup); + + /** + * Restore VM from backup + */ + boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String restorePointId); + + /** + * Restore a volume from a backup + */ + Pair restoreBackedUpVolume(long zoneId, String backupUuid, String restorePointId, String volumeUuid, + String hostIp, String dataStoreUuid); + + /** + * List VM Backups + */ + List listBackups(Long zoneId, VirtualMachine vm); + + Map getBackupMetrics(Long zoneId, List backupList); + + List listBackupRestorePoints(String backupUuid, VirtualMachine vm); +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupService.java b/api/src/main/java/org/apache/cloudstack/backup/BackupService.java new file mode 100644 index 000000000000..d4beb629fe0f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupService.java @@ -0,0 +1,37 @@ +//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 +//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 java.util.List; + +/** + * Backup and Recovery Services + */ +public interface BackupService { + /** + * Lists backup and recovery provider plugins + * @return list of providers + */ + List listBackupProviders(); + + /** + * Find backup provider by zone ID + * @param zoneId zone id + * @return backup provider + */ + BackupProvider getBackupProvider(final Long zoneId); +} diff --git a/api/src/main/java/org/apache/cloudstack/usage/UsageTypes.java b/api/src/main/java/org/apache/cloudstack/usage/UsageTypes.java index d0b700697f34..48cff3076fd9 100644 --- a/api/src/main/java/org/apache/cloudstack/usage/UsageTypes.java +++ b/api/src/main/java/org/apache/cloudstack/usage/UsageTypes.java @@ -44,6 +44,7 @@ public class UsageTypes { public static final int VM_SNAPSHOT = 25; public static final int VOLUME_SECONDARY = 26; public static final int VM_SNAPSHOT_ON_PRIMARY = 27; + public static final int BACKUP = 28; public static List listUsageTypes() { List responseList = new ArrayList(); @@ -68,6 +69,7 @@ public static List listUsageTypes() { responseList.add(new UsageTypeResponse(VM_SNAPSHOT, "VM Snapshot storage usage")); responseList.add(new UsageTypeResponse(VOLUME_SECONDARY, "Volume on secondary storage usage")); responseList.add(new UsageTypeResponse(VM_SNAPSHOT_ON_PRIMARY, "VM Snapshot on primary storage usage")); + responseList.add(new UsageTypeResponse(BACKUP, "Backup storage usage")); return responseList; } } diff --git a/client/pom.xml b/client/pom.xml index 3f1e0763d05c..33ce09703729 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -479,6 +479,11 @@ cloud-plugin-integrations-prometheus-exporter ${project.version} + + org.apache.cloudstack + cloud-plugin-backup-dummy + ${project.version} + @@ -759,6 +764,33 @@ META-INF/spring.schemas + + META-INF/services/com.sun.tools.xjc.Plugin + + + META-INF/cxf/cxf.extension + + + META-INF/extensions.xml + + + META-INF/cxf/extensions.xml + + + META-INF/cxf/bus-extensions.txt + + + META-INF/cxf/bus-extensions.xml + + + META-INF/wsdl.plugin.xml + + + META-INF/tools.service.validator.xml + + + META-INF/cxf/java2wsbeans.xml + @@ -893,25 +925,20 @@ cloud-plugin-network-cisco-vnmc ${project.version} - - - - mysqlha - - - noredist - - - org.apache.cloudstack - cloud-plugin-database-mysqlha + cloud-plugin-api-vmware-sioc + ${project.version} + + + org.apache.cloudstack + cloud-plugin-backup-veeam ${project.version} - vmwaresioc + mysqlha noredist @@ -920,7 +947,7 @@ org.apache.cloudstack - cloud-plugin-api-vmware-sioc + cloud-plugin-database-mysqlha ${project.version} diff --git a/core/src/main/resources/META-INF/cloudstack/backup/module.properties b/core/src/main/resources/META-INF/cloudstack/backup/module.properties new file mode 100644 index 000000000000..b85b65ceeead --- /dev/null +++ b/core/src/main/resources/META-INF/cloudstack/backup/module.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +name=backup +parent=backend diff --git a/core/src/main/resources/META-INF/cloudstack/backup/spring-core-lifecycle-backup-context-inheritable.xml b/core/src/main/resources/META-INF/cloudstack/backup/spring-core-lifecycle-backup-context-inheritable.xml new file mode 100644 index 000000000000..175d45e26752 --- /dev/null +++ b/core/src/main/resources/META-INF/cloudstack/backup/spring-core-lifecycle-backup-context-inheritable.xml @@ -0,0 +1,32 @@ + + + + + + + + + diff --git a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml index 2569d8b64871..affd441fb6c5 100644 --- a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml +++ b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml @@ -328,4 +328,8 @@ class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry"> + + + diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java index 5091ebd75df0..9b50e454471a 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java @@ -120,4 +120,6 @@ public interface NetworkDao extends GenericDao, StateDao listNetworkVO(List idset); + + NetworkVO findByVlan(String vlan); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java index 23936cbdeab1..cad01374b86d 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java @@ -697,4 +697,12 @@ public List listNetworkVO(List idset) { sc_2.addAnd("removed", SearchCriteria.Op.EQ, null); return this.search(sc_2, searchFilter_2); } + + @Override + public NetworkVO findByVlan(String vlan) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("broadcastType", BroadcastDomainType.Vlan); + sc.setParameters("broadcastUri", BroadcastDomainType.Vlan.toUri(vlan)); + return findOneBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDao.java b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDao.java index 74728f826ce5..99146dd9f6e9 100644 --- a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDao.java +++ b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDao.java @@ -51,4 +51,6 @@ List createSystemServiceOfferings(String name, String uniqueN ServiceOfferingVO getcomputeOffering(ServiceOfferingVO serviceOffering, Map customParameters); ServiceOfferingVO findDefaultSystemOffering(String offeringName, Boolean useLocalStorage); + + List listPublicByCpuAndMemory(Integer cpus, Integer memory); } diff --git a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java index 96b0c350a741..9fcc874c7aaf 100644 --- a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java @@ -51,6 +51,7 @@ public class ServiceOfferingDaoImpl extends GenericDaoBase UniqueNameSearch; protected final SearchBuilder ServiceOfferingsByKeywordSearch; + protected final SearchBuilder PublicCpuRamSearch; public ServiceOfferingDaoImpl() { super(); @@ -64,6 +65,12 @@ public ServiceOfferingDaoImpl() { ServiceOfferingsByKeywordSearch.or("name", ServiceOfferingsByKeywordSearch.entity().getName(), SearchCriteria.Op.EQ); ServiceOfferingsByKeywordSearch.or("displayText", ServiceOfferingsByKeywordSearch.entity().getDisplayText(), SearchCriteria.Op.EQ); ServiceOfferingsByKeywordSearch.done(); + + PublicCpuRamSearch = createSearchBuilder(); + PublicCpuRamSearch.and("cpu", PublicCpuRamSearch.entity().getCpu(), SearchCriteria.Op.EQ); + PublicCpuRamSearch.and("ram", PublicCpuRamSearch.entity().getRamSize(), SearchCriteria.Op.EQ); + PublicCpuRamSearch.and("system_use", PublicCpuRamSearch.entity().isSystemUse(), SearchCriteria.Op.EQ); + PublicCpuRamSearch.done(); } @Override @@ -246,4 +253,13 @@ public ServiceOfferingVO findDefaultSystemOffering(String offeringName, Boolean } return serviceOffering; } + + @Override + public List listPublicByCpuAndMemory(Integer cpus, Integer memory) { + SearchCriteria sc = PublicCpuRamSearch.create(); + sc.setParameters("cpu", cpus); + sc.setParameters("ram", memory); + sc.setParameters("system_use", false); + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDao.java index 89e2c8328363..3305752459d9 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDao.java @@ -19,6 +19,7 @@ import java.util.List; import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.Storage; import com.cloud.utils.db.GenericDao; public interface DiskOfferingDao extends GenericDao { @@ -31,4 +32,6 @@ public interface DiskOfferingDao extends GenericDao { DiskOfferingVO persistDeafultDiskOffering(DiskOfferingVO offering); + List listAllBySizeAndProvisioningType(long size, Storage.ProvisioningType provisioningType); + } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java index d93a05200f6e..05d44e6445d5 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java @@ -16,6 +16,10 @@ // under the License. package com.cloud.storage.dao; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -27,12 +31,15 @@ import com.cloud.offering.DiskOffering.Type; import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.Storage; import com.cloud.utils.db.Attribute; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.exception.CloudRuntimeException; @Component public class DiskOfferingDaoImpl extends GenericDaoBase implements DiskOfferingDao { @@ -43,7 +50,11 @@ public class DiskOfferingDaoImpl extends GenericDaoBase im private final SearchBuilder PrivateDiskOfferingSearch; private final SearchBuilder PublicDiskOfferingSearch; protected final SearchBuilder UniqueNameSearch; + private final String SizeDiskOfferingSearch = "SELECT * FROM disk_offering WHERE " + + "disk_size = ? AND provisioning_type = ? AND removed IS NULL"; + private final Attribute _typeAttr; + protected final static long GB_UNIT_BYTES = 1024 * 1024 * 1024; protected DiskOfferingDaoImpl() { PrivateDiskOfferingSearch = createSearchBuilder(); @@ -132,6 +143,38 @@ public DiskOfferingVO persistDeafultDiskOffering(DiskOfferingVO offering) { } } + protected long getClosestDiskSizeInGB(long sizeInBytes) { + if (sizeInBytes < 0) { + throw new CloudRuntimeException("Disk size should be greater than 0 bytes, received: " + sizeInBytes + " bytes"); + } + long div = sizeInBytes / GB_UNIT_BYTES; + long rest = sizeInBytes % GB_UNIT_BYTES; + return rest == 0L ? div : div + 1; + } + + @Override + public List listAllBySizeAndProvisioningType(long size, Storage.ProvisioningType provisioningType) { + StringBuilder sql = new StringBuilder(SizeDiskOfferingSearch); + TransactionLegacy txn = TransactionLegacy.currentTxn(); + List offerings = new ArrayList<>(); + try(PreparedStatement pstmt = txn.prepareStatement(sql.toString());){ + if(pstmt != null) { + pstmt.setLong(1, size); + pstmt.setString(2, provisioningType.toString()); + try(ResultSet rs = pstmt.executeQuery();) { + while (rs.next()) { + offerings.add(toEntityBean(rs, false)); + } + }catch (SQLException e) { + throw new CloudRuntimeException("Exception while listing disk offerings by size: " + e.getMessage(), e); + } + } + return offerings; + } catch (SQLException e) { + throw new CloudRuntimeException("Exception while listing disk offerings by size: " + e.getMessage(), e); + } + } + @Override public boolean remove(Long id) { DiskOfferingVO diskOffering = createForUpdate(); diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDao.java index 6216ef77ca2e..05afad67655f 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDao.java @@ -48,4 +48,8 @@ public interface VMTemplatePoolDao extends GenericDao listByTemplatePath(String templatePath); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDaoImpl.java index bb3985f6dc7e..32874701128d 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDaoImpl.java @@ -59,6 +59,7 @@ public class VMTemplatePoolDaoImpl extends GenericDaoBase TemplateStatesSearch; protected final SearchBuilder TemplatePoolStateSearch; protected final SearchBuilder updateStateSearch; + protected final SearchBuilder templatePathSearch; protected static final String UPDATE_TEMPLATE_HOST_REF = "UPDATE template_spool_ref SET download_state = ?, download_pct= ?, last_updated = ? " + ", error_str = ?, local_path = ?, job_id = ? " + "WHERE pool_id = ? and template_id = ?"; @@ -114,6 +115,12 @@ public VMTemplatePoolDaoImpl() { updateStateSearch.and("state", updateStateSearch.entity().getState(), Op.EQ); updateStateSearch.and("updatedCount", updateStateSearch.entity().getUpdatedCount(), Op.EQ); updateStateSearch.done(); + + templatePathSearch = createSearchBuilder(); + templatePathSearch.and("pool_id", templatePathSearch.entity().getPoolId(), Op.EQ); + templatePathSearch.and("local_path", templatePathSearch.entity().getLocalDownloadPath(), Op.EQ); + templatePathSearch.and("install_path", templatePathSearch.entity().getInstallPath(), Op.EQ); + templatePathSearch.done(); } @Override @@ -260,6 +267,23 @@ public VMTemplateStoragePoolVO findByHostTemplate(Long hostId, Long templateId) return (result.size() == 0) ? null : result.get(1); } + @Override + public VMTemplateStoragePoolVO findByPoolPath(Long poolId, String path) { + SearchCriteria sc = templatePathSearch.create(); + sc.setParameters("local_path", path); + sc.setParameters("install_path", path); + sc.setParameters("pool_id", poolId); + return findOneBy(sc); + } + + @Override + public List listByTemplatePath(String templatePath) { + SearchCriteria sc = templatePathSearch.create(); + sc.setParameters("local_path", templatePath); + sc.setParameters("install_path", templatePath); + return listBy(sc); + } + @Override public boolean updateState(State currentState, Event event, State nextState, DataObjectInStore vo, Object data) { VMTemplateStoragePoolVO templatePool = (VMTemplateStoragePoolVO)vo; diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java index 14f48ea06a86..040bd176418f 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java @@ -46,6 +46,8 @@ public interface VolumeDao extends GenericDao, StateDao findByInstanceAndType(long id, Volume.Type vType); + List findIncludingRemovedByInstanceAndType(long id, Volume.Type vType); + List findByInstanceIdAndPoolId(long instanceId, long poolId); List findByInstanceIdDestroyed(long vmId); diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java index 7c63b9c75268..e11e31839163 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java @@ -190,6 +190,14 @@ public List findByInstanceAndType(long id, Type vType) { return listBy(sc); } + @Override + public List findIncludingRemovedByInstanceAndType(long id, Type vType) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("instanceId", id); + sc.setParameters("vType", vType.toString()); + return listIncludingRemovedBy(sc); + } + @Override public List findByInstanceIdDestroyed(long vmId) { SearchCriteria sc = AllFieldsSearch.create(); diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageBackupVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageBackupVO.java new file mode 100644 index 000000000000..91b77bf0bb7a --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/usage/UsageBackupVO.java @@ -0,0 +1,172 @@ +// 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 com.cloud.usage; + +import java.util.Date; + +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 javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name = "usage_backup") +public class UsageBackupVO implements InternalIdentity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "zone_id") + private long zoneId; + + @Column(name = "account_id") + private long accountId; + + @Column(name = "domain_id") + private long domainId; + + @Column(name = "backup_id") + private long backupId; + + @Column(name = "vm_id") + private long vmId; + + @Column(name = "size") + private long size; + + @Column(name = "protected_size") + private long protectedSize; + + @Column(name = "created") + @Temporal(value = TemporalType.TIMESTAMP) + private Date created = null; + + @Column(name = "removed") + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + + protected UsageBackupVO() { + } + + public UsageBackupVO(long zoneId, long accountId, long domainId, long backupId, long vmId, Date created) { + this.zoneId = zoneId; + this.accountId = accountId; + this.domainId = domainId; + this.backupId = backupId; + this.vmId = vmId; + this.created = created; + } + + public UsageBackupVO(long id, long zoneId, long accountId, long domainId, long backupId, long vmId, long size, long protectedSize, Date created, Date removed) { + this.id = id; + this.zoneId = zoneId; + this.accountId = accountId; + this.domainId = domainId; + this.backupId = backupId; + this.vmId = vmId; + this.size = size; + this.protectedSize = protectedSize; + this.created = created; + this.removed = removed; + } + + @Override + public long getId() { + return id; + } + + public long getZoneId() { + return zoneId; + } + + public void setZoneId(long zoneId) { + this.zoneId = zoneId; + } + + public long getAccountId() { + return accountId; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + public long getDomainId() { + return domainId; + } + + public void setDomainId(long domainId) { + this.domainId = domainId; + } + + public long getBackupId() { + return backupId; + } + + public void setBackupId(long backupId) { + this.backupId = backupId; + } + + public long getVmId() { + return vmId; + } + + public void setVmId(long vmId) { + this.vmId = vmId; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + + public long getProtectedSize() { + return protectedSize; + } + + public void setProtectedSize(long protectedSize) { + this.protectedSize = protectedSize; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } +} diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDao.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDao.java new file mode 100644 index 000000000000..b171f22628d1 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDao.java @@ -0,0 +1,32 @@ +// 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 com.cloud.usage.dao; + +import java.util.Date; +import java.util.List; + +import org.apache.cloudstack.backup.Backup; + +import com.cloud.usage.UsageBackupVO; +import com.cloud.utils.db.GenericDao; + +public interface UsageBackupDao extends GenericDao { + void updateMetrics(Backup backup); + void removeUsage(Long accountId, Long zoneId, Long backupId); + List getUsageRecords(Long accountId, Date startDate, Date endDate); +} diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java new file mode 100644 index 000000000000..6453f4ba20d9 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java @@ -0,0 +1,140 @@ +// 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 com.cloud.usage.dao; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +import org.apache.cloudstack.backup.Backup; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.usage.UsageBackupVO; +import com.cloud.utils.DateUtil; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.db.TransactionStatus; + +@Component +public class UsageBackupDaoImpl extends GenericDaoBase implements UsageBackupDao { + public static final Logger LOGGER = Logger.getLogger(UsageBackupDaoImpl.class); + protected static final String GET_USAGE_RECORDS_BY_ACCOUNT = "SELECT id, zone_id, account_id, domain_id, backup_id, vm_id, size, protected_size, created, removed FROM cloud_usage.usage_backup WHERE " + + " account_id = ? AND ((removed IS NULL AND created <= ?) OR (created BETWEEN ? AND ?) OR (removed BETWEEN ? AND ?) " + + " OR ((created <= ?) AND (removed >= ?)))"; + + @Override + public void updateMetrics(final Backup backup) { + boolean result = Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public Boolean doInTransaction(final TransactionStatus status) { + final QueryBuilder qb = QueryBuilder.create(UsageBackupVO.class); + qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, backup.getAccountId()); + qb.and(qb.entity().getZoneId(), SearchCriteria.Op.EQ, backup.getZoneId()); + qb.and(qb.entity().getBackupId(), SearchCriteria.Op.EQ, backup.getId()); + final UsageBackupVO entry = findOneBy(qb.create()); + if (entry == null) { + return false; + } + entry.setSize(backup.getSize()); + entry.setProtectedSize(backup.getProtectedSize()); + return update(entry.getId(), entry); + } + }); + if (!result) { + LOGGER.warn("Failed to update VM Backup metrics for backup id: " + backup.getId()); + } + } + + @Override + public void removeUsage(Long accountId, Long zoneId, Long backupId) { + boolean result = Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public Boolean doInTransaction(final TransactionStatus status) { + final QueryBuilder qb = QueryBuilder.create(UsageBackupVO.class); + qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); + qb.and(qb.entity().getZoneId(), SearchCriteria.Op.EQ, zoneId); + qb.and(qb.entity().getBackupId(), SearchCriteria.Op.EQ, backupId); + final UsageBackupVO entry = findOneBy(qb.create()); + return remove(qb.create()) > 0; + } + }); + if (!result) { + LOGGER.warn("Failed to remove usage entry for backup id: " + backupId); + } + } + + @Override + public List getUsageRecords(Long accountId, Date startDate, Date endDate) { + List usageRecords = new ArrayList(); + TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.USAGE_DB); + PreparedStatement pstmt; + try { + int i = 1; + pstmt = txn.prepareAutoCloseStatement(GET_USAGE_RECORDS_BY_ACCOUNT); + pstmt.setLong(i++, accountId); + + pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); + pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); + pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); + pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) { + //id, zone_id, account_id, domain_iVMSnapshotVOd, vm_id, disk_offering_id, size, created, processed + Long id = Long.valueOf(rs.getLong(1)); + Long zoneId = Long.valueOf(rs.getLong(2)); + Long acctId = Long.valueOf(rs.getLong(3)); + Long domId = Long.valueOf(rs.getLong(4)); + Long backupId = Long.valueOf(rs.getLong(5)); + Long vmId = Long.valueOf(rs.getLong(6)); + Long size = Long.valueOf(rs.getLong(7)); + Long pSize = Long.valueOf(rs.getLong(8)); + Date createdDate = null; + Date removedDate = null; + String createdTS = rs.getString(9); + String removedTS = rs.getString(10); + + if (createdTS != null) { + createdDate = DateUtil.parseDateString(s_gmtTimeZone, createdTS); + } + if (removedTS != null) { + removedDate = DateUtil.parseDateString(s_gmtTimeZone, removedTS); + } + usageRecords.add(new UsageBackupVO(id, zoneId, acctId, domId, backupId, vmId, size, pSize, createdDate, removedDate)); + } + } catch (Exception e) { + txn.rollback(); + LOGGER.warn("Error getting VM backup usage records", e); + } finally { + txn.close(); + } + + return usageRecords; + } +} diff --git a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java index b0ebf2406f57..d673b1e929bb 100644 --- a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java @@ -573,4 +573,8 @@ public void setPowerHostId(Long hostId) { public PartitionType partitionType() { return PartitionType.VM; } + + public long getUserId() { + return userId; + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java index df4fb06325f9..da1da4e3da45 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java @@ -84,4 +84,6 @@ public interface NicDao extends GenericDao { Long getPeerRouterId(String publicMacAddress, long routerId); List listByVmIdAndKeyword(long instanceId, String keyword); + + NicVO findByInstanceIdAndMacAddress(long instanceId, String macAddress); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java index c125d80c5342..b0c4bfad46f3 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java @@ -355,4 +355,12 @@ public List listByVmIdAndKeyword(long instanceId, String keyword) { sc.setParameters("address", "%" + keyword + "%"); return listBy(sc); } + + @Override + public NicVO findByInstanceIdAndMacAddress(long instanceId, String macAddress) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("instance", instanceId); + sc.setParameters("macAddress", macAddress); + return findOneBy(sc); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java new file mode 100644 index 000000000000..13131f04f258 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java @@ -0,0 +1,112 @@ +// 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 java.util.Date; +import java.util.UUID; + +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 javax.persistence.Temporal; +import javax.persistence.TemporalType; + +@Entity +@Table(name = "backup_offering") +public class BackupOfferingVO implements BackupOffering { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "name") + private String name; + + @Column(name = "description") + private String description; + + @Column(name = "external_id") + private String externalId; + + @Column(name = "zone_id") + private long zoneId; + + @Column(name = "created") + @Temporal(value = TemporalType.TIMESTAMP) + private Date created; + + @Column(name = "removed") + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + + public BackupOfferingVO() { + this.uuid = UUID.randomUUID().toString(); + } + + public BackupOfferingVO(final long zoneId, final String externalId, final String name, final String description) { + this(); + this.zoneId = zoneId; + this.name = name; + this.description = description; + this.externalId = externalId; + this.created = new Date(); + } + + public BackupOfferingVO(final String externalId, final String name, final String description) { + this.name = name; + this.description = description; + this.externalId = externalId; + this.created = new Date(); + } + + public String getUuid() { + return uuid; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getExternalId() { + return externalId; + } + + @Override + public boolean isImported() { + return true; + } + + @Override + public long getZoneId() { + return zoneId; + } + + public String getDescription() { + return description; + } +} 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 new file mode 100644 index 000000000000..ad974704d047 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java @@ -0,0 +1,218 @@ +//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 +//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 java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +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 javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import com.google.gson.Gson; + +@Entity +@Table(name = "backups") +public class BackupVO implements Backup { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "vm_id") + private Long vmId; + + @Column(name = "offering_id") + private long offeringId; + + @Column(name = "external_id") + private String externalId; + + @Column(name = "volumes", length = 65535) + private String volumes; + + @Column(name = "size") + private Long size; + + @Column(name = "protected_size") + private Long protectedSize; + + @Column(name = "status") + private Status status; + + @Column(name = "account_id") + private long accountId; + + @Column(name = "zone_id") + private Long zoneId; + + @Column(name = "created") + @Temporal(value = TemporalType.TIMESTAMP) + private Date created; + + @Column(name = "removed") + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + + public BackupVO() { + this.uuid = UUID.randomUUID().toString(); + this.created = new Date(); + } + + public BackupVO(final Long vmId, final long offeringId, final Status status, final long accountId, final Long zoneId) { + this.uuid = UUID.randomUUID().toString(); + this.vmId = vmId; + this.offeringId = offeringId; + this.status = status; + this.accountId = accountId; + this.zoneId = zoneId; + this.created = new Date(); + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public Long getZoneId() { + return zoneId; + } + + public void setZoneId(Long zoneId) { + this.zoneId = zoneId; + } + + @Override + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + @Override + public Long getAccountId() { + return accountId; + } + + @Override + public Long getVmId() { + return vmId; + } + + @Override + public Status getStatus() { + return status; + } + + public Long getSize() { + return size; + } + + public Long getProtectedSize() { + return protectedSize; + } + + @Override + public Date getCreated() { + return created; + } + + public void setId(long id) { + this.id = id; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + public void setVmId(Long vmId) { + this.vmId = vmId; + } + + public void setStatus(Status status) { + this.status = status; + } + + public void setSize(Long size) { + this.size = size; + } + + public void setProtectedSize(Long protectedSize) { + this.protectedSize = protectedSize; + } + + public void setCreated(Date start) { + this.created = start; + } + + @Override + public List getBackedUpVolumes() { + return Arrays.asList(new Gson().fromJson(this.volumes, VolumeInfo[].class)); + } + + public void setBackedUpVolumes(List volumes) { + this.volumes = new Gson().toJson(volumes); + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + public String getVolumes() { + return volumes; + } + + protected void setVolumes(String volumes) { + this.volumes = volumes; + } + + @Override + public Long getOfferingId() { + return offeringId; + } + + public void setOfferingId(long offeringId) { + this.offeringId = offeringId; + } +} 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 new file mode 100644 index 000000000000..69c422eee1b3 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java @@ -0,0 +1,41 @@ +// 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 java.util.List; + +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupVO; + +import com.cloud.utils.db.GenericDao; + +public interface BackupDao extends GenericDao { + + Backup findByVmId(Long vmId); + Backup findByVmIdIncludingRemoved(Long vmId); + + List listByVmId(Long zoneId, Long vmId); + List listByAccountId(Long accountId); + List listByPolicyId(Long policyId); + List syncBackups(Long zoneId, Long vmId, List externalBackups); + List listByZoneAndState(Long zoneId, Backup.Status state); + + BackupResponse newBackupResponse(Backup backup); + BackupVO getBackupVO(Backup backup); +} 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 new file mode 100644 index 000000000000..41443f006603 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java @@ -0,0 +1,173 @@ +// 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 java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupVO; + +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.VMInstanceDao; + +public class BackupDaoImpl extends GenericDaoBase implements BackupDao { + + @Inject + AccountDao accountDao; + + @Inject + DataCenterDao dataCenterDao; + + @Inject + VMInstanceDao vmInstanceDao; + + @Inject + VolumeDao volumeDao; + + private SearchBuilder backupSearch; + + public BackupDaoImpl() { + } + + @PostConstruct + protected void init() { + backupSearch = createSearchBuilder(); + backupSearch.and("vm_id", backupSearch.entity().getVmId(), SearchCriteria.Op.EQ); + backupSearch.and("account_id", backupSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + backupSearch.and("zone_id", backupSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + backupSearch.and("external_id", backupSearch.entity().getExternalId(), SearchCriteria.Op.EQ); + backupSearch.and("policy_id", backupSearch.entity().getOfferingId(), SearchCriteria.Op.EQ); + backupSearch.and("status", backupSearch.entity().getStatus(), SearchCriteria.Op.EQ); + backupSearch.done(); + } + + @Override + public List listByAccountId(Long accountId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters("account_id", accountId); + return new ArrayList<>(listBy(sc)); + } + + @Override + public Backup findByVmId(Long vmId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters("vm_id", vmId); + return findOneBy(sc); + } + + @Override + public Backup findByVmIdIncludingRemoved(Long vmId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters("vm_id", vmId); + return findOneIncludingRemovedBy(sc); + } + + @Override + public List listByVmId(Long zoneId, Long vmId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters("vm_id", vmId); + sc.setParameters("zone_id", zoneId); + return new ArrayList<>(listBy(sc)); + } + + @Override + public List listByPolicyId(Long policyId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters("policy_id", policyId); + return new ArrayList<>(listBy(sc)); + } + + private Backup findByExternalId(Long zoneId, String externalId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters("external_id", externalId); + sc.setParameters("zone_id", zoneId); + return findOneBy(sc); + } + + public BackupVO getBackupVO(Backup backup) { + BackupVO backupVO = new BackupVO(); + backupVO.setZoneId(backup.getZoneId()); + backupVO.setAccountId(backup.getAccountId()); + backupVO.setExternalId(backup.getExternalId()); + backupVO.setVmId(backup.getVmId()); + backupVO.setStatus(backup.getStatus()); + backupVO.setCreated(backup.getCreated()); + return backupVO; + } + + public void removeExistingBackups(Long zoneId, Long vmId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters("vm_id", vmId); + sc.setParameters("zone_id", zoneId); + expunge(sc); + } + + @Override + public List syncBackups(Long zoneId, Long vmId, List externalBackups) { + for (Backup backup : externalBackups) { + BackupVO backupVO = getBackupVO(backup); + persist(backupVO); + } + return listByVmId(zoneId, vmId); + } + + @Override + public List listByZoneAndState(Long zoneId, Backup.Status state) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters("zone_id", zoneId); + if (state != null) { + sc.setParameters("status", state); + return new ArrayList<>(listIncludingRemovedBy(sc)); + } + return new ArrayList<>(listBy(sc)); + } + + @Override + public BackupResponse newBackupResponse(Backup backup) { + AccountVO account = accountDao.findById(backup.getAccountId()); + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); + DataCenterVO zone = dataCenterDao.findById(backup.getZoneId()); + + BackupResponse backupResponse = new BackupResponse(); + backupResponse.setZoneId(zone.getUuid()); + backupResponse.setId(backup.getUuid()); + backupResponse.setAccountId(account.getUuid()); + backupResponse.setExternalId(backup.getExternalId()); + backupResponse.setVmId(vm.getUuid()); + backupResponse.setVolumes(backup.getVolumes()); + backupResponse.setStatus(backup.getStatus()); + backupResponse.setSize(backup.getSize()); + backupResponse.setProtectedSize(backup.getProtectedSize()); + backupResponse.setCreatedDate(backup.getCreated()); + backupResponse.setObjectName("backup"); + return backupResponse; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java new file mode 100644 index 000000000000..ecce8ffb9fb5 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java @@ -0,0 +1,32 @@ +// 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 java.util.List; + +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.backup.BackupOfferingVO; + +import com.cloud.utils.db.GenericDao; + +public interface BackupOfferingDao extends GenericDao { + BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy); + List listByZone(Long zoneId); + BackupOffering listByExternalId(String externalId); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java new file mode 100644 index 000000000000..965c219635ee --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java @@ -0,0 +1,87 @@ +// 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 java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.backup.BackupOfferingVO; + +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +public class BackupOfferingDaoImpl extends GenericDaoBase implements BackupOfferingDao { + + @Inject + DataCenterDao dataCenterDao; + + private SearchBuilder backupPoliciesSearch; + + public BackupOfferingDaoImpl() { + } + + @PostConstruct + protected void init() { + backupPoliciesSearch = createSearchBuilder(); + backupPoliciesSearch.and("zone_id", backupPoliciesSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + backupPoliciesSearch.and("external_id", backupPoliciesSearch.entity().getExternalId(), SearchCriteria.Op.EQ); + backupPoliciesSearch.done(); + } + + @Override + public BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy) { + DataCenterVO zone = dataCenterDao.findById(policy.getZoneId()); + + BackupOfferingResponse response = new BackupOfferingResponse(); + if (policy.isImported()) { + response.setId(policy.getUuid()); + if (zone != null) { + response.setZoneId(zone.getUuid()); + } + } + response.setName(policy.getName()); + response.setDescription(policy.getDescription()); + response.setExternalId(policy.getExternalId()); + response.setObjectName("backupoffering"); + return response; + } + + @Override + public List listByZone(Long zoneId) { + SearchCriteria sc = backupPoliciesSearch.create(); + if (zoneId != null) { + sc.setParameters("zone_id", zoneId); + } + return new ArrayList<>(listBy(sc)); + } + + @Override + public BackupOffering listByExternalId(String externalId) { + SearchCriteria sc = backupPoliciesSearch.create(); + sc.setParameters("external_id", externalId); + return findOneBy(sc); + } +} 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 3e0d67b61a49..af2d9f46aede 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 @@ -218,6 +218,7 @@ + @@ -285,6 +286,8 @@ + + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql index 43e282daa045..a5f44ebbeb1d 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql @@ -21,3 +21,54 @@ -- KVM: enable storage data motion on KVM hypervisor_capabilities UPDATE `cloud`.`hypervisor_capabilities` SET `storage_motion_supported` = 1 WHERE `hypervisor_capabilities`.`hypervisor_type` = 'KVM'; + +-- Backup and Recovery + +CREATE TABLE IF NOT EXISTS `cloud`.`backup_offering` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `uuid` varchar(40) NOT NULL, + `name` varchar(255) NOT NULL COMMENT 'backup offering name', + `description` varchar(255) NOT NULL COMMENT 'backup offering description', + `external_id` varchar(80) NOT NULL COMMENT 'external ID on provider side', + `zone_id` bigint(20) unsigned NOT NULL COMMENT 'zone id', + `created` datetime DEFAULT NULL, + `removed` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + CONSTRAINT `fk_backup_offering__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud`.`backups` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `uuid` varchar(40) NOT NULL, + `vm_id` bigint(20) unsigned NOT NULL, + `offering_id` bigint(20) unsigned NOT NULL, + `external_id` varchar(80) COMMENT 'backup ID on provider side', + `volumes` text, + `status` varchar(20) NOT NULL, + `size` bigint(20) DEFAULT 0, + `protected_size` bigint(20) DEFAULT 0, + `account_id` bigint(20) unsigned NOT NULL, + `zone_id` bigint(20) unsigned NOT NULL, + `created` datetime DEFAULT NULL, + `removed` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_backup__account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_backup__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_backup__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud_usage`.`usage_backup` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `zone_id` bigint(20) unsigned NOT NULL, + `account_id` bigint(20) unsigned NOT NULL, + `domain_id` bigint(20) unsigned NOT NULL, + `backup_id` bigint(20) unsigned NOT NULL, + `vm_id` bigint(20) unsigned NOT NULL, + `size` bigint(20) DEFAULT 0, + `protected_size` bigint(20) DEFAULT 0, + `created` datetime NOT NULL, + `removed` datetime, + PRIMARY KEY (`id`), + INDEX `i_usage_backup` (`zone_id`,`account_id`,`backup_id`,`vm_id`,`created`) +) ENGINE=InnoDB CHARSET=utf8; diff --git a/engine/schema/src/test/java/com/cloud/storage/dao/DiskOfferingDaoImplTest.java b/engine/schema/src/test/java/com/cloud/storage/dao/DiskOfferingDaoImplTest.java new file mode 100644 index 000000000000..3dc36d6b4d38 --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/storage/dao/DiskOfferingDaoImplTest.java @@ -0,0 +1,56 @@ +// 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 com.cloud.storage.dao; + +import com.cloud.utils.exception.CloudRuntimeException; +import org.junit.Assert; +import org.junit.Test; + +public class DiskOfferingDaoImplTest { + + private final DiskOfferingDaoImpl dao = new DiskOfferingDaoImpl(); + + @Test(expected = CloudRuntimeException.class) + public void testGetClosestDiskSizeInGBNegativeSize() { + long size = -4 * DiskOfferingDaoImpl.GB_UNIT_BYTES; + dao.getClosestDiskSizeInGB(size); + } + + @Test + public void testGetClosestDiskSizeInGBSizeGB() { + int gbUnits = 5; + long size = gbUnits * DiskOfferingDaoImpl.GB_UNIT_BYTES; + long sizeInGB = dao.getClosestDiskSizeInGB(size); + Assert.assertEquals(gbUnits, sizeInGB); + } + + @Test + public void testGetClosestDiskSizeInGBSizeGBRest() { + int gbUnits = 5; + long size = gbUnits * DiskOfferingDaoImpl.GB_UNIT_BYTES + 12345; + long sizeInGB = dao.getClosestDiskSizeInGB(size); + Assert.assertEquals(gbUnits + 1, sizeInGB); + } + + @Test + public void testGetClosestDiskSizeInGBSizeLessOneGB() { + int gbUnits = 1; + long size = gbUnits * DiskOfferingDaoImpl.GB_UNIT_BYTES - 12345; + long sizeInGB = dao.getClosestDiskSizeInGB(size); + Assert.assertEquals(gbUnits, sizeInGB); + } +} diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java index 769f9aec92f0..581588f4ca8b 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java @@ -158,7 +158,8 @@ public List aggregatePendingQuotaRecordsForAccount(final AccountVO case QuotaTypes.ISO: case QuotaTypes.VOLUME: case QuotaTypes.VM_SNAPSHOT: - qu = updateQuotaDiskUsage(usageRecord, aggregationRatio, usageRecord.getUsageType()); + case QuotaTypes.BACKUP: + qu = updateQuotaDiskUsage(usageRecord, aggregationRatio, usageRecord.getUsageType()); if (qu != null) { quotaListForAccount.add(qu); } diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaTypes.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaTypes.java index 13788f7508e9..babb4edb5203 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaTypes.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaTypes.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.quota.constant; -import org.apache.cloudstack.usage.UsageTypes; - import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.apache.cloudstack.usage.UsageTypes; + public class QuotaTypes extends UsageTypes { public static final int CPU_CLOCK_RATE = 15; public static final int CPU_NUMBER = 16; @@ -57,6 +57,7 @@ public class QuotaTypes extends UsageTypes { quotaTypeList.put(VM_SNAPSHOT, new QuotaTypes(VM_SNAPSHOT, "VM_SNAPSHOT", "GB-Month", "VM Snapshot storage usage")); quotaTypeList.put(VOLUME_SECONDARY, new QuotaTypes(VOLUME_SECONDARY, "VOLUME_SECONDARY", "GB-Month", "Volume secondary storage usage")); quotaTypeList.put(VM_SNAPSHOT_ON_PRIMARY, new QuotaTypes(VM_SNAPSHOT_ON_PRIMARY, "VM_SNAPSHOT_ON_PRIMARY", "GB-Month", "VM Snapshot primary storage usage")); + quotaTypeList.put(BACKUP, new QuotaTypes(BACKUP, "BACKUP", "GB-Month", "Backup storage usage")); quotaTypeList.put(CPU_CLOCK_RATE, new QuotaTypes(CPU_CLOCK_RATE, "CPU_CLOCK_RATE", "Compute-Month", "Quota tariff for using 1 CPU of clock rate 100MHz")); quotaTypeList.put(CPU_NUMBER, new QuotaTypes(CPU_NUMBER, "CPU_NUMBER", "Compute-Month", "Quota tariff for running VM that has 1vCPU")); quotaTypeList.put(MEMORY, new QuotaTypes(MEMORY, "MEMORY", "Compute-Month", "Quota tariff for using 1MB of RAM")); diff --git a/plugins/backup/dummy/pom.xml b/plugins/backup/dummy/pom.xml new file mode 100644 index 000000000000..5d6d7bb5be71 --- /dev/null +++ b/plugins/backup/dummy/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + cloud-plugin-backup-dummy + Apache CloudStack Plugin - Dummy Backup and Recovery Plugin + + cloudstack-plugins + org.apache.cloudstack + 4.14.0.0-SNAPSHOT + ../../pom.xml + + + + org.apache.cloudstack + cloud-api + ${project.version} + + + org.apache.cloudstack + cloud-utils + ${project.version} + + + 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 new file mode 100644 index 000000000000..4d48744f88c3 --- /dev/null +++ b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java @@ -0,0 +1,130 @@ +// 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 java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.log4j.Logger; + +import com.cloud.utils.Pair; +import com.cloud.utils.component.AdapterBase; +import com.cloud.vm.VirtualMachine; + +public class DummyBackupProvider extends AdapterBase implements BackupProvider { + + private static final Logger s_logger = Logger.getLogger(DummyBackupProvider.class); + + @Inject + private BackupDao backupDao; + + @Override + public String getName() { + return "dummy"; + } + + @Override + public String getDescription() { + return "Dummy Backup Plugin"; + } + + @Override + public List listBackupOfferings(Long zoneId) { + s_logger.debug("Listing backup policies on Dummy B&R Plugin"); + BackupOffering policy1 = new BackupOfferingVO(1, "aaaa-aaaa", "Golden Policy", "Gold description"); + BackupOffering policy2 = new BackupOfferingVO(1, "bbbb-bbbb", "Silver Policy", "Silver description"); + return Arrays.asList(policy1, policy2); + } + + @Override + public boolean isBackupOffering(Long zoneId, String uuid) { + s_logger.debug("Checking if backup offering exists on the Dummy Backup Provider"); + return true; + } + + @Override + public Backup createBackup(BackupOffering policy, VirtualMachine vm, Backup backup) { + s_logger.debug("Creating VM backup for VM " + vm.getInstanceName() + " from backup offering " + policy.getName()); + + List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + BackupVO dummyBackup = (BackupVO) backup; + dummyBackup.setStatus(Backup.Status.BackedUp); + dummyBackup.setCreated(new Date()); + backups.add(dummyBackup); + return dummyBackup; + } + + @Override + public boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String restorePointId) { + s_logger.debug("Restoring vm " + vm.getUuid() + "from backup " + backupUuid + " on the Dummy Backup Provider"); + return true; + } + + @Override + public Pair restoreBackedUpVolume(long zoneId, String backupUuid, String restorePointId, String volumeUuid, + String hostIp, String dataStoreUuid) { + s_logger.debug("Restoring volume " + volumeUuid + "from backup " + backupUuid + " on the Dummy Backup Provider"); + return new Pair<>(true, null); + } + + @Override + public List listBackups(Long zoneId, VirtualMachine vm) { + s_logger.debug("Listing VM " + vm.getInstanceName() + "backups on the Dummy Backup Provider"); + return backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + } + + @Override + public Map getBackupMetrics(Long zoneId, List backupList) { + final Map metrics = new HashMap<>(); + final Backup.Metric metric = new Backup.Metric(1000L, 100L); + for (Backup backup : backupList) { + metrics.put(backup, metric); + } + return metrics; + } + + @Override + public boolean removeBackup(VirtualMachine vm, Backup vmBackup) { + s_logger.debug("Removing VM backup " + vmBackup.getUuid() + " for VM " + vm.getInstanceName() + " on the Dummy Backup Provider"); + final List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + for (final Backup backup : backups) { + if (backup.getExternalId().equals(vmBackup.getExternalId())) { + return true; + } + } + return false; + } + + @Override + public boolean takeBackup(Backup backup) { + s_logger.debug("Starting backup " + backup.getUuid() + " on Dummy provider"); + return true; + } + + @Override + public List listBackupRestorePoints(String backupUuid, VirtualMachine vm) { + return Arrays.asList( + new Backup.RestorePoint("aaaaaaaa", "22/08/2017", "Full"), + new Backup.RestorePoint("bbbbbbbb", "23/08/2017", "Incremental")); + } +} diff --git a/plugins/backup/dummy/src/main/resources/META-INF/cloudstack/dummy-backup/module.properties b/plugins/backup/dummy/src/main/resources/META-INF/cloudstack/dummy-backup/module.properties new file mode 100644 index 000000000000..5969fb290545 --- /dev/null +++ b/plugins/backup/dummy/src/main/resources/META-INF/cloudstack/dummy-backup/module.properties @@ -0,0 +1,18 @@ +# 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. +name=dummy-backup +parent=backup diff --git a/plugins/backup/dummy/src/main/resources/META-INF/cloudstack/dummy-backup/spring-backup-dummy-context.xml b/plugins/backup/dummy/src/main/resources/META-INF/cloudstack/dummy-backup/spring-backup-dummy-context.xml new file mode 100644 index 000000000000..e154f9fe945e --- /dev/null +++ b/plugins/backup/dummy/src/main/resources/META-INF/cloudstack/dummy-backup/spring-backup-dummy-context.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/plugins/backup/veeam/pom.xml b/plugins/backup/veeam/pom.xml new file mode 100644 index 000000000000..e82bf1b921f4 --- /dev/null +++ b/plugins/backup/veeam/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + cloud-plugin-backup-veeam + Apache CloudStack Plugin - Veeam Backup and Recovery Plugin + + cloudstack-plugins + org.apache.cloudstack + 4.14.0.0-SNAPSHOT + ../../pom.xml + + + + + org.apache.cloudstack + cloud-plugin-hypervisor-vmware + ${project.version} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${cs.jackson.version} + + + org.apache.commons + commons-lang3 + ${cs.commons-lang3.version} + + + com.github.tomakehurst + wiremock-standalone + ${cs.wiremock.version} + test + + + + 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 new file mode 100644 index 000000000000..4ee847e01a3a --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java @@ -0,0 +1,259 @@ +// 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 java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import org.apache.cloudstack.backup.veeam.VeeamClient; +import org.apache.cloudstack.backup.veeam.api.Job; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.commons.collections.CollectionUtils; +import org.apache.log4j.Logger; + +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.vmware.VmwareDatacenter; +import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMap; +import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao; +import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao; +import com.cloud.utils.Pair; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachine; + +public class VeeamBackupProvider extends AdapterBase implements BackupProvider, Configurable { + + private static final Logger LOG = Logger.getLogger(VeeamBackupProvider.class); + public static final String BACKUP_IDENTIFIER = "-CSBKP-"; + + public ConfigKey VeeamUrl = new ConfigKey<>("Advanced", String.class, + "backup.plugin.veeam.url", + "http://localhost:9399/api/", + "The Veeam backup and recovery URL.", true, ConfigKey.Scope.Zone); + + private ConfigKey VeeamUsername = new ConfigKey<>("Advanced", String.class, + "backup.plugin.veeam.host.username", + "administrator", + "The Veeam backup and recovery username.", true, ConfigKey.Scope.Zone); + + private ConfigKey VeeamPassword = new ConfigKey<>("Advanced", String.class, + "backup.plugin.veeam.host.password", + "P@ssword123", + "The Veeam backup and recovery password.", true, ConfigKey.Scope.Zone); + + private ConfigKey VeeamValidateSSLSecurity = new ConfigKey<>("Advanced", Boolean.class, "backup.plugin.veeam.validate.ssl", "true", + "When set to true, this will validate the SSL certificate when connecting to https/ssl enabled Veeam API service.", true, ConfigKey.Scope.Zone); + + private ConfigKey VeeamApiRequestTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.request.timeout", "300", + "The Veeam B&R API request timeout in seconds.", true, ConfigKey.Scope.Zone); + + @Inject + private VmwareDatacenterZoneMapDao vmwareDatacenterZoneMapDao; + @Inject + private VmwareDatacenterDao vmwareDatacenterDao; + + private VeeamClient getClient(final Long zoneId) { + try { + return new VeeamClient(VeeamUrl.valueIn(zoneId), VeeamUsername.valueIn(zoneId), VeeamPassword.valueIn(zoneId), + VeeamValidateSSLSecurity.valueIn(zoneId), VeeamApiRequestTimeout.valueIn(zoneId)); + } catch (URISyntaxException e) { + throw new CloudRuntimeException("Failed to parse Veeam API URL: " + e.getMessage()); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + LOG.error("Failed to build Veeam API client due to: ", e); + } + throw new CloudRuntimeException("Failed to build Veeam API client"); + } + + public List listBackupOfferings(final Long zoneId) { + List policies = new ArrayList<>(); + for (final BackupOffering policy : getClient(zoneId).listJobs()) { + if (!policy.getName().contains(BACKUP_IDENTIFIER)) { + policies.add(policy); + } + } + return policies; + } + + @Override + public boolean isBackupOffering(final Long zoneId, final String uuid) { + List policies = listBackupOfferings(zoneId); + if (CollectionUtils.isEmpty(policies)) { + return false; + } + for (final BackupOffering policy : policies) { + if (policy.getExternalId().equals(uuid)) { + return true; + } + } + return false; + } + + private VmwareDatacenter findVmwareDatacenterForVM(final VirtualMachine vm) { + if (vm == null || vm.getHypervisorType() != Hypervisor.HypervisorType.VMware) { + throw new CloudRuntimeException("The Veeam backup provider is only applicable for VMware VMs"); + } + final VmwareDatacenterZoneMap zoneMap = vmwareDatacenterZoneMapDao.findByZoneId(vm.getDataCenterId()); + if (zoneMap == null) { + throw new CloudRuntimeException("Failed to find a mapped VMware datacenter for zone id:" + vm.getDataCenterId()); + } + final VmwareDatacenter vmwareDatacenter = vmwareDatacenterDao.findById(zoneMap.getVmwareDcId()); + if (vmwareDatacenter == null) { + throw new CloudRuntimeException("Failed to find a valid VMware datacenter mapped for zone id:" + vm.getDataCenterId()); + } + return vmwareDatacenter; + } + + public boolean addVMToBackupOffering(final BackupOffering policy, final VirtualMachine vm) { + final VmwareDatacenter vmwareDatacenter = findVmwareDatacenterForVM(vm); + return getClient(vm.getDataCenterId()).removeVMFromVeeamJob(policy.getExternalId(), vm.getInstanceName(), vmwareDatacenter.getVcenterHost()); + } + + private String getGuestBackupName(final String instanceName, final String uuid) { + return String.format("%s%s%s", instanceName, BACKUP_IDENTIFIER, uuid); + } + + @Override + public Backup createBackup(final BackupOffering policy, final VirtualMachine vm, final Backup backup) { + final VeeamClient client = getClient(vm.getDataCenterId()); + final Job parentJob = client.listJob(policy.getExternalId()); + final String clonedJobName = getGuestBackupName(vm.getInstanceName(), backup.getUuid()); + if (client.cloneVeeamJob(parentJob, clonedJobName)) { + for (BackupOffering job : client.listJobs()) { + if (job.getName().equals(clonedJobName)) { + final Job clonedJob = client.listJob(job.getExternalId()); + if (clonedJob.getScheduleConfigured() && !clonedJob.getScheduleEnabled()) { + client.toggleJobSchedule(clonedJob.getId()); + } + final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm); + if (client.addVMToVeeamJob(job.getExternalId(), vm.getInstanceName(), vmwareDC.getVcenterHost())) { + BackupVO vmBackup = ((BackupVO) backup); + vmBackup.setStatus(Backup.Status.BackedUp); + vmBackup.setExternalId(job.getExternalId()); + vmBackup.setCreated(new Date()); + if (!takeBackup(vmBackup)) { + vmBackup.setStatus(Backup.Status.Failed); + LOG.warn("Veeam provider failed to start backup job after creating a new backup for VM id: " + vm.getId()); + } + return vmBackup; + } + } + } + } else { + LOG.error("Failed to clone pre-defined Veeam job (backup offering) for policy id: " + policy.getExternalId()); + } + ((BackupVO) backup).setStatus(Backup.Status.Error); + return backup; + } + + @Override + public boolean removeBackup(final VirtualMachine vm, final Backup backup) { + final VeeamClient client = getClient(vm.getDataCenterId()); + final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm); + try { + if (!client.removeVMFromVeeamJob(backup.getExternalId(), vm.getInstanceName(), vmwareDC.getVcenterHost())) { + LOG.warn("Failed to remove VM from Veeam Job id: " + backup.getExternalId()); + } + } catch (CloudRuntimeException e) { + LOG.debug("VM was removed from the job so could not remove again, trying to delete the veeam job now.", e); + } + + final String clonedJobName = getGuestBackupName(vm.getInstanceName(), backup.getUuid()); + if (!client.deleteJobAndBackup(clonedJobName)) { + LOG.warn("Failed to remove Veeam job and backup for job: " + clonedJobName); + } + return client.listJob(clonedJobName) == null; + } + + @Override + public boolean takeBackup(final Backup backup) { + final VeeamClient client = getClient(backup.getZoneId()); + return client.startBackupJob(backup.getExternalId()); + } + + @Override + public boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String restorePointId) { + return getClient(vm.getDataCenterId()).restoreFullVM(vm.getInstanceName(), restorePointId); + } + + @Override + public Pair restoreBackedUpVolume(long zoneId, String backupUuid, String restorePointId, + String volumeUuid, String hostIp, String dataStoreUuid) { + + return getClient(zoneId).restoreVMToDifferentLocation(restorePointId, hostIp, dataStoreUuid); + } + + @Override + public List listBackups(Long zoneId, VirtualMachine vm) { + //return getClient(zoneId).listAllBackups(); + return null; + } + + @Override + public Map getBackupMetrics(final Long zoneId, final List backupList) { + final Map metrics = new HashMap<>(); + final Map backendMetrics = getClient(zoneId).getBackupMetrics(); + for (final Backup backup : backupList) { + if (!backendMetrics.containsKey(backup.getUuid())) { + continue; + } + metrics.put(backup, backendMetrics.get(backup.getUuid())); + } + return metrics; + } + + @Override + public List listBackupRestorePoints(String backupUuid, VirtualMachine vm) { + String backupName = getGuestBackupName(vm.getInstanceName(), backupUuid); + return getClient(vm.getDataCenterId()).listRestorePoints(backupName, vm.getInstanceName()); + } + + @Override + public String getConfigComponentName() { + return BackupService.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[]{ + VeeamUrl, + VeeamUsername, + VeeamPassword, + VeeamValidateSSLSecurity, + VeeamApiRequestTimeout + }; + } + + @Override + public String getName() { + return "veeam"; + } + + @Override + public String getDescription() { + return "Veeam Backup Plugin"; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java new file mode 100644 index 000000000000..60494590850b --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java @@ -0,0 +1,104 @@ +// 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.veeam; + +import java.util.Date; +import java.util.List; + +import org.apache.cloudstack.backup.Backup; + +public class VeeamBackup implements Backup { + + private String name; + private String uid; + + public VeeamBackup(String name, String uid) { + this.name = name; + this.uid = uid; + } + + @Override + public Long getZoneId() { + return null; + } + + @Override + public Long getAccountId() { + return null; + } + + @Override + public String getExternalId() { + return null; + } + + @Override + public Long getVmId() { + return null; + } + + @Override + public Long getOfferingId() { + return null; + } + + @Override + public List getBackedUpVolumes() { + return null; + } + + @Override + public String getVolumes() { + return null; + } + + @Override + public Status getStatus() { + return null; + } + + @Override + public Long getSize() { + return null; + } + + @Override + public Long getProtectedSize() { + return null; + } + + @Override + public Date getCreated() { + return null; + } + + @Override + public Date getRemoved() { + return null; + } + + @Override + public String getUuid() { + return uid; + } + + @Override + public long getId() { + return -1; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackupOffering.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackupOffering.java new file mode 100644 index 000000000000..8ea62a99a1ac --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackupOffering.java @@ -0,0 +1,66 @@ +// 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.veeam; + +import org.apache.cloudstack.backup.BackupOffering; + +public class VeeamBackupOffering implements BackupOffering { + + private String name; + private String uid; + + public VeeamBackupOffering(String name, String uid) { + this.name = name; + this.uid = uid; + } + + @Override + public String getExternalId() { + return uid; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return "Veeam Backup Offering (Job)"; + } + + @Override + public boolean isImported() { + return false; + } + + @Override + public long getZoneId() { + return -1; + } + + @Override + public String getUuid() { + return uid; + } + + @Override + public long getId() { + return -1; + } +} 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 new file mode 100644 index 000000000000..fdd3a04d399e --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java @@ -0,0 +1,663 @@ +// 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.veeam; + +import static org.apache.cloudstack.backup.VeeamBackupProvider.BACKUP_IDENTIFIER; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import java.util.UUID; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509TrustManager; + +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.veeam.api.BackupJobCloneInfo; +import org.apache.cloudstack.backup.veeam.api.CreateObjectInJobSpec; +import org.apache.cloudstack.backup.veeam.api.EntityReferences; +import org.apache.cloudstack.backup.veeam.api.HierarchyItem; +import org.apache.cloudstack.backup.veeam.api.HierarchyItems; +import org.apache.cloudstack.backup.veeam.api.Job; +import org.apache.cloudstack.backup.veeam.api.JobCloneSpec; +import org.apache.cloudstack.backup.veeam.api.Link; +import org.apache.cloudstack.backup.veeam.api.ObjectInJob; +import org.apache.cloudstack.backup.veeam.api.ObjectsInJob; +import org.apache.cloudstack.backup.veeam.api.Ref; +import org.apache.cloudstack.backup.veeam.api.RestoreSession; +import org.apache.cloudstack.backup.veeam.api.Task; +import org.apache.cloudstack.utils.security.SSLUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.AuthCache; +import org.apache.http.client.CookieStore; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.client.BasicAuthCache; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.log4j.Logger; + +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.nio.TrustAllManager; +import com.cloud.utils.ssh.SshHelper; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; + +public class VeeamClient { + private static final Logger LOG = Logger.getLogger(VeeamClient.class); + + private final URI apiURI; + + private final HttpClient httpClient; + private final HttpClientContext httpContext = HttpClientContext.create(); + private final CookieStore httpCookieStore = new BasicCookieStore(); + + private String veeamServerIp; + private String veeamServerUsername; + private String veeamServerPassword; + private final int veeamServerPort = 22; + + public VeeamClient(final String url, final String username, final String password, final boolean validateCertificate, final int timeout) throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException { + this.apiURI = new URI(url); + + final CredentialsProvider provider = new BasicCredentialsProvider(); + provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password)); + final HttpHost adminHost = new HttpHost(this.apiURI.getHost(), this.apiURI.getPort(), this.apiURI.getScheme()); + final AuthCache authCache = new BasicAuthCache(); + authCache.put(adminHost, new BasicScheme()); + + this.httpContext.setCredentialsProvider(provider); + this.httpContext.setAuthCache(authCache); + + final RequestConfig config = RequestConfig.custom() + .setConnectTimeout(timeout * 1000) + .setConnectionRequestTimeout(timeout * 1000) + .setSocketTimeout(timeout * 1000) + .build(); + + if (!validateCertificate) { + final SSLContext sslcontext = SSLUtils.getSSLContext(); + sslcontext.init(null, new X509TrustManager[]{new TrustAllManager()}, new SecureRandom()); + final SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE); + this.httpClient = HttpClientBuilder.create() + .setDefaultCredentialsProvider(provider) + .setDefaultCookieStore(httpCookieStore) + .setDefaultRequestConfig(config) + .setSSLSocketFactory(factory) + .build(); + } else { + this.httpClient = HttpClientBuilder.create() + .setDefaultCredentialsProvider(provider) + .setDefaultCookieStore(httpCookieStore) + .setDefaultRequestConfig(config) + .build(); + } + + try { + final HttpResponse response = post("/sessionMngr/", null); + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) { + throw new CloudRuntimeException("Failed to create and authenticate Veeam API client, please check the settings."); + } + } catch (final IOException e) { + throw new CloudRuntimeException("Failed to authenticate Veeam API service due to:" + e.getMessage()); + } + + setVeeamSshCredentials(this.apiURI.getHost(), username, password); + } + + protected void setVeeamSshCredentials(String hostIp, String username, String password) { + this.veeamServerIp = hostIp; + this.veeamServerUsername = username; + this.veeamServerPassword = password; + } + + private void checkAuthFailure(final HttpResponse response) { + if (response != null && response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + final Credentials credentials = httpContext.getCredentialsProvider().getCredentials(AuthScope.ANY); + LOG.error("Veeam API authentication failed, please check Veeam configuration. Admin auth principal=" + credentials.getUserPrincipal() + ", password=" + credentials.getPassword() + ", API url=" + apiURI.toString()); + throw new ServerApiException(ApiErrorCode.UNAUTHORIZED, "Veeam B&R API call unauthorized, please ask your administrator to fix integration issues."); + } + } + + private void checkResponseOK(final HttpResponse response) { + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT) { + LOG.debug("Requested Veeam resource does not exist"); + return; + } + if (!(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK || + response.getStatusLine().getStatusCode() == HttpStatus.SC_ACCEPTED) && + response.getStatusLine().getStatusCode() != HttpStatus.SC_NO_CONTENT) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to get valid response from Veeam B&R API call, please ask your administrator to diagnose and fix issues."); + } + } + + private void checkResponseTimeOut(final Exception e) { + if (e instanceof ConnectTimeoutException || e instanceof SocketTimeoutException) { + throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, "Veeam API operation timed out, please try again."); + } + } + + private HttpResponse get(final String path) throws IOException { + final HttpGet request = new HttpGet(apiURI.toString() + path); + final HttpResponse response = httpClient.execute(request, httpContext); + checkAuthFailure(response); + return response; + } + + private HttpResponse post(final String path, final Object obj) throws IOException { + String xml = null; + if (obj != null) { + XmlMapper xmlMapper = new XmlMapper(); + xml = xmlMapper.writer() + .with(ToXmlGenerator.Feature.WRITE_XML_DECLARATION) + .writeValueAsString(obj); + // Remove invalid/empty xmlns + xml = xml.replace(" xmlns=\"\"", ""); + } + + final HttpPost request = new HttpPost(apiURI.toString() + path); + request.setHeader("Content-type", "application/xml"); + if (StringUtils.isNotBlank(xml)) { + request.setEntity(new StringEntity(xml)); + } + + final HttpResponse response = httpClient.execute(request, httpContext); + checkAuthFailure(response); + return response; + } + + private HttpResponse delete(final String path) throws IOException { + final HttpResponse response = httpClient.execute(new HttpDelete(apiURI.toString() + path), httpContext); + checkAuthFailure(response); + return response; + } + + /////////////////////////////////////////////////////////////////// + //////////////// Private Veeam Helper Methods ///////////////////// + /////////////////////////////////////////////////////////////////// + + private String findDCHierarchy(final String vmwareDcName) { + LOG.debug("Trying to find hierarchy ID for vmware datacenter: " + vmwareDcName); + + try { + final HttpResponse response = get("/hierarchyRoots"); + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + final EntityReferences references = objectMapper.readValue(response.getEntity().getContent(), EntityReferences.class); + for (final Ref ref : references.getRefs()) { + if (ref.getName().equals(vmwareDcName) && ref.getType().equals("HierarchyRootReference")) { + return ref.getUid(); + } + } + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + throw new CloudRuntimeException("Failed to find hierarchy reference for VMware datacenter " + vmwareDcName + " in Veeam, please ask administrator to check Veeam B&R manager configuration"); + } + + private String lookupVM(final String hierarchyId, final String vmName) { + LOG.debug("Trying to lookup VM from veeam hierarchy:" + hierarchyId + " for vm name:" + vmName); + + try { + final HttpResponse response = get(String.format("/lookup?host=%s&type=Vm&name=%s", hierarchyId, vmName)); + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + final HierarchyItems items = objectMapper.readValue(response.getEntity().getContent(), HierarchyItems.class); + if (items == null || items.getItems() == null || items.getItems().isEmpty()) { + throw new CloudRuntimeException("Could not find VM " + vmName + " in Veeam, please ask administrator to check Veeam B&R manager"); + } + for (final HierarchyItem item : items.getItems()) { + if (item.getObjectName().equals(vmName) && item.getObjectType().equals("Vm")) { + return item.getObjectRef(); + } + } + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + throw new CloudRuntimeException("Failed to lookup VM " + vmName + " in Veeam, please ask administrator to check Veeam B&R manager configuration"); + } + + private Task parseTaskResponse(HttpResponse response) throws IOException { + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + return objectMapper.readValue(response.getEntity().getContent(), Task.class); + } + + private RestoreSession parseRestoreSessionResponse(HttpResponse response) throws IOException { + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + return objectMapper.readValue(response.getEntity().getContent(), RestoreSession.class); + } + + // FIXME: configure task timeout/limits + private boolean checkTaskStatus(final HttpResponse response) throws IOException { + final Task task = parseTaskResponse(response); + for (int i = 0; i < 60; i++) { + final HttpResponse taskResponse = get("/tasks/" + task.getTaskId()); + final Task polledTask = parseTaskResponse(taskResponse); + if (polledTask.getState().equals("Finished")) { + final HttpResponse taskDeleteResponse = delete("/tasks/" + task.getTaskId()); + if (taskDeleteResponse.getStatusLine().getStatusCode() != HttpStatus.SC_NO_CONTENT) { + LOG.warn("Operation failed for veeam task id=" + task.getTaskId()); + } + if (polledTask.getResult().getSuccess().equals("true")) { + Pair pair = getRelatedLinkPair(polledTask.getLink()); + if (pair != null) { + String url = pair.first(); + String type = pair.second(); + String path = url.replace(apiURI.toString(), ""); + if (type.equals("RestoreSession")) { + for (int j = 0; j < 60; j++) { + HttpResponse relatedResponse = get(path); + RestoreSession session = parseRestoreSessionResponse(relatedResponse); + if (session.getResult().equals("Success")) { + return true; + } + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + throw new CloudRuntimeException("Related job type: " + type + " was not successful"); + } + } + return true; + } + throw new CloudRuntimeException("Failed to assign VM to backup offering due to: " + polledTask.getResult().getMessage()); + } + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + LOG.debug("Failed to sleep while polling for Veeam task status due to: ", e); + } + } + return false; + } + + private Pair getRelatedLinkPair(List links) { + for (Link link : links) { + if (link.getRel().equals("Related")) { + return new Pair<>(link.getHref(), link.getType()); + } + } + return null; + } + + //////////////////////////////////////////////////////// + //////////////// Public Veeam APIs ///////////////////// + //////////////////////////////////////////////////////// + + public Ref listBackupRepository(final String backupServerId) { + LOG.debug("Trying to list backup repository for backup server id: " + backupServerId); + try { + final HttpResponse response = get(String.format("/backupServers/%s/repositories", backupServerId)); + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + final EntityReferences references = objectMapper.readValue(response.getEntity().getContent(), EntityReferences.class); + for (final Ref ref : references.getRefs()) { + if (ref.getType().equals("RepositoryReference")) { + return ref; + } + } + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + return null; + } + + public List listAllBackups() { + LOG.debug("Trying to list Veeam backups"); + try { + final HttpResponse response = get("/backups"); + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + final EntityReferences entityReferences = objectMapper.readValue(response.getEntity().getContent(), EntityReferences.class); + final List backups = new ArrayList<>(); + for (final Ref ref : entityReferences.getRefs()) { + backups.add(new VeeamBackup(ref.getName(), ref.getUid())); + } + return backups; + } catch (final IOException e) { + LOG.error("Failed to list Veeam backups due to:", e); + checkResponseTimeOut(e); + } + return new ArrayList<>(); + } + + public List listJobs() { + LOG.debug("Trying to list backup policies that are Veeam jobs"); + try { + final HttpResponse response = get("/jobs"); + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + final EntityReferences entityReferences = objectMapper.readValue(response.getEntity().getContent(), EntityReferences.class); + final List policies = new ArrayList<>(); + for (final Ref ref : entityReferences.getRefs()) { + policies.add(new VeeamBackupOffering(ref.getName(), ref.getUid())); + } + return policies; + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + return new ArrayList<>(); + } + + public Job listJob(final String jobId) { + LOG.debug("Trying to list veeam job id: " + jobId); + try { + final HttpResponse response = get(String.format("/jobs/%s?format=Entity", + jobId.replace("urn:veeam:Job:", ""))); + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return objectMapper.readValue(response.getEntity().getContent(), Job.class); + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } catch (final ServerApiException e) { + LOG.error(e); + } + return null; + } + + public boolean toggleJobSchedule(final String jobId) { + LOG.debug("Trying to toggle schedule for Veeam job: " + jobId); + try { + final HttpResponse response = post(String.format("/jobs/%s?action=toggleScheduleEnabled", jobId), null); + return checkTaskStatus(response); + } catch (final IOException e) { + LOG.error("Failed to toggle Veeam job schedule due to:", e); + checkResponseTimeOut(e); + } + return false; + } + + public boolean startBackupJob(final String jobId) { + LOG.debug("Trying to start ad-hoc backup for Veeam job: " + jobId); + try { + final HttpResponse response = post(String.format("/jobs/%s?action=start", jobId), null); + return checkTaskStatus(response); + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + return false; + } + + public boolean cloneVeeamJob(final Job parentJob, final String clonedJobName) { + LOG.debug("Trying to clone veeam job: " + parentJob.getUid() + " with backup uuid: " + clonedJobName); + try { + final Ref repositoryRef = listBackupRepository(parentJob.getBackupServerId()); + final BackupJobCloneInfo cloneInfo = new BackupJobCloneInfo(); + cloneInfo.setJobName(clonedJobName); + cloneInfo.setFolderName(clonedJobName); + cloneInfo.setRepositoryUid(repositoryRef.getUid()); + final JobCloneSpec cloneSpec = new JobCloneSpec(cloneInfo); + final HttpResponse response = post(String.format("/jobs/%s?action=clone", parentJob.getId()), cloneSpec); + return checkTaskStatus(response); + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + return false; + } + + public boolean addVMToVeeamJob(final String jobId, final String vmwareInstanceName, final String vmwareDcName) { + LOG.debug("Trying to add VM to backup offering that is Veeam job: " + jobId); + try { + final String heirarchyId = findDCHierarchy(vmwareDcName); + final String veeamVmRefId = lookupVM(heirarchyId, vmwareInstanceName); + final CreateObjectInJobSpec vmToBackupJob = new CreateObjectInJobSpec(); + vmToBackupJob.setObjName(vmwareInstanceName); + vmToBackupJob.setObjRef(veeamVmRefId); + final HttpResponse response = post(String.format("/jobs/%s/includes", jobId), vmToBackupJob); + return checkTaskStatus(response); + } catch (final IOException e) { + LOG.error("Failed to add VM to Veeam job due to:", e); + checkResponseTimeOut(e); + } + throw new CloudRuntimeException("Failed to add VM to backup offering likely due to timeout, please check Veeam tasks"); + } + + public boolean removeVMFromVeeamJob(final String jobId, final String vmwareInstanceName, final String vmwareDcName) { + LOG.debug("Trying to remove VM from backup offering that is a Veeam job: " + jobId); + try { + final String heirarchyId = findDCHierarchy(vmwareDcName); + final String veeamVmRefId = lookupVM(heirarchyId, vmwareInstanceName); + final HttpResponse response = get(String.format("/jobs/%s/includes", jobId)); + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + final ObjectsInJob jobObjects = objectMapper.readValue(response.getEntity().getContent(), ObjectsInJob.class); + if (jobObjects == null || jobObjects.getObjects() == null) { + LOG.warn("No objects found in the Veeam job " + jobId); + return false; + } + for (final ObjectInJob jobObject : jobObjects.getObjects()) { + if (jobObject.getName().equals(vmwareInstanceName) && jobObject.getHierarchyObjRef().equals(veeamVmRefId)) { + final HttpResponse deleteResponse = delete(String.format("/jobs/%s/includes/%s", jobId, jobObject.getObjectInJobId())); + return checkTaskStatus(deleteResponse); + } + } + LOG.warn(vmwareInstanceName + " VM was not found to be attached to Veaam job (backup offering): " + jobId); + return false; + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + return false; + } + + public boolean restoreFullVM(final String vmwareInstanceName, final String restorePointId) { + LOG.debug("Trying to restore full VM: " + vmwareInstanceName + " from backup"); + try { + final HttpResponse response = post(String.format("/vmRestorePoints/%s?action=restore", restorePointId), null); + return checkTaskStatus(response); + } catch (final IOException e) { + LOG.error("Failed to restore full VM due to: ", e); + checkResponseTimeOut(e); + } + throw new CloudRuntimeException("Failed to restore full VM from backup"); + } + + ///////////////////////////////////////////////////////////////// + //////////////// Public Veeam PS based APIs ///////////////////// + ///////////////////////////////////////////////////////////////// + + /** + * Generate a single command to be passed through SSH + */ + protected String transformPowerShellCommandList(List cmds) { + StringJoiner joiner = new StringJoiner(";"); + joiner.add("PowerShell Add-PSSnapin VeeamPSSnapin"); + for (String cmd : cmds) { + joiner.add(cmd); + } + return joiner.toString(); + } + + /** + * Execute a list of commands in a single call on PowerShell through SSH + */ + protected Pair executePowerShellCommands(List cmds) { + try { + // FIXME: make ssh timeouts configurable + Pair pairResult = SshHelper.sshExecute(veeamServerIp, veeamServerPort, + veeamServerUsername, null, veeamServerPassword, + transformPowerShellCommandList(cmds), + 120000, 120000, 900000); + return pairResult; + } catch (Exception e) { + throw new CloudRuntimeException("Error while executing PowerShell commands due to: " + e.getMessage()); + } + } + + public boolean setJobSchedule(final String jobName) { + Pair result = executePowerShellCommands(Arrays.asList( + String.format("$job = Get-VBRJob -Name \"%s\"", jobName), + "if ($job) { Set-VBRJobSchedule -Job $job -Daily -At \"11:00\" -DailyKind Weekdays }" + )); + return result.first() && !result.second().isEmpty() && !result.second().contains("Failed to delete"); + } + + public boolean deleteJobAndBackup(final String jobName) { + Pair result = executePowerShellCommands(Arrays.asList( + String.format("$job = Get-VBRJob -Name \"%s\"", jobName), + "if ($job) { Remove-VBRJob -Job $job -Confirm:$false }", + String.format("$backup = Get-VBRBackup -Name \"%s\"", jobName), + "if ($backup) { Remove-VBRBackup -Backup $backup -FromDisk -Confirm:$false }", + "$repo = Get-VBRBackupRepository", + "Sync-VBRBackupRepository -Repository $repo" + )); + return result.first() && !result.second().contains("Failed to delete"); + } + + public Map getBackupMetrics() { + final String separator = "====="; + final List cmds = Arrays.asList( + "$backups = Get-VBRBackup", + "foreach ($backup in $backups) {" + + "$backup.JobName;" + + "$storageGroups = $backup.GetStorageGroups();" + + "foreach ($group in $storageGroups) {" + + "$usedSize = 0;" + + "$dataSize = 0;" + + "$sizePerStorage = $group.GetStorages().Stats.BackupSize;" + + "$dataPerStorage = $group.GetStorages().Stats.DataSize;" + + "foreach ($size in $sizePerStorage) {" + + "$usedSize += $size;" + + "}" + + "foreach ($size in $dataPerStorage) {" + + "$dataSize += $size;" + + "}" + + "$usedSize;" + + "$dataSize;" + + "}" + + "echo \"" + separator + "\"" + + "}" + ); + Pair response = executePowerShellCommands(cmds); + final Map sizes = new HashMap<>(); + for (final String block : response.second().split(separator + "\r\n")) { + final String[] parts = block.split("\r\n"); + if (parts.length != 3) { + continue; + } + final String backupName = parts[0]; + if (backupName != null && backupName.contains(BACKUP_IDENTIFIER)) { + final String[] names = backupName.split(BACKUP_IDENTIFIER); + sizes.put(names[names.length - 1], new Backup.Metric(Long.valueOf(parts[1]), Long.valueOf(parts[2]))); + } + } + return sizes; + } + + private Backup.RestorePoint getRestorePointFromBlock(String[] parts) { + String id = null; + String created = null; + String type = null; + for (String part : parts) { + if (part.matches("Id(\\s)+:(.)*")) { + String[] split = part.split(":"); + id = split[1].trim(); + } else if (part.matches("CreationTime(\\s)+:(.)*")) { + String [] split = part.split(":", 2); + created = split[1].trim(); + } else if (part.matches("Type(\\s)+:(.)*")) { + String [] split = part.split(":"); + type = split[1].trim(); + } + } + return new Backup.RestorePoint(id, created, type); + } + + public List listRestorePoints(String backupName, String vmInternalName) { + final List cmds = Arrays.asList( + String.format("$backup = Get-VBRBackup -Name \"%s\"", backupName), + String.format("Get-VBRRestorePoint -Backup:$backup -Name \"%s\"", vmInternalName) + ); + Pair response = executePowerShellCommands(cmds); + if (response == null || !response.first()) { + throw new CloudRuntimeException("Failed to list restore points"); + } + final List restorePoints = new ArrayList<>(); + for (final String block : response.second().split("\r\n\r\n")) { + if (block.isEmpty()) { + continue; + } + final String[] parts = block.split("\r\n"); + restorePoints.add(getRestorePointFromBlock(parts)); + } + return restorePoints; + } + + public Pair restoreVMToDifferentLocation(String restorePointId, String hostIp, String dataStoreUuid) { + final String restoreLocation = "CS-RSTR-" + UUID.randomUUID().toString(); + final String datastoreId = dataStoreUuid.replace("-",""); + final List cmds = Arrays.asList( + "$points = Get-VBRRestorePoint", + String.format("foreach($point in $points) { if ($point.Id -eq '%s') { break; } }",restorePointId), + String.format("$server = Get-VBRServer -Name \"%s\"", hostIp), + String.format("$ds = Find-VBRViDatastore -Server:$server -Name \"%s\"", datastoreId), + String.format("Start-VBRRestoreVM -RestorePoint:$point -Server:$server -Datastore:$ds -VMName \"%s\"", restoreLocation) + ); + Pair result = executePowerShellCommands(cmds); + if (result == null || !result.first()) { + throw new CloudRuntimeException("Failed to restore VM to location " + restoreLocation); + } + return new Pair<>(result.first(), restoreLocation); + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObject.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObject.java new file mode 100644 index 000000000000..6ecf08081d86 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObject.java @@ -0,0 +1,28 @@ +// 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.veeam; + +import java.util.List; + +public interface VeeamObject { + String getUuid(); + String getName(); + String getHref(); + String getType(); + List getLinks(); +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/BackupJobCloneInfo.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/BackupJobCloneInfo.java new file mode 100644 index 000000000000..fdb6081c6fec --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/BackupJobCloneInfo.java @@ -0,0 +1,58 @@ +// 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.veeam.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "BackupJobCloneInfo") +public class BackupJobCloneInfo { + + @JacksonXmlProperty(localName = "JobName") + private String jobName; + + @JacksonXmlProperty(localName = "FolderName") + private String folderName; + + @JacksonXmlProperty(localName = "RepositoryUid") + private String repositoryUid; + + public String getJobName() { + return jobName; + } + + public void setJobName(String jobName) { + this.jobName = jobName; + } + + public String getFolderName() { + return folderName; + } + + public void setFolderName(String folderName) { + this.folderName = folderName; + } + + public String getRepositoryUid() { + return repositoryUid; + } + + public void setRepositoryUid(String repositoryUid) { + this.repositoryUid = repositoryUid; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/CreateObjectInJobSpec.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/CreateObjectInJobSpec.java new file mode 100644 index 000000000000..16de447d3c06 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/CreateObjectInJobSpec.java @@ -0,0 +1,46 @@ +// 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.veeam.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "CreateObjectInJobSpec", namespace = "http://www.veeam.com/ent/v1.0") +public class CreateObjectInJobSpec { + @JacksonXmlProperty(localName = "HierarchyObjRef") + String objRef; + + @JacksonXmlProperty(localName = "HierarchyObjName") + String objName; + + public String getObjRef() { + return objRef; + } + + public void setObjRef(String objRef) { + this.objRef = objRef; + } + + public String getObjName() { + return objName; + } + + public void setObjName(String objName) { + this.objName = objName; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/EntityReferences.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/EntityReferences.java new file mode 100644 index 000000000000..928e0dae4a56 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/EntityReferences.java @@ -0,0 +1,39 @@ +// 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.veeam.api; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "EntityReferences") +public class EntityReferences { + @JacksonXmlProperty(localName = "Ref") + @JacksonXmlElementWrapper(localName = "Ref", useWrapping = false) + private List refs; + + public List getRefs() { + return refs; + } + + public void setRefs(List refs) { + this.refs = refs; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/HierarchyItem.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/HierarchyItem.java new file mode 100644 index 000000000000..46f0e5e420cc --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/HierarchyItem.java @@ -0,0 +1,68 @@ +// 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.veeam.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "HierarchyItem") +public class HierarchyItem { + @JacksonXmlProperty(localName = "Type", isAttribute = true) + private String type; + + @JacksonXmlProperty(localName = "ObjectRef") + private String objectRef; + + @JacksonXmlProperty(localName = "ObjectType") + private String objectType; + + @JacksonXmlProperty(localName = "ObjectName") + private String objectName; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getObjectRef() { + return objectRef; + } + + public void setObjectRef(String objectRef) { + this.objectRef = objectRef; + } + + public String getObjectType() { + return objectType; + } + + public void setObjectType(String objectType) { + this.objectType = objectType; + } + + public String getObjectName() { + return objectName; + } + + public void setObjectName(String objectName) { + this.objectName = objectName; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/HierarchyItems.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/HierarchyItems.java new file mode 100644 index 000000000000..a87c6b0f9839 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/HierarchyItems.java @@ -0,0 +1,39 @@ +// 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.veeam.api; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "HierarchyItems") +public class HierarchyItems { + @JacksonXmlProperty(localName = "HierarchyItem") + @JacksonXmlElementWrapper(localName = "HierarchyItem", useWrapping = false) + private List items; + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Job.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Job.java new file mode 100644 index 000000000000..e42ecf0d4ccc --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Job.java @@ -0,0 +1,163 @@ +// 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.veeam.api; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "Job") +public class Job { + + @JacksonXmlProperty(localName = "Name", isAttribute = true) + private String name; + + @JacksonXmlProperty(localName = "Href", isAttribute = true) + private String href; + + @JacksonXmlProperty(localName = "Type", isAttribute = true) + private String type; + + @JacksonXmlProperty(localName = "UID", isAttribute = true) + private String uid; + + @JacksonXmlProperty(localName = "Link") + @JacksonXmlElementWrapper(localName = "Links") + private List link; + + @JacksonXmlProperty(localName = "Platform") + private String platform; + + @JacksonXmlProperty(localName = "Description") + private String description; + + @JacksonXmlProperty(localName = "NextRun") + private String nextRun; + + @JacksonXmlProperty(localName = "JobType") + private String jobType; + + @JacksonXmlProperty(localName = "ScheduleEnabled") + private Boolean scheduleEnabled; + + @JacksonXmlProperty(localName = "ScheduleConfigured") + private Boolean scheduleConfigured; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getUid() { + return uid; + } + + public String getId() { + return uid.replace("urn:veeam:Job:", ""); + } + + public void setUid(String uid) { + this.uid = uid; + } + + public List getLink() { + return link; + } + + public void setLink(List link) { + this.link = link; + } + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getNextRun() { + return nextRun; + } + + public void setNextRun(String nextRun) { + this.nextRun = nextRun; + } + + public String getJobType() { + return jobType; + } + + public void setJobType(String jobType) { + this.jobType = jobType; + } + + public String getBackupServerId() { + for (final Link l : link) { + if (l.getType().equals("BackupServerReference")) { + return "" + l.getHref().split("backupServers/")[1]; + } + } + return null; + } + + public Boolean getScheduleEnabled() { + return scheduleEnabled; + } + + public void setScheduleEnabled(String scheduleEnabled) { + this.scheduleEnabled = Boolean.valueOf(scheduleEnabled); + } + + public Boolean getScheduleConfigured() { + return scheduleConfigured; + } + + public void setScheduleConfigured(String scheduleConfigured) { + this.scheduleConfigured = Boolean.valueOf(scheduleConfigured); + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/JobCloneSpec.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/JobCloneSpec.java new file mode 100644 index 000000000000..fd1bdfbdedfd --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/JobCloneSpec.java @@ -0,0 +1,41 @@ +// 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.veeam.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "JobCloneSpec", namespace = "http://www.veeam.com/ent/v1.0") +public class JobCloneSpec { + @JacksonXmlProperty(localName = "BackupJobCloneInfo") + @JacksonXmlElementWrapper(localName = "BackupJobCloneInfo", useWrapping = false) + BackupJobCloneInfo jobCloneInfo; + + public JobCloneSpec(final BackupJobCloneInfo jobCloneInfo) { + this.jobCloneInfo = jobCloneInfo; + } + + public BackupJobCloneInfo getJobCloneInfo() { + return jobCloneInfo; + } + + public void setJobCloneInfo(BackupJobCloneInfo jobCloneInfo) { + this.jobCloneInfo = jobCloneInfo; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Link.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Link.java new file mode 100644 index 000000000000..b89d77fa550a --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Link.java @@ -0,0 +1,69 @@ +// 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.veeam.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "Link") +public class Link { + + @JacksonXmlProperty(localName = "Name", isAttribute = true) + private String name; + + @JacksonXmlProperty(localName = "Href", isAttribute = true) + private String href; + + @JacksonXmlProperty(localName = "Type", isAttribute = true) + private String type; + + @JacksonXmlProperty(localName = "Rel", isAttribute = true) + private String rel; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getRel() { + return rel; + } + + public void setRel(String rel) { + this.rel = rel; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/ObjectInJob.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/ObjectInJob.java new file mode 100644 index 000000000000..987176e59f89 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/ObjectInJob.java @@ -0,0 +1,94 @@ +// 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.veeam.api; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "ObjectInJob") +public class ObjectInJob { + @JacksonXmlProperty(localName = "Href", isAttribute = true) + private String href; + + @JacksonXmlProperty(localName = "Type", isAttribute = true) + private String type; + + @JacksonXmlProperty(localName = "Link") + @JacksonXmlElementWrapper(localName = "Links") + private List link; + + @JacksonXmlProperty(localName = "ObjectInJobId", isAttribute = true) + private String objectInJobId; + + @JacksonXmlProperty(localName = "HierarchyObjRef", isAttribute = true) + private String hierarchyObjRef; + + @JacksonXmlProperty(localName = "Name", isAttribute = true) + private String name; + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List getLink() { + return link; + } + + public void setLink(List link) { + this.link = link; + } + + public String getObjectInJobId() { + return objectInJobId; + } + + public void setObjectInJobId(String objectInJobId) { + this.objectInJobId = objectInJobId; + } + + public String getHierarchyObjRef() { + return hierarchyObjRef; + } + + public void setHierarchyObjRef(String hierarchyObjRef) { + this.hierarchyObjRef = hierarchyObjRef; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/ObjectsInJob.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/ObjectsInJob.java new file mode 100644 index 000000000000..982dae592dca --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/ObjectsInJob.java @@ -0,0 +1,39 @@ +// 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.veeam.api; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "ObjectsInJob") +public class ObjectsInJob { + @JacksonXmlProperty(localName = "ObjectInJob") + @JacksonXmlElementWrapper(localName = "ObjectInJob", useWrapping = false) + private List objects; + + public List getObjects() { + return objects; + } + + public void setObjects(List objects) { + this.objects = objects; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Ref.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Ref.java new file mode 100644 index 000000000000..683fd3773f6c --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Ref.java @@ -0,0 +1,83 @@ +// 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.veeam.api; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "Ref") +public class Ref { + @JacksonXmlProperty(localName = "UID", isAttribute = true) + private String uid; + + @JacksonXmlProperty(localName = "Name", isAttribute = true) + private String name; + + @JacksonXmlProperty(localName = "Href", isAttribute = true) + private String href; + + @JacksonXmlProperty(localName = "Type", isAttribute = true) + private String type; + + @JacksonXmlProperty(localName = "Link") + @JacksonXmlElementWrapper(localName = "Links") + private List link; + + public List getLink() { + return link; + } + + public void setLink(List link) { + this.link = link; + } + + public String getUid() { + return uid; + } + + public void setUid(String uid) { + this.uid = uid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/RestoreSession.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/RestoreSession.java new file mode 100644 index 000000000000..0675e49fc276 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/RestoreSession.java @@ -0,0 +1,120 @@ +// 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.veeam.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +import java.util.List; + +@JacksonXmlRootElement(localName = "RestoreSession") +public class RestoreSession { + + @JacksonXmlProperty(localName = "Type", isAttribute = true) + private String type; + + @JacksonXmlProperty(localName = "Href", isAttribute = true) + private String href; + + @JacksonXmlProperty(localName = "Name", isAttribute = true) + private String name; + + @JacksonXmlProperty(localName = "UID", isAttribute = true) + private String uid; + + @JacksonXmlProperty(localName = "VmDisplayName", isAttribute = true) + private String vmDisplayName; + + @JacksonXmlProperty(localName = "Link") + @JacksonXmlElementWrapper(localName = "Links") + private List link; + + @JacksonXmlProperty(localName = "JobType") + private String jobType; + + @JacksonXmlProperty(localName = "CreationTimeUTC") + private String creationTimeUTC; + + @JacksonXmlProperty(localName = "EndTimeUTC") + private String endTimeUTC; + + @JacksonXmlProperty(localName = "State") + private String state; + + @JacksonXmlProperty(localName = "Result") + private String result; + + @JacksonXmlProperty(localName = "Progress") + private String progress; + + @JacksonXmlProperty(localName = "RestoredObjRef") + private String restoredObjRef; + + public List getLink() { + return link; + } + + public String getJobType() { + return jobType; + } + + public String getState() { + return state; + } + + public String getResult() { + return result; + } + + public String getType() { + return type; + } + + public String getHref() { + return href; + } + + public String getVmDisplayName() { + return vmDisplayName; + } + + public String getCreationTimeUTC() { + return creationTimeUTC; + } + + public String getEndTimeUTC() { + return endTimeUTC; + } + + public String getProgress() { + return progress; + } + + public String getName() { + return name; + } + + public String getUid() { + return uid; + } + + public String getRestoredObjRef() { + return restoredObjRef; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Result.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Result.java new file mode 100644 index 000000000000..26fc8634184f --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Result.java @@ -0,0 +1,47 @@ +// 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.veeam.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "Result") +public class Result { + + @JacksonXmlProperty(localName = "Success", isAttribute = true) + private String success; + + @JacksonXmlProperty(localName = "Message") + private String message; + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Task.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Task.java new file mode 100644 index 000000000000..e8d14e57d488 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Task.java @@ -0,0 +1,106 @@ +// 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.veeam.api; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "CreateObjectInJobSpec") +public class Task { + @JacksonXmlProperty(localName = "Type", isAttribute = true) + private String type; + + @JacksonXmlProperty(localName = "Href", isAttribute = true) + private String href; + + @JacksonXmlProperty(localName = "Link") + @JacksonXmlElementWrapper(localName = "Links") + private List link; + + @JacksonXmlProperty(localName = "TaskId") + private String taskId; + + @JacksonXmlProperty(localName = "State") + private String state; + + @JacksonXmlProperty(localName = "Operation") + private String operation; + + @JacksonXmlProperty(localName = "Result") + @JacksonXmlElementWrapper(localName = "Result", useWrapping = false) + private Result result; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public List getLink() { + return link; + } + + public void setLink(List link) { + this.link = link; + } + + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + + public Result getResult() { + return result; + } + + public void setResult(Result result) { + this.result = result; + } +} diff --git a/plugins/backup/veeam/src/main/resources/META-INF/cloudstack/veeam/module.properties b/plugins/backup/veeam/src/main/resources/META-INF/cloudstack/veeam/module.properties new file mode 100644 index 000000000000..ee40b21a323d --- /dev/null +++ b/plugins/backup/veeam/src/main/resources/META-INF/cloudstack/veeam/module.properties @@ -0,0 +1,18 @@ +# 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. +name=veeam +parent=backup diff --git a/plugins/backup/veeam/src/main/resources/META-INF/cloudstack/veeam/spring-backup-veeam-context.xml b/plugins/backup/veeam/src/main/resources/META-INF/cloudstack/veeam/spring-backup-veeam-context.xml new file mode 100644 index 000000000000..f2403cf37a4b --- /dev/null +++ b/plugins/backup/veeam/src/main/resources/META-INF/cloudstack/veeam/spring-backup-veeam-context.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java b/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java new file mode 100644 index 000000000000..509d5ecc34e1 --- /dev/null +++ b/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java @@ -0,0 +1,113 @@ +// 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.veeam; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; + +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.veeam.api.Job; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.cloud.utils.Pair; +import com.github.tomakehurst.wiremock.client.BasicCredentials; +import com.github.tomakehurst.wiremock.junit.WireMockRule; + +public class VeeamClientTest { + + private String adminUsername = "administrator"; + private String adminPassword = "P@ssword123"; + private VeeamClient client; + + @Rule + public WireMockRule wireMockRule = new WireMockRule(9399); + + @Before + public void setUp() throws Exception { + wireMockRule.stubFor(post(urlMatching(".*/sessionMngr/")) + .willReturn(aResponse() + .withStatus(201) + .withHeader("X-RestSvcSessionId", "some-session-auth-id") + .withBody(""))); + client = new VeeamClient("http://localhost:9399/api/", adminUsername, adminPassword, true, 60); + } + + @Test + public void testBasicAuth() { + verify(postRequestedFor(urlMatching(".*/sessionMngr/")) + .withBasicAuth(new BasicCredentials(adminUsername, adminPassword))); + } + + @Test + public void testVeeamJobs() { + wireMockRule.stubFor(get(urlMatching(".*/jobs")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/xml") + .withStatus(200) + .withBody("\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""))); + List policies = client.listJobs(); + verify(getRequestedFor(urlMatching(".*/jobs"))); + Assert.assertEquals(policies.size(), 1); + Assert.assertEquals(policies.get(0).getName(), "ZONE1-GOLD"); + } + + public void testJob() throws Exception { + VeeamClient client = new VeeamClient("http://10.2.2.89:9399/api/", adminUsername, adminPassword, true, 30); + Job j = client.listJob("8acac50d-3711-4c99-bf7b-76fe9c7e39c3"); + boolean result = client.cloneVeeamJob(j, "some-uuid-cloned"); + client.deleteJobAndBackup("some-uuid-cloned"); + } + + public void testSetJobSchedule() throws Exception { + VeeamClient client = new VeeamClient("http://10.2.2.89:9399/api/", adminUsername, adminPassword, true, 30); + client.setJobSchedule("ZONE1-GOLD"); + } + + public void testVeeamPS() throws Exception { + VeeamClient client = new VeeamClient("http://10.2.2.89:9399/api/", adminUsername, adminPassword, true, 30); + Map sizes = client.getBackupMetrics(); + Job j = client.listJob("ZONE1-GOLD_clone1"); + } + + public void testRestoreVolume() { + client.setVeeamSshCredentials("10.2.2.89", "administrator", "P@ssword123"); + Pair booleanStringPair = client.restoreVMToDifferentLocation("362fcba7-283b-4416-9763-55ec59bd1285", "10.2.2.9", "24abcb8f-4211-374f-a2e1-e5c0b7e88a2d"); + booleanStringPair.first(); + } +} \ No newline at end of file diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java index 072ab9f6fed0..142b68043f39 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java @@ -27,32 +27,26 @@ import javax.inject.Inject; -import com.cloud.agent.api.MigrateVmToPoolCommand; -import com.cloud.agent.api.UnregisterVMCommand; -import com.cloud.agent.api.storage.OVFPropertyTO; -import com.cloud.agent.api.to.VolumeTO; -import com.cloud.dc.ClusterDetailsDao; -import com.cloud.storage.StoragePool; -import com.cloud.storage.TemplateOVFPropertyVO; -import com.cloud.storage.VMTemplateStoragePoolVO; -import com.cloud.storage.VMTemplateStorageResourceAssoc; -import com.cloud.storage.dao.TemplateOVFPropertiesDao; -import com.cloud.storage.dao.VMTemplatePoolDao; +import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.storage.command.CopyCommand; -import org.apache.cloudstack.storage.command.DownloadCommand; import org.apache.cloudstack.storage.command.DeleteCommand; +import org.apache.cloudstack.storage.command.DownloadCommand; import org.apache.cloudstack.storage.command.StorageSubSystemCommand; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import com.cloud.agent.api.BackupSnapshotCommand; @@ -60,10 +54,13 @@ import com.cloud.agent.api.CreatePrivateTemplateFromSnapshotCommand; import com.cloud.agent.api.CreatePrivateTemplateFromVolumeCommand; import com.cloud.agent.api.CreateVolumeFromSnapshotCommand; +import com.cloud.agent.api.MigrateVmToPoolCommand; import com.cloud.agent.api.UnregisterNicCommand; +import com.cloud.agent.api.UnregisterVMCommand; import com.cloud.agent.api.storage.CopyVolumeCommand; import com.cloud.agent.api.storage.CreateEntityDownloadURLCommand; import com.cloud.agent.api.storage.CreateVolumeOVACommand; +import com.cloud.agent.api.storage.OVFPropertyTO; import com.cloud.agent.api.storage.PrepareOVAPackingCommand; import com.cloud.agent.api.to.DataObjectType; import com.cloud.agent.api.to.DataStoreTO; @@ -71,8 +68,11 @@ import com.cloud.agent.api.to.DiskTO; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.api.to.VolumeTO; import com.cloud.cluster.ClusterManager; +import com.cloud.dc.ClusterDetailsDao; import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; @@ -80,46 +80,92 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.HypervisorGuru; import com.cloud.hypervisor.HypervisorGuruBase; +import com.cloud.hypervisor.vmware.VmwareDatacenterVO; +import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMapVO; +import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao; +import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao; import com.cloud.hypervisor.vmware.manager.VmwareManager; +import com.cloud.hypervisor.vmware.mo.DatacenterMO; import com.cloud.hypervisor.vmware.mo.DiskControllerType; +import com.cloud.hypervisor.vmware.mo.NetworkMO; +import com.cloud.hypervisor.vmware.mo.VirtualDiskManagerMO; import com.cloud.hypervisor.vmware.mo.VirtualEthernetCardType; +import com.cloud.hypervisor.vmware.mo.VirtualMachineDiskInfoBuilder; +import com.cloud.hypervisor.vmware.mo.VirtualMachineMO; +import com.cloud.hypervisor.vmware.resource.VmwareContextFactory; +import com.cloud.hypervisor.vmware.util.VmwareContext; +import com.cloud.network.Network; import com.cloud.network.Network.Provider; import com.cloud.network.Network.Service; import com.cloud.network.NetworkModel; +import com.cloud.network.Networks; import com.cloud.network.Networks.BroadcastDomainType; import com.cloud.network.Networks.TrafficType; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.PhysicalNetworkDao; import com.cloud.network.dao.PhysicalNetworkTrafficTypeDao; import com.cloud.network.dao.PhysicalNetworkTrafficTypeVO; +import com.cloud.network.dao.PhysicalNetworkVO; import com.cloud.secstorage.CommandExecLogDao; import com.cloud.secstorage.CommandExecLogVO; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DataStoreRole; +import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.GuestOSHypervisorVO; import com.cloud.storage.GuestOSVO; import com.cloud.storage.Storage; +import com.cloud.storage.StoragePool; +import com.cloud.storage.TemplateOVFPropertyVO; +import com.cloud.storage.VMTemplateStoragePoolVO; +import com.cloud.storage.VMTemplateStorageResourceAssoc; +import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.GuestOSDao; import com.cloud.storage.dao.GuestOSHypervisorDao; +import com.cloud.storage.dao.TemplateOVFPropertiesDao; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VMTemplatePoolDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.secondary.SecondaryStorageVmManager; import com.cloud.template.VirtualMachineTemplate.BootloaderType; import com.cloud.utils.Pair; +import com.cloud.utils.UuidUtils; import com.cloud.utils.db.DB; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackWithException; +import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.Nic; import com.cloud.vm.NicProfile; import com.cloud.vm.NicVO; import com.cloud.vm.SecondaryStorageVmVO; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.Type; import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; +import com.google.gson.Gson; +import com.vmware.vim25.ManagedObjectReference; +import com.vmware.vim25.VirtualDevice; +import com.vmware.vim25.VirtualDeviceBackingInfo; +import com.vmware.vim25.VirtualDeviceConnectInfo; +import com.vmware.vim25.VirtualDisk; +import com.vmware.vim25.VirtualDiskFlatVer2BackingInfo; +import com.vmware.vim25.VirtualE1000; +import com.vmware.vim25.VirtualEthernetCardNetworkBackingInfo; +import com.vmware.vim25.VirtualMachineConfigSummary; +import com.vmware.vim25.VirtualMachineRuntimeInfo; public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Configurable { private static final Logger s_logger = Logger.getLogger(VMwareGuru.class); @@ -161,7 +207,21 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co @Inject VolumeDataFactory _volFactory; @Inject - private VMTemplatePoolDao templateSpoolDao; + private VmwareDatacenterDao vmwareDatacenterDao; + @Inject + private VmwareDatacenterZoneMapDao vmwareDatacenterZoneMapDao; + @Inject + private ServiceOfferingDao serviceOfferingDao; + @Inject + private VMTemplatePoolDao templateStoragePoolDao; + @Inject + private VMTemplateDao vmTemplateDao; + @Inject + private UserVmDao userVmDao; + @Inject + private DiskOfferingDao diskOfferingDao; + @Inject + private PhysicalNetworkDao physicalNetworkDao; @Inject private TemplateOVFPropertiesDao templateOVFPropertiesDao; @@ -398,7 +458,7 @@ public VirtualMachineTO implement(VirtualMachineProfile vm) { long dataCenterId = storagePoolVO.getDataCenterId(); List pools = _storagePoolDao.listByDataCenterId(dataCenterId); for (StoragePoolVO pool : pools) { - VMTemplateStoragePoolVO ref = templateSpoolDao.findByPoolTemplate(pool.getId(), vm.getTemplateId()); + VMTemplateStoragePoolVO ref = templateStoragePoolDao.findByPoolTemplate(pool.getId(), vm.getTemplateId()); if (ref != null && ref.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED) { templateInstallPath = ref.getInstallPath(); break; @@ -718,6 +778,692 @@ public Map getClusterSettings(long vmId) { return details; } + /** + * Get vmware datacenter mapped to the zoneId + */ + private VmwareDatacenterVO getVmwareDatacenter(long zoneId) { + VmwareDatacenterZoneMapVO zoneMap = vmwareDatacenterZoneMapDao.findByZoneId(zoneId); + long vmwareDcId = zoneMap.getVmwareDcId(); + return vmwareDatacenterDao.findById(vmwareDcId); + } + + /** + * Get Vmware datacenter MO + */ + private DatacenterMO getDatacenterMO(long zoneId) throws Exception { + VmwareDatacenterVO vmwareDatacenter = getVmwareDatacenter(zoneId); + VmwareContext context = VmwareContextFactory.getContext(vmwareDatacenter.getVcenterHost(), + vmwareDatacenter.getUser(), vmwareDatacenter.getPassword()); + DatacenterMO dcMo = new DatacenterMO(context, vmwareDatacenter.getVmwareDatacenterName()); + ManagedObjectReference dcMor = dcMo.getMor(); + if (dcMor == null) { + String msg = "Error while getting Vmware datacenter " + vmwareDatacenter.getVmwareDatacenterName(); + s_logger.error(msg); + throw new InvalidParameterValueException(msg); + } + return dcMo; + } + + /** + * Get guest OS ID for VM being imported. + * If it cannot be found it is mapped to: "Other (64-bit)" ID + */ + private Long getImportingVMGuestOs(VirtualMachineConfigSummary configSummary) { + String guestFullName = configSummary.getGuestFullName(); + GuestOSVO os = _guestOsDao.listByDisplayName(guestFullName); + return os != null ? os.getId() : _guestOsDao.listByDisplayName("Other (64-bit)").getId(); + } + + /** + * Create and persist service offering + */ + private ServiceOfferingVO createServiceOfferingForVMImporting(Integer cpus, Integer memory, Integer maxCpuUsage) { + String name = "Imported-" + cpus + "-" + memory; + ServiceOfferingVO vo = new ServiceOfferingVO(name, cpus, memory, maxCpuUsage, null, null, + false, name, Storage.ProvisioningType.THIN, false, false, + null, false, Type.User, false); + return serviceOfferingDao.persist(vo); + } + + /** + * Get service offering ID for VM being imported. + * If it cannot be found it creates one and returns its ID + */ + private Long getImportingVMServiceOffering(VirtualMachineConfigSummary configSummary, + VirtualMachineRuntimeInfo runtimeInfo) { + Integer numCpu = configSummary.getNumCpu(); + Integer memorySizeMB = configSummary.getMemorySizeMB(); + Integer maxCpuUsage = runtimeInfo.getMaxCpuUsage(); + List offerings = serviceOfferingDao.listPublicByCpuAndMemory(numCpu, memorySizeMB); + return CollectionUtils.isEmpty(offerings) ? + createServiceOfferingForVMImporting(numCpu, memorySizeMB, maxCpuUsage).getId() : + offerings.get(0).getId(); + } + + /** + * Check if disk is ROOT disk + */ + private boolean isRootDisk(VirtualDisk disk, Map disksMapping, Backup backup) { + if (!disksMapping.containsKey(disk)) { + return false; + } + VolumeVO volumeVO = disksMapping.get(disk); + if (volumeVO == null) { + List backedUpVolumes = backup.getBackedUpVolumes(); + for (Backup.VolumeInfo backedUpVolume : backedUpVolumes) { + if (backedUpVolume.getSize().equals(disk.getCapacityInBytes())) { + return backedUpVolume.getType().equals(Volume.Type.ROOT); + } + } + } else { + return volumeVO.getVolumeType().equals(Volume.Type.ROOT); + } + throw new CloudRuntimeException("Could not determinate ROOT disk for VM to import"); + } + + /** + * Check backing info + */ + private void checkBackingInfo(VirtualDeviceBackingInfo backingInfo) { + if (! (backingInfo instanceof VirtualDiskFlatVer2BackingInfo)) { + throw new CloudRuntimeException("Unsopported backing, expected " + VirtualDiskFlatVer2BackingInfo.class.getSimpleName()); + } + } + + /** + * Get pool ID from datastore UUID + */ + private Long getPoolIdFromDatastoreUuid(String datastoreUuid) { + String poolUuid = UuidUtils.normalize(datastoreUuid); + StoragePoolVO pool = _storagePoolDao.findByUuid(poolUuid); + if (pool == null) { + throw new CloudRuntimeException("Couldn't find storage pool " + poolUuid); + } + return pool.getId(); + } + + /** + * Get pool ID for disk + */ + private Long getPoolId(VirtualDisk disk) { + VirtualDeviceBackingInfo backing = disk.getBacking(); + checkBackingInfo(backing); + VirtualDiskFlatVer2BackingInfo info = (VirtualDiskFlatVer2BackingInfo) backing; + String[] fileNameParts = info.getFileName().split(" "); + String datastoreUuid = StringUtils.substringBetween(fileNameParts[0], "[", "]"); + return getPoolIdFromDatastoreUuid(datastoreUuid); + } + + /** + * Get volume name from filename + */ + private String getVolumeNameFromFileName(String fileName) { + String[] fileNameParts = fileName.split(" "); + String volumePath = fileNameParts[1]; + return volumePath.split("/")[1].replaceFirst(".vmdk", ""); + } + + /** + * Get root disk template path + */ + private String getRootDiskTemplatePath(VirtualDisk rootDisk) { + VirtualDeviceBackingInfo backing = rootDisk.getBacking(); + checkBackingInfo(backing); + VirtualDiskFlatVer2BackingInfo info = (VirtualDiskFlatVer2BackingInfo) backing; + VirtualDiskFlatVer2BackingInfo parent = info.getParent(); + return (parent != null) ? getVolumeNameFromFileName(parent.getFileName()) : getVolumeNameFromFileName(info.getFileName()); + } + + /** + * Get template MO + */ + private VirtualMachineMO getTemplate(DatacenterMO dcMo, String templatePath) throws Exception { + VirtualMachineMO template = dcMo.findVm(templatePath); + if (!template.isTemplate()) { + throw new CloudRuntimeException(templatePath + " is not a template"); + } + return template; + } + + /** + * Get template pool ID + */ + private Long getTemplatePoolId(VirtualMachineMO template) throws Exception { + VirtualMachineConfigSummary configSummary = template.getConfigSummary(); + String vmPathName = configSummary.getVmPathName(); + String[] pathParts = vmPathName.split(" "); + String dataStoreUuid = pathParts[0].replace("[", "").replace("]", ""); + return getPoolIdFromDatastoreUuid(dataStoreUuid); + } + + /** + * Get template size + */ + private Long getTemplateSize(VirtualMachineMO template, String vmInternalName, + Map disksMapping, Backup backup) throws Exception { + List disks = template.getVirtualDisks(); + if (CollectionUtils.isEmpty(disks)) { + throw new CloudRuntimeException("Couldn't find VM template size"); + } + return disks.get(0).getCapacityInBytes(); + } + + /** + * Create a VM Template record on DB + */ + private VMTemplateVO createVMTemplateRecord(String vmInternalName, long guestOsId, long accountId) { + Long nextTemplateId = vmTemplateDao.getNextInSequence(Long.class, "id"); + VMTemplateVO templateVO = new VMTemplateVO(nextTemplateId, "Imported-from-" + vmInternalName, + Storage.ImageFormat.OVA,false, false, false, Storage.TemplateType.USER, + null, false, 64, accountId, null, "Template imported from VM " + vmInternalName, + false, guestOsId, false, HypervisorType.VMware, null, null, + false, false, false); + return vmTemplateDao.persist(templateVO); + } + + /** + * Retrieve the template ID matching the template on templatePath. There are 2 cases: + * - There are no references on DB for primary storage -> create a template DB record and return its ID + * - There are references on DB for primary storage -> return template ID for any of those references + */ + private long getTemplateId(String templatePath, String vmInternalName, Long guestOsId, long accountId) { + List poolRefs = templateStoragePoolDao.listByTemplatePath(templatePath); + return CollectionUtils.isNotEmpty(poolRefs) ? + poolRefs.get(0).getTemplateId() : + createVMTemplateRecord(vmInternalName, guestOsId, accountId).getId(); + } + + /** + * Update template reference on primary storage, if needed + */ + private void updateTemplateRef(long templateId, Long poolId, String templatePath, Long templateSize) { + VMTemplateStoragePoolVO templateRef = templateStoragePoolDao.findByPoolPath(poolId, templatePath); + if (templateRef == null) { + templateRef = new VMTemplateStoragePoolVO(poolId, templateId, null, 100, + VMTemplateStorageResourceAssoc.Status.DOWNLOADED, templatePath, null, + null, templatePath, templateSize); + templateRef.setState(ObjectInDataStoreStateMachine.State.Ready); + templateStoragePoolDao.persist(templateRef); + } + } + + /** + * Get template ID for VM being imported. If it is not found, it is created + */ + private Long getImportingVMTemplate(List virtualDisks, DatacenterMO dcMo, String vmInternalName, + Long guestOsId, long accountId, Map disksMapping, Backup backup) throws Exception { + for (VirtualDisk disk : virtualDisks) { + if (isRootDisk(disk, disksMapping, backup)) { + VolumeVO volumeVO = disksMapping.get(disk); + if (volumeVO == null) { + String templatePath = getRootDiskTemplatePath(disk); + VirtualMachineMO template = getTemplate(dcMo, templatePath); + Long poolId = getTemplatePoolId(template); + Long templateSize = getTemplateSize(template, vmInternalName, disksMapping, backup); + long templateId = getTemplateId(templatePath, vmInternalName, guestOsId, accountId); + updateTemplateRef(templateId, poolId, templatePath, templateSize); + return templateId; + } else { + return volumeVO.getTemplateId(); + } + } + } + throw new CloudRuntimeException("Could not find ROOT disk for VM " + vmInternalName); + } + + /** + * If VM does not exist: create and persist VM + * If VM exists: update VM + */ + private VMInstanceVO getVM(String vmInternalName, long templateId, long guestOsId, + long serviceOfferingId, long zoneId, long accountId, long userId, + long domainId) { + VMInstanceVO existingVM = _vmDao.findVMByInstanceName(vmInternalName); + if (existingVM != null) { + existingVM.setTemplateId(templateId); + existingVM.setGuestOSId(guestOsId); + existingVM.setServiceOfferingId(serviceOfferingId); + return _vmDao.persist(existingVM); + } else { + long id = userVmDao.getNextInSequence(Long.class, "id"); + UserVmVO vmInstanceVO = new UserVmVO(id, vmInternalName, vmInternalName, templateId, HypervisorType.VMware, + guestOsId, false, false, domainId, accountId, userId, serviceOfferingId, + null, vmInternalName, null); + vmInstanceVO.setDataCenterId(zoneId); + return userVmDao.persist(vmInstanceVO); + } + } + + /** + * Create and persist volume + */ + private VolumeVO createVolumeRecord(Volume.Type type, String volumeName, long zoneId, long domainId, + long accountId, long diskOfferingId, Storage.ProvisioningType provisioningType, + Long size, long instanceId, Long poolId, long templateId, Integer unitNumber, VirtualMachineDiskInfo diskInfo) { + VolumeVO volumeVO = new VolumeVO(type, volumeName, zoneId, domainId, accountId, diskOfferingId, + provisioningType, size, null, null, null); + volumeVO.setFormat(Storage.ImageFormat.OVA); + volumeVO.setPath(volumeName); + volumeVO.setState(Volume.State.Ready); + volumeVO.setInstanceId(instanceId); + volumeVO.setPoolId(poolId); + volumeVO.setTemplateId(templateId); + volumeVO.setChainInfo(new Gson().toJson(diskInfo)); + if (unitNumber != null) { + volumeVO.setDeviceId(unitNumber.longValue()); + } + return _volumeDao.persist(volumeVO); + } + + /** + * Get volume name from VM disk + */ + private String getVolumeName(VirtualDisk disk, VirtualMachineMO vmToImport) throws Exception { + return vmToImport.getVmdkFileBaseName(disk); + } + + /** + * Get provisioning type for VM disk info + */ + private Storage.ProvisioningType getProvisioningType(VirtualDiskFlatVer2BackingInfo backing) { + Boolean thinProvisioned = backing.isThinProvisioned(); + if (BooleanUtils.isTrue(thinProvisioned)) { + return Storage.ProvisioningType.THIN; + } + return Storage.ProvisioningType.SPARSE; + } + + /** + * Get disk offering ID for volume being imported. If it is not found it is mapped to "Custom" ID + */ + private long getDiskOfferingId(long size, Storage.ProvisioningType provisioningType) { + List offerings = diskOfferingDao.listAllBySizeAndProvisioningType(size, provisioningType); + return CollectionUtils.isNotEmpty(offerings) ? + offerings.get(0).getId() : + diskOfferingDao.findByUniqueName("Cloud.Com-Custom").getId(); + } + + protected VolumeVO updateVolume(VirtualDisk disk, Map disksMapping, VirtualMachineMO vmToImport, Long poolId) throws Exception { + VolumeVO volumeVO = disksMapping.get(disk); + String volumeName = getVolumeName(disk, vmToImport); + volumeVO.setPath(volumeName); + volumeVO.setPoolId(poolId); + VirtualMachineDiskInfo diskInfo = getDiskInfo(vmToImport, poolId, volumeName); + volumeVO.setChainInfo(new Gson().toJson(diskInfo)); + _volumeDao.update(volumeVO.getId(), volumeVO); + return volumeVO; + } + + /** + * Get volumes for VM being imported + */ + private void importVMVolumes(VMInstanceVO vmInstanceVO, List virtualDisks, + Map disksMapping, VirtualMachineMO vmToImport, Backup backup) throws Exception { + long zoneId = vmInstanceVO.getDataCenterId(); + long accountId = vmInstanceVO.getAccountId(); + long domainId = vmInstanceVO.getDomainId(); + long templateId = vmInstanceVO.getTemplateId(); + long instanceId = vmInstanceVO.getId(); + + List vols = new ArrayList<>(); + for (VirtualDisk disk : virtualDisks) { + Long poolId = getPoolId(disk); + VolumeVO volumeVO; + if (disksMapping.containsKey(disk) && disksMapping.get(disk) != null) { + volumeVO = updateVolume(disk, disksMapping, vmToImport, poolId); + } else { + volumeVO = createVolume(disk, vmToImport, domainId, zoneId, accountId, instanceId, poolId, templateId, backup, true); + } + vols.add(volumeVO); + } + removeUnusedVolumes(vols, vmInstanceVO); + } + + private void removeUnusedVolumes(List vols, VMInstanceVO vmInstanceVO) { + List existingVolumes = _volumeDao.findByInstance(vmInstanceVO.getId()); + if (existingVolumes.size() != vols.size()) { + for (VolumeVO existingVol : existingVolumes) { + if (!vols.contains(existingVol)) { + _volumeDao.remove(existingVol.getId()); + } + } + } + } + + private VirtualMachineDiskInfo getDiskInfo(VirtualMachineMO vmMo, Long poolId, String volumeName) throws Exception { + VirtualMachineDiskInfoBuilder diskInfoBuilder = vmMo.getDiskInfoBuilder(); + String poolName = _storagePoolDao.findById(poolId).getUuid().replace("-", ""); + return diskInfoBuilder.getDiskInfoByBackingFileBaseName(volumeName, poolName); + } + + private VolumeVO createVolume(VirtualDisk disk, VirtualMachineMO vmToImport, long domainId, long zoneId, + long accountId, long instanceId, Long poolId, long templateId, Backup backup, boolean isImport) throws Exception { + Long size = disk.getCapacityInBytes(); + List backedUpVolumes = backup.getBackedUpVolumes(); + Volume.Type type = Volume.Type.DATADISK; + if (isImport) { + for (Backup.VolumeInfo volumeInfo : backedUpVolumes) { + if (volumeInfo.getSize().equals(disk.getCapacityInBytes())) { + type = volumeInfo.getType(); + } + } + } + VirtualDeviceBackingInfo backing = disk.getBacking(); + checkBackingInfo(backing); + VirtualDiskFlatVer2BackingInfo info = (VirtualDiskFlatVer2BackingInfo) backing; + String volumeName = getVolumeName(disk, vmToImport); + Storage.ProvisioningType provisioningType = getProvisioningType(info); + long diskOfferingId = getDiskOfferingId(size, provisioningType); + Integer unitNumber = disk.getUnitNumber(); + VirtualMachineDiskInfo diskInfo = getDiskInfo(vmToImport, poolId, volumeName); + return createVolumeRecord(type, volumeName, zoneId, domainId, accountId, diskOfferingId, + provisioningType, size, instanceId, poolId, templateId, unitNumber, diskInfo); + } + + /** + * Get physical network ID from zoneId and Vmware label + */ + private long getPhysicalNetworkId(Long zoneId, String tag) { + List physicalNetworks = physicalNetworkDao.listByZone(zoneId); + for (PhysicalNetworkVO physicalNetwork : physicalNetworks) { + PhysicalNetworkTrafficTypeVO vo = _physicalNetworkTrafficTypeDao.findBy(physicalNetwork.getId(), TrafficType.Guest); + if (vo == null) { + continue; + } + String vmwareNetworkLabel = vo.getVmwareNetworkLabel(); + if (!vmwareNetworkLabel.startsWith(tag)) { + throw new CloudRuntimeException("Vmware network label does not start with: " + tag); + } + return physicalNetwork.getId(); + } + throw new CloudRuntimeException("Could not find guest physical network matching tag: " + tag + " on zone " + zoneId); + } + + /** + * Create and persist network + */ + private NetworkVO createNetworkRecord(Long zoneId, String tag, String vlan, long accountId, long domainId) { + Long physicalNetworkId = getPhysicalNetworkId(zoneId, tag); + final long id = _networkDao.getNextInSequence(Long.class, "id"); + NetworkVO networkVO = new NetworkVO(id, TrafficType.Guest, Networks.Mode.Dhcp, BroadcastDomainType.Vlan, 9L, + domainId, accountId, id, "Imported-network-" + id, "Imported-network-" + id, null, Network.GuestType.Isolated, + zoneId, physicalNetworkId, ControlledEntity.ACLType.Account, false, null, false); + networkVO.setBroadcastUri(BroadcastDomainType.Vlan.toUri(vlan)); + networkVO.setGuruName("ExternalGuestNetworkGuru"); + networkVO.setState(Network.State.Implemented); + return _networkDao.persist(networkVO); + } + + /** + * Get network from VM network name + */ + private NetworkVO getGuestNetworkFromNetworkMorName(String name, long accountId, Long zoneId, long domainId) { + String prefix = "cloud.guest."; + String nameWithoutPrefix = name.replace(prefix, ""); + String[] parts = nameWithoutPrefix.split("\\."); + String vlan = parts[0]; + String tag = parts[parts.length - 1]; + String[] tagSplit = tag.split("-"); + tag = tagSplit[tagSplit.length - 1]; + NetworkVO networkVO = _networkDao.findByVlan(vlan); + if (networkVO == null) { + networkVO = createNetworkRecord(zoneId, tag, vlan, accountId, domainId); + } + return networkVO; + } + + /** + * Get map between VM networks and its IDs on CloudStack + */ + private Map getNetworksMapping(String[] vmNetworkNames, long accountId, long zoneId, long domainId) { + Map mapping = new HashMap<>(); + for (String networkName : vmNetworkNames) { + NetworkVO networkVO = getGuestNetworkFromNetworkMorName(networkName, accountId, zoneId, domainId); + mapping.put(networkName, networkVO); + } + return mapping; + } + + /** + * Get network MO from VM NIC + */ + private NetworkMO getNetworkMO(VirtualE1000 nic, VmwareContext context) { + VirtualDeviceConnectInfo connectable = nic.getConnectable(); + VirtualEthernetCardNetworkBackingInfo info = (VirtualEthernetCardNetworkBackingInfo) nic.getBacking(); + ManagedObjectReference networkMor = info.getNetwork(); + if (networkMor == null) { + throw new CloudRuntimeException("Could not find network for NIC on: " + nic.getMacAddress()); + } + return new NetworkMO(context, networkMor); + } + + /** + * Create and persist NIC + */ + private NicVO createNicRecord(String macAddress, Long networkId, VMInstanceVO vmInstanceVO) { + NicVO nicVO = new NicVO("ExternalGuestNetworkGuru", vmInstanceVO.getId(), networkId, vmInstanceVO.getType()); + nicVO.setMacAddress(macAddress); + nicVO.setAddressFormat(Networks.AddressFormat.Ip4); + nicVO.setReservationStrategy(Nic.ReservationStrategy.Start); + nicVO.setState(Nic.State.Reserved); + nicVO.setDefaultNic(true); + return _nicDao.persist(nicVO); + } + + /** + * Remove volumes and nics if the VM already existed on CloudStack. No action performed if not existing + */ + private Pair, List> cleanupExistingVMVolumesAndNics(String vmInternalName) { + VMInstanceVO existingVm = _vmDao.findVMByInstanceName(vmInternalName); + if (existingVm != null) { + List rootDisks = _volumeDao.findReadyRootVolumesByInstance(existingVm.getId()); + List datadisks = _volumeDao.findByInstanceAndType(existingVm.getId(), Volume.Type.DATADISK); + _volumeDao.deleteVolumesByInstance(existingVm.getId()); + _nicDao.removeNicsForInstance(existingVm.getId()); + return new Pair<>(rootDisks, datadisks); + } + return new Pair<>(null, null); + } + + private Pair getNicMacAddressAndNetworkName(VirtualDevice nicDevice, VmwareContext context) throws Exception { + VirtualE1000 nic = (VirtualE1000) nicDevice; + String macAddress = nic.getMacAddress(); + NetworkMO networkMO = getNetworkMO(nic, context); + String networkName = networkMO.getName(); + return new Pair<>(macAddress, networkName); + } + + private void importVMNics(VirtualDevice[] nicDevices, DatacenterMO dcMo, Map networksMapping, + VMInstanceVO vmInstanceVO) throws Exception { + VmwareContext context = dcMo.getContext(); + List nics = new ArrayList<>(); + for (VirtualDevice nicDevice : nicDevices) { + Pair pair = getNicMacAddressAndNetworkName(nicDevice, context); + String macAddress = pair.first(); + String networkName = pair.second(); + NetworkVO networkVO = networksMapping.get(networkName); + NicVO nicVO = _nicDao.findByNetworkIdAndMacAddress(networkVO.getId(), macAddress); + if (nicVO == null) { + nicVO = createNicRecord(macAddress, networkVO.getId(), vmInstanceVO); + } + nics.add(nicVO); + } + removeUnusedNics(nics, vmInstanceVO); + } + + private void removeUnusedNics(List nics, VMInstanceVO vmInstanceVO) { + List existingVMNics = _nicDao.listByVmId(vmInstanceVO.getId()); + if (nics.size() != existingVMNics.size()) { + for (NicVO existingNic : existingVMNics) { + if (!nics.contains(existingNic)) { + _nicDao.remove(existingNic.getId()); + } + } + } + } + + private Map getDisksMapping(Backup backup, List virtualDisks) { + Map map = new HashMap<>(); + List backedUpVolumes = backup.getBackedUpVolumes(); + Map usedVols = new HashMap<>(); + for (Backup.VolumeInfo backedUpVol : backedUpVolumes) { + for (VirtualDisk disk : virtualDisks) { + if (!map.containsKey(disk) && backedUpVol.getSize().equals(disk.getCapacityInBytes()) + && !usedVols.containsKey(backedUpVol.getUuid())) { + String volId = backedUpVol.getUuid(); + VolumeVO vol = _volumeDao.findByUuid(volId); + usedVols.put(backedUpVol.getUuid(), true); + map.put(disk, vol); + } + } + } + return map; + } + + /** + * Find VM on datacenter + */ + private VirtualMachineMO findVM(DatacenterMO dcMo, String path) throws Exception { + VirtualMachineMO vm = dcMo.findVm(path); + if (vm == null) { + throw new CloudRuntimeException("Error finding VM: " + path); + } + return vm; + } + + /** + * Find restored volume based on volume info + */ + private VirtualDisk findRestoredVolume(Backup.VolumeInfo volumeInfo, VirtualMachineMO vm) throws Exception { + List virtualDisks = vm.getVirtualDisks(); + for (VirtualDisk disk: virtualDisks) { + if (disk.getCapacityInBytes().equals(volumeInfo.getSize())) { + return disk; + } + } + throw new CloudRuntimeException("Volume to restore could not be found"); + } + + /** + * Get volume full path + */ + private String getVolumeFullPath(VirtualDisk disk) { + VirtualDeviceBackingInfo backing = disk.getBacking(); + checkBackingInfo(backing); + VirtualDiskFlatVer2BackingInfo info = (VirtualDiskFlatVer2BackingInfo) backing; + return info.getFileName(); + } + + /** + * Get dest volume full path + */ + private String getDestVolumeFullPath(VirtualDisk restoredDisk, VirtualMachineMO restoredVm, + VirtualMachineMO vmMo) throws Exception { + VirtualDisk vmDisk = vmMo.getVirtualDisks().get(0); + String vmDiskPath = vmMo.getVmdkFileBaseName(vmDisk); + String vmDiskFullPath = getVolumeFullPath(vmMo.getVirtualDisks().get(0)); + String restoredVolumePath = restoredVm.getVmdkFileBaseName(restoredDisk); + return vmDiskFullPath.replace(vmDiskPath, restoredVolumePath); + } + + /** + * Get dest datastore mor + */ + private ManagedObjectReference getDestStoreMor(VirtualMachineMO vmMo) throws Exception { + VirtualDisk vmDisk = vmMo.getVirtualDisks().get(0); + VirtualDeviceBackingInfo backing = vmDisk.getBacking(); + checkBackingInfo(backing); + VirtualDiskFlatVer2BackingInfo info = (VirtualDiskFlatVer2BackingInfo) backing; + return info.getDatastore(); + } + + @Override + public VirtualMachine importVirtualMachine(long zoneId, long domainId, long accountId, long userId, + String vmInternalName, Backup backup) throws Exception { + DatacenterMO dcMo = getDatacenterMO(zoneId); + VirtualMachineMO vmToImport = dcMo.findVm(vmInternalName); + if (vmToImport == null) { + throw new CloudRuntimeException("Error finding VM: " + vmInternalName); + } + VirtualMachineConfigSummary configSummary = vmToImport.getConfigSummary(); + VirtualMachineRuntimeInfo runtimeInfo = vmToImport.getRuntimeInfo(); + List virtualDisks = vmToImport.getVirtualDisks(); + String[] vmNetworkNames = vmToImport.getNetworks(); + VirtualDevice[] nicDevices = vmToImport.getNicDevices(); + + return Transaction.execute(new TransactionCallbackWithException() { + @Override + public VMInstanceVO doInTransaction(TransactionStatus status) throws Exception { + Map disksMapping = getDisksMapping(backup, virtualDisks); + Map networksMapping = getNetworksMapping(vmNetworkNames, accountId, zoneId, domainId); + + long guestOsId = getImportingVMGuestOs(configSummary); + long serviceOfferingId = getImportingVMServiceOffering(configSummary, runtimeInfo); + long templateId = getImportingVMTemplate(virtualDisks, dcMo, vmInternalName, guestOsId, accountId, disksMapping, backup); + + VMInstanceVO vmInstanceVO = getVM(vmInternalName, templateId, guestOsId, + serviceOfferingId, zoneId, accountId, userId, domainId); + + importVMNics(nicDevices, dcMo, networksMapping, vmInstanceVO); + importVMVolumes(vmInstanceVO, virtualDisks, disksMapping, vmToImport, backup); + return vmInstanceVO; + } + }); + } + + @Override + public boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location, Backup.VolumeInfo volumeInfo, + VirtualMachine vm, long poolId, Backup backup) throws Exception { + DatacenterMO dcMo = getDatacenterMO(zoneId); + VirtualMachineMO vmRestored = findVM(dcMo, location); + VirtualMachineMO vmMo = findVM(dcMo, vm.getInstanceName()); + VirtualDisk restoredDisk = findRestoredVolume(volumeInfo, vmRestored); + String diskPath = vmRestored.getVmdkFileBaseName(restoredDisk); + + s_logger.debug("Restored disk size=" + restoredDisk.getCapacityInKB() + " path=" + diskPath); + + // Detach restored VM disks + vmRestored.detachAllDisks(); + + String srcPath = getVolumeFullPath(restoredDisk); + String destPath = getDestVolumeFullPath(restoredDisk, vmRestored, vmMo); + + VirtualDiskManagerMO virtualDiskManagerMO = new VirtualDiskManagerMO(dcMo.getContext()); + + // Copy volume to the VM folder + virtualDiskManagerMO.moveVirtualDisk(srcPath, dcMo.getMor(), destPath, dcMo.getMor(), true); + + try { + // Attach volume to VM + vmMo.attachDisk(new String[] {destPath}, getDestStoreMor(vmMo)); + } catch (Exception e) { + s_logger.error("Failed to attach the restored volume: " + diskPath, e); + return false; + } finally { + // Destroy restored VM + vmRestored.destroy(); + } + + VirtualDisk attachedDisk = getAttachedDisk(vmMo, diskPath); + if (attachedDisk == null) { + s_logger.error("Failed to get the attached the (restored) volume " + diskPath); + return false; + } + createVolume(attachedDisk, vmMo, vm.getDomainId(), vm.getDataCenterId(), vm.getAccountId(), vm.getId(), + poolId, vm.getTemplateId(), backup, false); + + return true; + } + + private VirtualDisk getAttachedDisk(VirtualMachineMO vmMo, String diskPath) throws Exception { + for (VirtualDisk disk : vmMo.getVirtualDisks()) { + if (vmMo.getVmdkFileBaseName(disk).equals(diskPath)) { + return disk; + } + } + return null; + } + @Override public List finalizeMigrate(VirtualMachine vm, StoragePool destination) { List commands = new ArrayList(); diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java index 1d3c1ad9d69f..c4b939a11493 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java @@ -199,7 +199,7 @@ public class VmwareManagerImpl extends ManagerBase implements VmwareManager, Vmw @Inject private PrimaryDataStoreDao primaryStorageDao; @Inject - private VMTemplatePoolDao templateDataStoreDao; + private VMTemplatePoolDao templateStoragePoolDao; @Inject private TemplateJoinDao templateDao; @Inject @@ -1443,7 +1443,7 @@ private void startTemplateCleanJobSchedule() { */ private Runnable getCleanupFullyClonedTemplatesTask() { return new CleanupFullyClonedTemplatesTask(primaryStorageDao, - templateDataStoreDao, + templateStoragePoolDao, templateDao, vmInstanceDao, cloneSettingDao, diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java index 02f758ba844b..421b0bb726ea 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -44,19 +44,6 @@ import javax.naming.ConfigurationException; import javax.xml.datatype.XMLGregorianCalendar; -import com.cloud.agent.api.storage.OVFPropertyTO; -import com.cloud.utils.crypt.DBEncryptionUtil; -import com.vmware.vim25.ArrayUpdateOperation; -import com.vmware.vim25.VAppOvfSectionInfo; -import com.vmware.vim25.VAppOvfSectionSpec; -import com.vmware.vim25.VAppProductInfo; -import com.vmware.vim25.VAppProductSpec; -import com.vmware.vim25.VAppPropertyInfo; -import com.vmware.vim25.VAppPropertySpec; -import com.vmware.vim25.VmConfigInfo; -import com.vmware.vim25.VmConfigSpec; -import org.apache.commons.collections.CollectionUtils; - import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.storage.command.CopyCommand; import org.apache.cloudstack.storage.command.StorageSubSystemCommand; @@ -66,6 +53,7 @@ import org.apache.cloudstack.storage.to.TemplateObjectTO; import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.apache.log4j.Logger; @@ -184,6 +172,7 @@ import com.cloud.agent.api.storage.DestroyCommand; import com.cloud.agent.api.storage.MigrateVolumeAnswer; import com.cloud.agent.api.storage.MigrateVolumeCommand; +import com.cloud.agent.api.storage.OVFPropertyTO; import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer; import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand; import com.cloud.agent.api.storage.ResizeVolumeAnswer; @@ -255,6 +244,7 @@ import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; import com.cloud.utils.Ternary; +import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.db.DB; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.ExceptionUtil; @@ -270,6 +260,7 @@ import com.cloud.vm.VmDetailConstants; import com.google.gson.Gson; import com.vmware.vim25.AboutInfo; +import com.vmware.vim25.ArrayUpdateOperation; import com.vmware.vim25.BoolPolicy; import com.vmware.vim25.ComputeResourceSummary; import com.vmware.vim25.CustomFieldStringValue; @@ -297,6 +288,12 @@ import com.vmware.vim25.PerfQuerySpec; import com.vmware.vim25.RuntimeFaultFaultMsg; import com.vmware.vim25.ToolsUnavailableFaultMsg; +import com.vmware.vim25.VAppOvfSectionInfo; +import com.vmware.vim25.VAppOvfSectionSpec; +import com.vmware.vim25.VAppProductInfo; +import com.vmware.vim25.VAppProductSpec; +import com.vmware.vim25.VAppPropertyInfo; +import com.vmware.vim25.VAppPropertySpec; import com.vmware.vim25.VMwareDVSPortSetting; import com.vmware.vim25.VimPortType; import com.vmware.vim25.VirtualDevice; @@ -321,6 +318,8 @@ import com.vmware.vim25.VirtualMachineToolsStatus; import com.vmware.vim25.VirtualMachineVideoCard; import com.vmware.vim25.VirtualUSBController; +import com.vmware.vim25.VmConfigInfo; +import com.vmware.vim25.VmConfigSpec; import com.vmware.vim25.VmwareDistributedVirtualSwitchVlanIdSpec; public class VmwareResource implements StoragePoolResource, ServerResource, VmwareHostService, VirtualRouterDeployer { diff --git a/plugins/pom.xml b/plugins/pom.xml index 4a96b4d2a433..2ecd69722118 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -58,6 +58,8 @@ api/rate-limit api/solidfire-intg-test + backup/dummy + ca/root-ca database/quota @@ -187,21 +189,12 @@ + api/vmware-sioc + backup/veeam hypervisors/vmware network-elements/cisco-vnmc - - vmware-sioc - - - noredist - - - - api/vmware-sioc - - mysqlha diff --git a/pom.xml b/pom.xml index 9a438bd60695..615b024b2332 100644 --- a/pom.xml +++ b/pom.xml @@ -122,7 +122,7 @@ 1.59 3.2.5 8.7 - 3.2.0 + 3.2.6 2.6.11 0.0.23 2.4.12 @@ -130,8 +130,8 @@ 23.6-jre 4.5.4 4.4.8 + 2.9.6 2.15 - 2.9.2 1.9.2 0.16 3.22.0-GA @@ -161,6 +161,7 @@ 8.0.30 1.0.0-build221 6.7 + 0.5.0 6.2.0-3.1 1.4.01 3.1.3 diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index acd73622642b..1b683c189757 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -39,6 +39,7 @@ import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.AsyncJobResponse; +import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.DomainRouterResponse; @@ -60,9 +61,14 @@ import org.apache.cloudstack.api.response.TemplateResponse; import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.VpcOfferingResponse; import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; @@ -446,6 +452,8 @@ public class ApiDBUtils { static ResourceMetaDataService s_resourceDetailsService; static HostGpuGroupsDao s_hostGpuGroupsDao; static VGPUTypesDao s_vgpuTypesDao; + static BackupDao s_backupDao; + static BackupOfferingDao s_backupOfferingDao; @Inject private ManagementServer ms; @@ -684,6 +692,10 @@ public class ApiDBUtils { private HostGpuGroupsDao hostGpuGroupsDao; @Inject private VGPUTypesDao vgpuTypesDao; + @Inject + private BackupDao backupDao; + @Inject + private BackupOfferingDao backupOfferingDao; @PostConstruct void init() { @@ -806,6 +818,8 @@ void init() { s_resourceDetailsService = resourceDetailsService; s_hostGpuGroupsDao = hostGpuGroupsDao; s_vgpuTypesDao = vgpuTypesDao; + s_backupDao = backupDao; + s_backupOfferingDao = backupOfferingDao; } // /////////////////////////////////////////////////////////// @@ -2033,4 +2047,12 @@ public static boolean isAdmin(Account account) { public static List listResourceTagViewByResourceUUID(String resourceUUID, ResourceObjectType resourceType) { return s_tagJoinDao.listBy(resourceUUID, resourceType); } + + public static BackupResponse newBackupResponse(Backup backup) { + return s_backupDao.newBackupResponse(backup); + } + + public static BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy) { + return s_backupOfferingDao.newBackupOfferingResponse(policy); + } } diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 8e6731dd20d0..1932edbbaca6 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -31,8 +31,6 @@ import javax.inject.Inject; -import com.cloud.vm.snapshot.VMSnapshotVO; -import com.cloud.vm.snapshot.dao.VMSnapshotDao; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.affinity.AffinityGroup; @@ -51,6 +49,8 @@ import org.apache.cloudstack.api.response.AutoScalePolicyResponse; import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse; import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse; +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.CapabilityResponse; import org.apache.cloudstack.api.response.CapacityResponse; import org.apache.cloudstack.api.response.ClusterResponse; @@ -142,6 +142,10 @@ import org.apache.cloudstack.api.response.VpcResponse; import org.apache.cloudstack.api.response.VpnUsersResponse; import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; @@ -336,6 +340,8 @@ import com.cloud.vm.dao.NicExtraDhcpOptionDao; import com.cloud.vm.dao.NicSecondaryIpVO; import com.cloud.vm.snapshot.VMSnapshot; +import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; public class ApiResponseHelper implements ResponseGenerator { @@ -376,6 +382,10 @@ public class ApiResponseHelper implements ResponseGenerator { NetworkDetailsDao networkDetailsDao; @Inject private VMSnapshotDao vmSnapshotDao; + @Inject + private BackupDao backupDao; + @Inject + private BackupOfferingDao backupOfferingDao; @Override public UserResponse createUserResponse(User user) { @@ -3646,6 +3656,26 @@ public UsageRecordResponse createUsageResponse(Usage usageRecord, Map getClusterSettings(long vmId) { } @Override + public VirtualMachine importVirtualMachine(long zoneId, long domainId, long accountId, long userId, + String vmInternalName, Backup backup) throws Exception { + return null; + } + + @Override + public boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location, Backup.VolumeInfo volumeInfo, + VirtualMachine vm, long poolId, Backup backup) throws Exception { + return false; + } + public List finalizeMigrate(VirtualMachine vm, StoragePool destination) { return null; } diff --git a/server/src/main/java/com/cloud/usage/UsageServiceImpl.java b/server/src/main/java/com/cloud/usage/UsageServiceImpl.java index 531b29170438..4f09d13f3ccb 100644 --- a/server/src/main/java/com/cloud/usage/UsageServiceImpl.java +++ b/server/src/main/java/com/cloud/usage/UsageServiceImpl.java @@ -16,6 +16,28 @@ // under the License. package com.cloud.usage; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.api.command.admin.usage.GenerateUsageRecordsCmd; +import org.apache.cloudstack.api.command.admin.usage.ListUsageRecordsCmd; +import org.apache.cloudstack.api.command.admin.usage.RemoveRawUsageRecordsCmd; +import org.apache.cloudstack.api.response.UsageTypeResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.usage.Usage; +import org.apache.cloudstack.usage.UsageService; +import org.apache.cloudstack.usage.UsageTypes; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + import com.cloud.configuration.Config; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; @@ -55,26 +77,6 @@ import com.cloud.utils.db.TransactionLegacy; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.VMInstanceDao; -import org.apache.cloudstack.api.command.admin.usage.GenerateUsageRecordsCmd; -import org.apache.cloudstack.api.command.admin.usage.ListUsageRecordsCmd; -import org.apache.cloudstack.api.command.admin.usage.RemoveRawUsageRecordsCmd; -import org.apache.cloudstack.api.response.UsageTypeResponse; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.usage.Usage; -import org.apache.cloudstack.usage.UsageService; -import org.apache.cloudstack.usage.UsageTypes; -import org.apache.log4j.Logger; -import org.springframework.stereotype.Component; - -import javax.inject.Inject; -import javax.naming.ConfigurationException; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.TimeZone; @Component public class UsageServiceImpl extends ManagerBase implements UsageService, Manager { @@ -267,6 +269,7 @@ public Pair, Integer> getUsageRecords(ListUsageRecordsCmd case UsageTypes.RUNNING_VM: case UsageTypes.ALLOCATED_VM: case UsageTypes.VM_SNAPSHOT: + case UsageTypes.BACKUP: VMInstanceVO vm = _vmDao.findByUuidIncludingRemoved(usageId); if (vm != null) { usageDbId = vm.getId(); diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java new file mode 100644 index 000000000000..a699dd199f18 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -0,0 +1,582 @@ +// 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 java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.api.command.admin.backup.DeleteBackupOfferingCmd; +import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd; +import org.apache.cloudstack.api.command.admin.backup.ListBackupProviderOfferingsCmd; +import org.apache.cloudstack.api.command.admin.backup.ListBackupProvidersCmd; +import org.apache.cloudstack.api.command.admin.vm.ImportVMCmdByAdmin; +import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd; +import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; +import org.apache.cloudstack.api.command.user.backup.DeleteBackupCmd; +import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; +import org.apache.cloudstack.api.command.user.backup.ListBackupsCmd; +import org.apache.cloudstack.api.command.user.backup.RestoreBackupCmd; +import org.apache.cloudstack.api.command.user.backup.RestoreVolumeFromBackupAndAttachToVMCmd; +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.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.poll.BackgroundPollManager; +import org.apache.cloudstack.poll.BackgroundPollTask; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.dc.DataCenter; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.event.ActionEvent; +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventUtils; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.storage.ScopeType; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.usage.dao.UsageBackupDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountService; +import com.cloud.utils.Pair; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.VMInstanceDao; +import com.google.common.base.Strings; + +@Component +public class BackupManagerImpl extends ManagerBase implements BackupManager { + private static final Logger LOG = Logger.getLogger(BackupManagerImpl.class); + + @Inject + private BackupOfferingDao backupOfferingDao; + @Inject + private VMInstanceDao vmInstanceDao; + @Inject + private AccountService accountService; + @Inject + private AccountManager accountManager; + @Inject + private BackupDao backupDao; + @Inject + private UsageBackupDao usageBackupDao; + @Inject + private VolumeDao volumeDao; + @Inject + private DataCenterDao dataCenterDao; + @Inject + private BackgroundPollManager backgroundPollManager; + @Inject + private HostDao hostDao; + @Inject + private HypervisorGuruManager hypervisorGuruManager; + @Inject + private PrimaryDataStoreDao primaryDataStoreDao; + @Inject + private DiskOfferingDao diskOfferingDao; + + private static Map backupProvidersMap = new HashMap<>(); + private List backupProviders; + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_IMPORT_OFFERING, eventDescription = "importing backup offering", async = true) + public BackupOffering importBackupOffering(final Long zoneId, final String offeringExternalId, + final String offeringName, final String offeringDescription) { + final BackupOffering existingOffering = backupOfferingDao.listByExternalId(offeringExternalId); + if (existingOffering != null) { + throw new CloudRuntimeException("A backup offering with external ID " + offeringExternalId + " already exists"); + } + final BackupProvider provider = getBackupProvider(zoneId); + if (!provider.isBackupOffering(zoneId, offeringExternalId)) { + throw new CloudRuntimeException("Policy " + offeringExternalId + " does not exist on provider " + provider.getName() + " on zone " + zoneId); + } + final BackupOfferingVO policy = new BackupOfferingVO(zoneId, offeringExternalId, offeringName, offeringDescription); + final BackupOfferingVO savedPolicy = backupOfferingDao.persist(policy); + if (savedPolicy == null) { + throw new CloudRuntimeException("Unable to create backup offering: " + offeringExternalId + ", name: " + offeringName); + } + LOG.debug("Successfully created backup offering " + offeringName + " mapped to backup provider offering " + offeringExternalId); + return savedPolicy; + } + + @Override + public List listBackupOfferings(final Long zoneId, final Long offeringId) { + if (offeringId != null) { + BackupOfferingVO policy = backupOfferingDao.findById(offeringId); + if (policy == null) { + throw new CloudRuntimeException("Offering ID " + offeringId + " does not exist"); + } + return Collections.singletonList(policy); + } + return backupOfferingDao.listByZone(zoneId); + } + + @Override + public List listBackupProviderOfferings(final Long zoneId) { + if (zoneId == null || zoneId < 1) { + throw new CloudRuntimeException("Invalid zone ID passed"); + } + 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"); + } + final BackupProvider backupProvider = getBackupProvider(zoneId); + LOG.debug("Listing external backup policies for the backup provider registered in zone " + zoneId); + return backupProvider.listBackupOfferings(zoneId); } + + @Override + public List listBackups(final Long id, final Long vmId) { + final Account callerAccount = CallContext.current().getCallingAccount(); + final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + if (id != null) { + return Collections.singletonList(backupDao.findById(id)); + } + if (accountService.isRootAdmin(callerAccount.getId())) { + return new ArrayList<>(backupDao.listAll()); + } else { + return new ArrayList<>(backupDao.listByAccountId(callerAccount.getId())); + } + } + accountManager.checkAccess(callerAccount, null, true, vm); + return backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + } + + @Override + public List listBackupRestorePoints(final Long backupId) { + BackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Could not find backup " + backupId); + } + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); + if (vm == null) { + throw new CloudRuntimeException("Could not find VM: " + backup.getVmId()); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + return backupProvider.listBackupRestorePoints(backup.getUuid(), vm); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_CREATE, eventDescription = "creating VM backup", async = true) + public Backup createBackup(final Long vmId, final Long offeringId) { + final VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("Did not find VM by provided ID"); + } + + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + final Backup vmBackup = backupDao.findByVmId(vm.getId()); + if (vmBackup != null) { + if (offeringId != null && !vmBackup.getOfferingId().equals(offeringId)) { + throw new CloudRuntimeException("VM backup(s) already exists and a different offering ID is provided than previously configured"); + } + final BackupProvider backupProvider = getBackupProvider(vmBackup.getZoneId()); + if (backupProvider != null && backupProvider.takeBackup(vmBackup)) { + return vmBackup; + } + LOG.warn("VM already has backup(s), failed to take backup"); + return null; + } + + final BackupOfferingVO policy = backupOfferingDao.findById(offeringId); + if (policy == null) { + throw new CloudRuntimeException("Provided backup offering does not exist"); + } + + BackupVO backup = new BackupVO(vmId, offeringId, Backup.Status.Allocated, vm.getAccountId(), vm.getDataCenterId()); + setBackupVolumes(backup, vm); + backup = backupDao.persist(backup); + if (backup == null) { + throw new CloudRuntimeException("Failed to persist backup object in database"); + } + + final BackupProvider backupProvider = getBackupProvider(vm.getDataCenterId()); + if (backupProvider == null) { + throw new CloudRuntimeException("Failed to find backup provider for the zone"); + } + backup = (BackupVO) backupProvider.createBackup(policy, vm, backup); + if (backup == null) { + throw new CloudRuntimeException("Backup provider failed to create backup for VM: " + vm.getUuid()); + } + if (backupDao.update(backup.getId(), backup)) { + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_CREATE, vm.getAccountId(), vm.getDataCenterId(), backup.getId(), + vm.getUuid(), backup.getOfferingId(), backup.getVmId(), null, + Backup.class.getSimpleName(), backup.getUuid()); + } + return backup; + } + + private void setBackupVolumes(BackupVO backup, VMInstanceVO vm) { + List vmVolumes = volumeDao.findByInstance(vm.getId()); + List volInfo = createVolumeInfoFromVolumes(vmVolumes); + backup.setBackedUpVolumes(volInfo); + } + + private List createVolumeInfoFromVolumes(List vmVolumes) { + List list = new ArrayList<>(); + for (VolumeVO vol : vmVolumes) { + list.add(new Backup.VolumeInfo(vol.getUuid(), vol.getPath(), vol.getVolumeType(), vol.getSize())); + } + return list; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_DELETE, eventDescription = "deleting VM backup", async = true) + public boolean deleteBackup(final Long backupId) { + final BackupVO backup = backupDao.findByIdIncludingRemoved(backupId); + if (backup == null) { + throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + } + final Long vmId = backup.getVmId(); + final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM " + vmId + " does not exist"); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + final BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + boolean result = backupProvider.removeBackup(vm, backup); + if (result) { + backup.setStatus(Backup.Status.Expunged); + if (backupDao.update(backup.getId(), backup)) { + backupDao.remove(backup.getId()); + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_DELETE, vm.getAccountId(), vm.getDataCenterId(), backup.getId(), + vm.getUuid(), backup.getOfferingId(), backup.getVmId(), null, + Backup.class.getSimpleName(), backup.getUuid()); + return true; + } + } + // FIXME: fsm to deal with GC+deletion + backup.setStatus(Backup.Status.Removed); + if (backupDao.update(backup.getId(), backup)) { + backupDao.remove(backupId); + return true; + } + + return result; + } + + public boolean importVM(long zoneId, long domainId, long accountId, long userId, + String vmInternalName, Hypervisor.HypervisorType hypervisorType, Backup backup) { + //TODO: Remove it from the backup manager interface + //TODO: refactor with VM ingestion feature in future + HypervisorGuru guru = hypervisorGuruManager.getGuru(hypervisorType); + try { + guru.importVirtualMachine(zoneId, domainId, accountId, userId, vmInternalName, backup); + } catch (Exception e) { + LOG.error("Failed to import VM", e); + throw new CloudRuntimeException("Error during vm import: " + e.getMessage()); + } + return true; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true) + public boolean restoreBackup(final Long backupId, final String restorePointId) { + BackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + } + Long vmId = backup.getVmId(); + BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM " + vmId + " couldn't be found on existing or removed VMs"); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + if (vm.getRemoved() == null && !vm.getState().equals(VirtualMachine.State.Stopped) && + !vm.getState().equals(VirtualMachine.State.Destroyed)) { + throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup"); + } + if (!backupProvider.restoreVMFromBackup(vm, backup.getExternalId(), restorePointId)) { + throw new CloudRuntimeException("Error restoring VM " + vm.getId() + " from backup " + backup.getId()); + } + importVM(vm.getDataCenterId(), vm.getDomainId(), vm.getAccountId(), vm.getUserId(), + vm.getInstanceName(), vm.getHypervisorType(), backup); + return true; + } + + private Backup.VolumeInfo getVolumeInfo(List backedUpVolumes, String volumeUuid) { + for (Backup.VolumeInfo volInfo : backedUpVolumes) { + if (volInfo.getUuid().equals(volumeUuid)) { + return volInfo; + } + } + return null; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true) + public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long vmId, final Long backupId, final String restorePointId) throws Exception { + if (Strings.isNullOrEmpty(backedUpVolumeUuid)) { + throw new CloudRuntimeException("Invalid volume ID passed"); + } + BackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + } + VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM " + vmId + " does not exist"); + } + + VMInstanceVO vmFromBackup = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); + if (vmFromBackup != null) { + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vmFromBackup); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + Pair restoreInfo = getRestoreVolumeHostAndDatastore(vm); + String hostIp = restoreInfo.first(); + String datastoreUuid = restoreInfo.second(); + + LOG.debug("Asking provider to restore volume " + backedUpVolumeUuid + " from backup " + backupId + + " and restore point " + restorePointId + " and attach it to VM: " + vm.getUuid()); + BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + Pair result = backupProvider.restoreBackedUpVolume(backup.getZoneId(), backup.getUuid(), + restorePointId, backedUpVolumeUuid, hostIp, datastoreUuid); + if (!result.first()) { + throw new CloudRuntimeException("Error restoring volume " + backedUpVolumeUuid); + } + if (!attachVolumeToVM(backup.getZoneId(), result.second(), backup.getBackedUpVolumes(), + backedUpVolumeUuid, vm, datastoreUuid, backup)) { + throw new CloudRuntimeException("Error attaching volume " + backedUpVolumeUuid + " to VM " + vm.getUuid()); + } + return true; + } + + /** + * Get the pair: hostIp, datastoreUuid in which to restore the volume, based on the VM to be attached information + */ + private Pair getRestoreVolumeHostAndDatastore(VMInstanceVO vm) { + List rootVmVolume = volumeDao.findIncludingRemovedByInstanceAndType(vm.getId(), Volume.Type.ROOT); + Long poolId = rootVmVolume.get(0).getPoolId(); + StoragePoolVO storagePoolVO = primaryDataStoreDao.findById(poolId); + String datastoreUuid = storagePoolVO.getUuid(); + String hostIp = vm.getHostId() == null ? + getHostIp(storagePoolVO) : + hostDao.findById(vm.getHostId()).getPrivateIpAddress(); + return new Pair<>(hostIp, datastoreUuid); + } + + /** + * Find a host IP from storage pool access + */ + private String getHostIp(StoragePoolVO storagePoolVO) { + List hosts = null; + if (storagePoolVO.getScope().equals(ScopeType.CLUSTER)) { + hosts = hostDao.findByClusterId(storagePoolVO.getClusterId()); + + } else if (storagePoolVO.getScope().equals(ScopeType.ZONE)) { + hosts = hostDao.findByDataCenterId(storagePoolVO.getDataCenterId()); + } + return hosts.get(0).getPrivateIpAddress(); + } + + /** + * Attach volume to VM + */ + private boolean attachVolumeToVM(Long zoneId, String restoredVolumeLocation, List backedUpVolumes, + String volumeUuid, VMInstanceVO vm, String datastoreUuid, Backup backup) throws Exception { + HypervisorGuru guru = hypervisorGuruManager.getGuru(vm.getHypervisorType()); + Backup.VolumeInfo volumeInfo = getVolumeInfo(backedUpVolumes, volumeUuid); + if (volumeInfo == null) { + throw new CloudRuntimeException("Failed to find volume in the backedup volumes of ID " + volumeUuid); + } + volumeInfo.setType(Volume.Type.DATADISK); + + LOG.debug("Attaching the restored volume to VM " + vm.getId()); + StoragePoolVO pool = primaryDataStoreDao.findByUuid(datastoreUuid); + try { + return guru.attachRestoredVolumeToVirtualMachine(zoneId, restoredVolumeLocation, volumeInfo, vm, pool.getId(), backup); + } catch (Exception e) { + throw new CloudRuntimeException("Error attach restored volume to VM " + vm.getUuid() + " due to: " + e.getMessage()); + } + } + + @Override + public boolean deleteBackupOffering(final Long policyId) { + if (!backupDao.listByPolicyId(policyId).isEmpty()) { + throw new CloudRuntimeException("Cannot allow deletion of backup offering due to use in existing VM backups, please delete the VM backups first"); + } + BackupOfferingVO policy = backupOfferingDao.findById(policyId); + if (policy == null) { + throw new CloudRuntimeException("Could not find a backup offering with id: " + policyId); + } + return backupOfferingDao.expunge(policy.getId()); + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + backgroundPollManager.submitTask(new BackupSyncTask(this)); + return true; + } + + public boolean isEnabled(final Long zoneId) { + return BackupFrameworkEnabled.valueIn(zoneId); + } + + @Override + public List listBackupProviders() { + return backupProviders; + } + + @Override + public BackupProvider getBackupProvider(final Long zoneId) { + final String name = BackupProviderPlugin.valueIn(zoneId); + if (Strings.isNullOrEmpty(name)) { + throw new CloudRuntimeException("Invalid backup provider name configured in zone id: " + zoneId); + } + if (!backupProvidersMap.containsKey(name)) { + throw new CloudRuntimeException("Failed to find backup provider for zone id:" + zoneId); + } + return backupProvidersMap.get(name); + } + + @Override + public List> getCommands() { + final List> cmdList = new ArrayList>(); + // Offerings + cmdList.add(ListBackupProvidersCmd.class); + cmdList.add(ListBackupProviderOfferingsCmd.class); + cmdList.add(ImportBackupOfferingCmd.class); + cmdList.add(ListBackupOfferingsCmd.class); + cmdList.add(DeleteBackupOfferingCmd.class); + // Operations + cmdList.add(CreateBackupCmd.class); + cmdList.add(ListBackupsCmd.class); + cmdList.add(RestoreBackupCmd.class); + cmdList.add(DeleteBackupCmd.class); + cmdList.add(RestoreVolumeFromBackupAndAttachToVMCmd.class); + cmdList.add(ImportVMCmdByAdmin.class); + // Schedule + cmdList.add(CreateBackupScheduleCmd.class); + return cmdList; + } + + @Override + public String getConfigComponentName() { + return BackupService.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[]{ + BackupFrameworkEnabled, + BackupProviderPlugin, + BackupSyncPollingInterval + }; + } + + public void setBackupProviders(final List backupProviders) { + this.backupProviders = backupProviders; + } + + @Override + public boolean start() { + initializeBackupProviderMap(); + return true; + } + + private void initializeBackupProviderMap() { + if (backupProviders != null) { + for (final BackupProvider backupProvider : backupProviders) { + backupProvidersMap.put(backupProvider.getName().toLowerCase(), backupProvider); + } + } + } + + //////////////////////////////////////////////////// + /////////////// Background Tasks /////////////////// + //////////////////////////////////////////////////// + + /** + * This background task syncs backups from providers side in CloudStack db + * along with creation of usage records + */ + private final class BackupSyncTask extends ManagedContextRunnable implements BackgroundPollTask { + private BackupManager backupManager; + + public BackupSyncTask(final BackupManager backupManager) { + this.backupManager = backupManager; + } + + @Override + protected void runInContext() { + try { + if (LOG.isTraceEnabled()) { + LOG.trace("Backup sync background task is running..."); + } + for (final DataCenter dataCenter : dataCenterDao.listAllZones()) { + if (dataCenter == null || !isEnabled(dataCenter.getId())) { + continue; + } + + // GC and expunge removed backups + for (final Backup backup : backupDao.listByZoneAndState(dataCenter.getId(), Backup.Status.Removed)) { + backupManager.deleteBackup(backup.getId()); + } + + // Sync backup size usages + final List backups = backupDao.listByZoneAndState(dataCenter.getId(), null); + if (backups.isEmpty()) { + continue; + } + final BackupProvider backupProvider = getBackupProvider(dataCenter.getId()); + final Map metrics = backupProvider.getBackupMetrics(dataCenter.getId(), backups); + for (final Backup backup : metrics.keySet()) { + final Backup.Metric metric = metrics.get(backup); + final BackupVO backupVO = (BackupVO) backup; + backupVO.setSize(metric.getBackupSize()); + backupVO.setProtectedSize(metric.getDataSize()); + if (backupDao.update(backupVO.getId(), backupVO)) { + usageBackupDao.updateMetrics(backup); + } + } + } + } catch (final Throwable t) { + LOG.error("Error trying to run backup-sync background task", t); + } + } + + @Override + public Long getDelay() { + return BackupSyncPollingInterval.value() * 1000L; + } + } +} diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index 2f67c4248d35..0c9cb7ee440f 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -300,4 +300,9 @@ + + + + + diff --git a/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java b/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java index 11f3f81f357d..8a9431fd653e 100644 --- a/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java +++ b/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java @@ -235,4 +235,9 @@ public int getNonSystemNetworkCountByVpcId(final long vpcId) { public List listNetworkVO(List idset) { return null; } + + @Override + public NetworkVO findByVlan(String vlan) { + return null; + } } \ No newline at end of file diff --git a/test/integration/smoke/test_backup_recovery.py b/test/integration/smoke/test_backup_recovery.py new file mode 100644 index 000000000000..989a5f27c140 --- /dev/null +++ b/test/integration/smoke/test_backup_recovery.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python +# 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. + +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import (cleanup_resources) +from marvin.lib.base import (Account, ServiceOffering, VirtualMachine, BackupOffering, Configurations, Backup) +from marvin.lib.common import (get_domain, get_zone, get_template) +from nose.plugins.attrib import attr +from marvin.codes import FAILED + +class TestDummyBackupAndRecovery(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + # Setup + + cls.testClient = super(TestDummyBackupAndRecovery, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + cls.services = cls.testClient.getParsedTestDataConfig() + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.services["mode"] = cls.zone.networktype + cls.hypervisor = cls.testClient.getHypervisorInfo() + cls.domain = get_domain(cls.api_client) + cls.template = get_template(cls.api_client, cls.zone.id, cls.services["ostype"]) + if cls.template == FAILED: + assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] + cls.services["small"]["zoneid"] = cls.zone.id + cls.services["small"]["template"] = cls.template.id + cls.account = Account.create(cls.api_client, cls.services["account"], domainid=cls.domain.id) + cls.offering = ServiceOffering.create(cls.api_client,cls.services["service_offerings"]["small"]) + cls.vm = VirtualMachine.create(cls.api_client, cls.services["small"], accountid=cls.account.name, + domainid=cls.account.domainid, serviceofferingid=cls.offering.id, + mode=cls.services["mode"]) + cls._cleanup = [cls.offering, cls.account] + + # Check backup configuration values, set them to enable the dummy provider + + backup_enabled_cfg = Configurations.list(cls.api_client, name='backup.framework.enabled', zoneid=cls.zone.id) + backup_provider_cfg = Configurations.list(cls.api_client, name='backup.framework.provider.plugin', zoneid=cls.zone.id) + cls.backup_enabled = backup_enabled_cfg[0].value + cls.backup_provider = backup_provider_cfg[0].value + + if not cls.backup_enabled: + Configurations.update(cls.api_client, 'backup.framework.enabled', 'true', zoneid=cls.zone.id) + if not cls.backup_provider == "dummy": + Configurations.update(cls.api_client, 'backup.framework.provider.plugin', 'dummy', zoneid=cls.zone.id) + + # Import a dummy backup offering to use on tests + + cls.external_policies = BackupOffering.listExternal(cls.api_client, cls.zone.id) + cls.debug("Importing backup offering %s - %s" % (cls.external_policies[0].externalid, cls.external_policies[0].name)) + cls.policy = BackupOffering.importExisting(cls.api_client, cls.zone.id, cls.external_policies[0].externalid, + cls.external_policies[0].name, cls.external_policies[0].description) + cls._cleanup.append(cls.policy) + + return + + @classmethod + def tearDownClass(cls): + try: + # Restore original backup framework values values + if not cls.backup_enabled: + Configurations.update(cls.api_client, 'backup.framework.enabled', cls.backup_enabled, zoneid=cls.zone.id) + if not cls.backup_provider == "dummy": + Configurations.update(cls.api_client, 'backup.framework.provider.plugin', cls.backup_provider, zoneid=cls.zone.id) + + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(tags=["advanced", "backup"], required_hardware="false") + def test_import_backup_policies(self): + """ + Import existing backup policies from Dummy Backup and Recovery Provider + """ + + # Validate the following: + # 1. Import a backup offering from the dummy provider + # 2. List internal backup policies, policy id should be listed + # 3. Delete backup offering + # 4. List internal backup policies, policy id should not be listed + + # Import backup offering + ext_policy = self.external_policies[1] + self.debug("Importing backup offering %s - %s" % (ext_policy.externalid, ext_policy.name)) + policy = BackupOffering.importExisting(self.apiclient, self.zone.id, ext_policy.externalid, + ext_policy.name, ext_policy.description) + + # Verify policy is listed + imported_policies = BackupOffering.listInternal(self.apiclient, self.zone.id) + self.assertIsInstance(imported_policies, list, "List Backup Policies should return a valid response") + self.assertNotEqual(len(imported_policies), 0, "Check if the list API returns a non-empty response") + matching_policies = [x for x in imported_policies if x.id == policy.id] + self.assertNotEqual(len(matching_policies), 0, "Check if there is a matching policy") + + # Delete backup offering + self.debug("Deleting backup offering %s" % policy.id) + policy.delete(self.apiclient) + + # Verify policy is not listed + imported_policies = BackupOffering.listInternal(self.apiclient, self.zone.id) + self.assertIsInstance(imported_policies, list, "List Backup Policies should return a valid response") + matching_policies = [x for x in imported_policies if x.id == policy.id] + self.assertEqual(len(matching_policies), 0, "Check there is not a matching policy") + + @attr(tags=["advanced", "backup"], required_hardware="false") + def test_add_vm_to_backup_Policy(self): + """ + Assign a VM to a backup offering + """ + + # Validate the following: + # 1. Add VM to backup offering + # 2. Verify a mapping between the VM and the backup offering exists + # 3. Remove VM from backup offering + # 4. Verify there is no mapping between the VM and the backup offering + + # Add VM to backup offering + self.debug("Adding VM %s to backup offering %s" % (self.vm.id, self.policy.id)) + self.policy.addVM(self.apiclient, self.vm.id) + + # Verify a mapping between backup offering and VM is created on DB + mappings = BackupOffering.listVMMappings(self.apiclient, self.policy.id, self.vm.id, self.zone.id) + self.assertNotEqual(len(mappings), 0, "A mapping between VM and backup offering should exist") + self.assertNotEqual(mappings[0], None, "A mapping between VM and backup offering should exist") + + # Remove VM from backup offering + self.debug("Removing VM %s from backup offering %s" % (self.vm.id, self.policy.id)) + self.policy.removeVM(self.apiclient, self.vm.id) + + # Verify mapping is removed + zone_mappings = BackupOffering.listVMMappings(self.apiclient, zoneid=self.zone.id) + matching_mappings = [x for x in zone_mappings if x.policyid == self.policy.id and x.virtualmachineid == self.vm.id] + self.assertEqual(len(matching_mappings), 0, "The mapping between VM and backup offering should be removed") + + @attr(tags=["advanced", "backup"], required_hardware="false") + def test_vm_backup_lifecycle(self): + """ + Test VM backup lifecycle + """ + + # Validate the following: + # 1. List VM backups, verify no backups are created + # 2. Add VM to policy + # 3. Create VM backup + # 4. List VM backups, verify backup is created + # 5. Delete VM backup + # 6. List VM backups, verify backup is deleted + # 7. Remove VM from policy + + # Verify there are no backups for the VM + backups = Backup.list(self.apiclient, self.vm.id) + self.assertEqual(backups, None, "There should not exist any backup for the VM") + + # Create a VM backup + self.policy.addVM(self.apiclient, self.vm.id) + Backup.create(self.apiclient, self.vm.id) + + # Verify backup is created for the VM + backups = Backup.list(self.apiclient, self.vm.id) + self.assertEqual(len(backups), 1, "There should exist only one backup for the VM") + backup = backups[0] + + # Delete backup + Backup.delete(self.apiclient, backup.id) + + # Verify backup is deleted + backups = Backup.list(self.apiclient, self.vm.id) + self.assertEqual(backups, None, "There should not exist any backup for the VM") + + # Remove VM from policy + self.policy.removeVM(self.apiclient, self.vm.id) diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index 1c5cf27128a5..bfd1a43a2f67 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -189,6 +189,8 @@ 'Sioc' : 'Sioc', 'Diagnostics': 'Diagnostics', 'Management': 'Management', + 'Backup' : 'Backup', + 'Restore' : 'Backup' } diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index e7572e918015..c5ff72ddc9e7 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -5241,3 +5241,127 @@ def delete(self, apiclient, resourceid, resourcetype): cmd.resourceid = resourceid cmd.resourcetype = resourcetype return (apiclient.removeResourceDetail(cmd)) + +# Backup and Recovery + +class BackupOffering: + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def importExisting(self, apiclient, zoneid, externalid, name, description): + """Import existing backup offering from the provider""" + + cmd = importBackupOffering.importBackupOfferingCmd() + cmd.zoneid = zoneid + cmd.externalid = externalid + cmd.name = name + cmd.description = description + return BackupOffering(apiclient.importBackupOffering(cmd).__dict__) + + @classmethod + def listInternalById(self, apiclient, id): + """List imported backup policies by id""" + + cmd = listBackupPolicies.listBackupPoliciesCmd() + cmd.id = id + return (apiclient.listBackupPolicies(cmd)) + + @classmethod + def listInternal(self, apiclient, zoneid): + """List imported backup policies""" + + cmd = listBackupPolicies.listBackupPoliciesCmd() + cmd.zoneid = zoneid + return (apiclient.listBackupPolicies(cmd)) + + @classmethod + def listExternal(self, apiclient, zoneid): + """List external backup policies""" + + cmd = listBackupPolicies.listBackupPoliciesCmd() + cmd.zoneid = zoneid + cmd.external = True + return (apiclient.listBackupPolicies(cmd)) + + def delete(self, apiclient): + """Delete an imported backup offering""" + + cmd = deleteBackupOffering.deleteBackupOfferingCmd() + cmd.id = self.id + return (apiclient.deleteBackupOffering(cmd)) + + def addVM(self, apiclient, vmid): + """Add a VM to a backup offering""" + + cmd = addVMToBackupOffering.addVMToBackupOfferingCmd() + cmd.policyid = self.id + cmd.virtualmachineid = vmid + return (apiclient.addVMToBackupOffering(cmd)) + + def removeVM(self, apiclient, vmid): + """Remove a VM from a backup offering""" + + cmd = removeVMFromBackupOffering.removeVMFromBackupOfferingCmd() + cmd.policyid = self.id + cmd.virtualmachineid = vmid + return (apiclient.removeVMFromBackupOffering(cmd)) + + @classmethod + def listVMMappings(self, apiclient, policyid=None, vmid=None, zoneid=None): + """List VM - Backup policies mappings""" + + cmd = listBackupOfferingVMMappings.listBackupOfferingVMMappingsCmd() + if vmid: + cmd.virtualmachineid = vmid + if zoneid: + cmd.zoneid = zoneid + if policyid: + cmd.policyid = policyid + return (apiclient.listBackupOfferingVMMappings(cmd)) + +class Backup: + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(self, apiclient, vmid): + """Create VM backup""" + + cmd = createBackup.createBackupCmd() + cmd.virtualmachineid = vmid + return (apiclient.createBackup(cmd)) + + @classmethod + def delete(self, apiclient, id): + """Delete VM backup""" + + cmd = deleteBackup.deleteBackupCmd() + cmd.id = id + return (apiclient.deleteBackup(cmd)) + + @classmethod + def list(self, apiclient, vmid): + """List VM backups""" + + cmd = listBackups.listBackupsCmd() + cmd.virtualmachineid = vmid + return (apiclient.listBackups(cmd)) + + def restoreVM(self, apiclient): + """Restore VM from backup""" + + cmd = restoreVMFromBackup.restoreVMFromBackupCmd() + cmd.id = self.id + return (apiclient.restoreVMFromBackup(cmd)) + + def restoreVolumeAndAttachToVM(self, apiclient, volumeid, vmid): + """Restore volume from backup and attach it to VM""" + + cmd = restoreVolumeFromBackupAndAttachToVM.restoreVolumeFromBackupAndAttachToVMCmd() + cmd.id = self.id + cmd.volumeid = volumeid + cmd.virtualmachineid = vmid + return (apiclient.restoreVolumeFromBackupAndAttachToVM(cmd)) diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 4f186cabf522..655547770608 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -12280,11 +12280,13 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it } .restart .icon, +.restoreBackup .icon, .releaseDedicatedZone .icon { background-position: 0 -63px; } .restart:hover .icon, +.restoreBackup:hover .icon, .releaseDedicatedZone:hover .icon { background-position: 0 -645px; } @@ -12400,12 +12402,14 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it .snapshot .icon, .takeSnapshot .icon, +.startBackup .icon, .storageSnapshot .icon { background-position: -36px -91px; } .snapshot:hover .icon, .takeSnapshot:hover .icon, +.startBackup:hover .icon, .storageSnapshot:hover .icon { background-position: -36px -673px; } @@ -12735,10 +12739,12 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it background-position: -197px -647px; } +.createBackup .icon, .forceReconnect .icon { background-position: -196px -95px; } +.createBackup:hover .icon, .forceReconnect:hover .icon { background-position: -196px -677px; } diff --git a/ui/l10n/en.js b/ui/l10n/en.js index 4ce59e08599b..c9db38869027 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -134,6 +134,7 @@ var dictionary = { "label.action.create.vm.processing":"Creating VM....", "label.action.create.volume":"Create Volume", "label.action.create.volume.processing":"Creating Volume....", +"label.action.delete.backup.offering":"Delete Backup Offering", "label.action.delete.IP.range":"Delete IP Range", "label.action.delete.IP.range.processing":"Deleting IP Range....", "label.action.delete.ISO":"Delete ISO", @@ -779,6 +780,7 @@ var dictionary = { "label.every":"Every", "label.example":"Example", "label.expunge":"Expunge", +"label.external.id":"External ID", "label.external.link":"External link", "label.extractable":"Extractable", "label.extractable.lower":"extractable", @@ -1058,6 +1060,8 @@ var dictionary = { "label.menu.alerts":"Alerts", "label.menu.all.accounts":"All Accounts", "label.menu.all.instances":"All Instances", +"label.menu.backup":"Backup", +"label.menu.backup.offerings":"Backup Offerings", "label.menu.community.isos":"Community ISOs", "label.menu.community.templates":"Community Templates", "label.menu.configuration":"Configuration", @@ -1787,6 +1791,7 @@ var dictionary = { "label.view.more":"View more", "label.view.secondary.ips":"View secondary IPs", "label.viewing":"Viewing", +"label.virtual.size":"Virtual Size", "label.virtual.appliance":"Virtual Appliance", "label.virtual.appliance.details":"Virtual applicance details", "label.virtual.appliances":"Virtual Appliances", @@ -1821,6 +1826,7 @@ var dictionary = { "label.vm.stop":"Stop", "label.vmfs":"VMFS", "label.vms":"VMs", +"label.backup":"Backups", "label.vmsnapshot":"VM Snapshots", "label.vmsnapshot.current":"isCurrent", "label.vmsnapshot.memory":"Snapshot memory", @@ -1905,6 +1911,7 @@ var dictionary = { "message.action.cancel.maintenance.mode":"Please confirm that you want to cancel this maintenance.", "message.action.change.service.warning.for.instance":"Your instance must be stopped before attempting to change its current service offering.", "message.action.change.service.warning.for.router":"Your router must be stopped before attempting to change its current service offering.", +"message.action.delete.backup.offering":"Please confirm that you want to delete this backup offering?", "message.action.delete.ISO":"Please confirm that you want to delete this ISO.", "message.action.delete.ISO.for.all.zones":"The ISO is used by all zones. Please confirm that you want to delete it from all zones.", "message.action.delete.cluster":"Please confirm that you want to delete this cluster.", diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index 62a602263f44..e1348a1feb73 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -28,7 +28,7 @@ sectionSelect: { preFilter: function(args) { if(isAdmin()) - return ["serviceOfferings", "systemServiceOfferings", "diskOfferings", "networkOfferings", "vpcOfferings"]; + return ["serviceOfferings", "systemServiceOfferings", "diskOfferings", "networkOfferings", "vpcOfferings", "backupOfferings"]; else if(isDomainAdmin()) return ["serviceOfferings", "diskOfferings"]; else @@ -2926,6 +2926,140 @@ } }, + backupOfferings: { + type: 'select', + title: 'label.menu.backup.offerings', + listView: { + id: 'backupOfferings', + label: 'label.menu.backup.offerings', + fields: { + name: { + label: 'label.name', + editable: true + }, + description: { + label: 'label.description' + }, + zoneid: { + label: 'label.zone', + } + }, + + actions: { + }, + + dataProvider: function(args) { + var data = {}; + listViewDataProvider(args, data); + + $.ajax({ + url: createURL('listBackupOfferings'), + data: data, + success: function(json) { + var items = json.listbackupofferingsresponse.backupoffering; + args.response.success({ + data: items + }); + }, + error: function(data) { + args.response.error(parseXMLHttpResponse(data)); + } + }); + }, + + detailView: { + name: 'label.system.backup.offering.details', + actions: { + remove: { + label: 'label.action.delete.backup.offering', + messages: { + confirm: function(args) { + return 'message.action.delete.backup.offering'; + }, + notification: function(args) { + return 'label.action.delete.backup.offering'; + } + }, + action: function(args) { + var data = { + id: args.context.backupOfferings[0].id + }; + $.ajax({ + url: createURL('deleteBackupOffering'), + data: data, + success: function(json) { + args.response.success(); + }, + error: function(data) { + args.response.error(parseXMLHttpResponse(data)); + } + }); + }, + notification: { + poll: function(args) { + args.complete(); + } + } + } + }, + + tabs: { + details: { + title: 'label.details', + + fields: [{ + name: { + label: 'label.name', + isEditable: true, + validation: { + required: true + } + } + }, { + id: { + label: 'label.id' + }, + description: { + label: 'label.description', + isEditable: true, + validation: { + required: true + } + }, + externalid: { + label: 'label.external.id', + }, + zoneid: { + label: 'label.zone.id' + }, + created: { + label: 'label.created', + converter: cloudStack.converters.toLocalDate + } + }], + + dataProvider: function(args) { + var data = { + id: args.context.backupOfferings[0].id + }; + $.ajax({ + url: createURL('listBackupOfferings'), + data: data, + success: function(json) { + var item = json.listbackupofferingsresponse.backupoffering[0]; + args.response.success({ + actionFilter: backupOfferingActionfilter, + data: item + }); + } + }); + } + } + } + } + } + }, + networkOfferings: { type: 'select', title: 'label.menu.network.offerings', @@ -5574,6 +5708,13 @@ return allowedActions; }; + var backupOfferingActionfilter = function(args) { + var jsonObj = args.context.item; + var allowedActions = []; + allowedActions.push("remove"); + return allowedActions; + }; + var diskOfferingActionfilter = function(args) { var jsonObj = args.context.item; var allowedActions = []; diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index 436183af8357..548dcdf083e8 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -795,6 +795,9 @@ }, { path: 'storage.vmsnapshots', label: 'label.snapshots' + }, { + path: 'storage.backups', + label: 'label.backup' }, { path: 'affinityGroups', label: 'label.affinity.groups' @@ -1257,6 +1260,76 @@ poll: pollAsyncJobResult } }, + + createBackup: { + messages: { + notification: function() { + return 'Create VM Backup'; + } + }, + label: 'Create VM Backup', + createForm: { + title: 'Create VM Backup', + desc: 'Please select a backup offering for the VM backup with a name and description', + fields: { + name: { + label: 'label.name', + }, + description: { + label: 'label.description' + }, + policy: { + label: 'Backup Offering', + select: function(args) { + var items = []; + $.ajax({ + url: createURL('listBackupOfferings'), + dataType: 'json', + async: false, + success: function(json) { + var policies = json.listbackupofferingsresponse.backupoffering; + $(policies).each(function(index, policy) { + items.push({ + id: policy.id, + description: policy.name + }); + }); + args.response.success({ + data: items + }); + } + }); + } + } + } + }, + action: function(args) { + var data = { + virtualmachineid: args.context.instances[0].id, + name: args.data.name, + description: args.data.description, + policyid: args.data.policy + }; + $.ajax({ + url: createURL('createBackup'), + data: data, + dataType: 'json', + async: true, + success: function(json) { + var jid = json.createbackupresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + destroy: vmDestroyAction(), expunge: { label: 'label.action.expunge.instance', @@ -3717,6 +3790,7 @@ allowedActions.push("expunge"); } } + allowedActions.push("createBackup"); return allowedActions; } diff --git a/ui/scripts/storage.js b/ui/scripts/storage.js index a51e8f33492d..f7a63d2c936c 100644 --- a/ui/scripts/storage.js +++ b/ui/scripts/storage.js @@ -2647,6 +2647,258 @@ } //detailview end } + }, + + /** + * Backups + */ + backups: { + type: 'select', + title: 'label.backup', + listView: { + id: 'backups', + isMaximized: true, + fields: { + name: { + label: 'label.name' + }, + status: { + label: 'label.state', + indicator: { + 'BackedUp': 'on', + 'Failed': 'off', + 'Error': 'off' + } + }, + zoneid: { + label: 'label.zone' + } + }, + + dataProvider: function(args) { + var data = { + listAll: true + }; + listViewDataProvider(args, data); + + if (args.context != null) { + if ("instances" in args.context) { + $.extend(data, { + virtualmachineid: args.context.instances[0].id + }); + } + } + + $.ajax({ + url: createURL('listBackups'), + data: data, + dataType: "json", + async: true, + success: function(json) { + var jsonObj; + jsonObj = json.listbackupsresponse.backup; + args.response.success({ + actionFilter: backupActionfilter, + data: jsonObj + }); + } + }); + }, + //dataProvider end + detailView: { + tabs: { + details: { + title: 'label.details', + fields: { + id: { + label: 'label.id' + }, + name: { + label: 'label.name' + }, + status: { + label: 'label.state' + }, + externalid: { + label: 'label.external.id' + }, + size: { + label: 'label.size' + }, + virtualsize: { + label: 'label.virtual.size' + }, + volumes: { + label: 'label.volumes' + }, + virtualmachineid: { + label: 'label.vm.id' + }, + accountid: { + label: 'label.account' + }, + zoneid: { + label: 'label.zone' + }, + created: { + label: 'label.date', + converter: cloudStack.converters.toLocalDate + } + }, + dataProvider: function(args) { + $.ajax({ + url: createURL("listBackups&id=" + args.context.backups[0].id), + dataType: "json", + async: true, + success: function(json) { + var jsonObj; + jsonObj = json.listbackupsresponse.backup[0]; + args.response.success({ + actionFilter: backupActionfilter, + data: jsonObj + }); + } + }); + }, + tags: cloudStack.api.tags({ + resourceType: 'Backup', + contextId: 'backups' + }) + } + }, + actions: { + remove: { + label: 'Delete Backup', + messages: { + confirm: function(args) { + return 'Are you sure you want to delete the backup?'; + }, + notification: function(args) { + return 'Delete Backup'; + } + }, + action: function(args) { + $.ajax({ + url: createURL("deleteBackup&id=" + args.context.backups[0].id), + dataType: "json", + async: true, + success: function(json) { + var jid = json.deletebackupresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + + startBackup: { + label: 'Start Ad-hoc Backup', + messages: { + confirm: function(args) { + return 'Please confirm that you want to start an ad-hoc backup of the VM?'; + }, + notification: function(args) { + return 'Ad-hoc VM Backup'; + } + }, + action: function(args) { + $.ajax({ + url: createURL("startBackup&id=" + args.context.backups[0].id), + dataType: "json", + async: true, + success: function(json) { + var jid = json.startbackupresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + + restoreBackup: { + label: 'Restore Backup', + messages: { + confirm: function(args) { + return 'Please confirm that you want to restore the vm backup on the stopped VM?'; + }, + notification: function(args) { + return 'Backup restored for the VM'; + } + }, + createForm: { + title: 'Restore Backup', + desc: 'Please confirm that you want to restore the VM backup on the stopped VM?', + fields: { + restorepoint: { + label: 'Restore Point', + validation: { + required: true + }, + select: function(args) { + $.ajax({ + url: createURL("listRestorePoints&backupid=" + args.context.backups[0].id), + dataType: "json", + async: true, + success: function(json) { + var rps = json.listrestorepointsresponse.restorepoint; + var items = []; + $(rps).each(function() { + items.push({ + id: this.id, + description: this.created + " (" + this.type + ")" + }); + }); + args.response.success({ + data: items + }); + + } + }); + } + } + } + }, + action: function(args) { + var data = { + restorepointid: args.data.restorepoint, + backupid: args.context.backups[0].id + }; + $.ajax({ + url: createURL("restoreBackup"), + data: data, + dataType: "json", + async: true, + success: function(json) { + var jid = json.restorebackupresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + + }, + notification: { + poll: pollAsyncJobResult + } + } + } + } + //detailview end + } } } }; @@ -2763,4 +3015,21 @@ return allowedActions; } + var backupActionfilter = cloudStack.actionFilter.backupActionfilter = function(args) { + var jsonObj = args.context.item; + + if (jsonObj.state == 'Destroyed') { + return []; + } + + var allowedActions = []; + allowedActions.push("remove"); + allowedActions.push("startBackup"); + allowedActions.push("restoreBackup"); + + return allowedActions; + }; + + + })(cloudStack); diff --git a/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java b/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java index 1abe5f75eb6f..6a39755808d5 100644 --- a/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java +++ b/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java @@ -57,6 +57,7 @@ import com.cloud.usage.dao.UsageNetworkOfferingDao; import com.cloud.usage.dao.UsagePortForwardingRuleDao; import com.cloud.usage.dao.UsageSecurityGroupDao; +import com.cloud.usage.dao.UsageBackupDao; import com.cloud.usage.dao.UsageVMSnapshotOnPrimaryDao; import com.cloud.usage.dao.UsageStorageDao; import com.cloud.usage.dao.UsageVMInstanceDao; @@ -71,12 +72,13 @@ import com.cloud.usage.parser.PortForwardingUsageParser; import com.cloud.usage.parser.SecurityGroupUsageParser; import com.cloud.usage.parser.StorageUsageParser; +import com.cloud.usage.parser.BackupUsageParser; import com.cloud.usage.parser.VMInstanceUsageParser; +import com.cloud.usage.parser.VMSanpshotOnPrimaryParser; import com.cloud.usage.parser.VMSnapshotUsageParser; import com.cloud.usage.parser.VPNUserUsageParser; import com.cloud.usage.parser.VmDiskUsageParser; import com.cloud.usage.parser.VolumeUsageParser; -import com.cloud.usage.parser.VMSanpshotOnPrimaryParser; import com.cloud.user.Account; import com.cloud.user.AccountVO; import com.cloud.user.UserStatisticsVO; @@ -150,6 +152,8 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna @Inject private UsageVMSnapshotOnPrimaryDao _usageSnapshotOnPrimaryDao; @Inject + private UsageBackupDao usageBackupDao; + @Inject private QuotaManager _quotaManager; @Inject private QuotaAlertManager _alertManager; @@ -956,6 +960,12 @@ private boolean parseHelperTables(AccountVO account, Date currentStartDate, Date s_logger.debug("VM Snapshot on primary usage successfully parsed? " + parsed + " (for account: " + account.getAccountName() + ", id: " + account.getId() + ")"); } } + parsed = BackupUsageParser.parse(account, currentStartDate, currentEndDate); + if (s_logger.isDebugEnabled()) { + if (!parsed) { + s_logger.debug("VM Backup usage successfully parsed? " + parsed + " (for account: " + account.getAccountName() + ", id: " + account.getId() + ")"); + } + } return parsed; } @@ -987,6 +997,8 @@ private void createHelperRecord(UsageEventVO event) { createVMSnapshotEvent(event); } else if (isVmSnapshotOnPrimaryEvent(eventType)) { createVmSnapshotOnPrimaryEvent(event); + } else if (isBackupEvent(eventType)) { + createBackupEvent(event); } } @@ -1068,6 +1080,10 @@ private boolean isVmSnapshotOnPrimaryEvent(String eventType) { return (eventType.equals(EventTypes.EVENT_VM_SNAPSHOT_ON_PRIMARY) || eventType.equals(EventTypes.EVENT_VM_SNAPSHOT_OFF_PRIMARY)); } + private boolean isBackupEvent(String eventType) { + return eventType != null && (eventType.equals(EventTypes.EVENT_VM_BACKUP_CREATE) || eventType.equals(EventTypes.EVENT_VM_BACKUP_DELETE)); + } + private void createVMHelperEvent(UsageEventVO event) { // One record for handling VM.START and VM.STOP @@ -1884,6 +1900,24 @@ private void createVmSnapshotOnPrimaryEvent(UsageEventVO event) { } } + private void createBackupEvent(final UsageEventVO event) { + Long backupId = event.getResourceId(); + Long vmId = event.getTemplateId(); + Long zoneId = event.getZoneId(); + Long accountId = event.getAccountId(); + Date created = event.getCreateDate(); + Account account = _accountDao.findByIdIncludingRemoved(event.getAccountId()); + Long domainId = account.getDomainId(); + + if (EventTypes.EVENT_VM_BACKUP_CREATE.equals(event.getType())) { + final UsageBackupVO backupVO = new UsageBackupVO(zoneId, accountId, domainId, backupId, vmId, created); + usageBackupDao.persist(backupVO); + } else if (EventTypes.EVENT_VM_BACKUP_DELETE.equals(event.getType())) { + usageBackupDao.removeUsage(accountId, zoneId, backupId); + } + + } + private class Heartbeat extends ManagedContextRunnable { @Override protected void runInContext() { diff --git a/usage/src/main/java/com/cloud/usage/parser/BackupUsageParser.java b/usage/src/main/java/com/cloud/usage/parser/BackupUsageParser.java new file mode 100644 index 000000000000..3cf2cac53822 --- /dev/null +++ b/usage/src/main/java/com/cloud/usage/parser/BackupUsageParser.java @@ -0,0 +1,124 @@ +// 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 com.cloud.usage.parser; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.usage.UsageTypes; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.usage.UsageBackupVO; +import com.cloud.usage.UsageVO; +import com.cloud.usage.dao.UsageDao; +import com.cloud.usage.dao.UsageBackupDao; +import com.cloud.user.AccountVO; + +@Component +public class BackupUsageParser { + public static final Logger LOGGER = Logger.getLogger(BackupUsageParser.class); + + private static UsageDao s_usageDao; + private static UsageBackupDao s_usageBackupDao; + + @Inject + private UsageDao usageDao; + @Inject + private UsageBackupDao usageBackupDao; + + @PostConstruct + void init() { + s_usageDao = usageDao; + s_usageBackupDao = usageBackupDao; + } + + public static boolean parse(AccountVO account, Date startDate, Date endDate) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Parsing all VM Backup usage events for account: " + account.getId()); + } + if ((endDate == null) || endDate.after(new Date())) { + endDate = new Date(); + } + + final List usageBackups = s_usageBackupDao.getUsageRecords(account.getId(), startDate, endDate); + if (usageBackups == null || usageBackups.isEmpty()) { + LOGGER.debug("No VM Backup usage for this period"); + return true; + } + + final Map vmUsageMap = new HashMap<>(); + for (final UsageBackupVO usageBackup : usageBackups) { + final Long vmId = usageBackup.getVmId(); + final Long zoneId = usageBackup.getZoneId(); + if (vmUsageMap.get(vmId) == null) { + vmUsageMap.put(vmId, new BackupUsageParser.BackupInfo(new Backup.Metric(0L, 0L), zoneId, vmId)); + } + final Backup.Metric metric = vmUsageMap.get(vmId).getMetric(); + metric.setBackupSize(metric.getBackupSize() + usageBackup.getSize()); + metric.setDataSize(metric.getDataSize() + usageBackup.getProtectedSize()); + } + + for (final BackupInfo backupInfo : vmUsageMap.values()) { + final Long vmId = backupInfo.getVmId(); + final Long zoneId = backupInfo.getZoneId(); + final Double rawUsage = (double) backupInfo.getMetric().getBackupSize(); + final Double sizeGib = rawUsage / (1024.0 * 1024.0 * 1024.0); + final String description = String.format("Backup usage VM ID: %d", vmId); + final String usageDisplay = String.format("%.4f GiB", sizeGib); + + final UsageVO usageRecord = + new UsageVO(zoneId, account.getAccountId(), account.getDomainId(), description, usageDisplay, + UsageTypes.BACKUP, rawUsage, vmId, null, null, null, vmId, + backupInfo.getMetric().getBackupSize(), backupInfo.getMetric().getDataSize(), startDate, endDate); + s_usageDao.persist(usageRecord); + } + + return true; + } + + static class BackupInfo { + Backup.Metric metric; + Long zoneId; + Long vmId; + + public BackupInfo(Backup.Metric metric, Long zoneId, Long vmId) { + this.metric = metric; + this.zoneId = zoneId; + this.vmId = vmId; + } + + public Backup.Metric getMetric() { + return metric; + } + + public Long getZoneId() { + return zoneId; + } + + public Long getVmId() { + return vmId; + } + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/cloud/utils/UuidUtils.java b/utils/src/main/java/com/cloud/utils/UuidUtils.java index 9c4a75633548..e733eff6da30 100644 --- a/utils/src/main/java/com/cloud/utils/UuidUtils.java +++ b/utils/src/main/java/com/cloud/utils/UuidUtils.java @@ -19,6 +19,7 @@ package com.cloud.utils; +import com.cloud.utils.exception.CloudRuntimeException; import org.apache.xerces.impl.xpath.regex.RegularExpression; public class UuidUtils { @@ -31,4 +32,25 @@ public static boolean validateUUID(String uuid) { RegularExpression regex = new RegularExpression("[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}"); return regex.matches(uuid); } + + /** + * Returns a valid UUID in string format from a 32 digit UUID string without hyphens. + * Example: 24abcb8f4211374fa2e1e5c0b7e88a2d -> 24abcb8f-4211-374f-a2e1-e5c0b7e88a2d + */ + public static String normalize(String noHyphen) { + if (noHyphen.length() != 32 || noHyphen.contains("-")) { + throw new CloudRuntimeException("Invalid string format"); + } + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(noHyphen.substring(0, 8)).append("-") + .append(noHyphen.substring(8, 12)).append("-") + .append(noHyphen.substring(12, 16)).append("-") + .append(noHyphen.substring(16, 20)).append("-") + .append(noHyphen.substring(20, 32)); + String uuid = stringBuilder.toString(); + if (!validateUUID(uuid)) { + throw new CloudRuntimeException("Error generating UUID"); + } + return uuid; + } } \ No newline at end of file diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatastoreMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatastoreMO.java index 817320b5a219..fa0c380eb062 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatastoreMO.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatastoreMO.java @@ -214,7 +214,7 @@ public boolean deleteFile(String path, ManagedObjectReference morDc, boolean tes return false; } - boolean copyDatastoreFile(String srcFilePath, ManagedObjectReference morSrcDc, ManagedObjectReference morDestDs, String destFilePath, + public boolean copyDatastoreFile(String srcFilePath, ManagedObjectReference morSrcDc, ManagedObjectReference morDestDs, String destFilePath, ManagedObjectReference morDestDc, boolean forceOverwrite) throws Exception { String srcDsName = getName(); diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/NetworkMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/NetworkMO.java index e2797d39c1e2..85006e3798dd 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/NetworkMO.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/NetworkMO.java @@ -21,6 +21,7 @@ import com.vmware.vim25.ManagedObjectReference; import com.cloud.hypervisor.vmware.util.VmwareContext; +import com.vmware.vim25.NetworkSummary; public class NetworkMO extends BaseMO { public NetworkMO(VmwareContext context, ManagedObjectReference morCluster) { @@ -38,4 +39,12 @@ public void destroyNetwork() throws Exception { public List getVMsOnNetwork() throws Exception { return _context.getVimClient().getDynamicProperty(_mor, "vm"); } + + public String getName() throws Exception { + return _context.getVimClient().getDynamicProperty(_mor, "name"); + } + + public NetworkSummary getSummary() throws Exception { + return _context.getVimClient().getDynamicProperty(_mor, "summary"); + } } diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareClient.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareClient.java index 3050f0a19f05..3869768d39c1 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareClient.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareClient.java @@ -102,7 +102,7 @@ public boolean verify(String urlHostName, SSLSession session) { vimService = new VimService(); } catch (Exception e) { s_logger.info("[ignored]" - + "failed to trust all certificates blindly: " + e.getLocalizedMessage()); + + "failed to trust all certificates blindly: ", e); } } From 6e8778b1fe1d8fb41fc8047231c7db8fba799cc1 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Tue, 19 Nov 2019 15:31:29 +0530 Subject: [PATCH 02/81] remove id req. Signed-off-by: Rohit Yadav --- .../api/command/user/backup/ListBackupOfferingsCmd.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java index 0857b1555441..143884119bce 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java @@ -78,7 +78,6 @@ public Long getPolicyId() { @Override public void execute() throws ResourceUnavailableException, ServerApiException, ConcurrentOperationException { - validateParameters(); try { final List backupOfferings = backupManager.listBackupOfferings(getZoneId(), getPolicyId()); setupResponseBackupOfferingsList(backupOfferings); @@ -89,12 +88,6 @@ public void execute() throws ResourceUnavailableException, ServerApiException, C } } - private void validateParameters() { - if (getZoneId() == null) { - throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please provide a valid zone ID"); - } - } - @Override public String getCommandName() { return APINAME.toLowerCase() + RESPONSE_SUFFIX; From b75f35e341b0f91ffb9478d580c87767bd25c1c9 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Tue, 19 Nov 2019 16:35:45 +0530 Subject: [PATCH 03/81] apidoc: fix section name Signed-off-by: Rohit Yadav --- tools/apidoc/gen_toc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index bfd1a43a2f67..832258885949 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -189,8 +189,8 @@ 'Sioc' : 'Sioc', 'Diagnostics': 'Diagnostics', 'Management': 'Management', - 'Backup' : 'Backup', - 'Restore' : 'Backup' + 'Backup' : 'Backup and Recovery', + 'Restore' : 'Backup and Recovery' } From 21509bdf1c7b1894b3ad5ca94bf23aa1b7fc5352 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Wed, 20 Nov 2019 13:50:28 +0530 Subject: [PATCH 04/81] don't allow duplicates by name in the same zone Signed-off-by: Rohit Yadav --- .../cloudstack/backup/dao/BackupOfferingDao.java | 3 ++- .../backup/dao/BackupOfferingDaoImpl.java | 13 ++++++++++++- .../apache/cloudstack/backup/BackupManagerImpl.java | 7 ++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java index ecce8ffb9fb5..ad64cd8bb8b1 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java @@ -28,5 +28,6 @@ public interface BackupOfferingDao extends GenericDao { BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy); List listByZone(Long zoneId); - BackupOffering listByExternalId(String externalId); + BackupOffering findByExternalId(String externalId, Long zoneId); + BackupOffering findByName(String name, Long zoneId); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java index 965c219635ee..e3e200c12b27 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java @@ -46,6 +46,7 @@ public BackupOfferingDaoImpl() { @PostConstruct protected void init() { backupPoliciesSearch = createSearchBuilder(); + backupPoliciesSearch.and("name", backupPoliciesSearch.entity().getName(), SearchCriteria.Op.EQ); backupPoliciesSearch.and("zone_id", backupPoliciesSearch.entity().getZoneId(), SearchCriteria.Op.EQ); backupPoliciesSearch.and("external_id", backupPoliciesSearch.entity().getExternalId(), SearchCriteria.Op.EQ); backupPoliciesSearch.done(); @@ -79,9 +80,19 @@ public List listByZone(Long zoneId) { } @Override - public BackupOffering listByExternalId(String externalId) { + public BackupOffering findByExternalId(String externalId, Long zoneId) { SearchCriteria sc = backupPoliciesSearch.create(); sc.setParameters("external_id", externalId); + if (zoneId != null) { + sc.setParameters("zone_id", zoneId); + } return findOneBy(sc); } + + @Override + public BackupOffering findByName(String name, Long zoneId) { + SearchCriteria sc = backupPoliciesSearch.create(); + sc.setParameters("name", name); + sc.setParameters("zone_id", zoneId); + return findOneBy(sc); } } 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 a699dd199f18..315822c05bf6 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -115,14 +115,19 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_IMPORT_OFFERING, eventDescription = "importing backup offering", async = true) public BackupOffering importBackupOffering(final Long zoneId, final String offeringExternalId, final String offeringName, final String offeringDescription) { - final BackupOffering existingOffering = backupOfferingDao.listByExternalId(offeringExternalId); + final BackupOffering existingOffering = backupOfferingDao.findByExternalId(offeringExternalId, zoneId); if (existingOffering != null) { throw new CloudRuntimeException("A backup offering with external ID " + offeringExternalId + " already exists"); } + if (backupOfferingDao.findByName(offeringName, zoneId) != null) { + throw new CloudRuntimeException("A backup offering with the same name already exists in this zone"); + } + final BackupProvider provider = getBackupProvider(zoneId); if (!provider.isBackupOffering(zoneId, offeringExternalId)) { throw new CloudRuntimeException("Policy " + offeringExternalId + " does not exist on provider " + provider.getName() + " on zone " + zoneId); } + final BackupOfferingVO policy = new BackupOfferingVO(zoneId, offeringExternalId, offeringName, offeringDescription); final BackupOfferingVO savedPolicy = backupOfferingDao.persist(policy); if (savedPolicy == null) { From 3edec4994ab30d899cdf9e493f3aab3e8f5ab9c5 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Wed, 20 Nov 2019 14:12:23 +0530 Subject: [PATCH 05/81] implement the asyncjob based scheduler design in db Signed-off-by: Rohit Yadav --- .../org/apache/cloudstack/backup/Backup.java | 6 ++ .../apache/cloudstack/backup/BackupVO.java | 62 +++++++++++++++++++ .../META-INF/db/schema-41300to41400.sql | 5 ++ 3 files changed, 73 insertions(+) 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 d64e31c07fb7..3e51ef3576d0 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -24,6 +24,7 @@ import org.apache.cloudstack.api.InternalIdentity; import com.cloud.storage.Volume; +import com.cloud.utils.DateUtil; import com.cloud.utils.StringUtils; public interface Backup extends InternalIdentity, Identity { @@ -141,6 +142,11 @@ public String toString() { Long getSize(); Long getProtectedSize(); String getExternalId(); + DateUtil.IntervalType getScheduleType(); + String getSchedule(); + String getTimezone(); + Date getScheduledTimestamp(); + Long getAsyncJobId(); Long getAccountId(); Long getZoneId(); Date getCreated(); 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 ad974704d047..e30ddbbd23d0 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 @@ -31,6 +31,7 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; +import com.cloud.utils.DateUtil; import com.google.gson.Gson; @Entity @@ -65,6 +66,22 @@ public class BackupVO implements Backup { @Column(name = "status") private Status status; + @Column(name = "schedule_type") + private Short scheduleType; + + @Column(name = "schedule") + String schedule; + + @Column(name = "timezone") + String timezone; + + @Column(name = "scheduled_timestamp") + @Temporal(value = TemporalType.TIMESTAMP) + Date scheduledTimestamp; + + @Column(name = "async_job_id") + Long asyncJobId; + @Column(name = "account_id") private long accountId; @@ -145,6 +162,31 @@ public Long getProtectedSize() { return protectedSize; } + @Override + public DateUtil.IntervalType getScheduleType() { + return scheduleType == null ? null : DateUtil.getIntervalType(scheduleType); + } + + @Override + public String getSchedule() { + return schedule; + } + + @Override + public String getTimezone() { + return timezone; + } + + @Override + public Date getScheduledTimestamp() { + return scheduledTimestamp; + } + + @Override + public Long getAsyncJobId() { + return asyncJobId; + } + @Override public Date getCreated() { return created; @@ -178,6 +220,26 @@ public void setProtectedSize(Long protectedSize) { this.protectedSize = protectedSize; } + public void setScheduleType(short scheduleType) { + this.scheduleType = scheduleType; + } + + public void setSchedule(String schedule) { + this.schedule = schedule; + } + + public void setTimezone(String timezone) { + this.timezone = timezone; + } + + public void setScheduledTimestamp(Date scheduledTimestamp) { + this.scheduledTimestamp = scheduledTimestamp; + } + + public void setAsyncJobId(Long asyncJobId) { + this.asyncJobId = asyncJobId; + } + public void setCreated(Date start) { this.created = start; } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql index a5f44ebbeb1d..1767cd85db90 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql @@ -48,6 +48,11 @@ CREATE TABLE IF NOT EXISTS `cloud`.`backups` ( `status` varchar(20) NOT NULL, `size` bigint(20) DEFAULT 0, `protected_size` bigint(20) DEFAULT 0, + `schedule_type` int(4) DEFAULT NULL COMMENT 'backup schedulet type e.g. hourly, daily, etc.', + `schedule` varchar(100) DEFAULT NULL COMMENT 'schedule time of execution', + `timezone` varchar(100) DEFAULT NULL COMMENT 'the timezone in which the schedule time is specified', + `scheduled_timestamp` datetime DEFAULT NULL COMMENT 'Time at which the backup was scheduled for execution', + `async_job_id` bigint(20) unsigned DEFAULT NULL COMMENT 'If this schedule is being executed, it is the id of the create aysnc_job. Before that it is null', `account_id` bigint(20) unsigned NOT NULL, `zone_id` bigint(20) unsigned NOT NULL, `created` datetime DEFAULT NULL, From f1b957b4f86c593bb56376afdde1e54fc5421887 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 21 Nov 2019 12:56:30 +0530 Subject: [PATCH 06/81] fix missing methods Signed-off-by: Rohit Yadav --- .../cloudstack/backup/veeam/VeeamBackup.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java index 60494590850b..3bf7959ac563 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java @@ -22,6 +22,8 @@ import org.apache.cloudstack.backup.Backup; +import com.cloud.utils.DateUtil; + public class VeeamBackup implements Backup { private String name; @@ -47,6 +49,31 @@ public String getExternalId() { return null; } + @Override + public DateUtil.IntervalType getScheduleType() { + return null; + } + + @Override + public String getSchedule() { + return null; + } + + @Override + public String getTimezone() { + return null; + } + + @Override + public Date getScheduledTimestamp() { + return null; + } + + @Override + public Long getAsyncJobId() { + return null; + } + @Override public Long getVmId() { return null; From cff613d5e311abc098020b9d6aab6f7317424764 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 22 Nov 2019 01:01:08 +0530 Subject: [PATCH 07/81] schedule apis Signed-off-by: Rohit Yadav --- .../main/java/com/cloud/event/EventTypes.java | 4 +- .../user/backup/CreateBackupScheduleCmd.java | 2 +- .../user/backup/DeleteBackupScheduleCmd.java | 111 ++++++++++++++++++ .../user/backup/ListBackupSchedulesCmd.java | 102 ++++++++++++++++ .../user/backup/UpdateBackupScheduleCmd.java | 103 ++++++++++++++++ .../api/response/BackupScheduleResponse.java | 92 +++++++++++++++ .../cloudstack/backup/BackupManagerImpl.java | 6 + 7 files changed, 418 insertions(+), 2 deletions(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupSchedulesCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 1b6a688e859b..c19087a12c06 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -478,8 +478,10 @@ public class EventTypes { 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_DELETE = "BACKUP.DELETE"; - public static final String EVENT_VM_BACKUP_CUSTOM_SCHEDULE = "BACKUP.CUSTOM.SCHEDULE"; public static final String EVENT_VM_BACKUP_RESTORE_VOLUME_TO_VM = "BACKUP.RESTORE.VOLUME.TO.VM"; + public static final String EVENT_VM_BACKUP_SCHEDULE_CREATE = "BACKUP.SCHEDULE.CREATE"; + public static final String EVENT_VM_BACKUP_SCHEDULE_UPDATE = "BACKUP.SCHEDULE.UPDATE"; + public static final String EVENT_VM_BACKUP_SCHEDULE_DELETE = "BACKUP.SCHEDULE.DELETE"; // external network device events public static final String EVENT_EXTERNAL_NVP_CONTROLLER_ADD = "PHYSICAL.NVPCONTROLLER.ADD"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java index ad7befb7f9ca..07fc56987710 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java @@ -97,7 +97,7 @@ public long getEntityOwnerId() { @Override public String getEventType() { - return EventTypes.EVENT_VM_BACKUP_CUSTOM_SCHEDULE; + return EventTypes.EVENT_VM_BACKUP_SCHEDULE_CREATE; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java new file mode 100644 index 000000000000..6260ac04d095 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java @@ -0,0 +1,111 @@ +// 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.backup; + +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.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.exception.CloudRuntimeException; + +@APICommand(name = DeleteBackupScheduleCmd.APINAME, + description = "Deletes a VM backup schedule", + responseObject = SuccessResponse.class, since = "4.14.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class DeleteBackupScheduleCmd extends BaseAsyncCmd { + public static final String APINAME = "deleteBackupSchedule"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + required = true, + description = "ID of the backup whose schedule needs to be deleted") + private Long backupId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return backupId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + boolean result = false; // TODO: use backupManager + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new CloudRuntimeException("Error while deleting backup of VM"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_DELETE; + } + + @Override + public String getEventDescription() { + return "Deleting backup schedule for backup ID " + backupId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupSchedulesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupSchedulesCmd.java new file mode 100644 index 000000000000..3e1837f55e65 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupSchedulesCmd.java @@ -0,0 +1,102 @@ +// 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.backup; + +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.BaseBackupListCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.BackupScheduleResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +@APICommand(name = ListBackupSchedulesCmd.APINAME, + description = "Lists backup schedules", + responseObject = BackupScheduleResponse.class, since = "4.14.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListBackupSchedulesCmd extends BaseBackupListCmd { + public static final String APINAME = "listBackupSchedules"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + description = "id of the VM backup") + private Long id; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + description = "id of the VM") + private Long vmId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public Long getVmId() { + return vmId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try{ + // TODO API stuff + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} \ No newline at end of file diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java new file mode 100644 index 000000000000..cf927ac3321d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java @@ -0,0 +1,103 @@ +// 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.backup; + +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.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.BackupScheduleResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +@APICommand(name = UpdateBackupScheduleCmd.APINAME, + description = "Updates backup schedule of a VM backup with an existing schedule", + responseObject = BackupScheduleResponse.class, since = "4.14.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class UpdateBackupScheduleCmd extends BaseAsyncCmd { + public static final String APINAME = "updateBackupSchedule"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.BACKUP_ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + required = true, + description = "ID of the backup") + private Long backupId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getBackupId() { + return backupId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + // TODO implement API + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_SCHEDULE_UPDATE; + } + + @Override + public String getEventDescription() { + return "Updating VM backup schedule for VM backup ID: " + backupId; + } +} \ No newline at end of file diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java new file mode 100644 index 000000000000..e67f8e927ea4 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java @@ -0,0 +1,92 @@ +// 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.response; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.backup.Backup; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = Backup.class) +public class BackupScheduleResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "internal id of the backup") + private String id; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_NAME) + @Param(description = "name of the VM") + private String vmName; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) + @Param(description = "ID of the vm") + private String vmId; + + @SerializedName(ApiConstants.ZONE_ID) + @Param(description = "zone id") + private String zoneId; + + @SerializedName(ApiConstants.ACCOUNT_ID) + @Param(description = "account id") + private String accountId; + + // TODO: schedule related fields here + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + public String getVmId() { + return vmId; + } + + public void setVmId(String vmId) { + this.vmId = vmId; + } +} 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 315822c05bf6..0fdc44a7116a 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -33,10 +33,13 @@ import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd; import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.DeleteBackupCmd; +import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; +import org.apache.cloudstack.api.command.user.backup.ListBackupSchedulesCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupsCmd; import org.apache.cloudstack.api.command.user.backup.RestoreBackupCmd; import org.apache.cloudstack.api.command.user.backup.RestoreVolumeFromBackupAndAttachToVMCmd; +import org.apache.cloudstack.api.command.user.backup.UpdateBackupScheduleCmd; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.context.CallContext; @@ -491,6 +494,9 @@ public List> getCommands() { cmdList.add(ImportVMCmdByAdmin.class); // Schedule cmdList.add(CreateBackupScheduleCmd.class); + cmdList.add(ListBackupSchedulesCmd.class); + cmdList.add(UpdateBackupScheduleCmd.class); + cmdList.add(DeleteBackupScheduleCmd.class); return cmdList; } From 9f6a2e69fe5aa1b04925cac9dca0c5c8bc11d514 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 22 Nov 2019 01:54:16 +0530 Subject: [PATCH 08/81] more apis, backend support and refactorings Signed-off-by: Rohit Yadav --- .../main/java/com/cloud/event/EventTypes.java | 2 + .../apache/cloudstack/api/ApiConstants.java | 1 + ...signVirtualMachineToBackupOfferingCmd.java | 115 ++++++++++++++++++ .../command/user/backup/CreateBackupCmd.java | 16 +-- .../user/backup/DeleteBackupScheduleCmd.java | 2 +- ...veVirtualMachineFromBackupOfferingCmd.java | 115 ++++++++++++++++++ .../cloudstack/backup/BackupManager.java | 18 ++- .../cloudstack/backup/BackupProvider.java | 18 ++- .../backup/DummyBackupProvider.java | 11 +- .../backup/VeeamBackupProvider.java | 14 ++- .../cloudstack/backup/BackupManagerImpl.java | 107 +++++++++++----- .../com/cloud/usage/UsageManagerImpl.java | 7 +- 12 files changed, 366 insertions(+), 60 deletions(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index c19087a12c06..e2dcdd56cc06 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -475,6 +475,8 @@ public class EventTypes { // Backup and Recovery events public static final String EVENT_VM_BACKUP_IMPORT_OFFERING = "BACKUP.IMPORT.OFFERING"; + public static final String EVENT_VM_BACKUP_OFFERING_ASSIGN = "BACKUP.OFFERING.ASSIGN"; + 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_DELETE = "BACKUP.DELETE"; 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 fde6079ed00a..bff2fd1d9636 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -33,6 +33,7 @@ public class ApiConstants { public static final String LIST_LB_VMIPS = "lbvmips"; public static final String AVAILABLE = "available"; public static final String BACKUP_ID = "backupid"; + public static final String BACKUP_OFFERING_ID = "backupofferingid"; public static final String BITS = "bits"; public static final String BOOTABLE = "bootable"; public static final String BIND_DN = "binddn"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java new file mode 100644 index 000000000000..bfa0df8d22d3 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java @@ -0,0 +1,115 @@ +// 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.backup; + +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.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +@APICommand(name = AssignVirtualMachineToBackupOfferingCmd.APINAME, + description = "Assigns a backup offering to a VM", + responseObject = BackupResponse.class, since = "4.14.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class AssignVirtualMachineToBackupOfferingCmd extends BaseAsyncCmd { + public static final String APINAME = "assignVirtualMachineToBackupOffering"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + required = true, + description = "ID of the virtual machine") + private Long vmId; + + @Parameter(name = ApiConstants.BACKUP_OFFERING_ID, + type = CommandType.UUID, + entityType = BackupOfferingResponse.class, + required = true, + description = "ID of the backup offering") + private Long offeringId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getVmId() { + return vmId; + } + + public Long getOfferingId() { + return offeringId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + // TODO implement API + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN; + } + + @Override + public String getEventDescription() { + return "Assigning VM to backup offering ID: " + offeringId; + } +} \ No newline at end of file diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java index 427cc5e3570d..82c98fb551b4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java @@ -27,7 +27,6 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.backup.Backup; @@ -60,16 +59,9 @@ public class CreateBackupCmd extends BaseAsyncCmd { type = CommandType.UUID, entityType = UserVmResponse.class, required = true, - description = "id of the VM") + description = "ID of the VM") private Long vmId; - @Parameter(name = ApiConstants.POLICY_ID, - type = CommandType.UUID, - entityType = BackupOfferingResponse.class, - required = true, - description = "id of the backup offering") - private Long policyId; - ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -78,10 +70,6 @@ public Long getVmId() { return vmId; } - public Long getPolicyId() { - return policyId; - } - ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -89,7 +77,7 @@ public Long getPolicyId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - Backup backup = backupManager.createBackup(vmId, policyId); + Backup backup = backupManager.createBackup(getVmId()); if (backup != null) { BackupResponse response = _responseGenerator.createBackupResponse(backup); response.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java index 6260ac04d095..e5ee2dac14f7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java @@ -101,7 +101,7 @@ public long getEntityOwnerId() { @Override public String getEventType() { - return EventTypes.EVENT_VM_BACKUP_DELETE; + return EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java new file mode 100644 index 000000000000..a862cd05e7a0 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java @@ -0,0 +1,115 @@ +// 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.backup; + +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.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +@APICommand(name = RemoveVirtualMachineFromBackupOfferingCmd.APINAME, + description = "Removes a VM from a backup offering", + responseObject = BackupResponse.class, since = "4.14.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class RemoveVirtualMachineFromBackupOfferingCmd extends BaseAsyncCmd { + public static final String APINAME = "removeVirtualMachineFromBackupOffering"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + required = true, + description = "ID of the virtual machine") + private Long vmId; + + @Parameter(name = ApiConstants.BACKUP_OFFERING_ID, + type = CommandType.UUID, + entityType = BackupOfferingResponse.class, + required = true, + description = "ID of the backup offering") + private Long offeringId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getVmId() { + return vmId; + } + + public Long getOfferingId() { + return offeringId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + // TODO implement API + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE; + } + + @Override + public String getEventDescription() { + return "Removing VM to backup offering ID: " + offeringId; + } +} \ No newline at end of file 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 911cedb1cd83..d73ffadff7d9 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -85,12 +85,28 @@ BackupOffering importBackupOffering(final Long zoneId, final String offeringExte */ List listBackupRestorePoints(final Long backupId); + /** + * Assigns a VM to a backup offering + * @param vmId + * @param offeringId + * @return + */ + Backup assignVMToBackupOffering(final Long vmId, final Long offeringId); + + /** + * Removes a VM from a backup offering + * @param vmId + * @param offeringId + * @return + */ + boolean removeVMFromBackupOffering(final Long vmId, final Long offeringId); + /** * Creates backup of a VM * @param vmId Virtual Machine ID * @return returns operation success */ - Backup createBackup(final Long vmId, final Long policyId); + Backup createBackup(final Long vmId); /** * Deletes a backup 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 3361a28ce355..c17887588e94 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -48,12 +48,13 @@ public interface BackupProvider { boolean isBackupOffering(Long zoneId, String uuid); /** - * Creates backup of a VM assigned to a policy - * @param policy + * Assign a VM to a backup offering or policy * @param vm - * @return true if backup successfully starts + * @param backup + * @param policy + * @return */ - Backup createBackup(BackupOffering policy, VirtualMachine vm, Backup backup); + Backup assignVMToBackupOffering(VirtualMachine vm, Backup backup, BackupOffering backupOffering); /** * Removes a VM backup @@ -61,7 +62,7 @@ public interface BackupProvider { * @param backup * @return */ - boolean removeBackup(VirtualMachine vm, Backup backup); + boolean removeVMFromBackupOffering(VirtualMachine vm, Backup backup); /** * Starts and creates an adhoc backup process @@ -71,6 +72,13 @@ public interface BackupProvider { */ boolean takeBackup(Backup backup); + /** + * Delete an existing backup + * @param backup + * @return + */ + boolean deleteBackup(Backup backup); + /** * Restore VM from backup */ 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 4d48744f88c3..ff1a6fb22f1d 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 @@ -63,8 +63,8 @@ public boolean isBackupOffering(Long zoneId, String uuid) { } @Override - public Backup createBackup(BackupOffering policy, VirtualMachine vm, Backup backup) { - s_logger.debug("Creating VM backup for VM " + vm.getInstanceName() + " from backup offering " + policy.getName()); + public Backup assignVMToBackupOffering(VirtualMachine vm, Backup backup, BackupOffering backupOffering) { + s_logger.debug("Creating VM backup for VM " + vm.getInstanceName() + " from backup offering " + backupOffering.getName()); List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); BackupVO dummyBackup = (BackupVO) backup; @@ -104,7 +104,7 @@ public Map getBackupMetrics(Long zoneId, List bac } @Override - public boolean removeBackup(VirtualMachine vm, Backup vmBackup) { + public boolean removeVMFromBackupOffering(VirtualMachine vm, Backup vmBackup) { s_logger.debug("Removing VM backup " + vmBackup.getUuid() + " for VM " + vm.getInstanceName() + " on the Dummy Backup Provider"); final List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); for (final Backup backup : backups) { @@ -121,6 +121,11 @@ public boolean takeBackup(Backup backup) { return true; } + @Override + public boolean deleteBackup(Backup backup) { + return true; + } + @Override public List listBackupRestorePoints(String backupUuid, VirtualMachine vm) { return Arrays.asList( 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 4ee847e01a3a..1693f59614be 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 @@ -137,9 +137,9 @@ private String getGuestBackupName(final String instanceName, final String uuid) } @Override - public Backup createBackup(final BackupOffering policy, final VirtualMachine vm, final Backup backup) { + public Backup assignVMToBackupOffering(final VirtualMachine vm, final Backup backup, final BackupOffering backupOffering) { final VeeamClient client = getClient(vm.getDataCenterId()); - final Job parentJob = client.listJob(policy.getExternalId()); + final Job parentJob = client.listJob(backupOffering.getExternalId()); final String clonedJobName = getGuestBackupName(vm.getInstanceName(), backup.getUuid()); if (client.cloneVeeamJob(parentJob, clonedJobName)) { for (BackupOffering job : client.listJobs()) { @@ -163,14 +163,14 @@ public Backup createBackup(final BackupOffering policy, final VirtualMachine vm, } } } else { - LOG.error("Failed to clone pre-defined Veeam job (backup offering) for policy id: " + policy.getExternalId()); + LOG.error("Failed to clone pre-defined Veeam job (backup offering) for backup offering ID: " + backupOffering.getExternalId()); } ((BackupVO) backup).setStatus(Backup.Status.Error); return backup; } @Override - public boolean removeBackup(final VirtualMachine vm, final Backup backup) { + public boolean removeVMFromBackupOffering(final VirtualMachine vm, final Backup backup) { final VeeamClient client = getClient(vm.getDataCenterId()); final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm); try { @@ -194,6 +194,12 @@ public boolean takeBackup(final Backup backup) { return client.startBackupJob(backup.getExternalId()); } + @Override + public boolean deleteBackup(Backup backup) { + // Veeam does not support removal of a restore point or point-in-time backup + throw new CloudRuntimeException("Backend plugin does not allow removal of backup, to delete the backup chain remove VM from the backup offering"); + } + @Override public boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String restorePointId) { return getClient(vm.getDataCenterId()).restoreFullVM(vm.getInstanceName(), restorePointId); 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 0fdc44a7116a..fae24c4080f8 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -199,56 +199,110 @@ public List listBackupRestorePoints(final Long backupId) { } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_CREATE, eventDescription = "creating VM backup", async = true) - public Backup createBackup(final Long vmId, final Long offeringId) { + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, eventDescription = "assign VM to backup offering", async = true) + public Backup assignVMToBackupOffering(Long vmId, Long offeringId) { final VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { throw new CloudRuntimeException("Did not find VM by provided ID"); } + final BackupOfferingVO offering = backupOfferingDao.findById(offeringId); + if (offering == null) { + throw new CloudRuntimeException("Provided backup offering does not exist"); + } + + final BackupProvider backupProvider = getBackupProvider(vm.getDataCenterId()); + if (backupProvider == null) { + throw new CloudRuntimeException("Failed to get the backup provider for the zone, please contact the administrator"); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); final Backup vmBackup = backupDao.findByVmId(vm.getId()); if (vmBackup != null) { - if (offeringId != null && !vmBackup.getOfferingId().equals(offeringId)) { - throw new CloudRuntimeException("VM backup(s) already exists and a different offering ID is provided than previously configured"); - } - final BackupProvider backupProvider = getBackupProvider(vmBackup.getZoneId()); - if (backupProvider != null && backupProvider.takeBackup(vmBackup)) { - return vmBackup; - } - LOG.warn("VM already has backup(s), failed to take backup"); - return null; - } - - final BackupOfferingVO policy = backupOfferingDao.findById(offeringId); - if (policy == null) { - throw new CloudRuntimeException("Provided backup offering does not exist"); + throw new CloudRuntimeException("VM is already assigned to a backup offering, please remove the previous assignment"); } BackupVO backup = new BackupVO(vmId, offeringId, Backup.Status.Allocated, vm.getAccountId(), vm.getDataCenterId()); setBackupVolumes(backup, vm); backup = backupDao.persist(backup); if (backup == null) { - throw new CloudRuntimeException("Failed to persist backup object in database"); + throw new CloudRuntimeException("Failed to persist VM backup object in database"); } - final BackupProvider backupProvider = getBackupProvider(vm.getDataCenterId()); - if (backupProvider == null) { - throw new CloudRuntimeException("Failed to find backup provider for the zone"); - } - backup = (BackupVO) backupProvider.createBackup(policy, vm, backup); + backup = (BackupVO) backupProvider.assignVMToBackupOffering(vm, backup, offering); if (backup == null) { - throw new CloudRuntimeException("Backup provider failed to create backup for VM: " + vm.getUuid()); + throw new CloudRuntimeException("Backup provider failed to assign VM to the backup offering, for VM: " + vm.getUuid()); } if (backupDao.update(backup.getId(), backup)) { - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_CREATE, vm.getAccountId(), vm.getDataCenterId(), backup.getId(), + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, vm.getAccountId(), vm.getDataCenterId(), backup.getId(), vm.getUuid(), backup.getOfferingId(), backup.getVmId(), null, Backup.class.getSimpleName(), backup.getUuid()); + } else { + throw new CloudRuntimeException("Failed to update VM backup in the database, please try again"); } return backup; } + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, eventDescription = "remove VM from backup offering", async = true) + public boolean removeVMFromBackupOffering(Long vmId, Long offeringId) { + final VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("Did not find VM by provided ID"); + } + + final Backup backup = backupDao.findByVmId(vmId); + if (backup == null) { + LOG.debug("VM has no backups or backup offering configuration, skipping removal."); + return true; + } + + if (!backup.getOfferingId().equals(offeringId)) { + throw new CloudRuntimeException("Current VM offering assignment does not match provided backup offering ID"); + } + + final BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + if (backupProvider == null) { + throw new CloudRuntimeException("Failed to get the backup provider for the zone, please contact the administrator"); + } + + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + boolean result = backupProvider.removeVMFromBackupOffering(vm, backup); + if (result) { + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), backup.getId(), + vm.getUuid(), backup.getOfferingId(), backup.getVmId(), null, + Backup.class.getSimpleName(), backup.getUuid()); + return true; + } + + return false; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_CREATE, eventDescription = "creating VM backup", async = true) + public Backup createBackup(final Long vmId) { + final VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("Did not find VM by provided ID"); + } + + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + final Backup backup = backupDao.findByVmId(vm.getId()); + if (backup == null) { + throw new CloudRuntimeException("VM backup is not configured, please assign to an offering or define a custom schedule"); + } + + final BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + if (backupProvider != null && backupProvider.takeBackup(backup)) { + // TODO: handle update etc? + return backup; + } + throw new CloudRuntimeException("Failed to create VM backup"); + } + private void setBackupVolumes(BackupVO backup, VMInstanceVO vm) { List vmVolumes = volumeDao.findByInstance(vm.getId()); List volInfo = createVolumeInfoFromVolumes(vmVolumes); @@ -277,14 +331,11 @@ public boolean deleteBackup(final Long backupId) { } accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); final BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); - boolean result = backupProvider.removeBackup(vm, backup); + boolean result = backupProvider.deleteBackup(backup); if (result) { backup.setStatus(Backup.Status.Expunged); if (backupDao.update(backup.getId(), backup)) { backupDao.remove(backup.getId()); - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_DELETE, vm.getAccountId(), vm.getDataCenterId(), backup.getId(), - vm.getUuid(), backup.getOfferingId(), backup.getVmId(), null, - Backup.class.getSimpleName(), backup.getUuid()); return true; } } diff --git a/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java b/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java index 6a39755808d5..fd55faf81003 100644 --- a/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java +++ b/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java @@ -1081,7 +1081,7 @@ private boolean isVmSnapshotOnPrimaryEvent(String eventType) { } private boolean isBackupEvent(String eventType) { - return eventType != null && (eventType.equals(EventTypes.EVENT_VM_BACKUP_CREATE) || eventType.equals(EventTypes.EVENT_VM_BACKUP_DELETE)); + return eventType != null && (eventType.equals(EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN) || eventType.equals(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE)); } private void createVMHelperEvent(UsageEventVO event) { @@ -1909,13 +1909,12 @@ private void createBackupEvent(final UsageEventVO event) { Account account = _accountDao.findByIdIncludingRemoved(event.getAccountId()); Long domainId = account.getDomainId(); - if (EventTypes.EVENT_VM_BACKUP_CREATE.equals(event.getType())) { + if (EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN.equals(event.getType())) { final UsageBackupVO backupVO = new UsageBackupVO(zoneId, accountId, domainId, backupId, vmId, created); usageBackupDao.persist(backupVO); - } else if (EventTypes.EVENT_VM_BACKUP_DELETE.equals(event.getType())) { + } else if (EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE.equals(event.getType())) { usageBackupDao.removeUsage(accountId, zoneId, backupId); } - } private class Heartbeat extends ManagedContextRunnable { From 4e1cf0d26b3698d5487115ae4aed987b82fba6dc Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 22 Nov 2019 02:04:30 +0530 Subject: [PATCH 09/81] add assignment/removal APIs Signed-off-by: Rohit Yadav --- .../java/org/apache/cloudstack/backup/BackupManagerImpl.java | 5 +++++ 1 file changed, 5 insertions(+) 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 fae24c4080f8..b8acedccebaf 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -30,6 +30,7 @@ import org.apache.cloudstack.api.command.admin.backup.ListBackupProviderOfferingsCmd; import org.apache.cloudstack.api.command.admin.backup.ListBackupProvidersCmd; import org.apache.cloudstack.api.command.admin.vm.ImportVMCmdByAdmin; +import org.apache.cloudstack.api.command.user.backup.AssignVirtualMachineToBackupOfferingCmd; import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd; import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.DeleteBackupCmd; @@ -37,6 +38,7 @@ import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupSchedulesCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupsCmd; +import org.apache.cloudstack.api.command.user.backup.RemoveVirtualMachineFromBackupOfferingCmd; import org.apache.cloudstack.api.command.user.backup.RestoreBackupCmd; import org.apache.cloudstack.api.command.user.backup.RestoreVolumeFromBackupAndAttachToVMCmd; import org.apache.cloudstack.api.command.user.backup.UpdateBackupScheduleCmd; @@ -536,6 +538,9 @@ public List> getCommands() { cmdList.add(ImportBackupOfferingCmd.class); cmdList.add(ListBackupOfferingsCmd.class); cmdList.add(DeleteBackupOfferingCmd.class); + // Assignment + cmdList.add(AssignVirtualMachineToBackupOfferingCmd.class); + cmdList.add(RemoveVirtualMachineFromBackupOfferingCmd.class); // Operations cmdList.add(CreateBackupCmd.class); cmdList.add(ListBackupsCmd.class); From a2e6452c2530c0bd7a29300014976557676fe488 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Sun, 1 Dec 2019 01:23:41 +0530 Subject: [PATCH 10/81] list api pagination and searching etc. Signed-off-by: Rohit Yadav --- .../user/backup/ListBackupOfferingsCmd.java | 8 +-- .../cloudstack/backup/BackupManager.java | 6 +- .../cloudstack/backup/dao/BackupDao.java | 2 +- .../cloudstack/backup/dao/BackupDaoImpl.java | 6 +- .../cloudstack/backup/BackupManagerImpl.java | 58 +++++++++++++------ 5 files changed, 52 insertions(+), 28 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java index 143884119bce..bd4d8ebc18f9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java @@ -54,7 +54,7 @@ public class ListBackupOfferingsCmd extends BaseBackupListCmd { @Parameter(name = ApiConstants.ID, type = BaseCmd.CommandType.UUID, entityType = BackupOfferingResponse.class, description = "The backup offering ID") - private Long policyId; + private Long offeringId; @Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class, description = "The zone ID") @@ -68,8 +68,8 @@ public Long getZoneId() { return zoneId; } - public Long getPolicyId() { - return policyId; + public Long getOfferingId() { + return offeringId; } ///////////////////////////////////////////////////// @@ -79,7 +79,7 @@ public Long getPolicyId() { @Override public void execute() throws ResourceUnavailableException, ServerApiException, ConcurrentOperationException { try { - final List backupOfferings = backupManager.listBackupOfferings(getZoneId(), getPolicyId()); + final List backupOfferings = backupManager.listBackupOfferings(this); setupResponseBackupOfferingsList(backupOfferings); } catch (InvalidParameterValueException e) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage()); 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 d73ffadff7d9..0e41ad109dcb 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -19,6 +19,7 @@ import java.util.List; +import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; @@ -57,10 +58,9 @@ BackupOffering importBackupOffering(final Long zoneId, final String offeringExte /** * List backup offerings - * @param zoneId zone id - * @param policyId if not null, only the policy with this id is listed + * @param ListBackupOfferingsCmd API cmd */ - List listBackupOfferings(final Long zoneId, final Long policyId); + List listBackupOfferings(final ListBackupOfferingsCmd cmd); /** * List backup provider offerings 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 69c422eee1b3..c65b1853daaa 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 @@ -32,7 +32,7 @@ public interface BackupDao extends GenericDao { List listByVmId(Long zoneId, Long vmId); List listByAccountId(Long accountId); - List listByPolicyId(Long policyId); + List listByOfferingId(Long offeringId); List syncBackups(Long zoneId, Long vmId, List externalBackups); List listByZoneAndState(Long zoneId, Backup.Status state); 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 41443f006603..59c5c4df6e35 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 @@ -64,7 +64,7 @@ protected void init() { backupSearch.and("account_id", backupSearch.entity().getAccountId(), SearchCriteria.Op.EQ); backupSearch.and("zone_id", backupSearch.entity().getZoneId(), SearchCriteria.Op.EQ); backupSearch.and("external_id", backupSearch.entity().getExternalId(), SearchCriteria.Op.EQ); - backupSearch.and("policy_id", backupSearch.entity().getOfferingId(), SearchCriteria.Op.EQ); + backupSearch.and("offering_id", backupSearch.entity().getOfferingId(), SearchCriteria.Op.EQ); backupSearch.and("status", backupSearch.entity().getStatus(), SearchCriteria.Op.EQ); backupSearch.done(); } @@ -99,9 +99,9 @@ public List listByVmId(Long zoneId, Long vmId) { } @Override - public List listByPolicyId(Long policyId) { + public List listByOfferingId(Long offeringId) { SearchCriteria sc = backupSearch.create(); - sc.setParameters("policy_id", policyId); + sc.setParameters("offering_id", offeringId); return new ArrayList<>(listBy(sc)); } 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 b8acedccebaf..38bcf44e0371 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -76,6 +76,9 @@ import com.cloud.user.AccountService; import com.cloud.utils.Pair; import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; @@ -130,28 +133,48 @@ public BackupOffering importBackupOffering(final Long zoneId, final String offer final BackupProvider provider = getBackupProvider(zoneId); if (!provider.isBackupOffering(zoneId, offeringExternalId)) { - throw new CloudRuntimeException("Policy " + offeringExternalId + " does not exist on provider " + provider.getName() + " on zone " + zoneId); + throw new CloudRuntimeException("Backup offering '" + offeringExternalId + "' does not exist on provider " + provider.getName() + " on zone " + zoneId); } - final BackupOfferingVO policy = new BackupOfferingVO(zoneId, offeringExternalId, offeringName, offeringDescription); - final BackupOfferingVO savedPolicy = backupOfferingDao.persist(policy); - if (savedPolicy == null) { + final BackupOfferingVO offering = new BackupOfferingVO(zoneId, offeringExternalId, offeringName, offeringDescription); + final BackupOfferingVO savedOffering = backupOfferingDao.persist(offering); + if (savedOffering == null) { throw new CloudRuntimeException("Unable to create backup offering: " + offeringExternalId + ", name: " + offeringName); } LOG.debug("Successfully created backup offering " + offeringName + " mapped to backup provider offering " + offeringExternalId); - return savedPolicy; + return savedOffering; } @Override - public List listBackupOfferings(final Long zoneId, final Long offeringId) { + public List listBackupOfferings(final ListBackupOfferingsCmd cmd) { + final Long offeringId = cmd.getOfferingId(); + final Long zoneId = cmd.getZoneId(); + final String keyword = cmd.getKeyword(); + if (offeringId != null) { - BackupOfferingVO policy = backupOfferingDao.findById(offeringId); - if (policy == null) { + BackupOfferingVO offering = backupOfferingDao.findById(offeringId); + if (offering == null) { throw new CloudRuntimeException("Offering ID " + offeringId + " does not exist"); } - return Collections.singletonList(policy); + return Collections.singletonList(offering); + } + + final Filter searchFilter = new Filter(BackupOfferingVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal()); + SearchBuilder sb = backupOfferingDao.createSearchBuilder(); + sb.and("zone_id", sb.entity().getZoneId(), SearchCriteria.Op.EQ); + sb.and("name", sb.entity().getName(), SearchCriteria.Op.LIKE); + + final SearchCriteria sc = sb.create(); + + if (zoneId != null) { + sc.setParameters("zone_id", zoneId); } - return backupOfferingDao.listByZone(zoneId); + + if (keyword != null) { + sc.setParameters("name", "%" + keyword + "%"); + } + Pair, Integer> result = backupOfferingDao.searchAndCount(sc, searchFilter); + return new ArrayList<>(result.first()); } @Override @@ -165,7 +188,8 @@ public List listBackupProviderOfferings(final Long zoneId) { } final BackupProvider backupProvider = getBackupProvider(zoneId); LOG.debug("Listing external backup policies for the backup provider registered in zone " + zoneId); - return backupProvider.listBackupOfferings(zoneId); } + return backupProvider.listBackupOfferings(zoneId); + } @Override public List listBackups(final Long id, final Long vmId) { @@ -490,15 +514,15 @@ private boolean attachVolumeToVM(Long zoneId, String restoredVolumeLocation, Lis } @Override - public boolean deleteBackupOffering(final Long policyId) { - if (!backupDao.listByPolicyId(policyId).isEmpty()) { + public boolean deleteBackupOffering(final Long offeringId) { + if (!backupDao.listByOfferingId(offeringId).isEmpty()) { throw new CloudRuntimeException("Cannot allow deletion of backup offering due to use in existing VM backups, please delete the VM backups first"); } - BackupOfferingVO policy = backupOfferingDao.findById(policyId); - if (policy == null) { - throw new CloudRuntimeException("Could not find a backup offering with id: " + policyId); + BackupOfferingVO offering = backupOfferingDao.findById(offeringId); + if (offering == null) { + throw new CloudRuntimeException("Could not find a backup offering with id: " + offeringId); } - return backupOfferingDao.expunge(policy.getId()); + return backupOfferingDao.remove(offering.getId()); } @Override From 50c44a6cff42111e550709e1bdd6ed64a9e92bc8 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Sun, 1 Dec 2019 01:54:33 +0530 Subject: [PATCH 11/81] api layer changes and refactorings Signed-off-by: Rohit Yadav --- .../admin/backup/ImportBackupOfferingCmd.java | 16 +- .../ListBackupProviderOfferingsCmd.java | 12 +- .../admin/backup/ListBackupProvidersCmd.java | 3 +- .../command/admin/vm/ImportVMCmdByAdmin.java | 103 --------- ...signVirtualMachineToBackupOfferingCmd.java | 13 +- ...veVirtualMachineFromBackupOfferingCmd.java | 14 +- .../cloudstack/backup/BackupManager.java | 45 ++-- .../backup/dao/BackupOfferingDao.java | 3 - .../backup/dao/BackupOfferingDaoImpl.java | 12 -- .../cloudstack/backup/BackupManagerImpl.java | 200 +++++++++--------- 10 files changed, 158 insertions(+), 263 deletions(-) delete mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVMCmdByAdmin.java diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java index 3ac340dbdf9d..125f8df52287 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java @@ -57,7 +57,7 @@ public class ImportBackupOfferingCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "the name of the backup offering") - private String policyName; + private String name; @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = true, description = "the description of the backup offering") @@ -67,7 +67,7 @@ public class ImportBackupOfferingCmd extends BaseAsyncCmd { type = CommandType.STRING, required = true, description = "The backup offering ID (from backup provider side)") - private String policyExternalId; + private String externalId; @Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class, description = "The zone ID", required = true) @@ -77,12 +77,12 @@ public class ImportBackupOfferingCmd extends BaseAsyncCmd { /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public String getPolicyName() { - return policyName; + public String getName() { + return name; } - public String getPolicyExternalId() { - return policyExternalId; + public String getExternalId() { + return externalId; } public Long getZoneId() { @@ -100,7 +100,7 @@ public String getDescription() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - BackupOffering policy = backupManager.importBackupOffering(getZoneId(), getPolicyExternalId(), getPolicyName(), getDescription()); + BackupOffering policy = backupManager.importBackupOffering(getZoneId(), getExternalId(), getName(), getDescription()); if (policy != null) { BackupOfferingResponse response = _responseGenerator.createBackupOfferingResponse(policy); response.setResponseName(getCommandName()); @@ -132,6 +132,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Importing backup offering: " + policyName + " (externalId=" + policyExternalId + ") on zone " + zoneId ; + return "Importing backup offering: " + name + " (external ID: " + externalId + ") on zone ID " + zoneId ; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java index a452f756a513..5df57cae6ddc 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java @@ -68,6 +68,12 @@ public Long getZoneId() { /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// + private void validateParameters() { + if (getZoneId() == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please provide a valid zone ID "); + } + } + @Override public void execute() throws ResourceUnavailableException, ServerApiException, ConcurrentOperationException { validateParameters(); @@ -81,12 +87,6 @@ public void execute() throws ResourceUnavailableException, ServerApiException, C } } - private void validateParameters() { - if (getZoneId() == null) { - throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please provide a valid zone ID "); - } - } - @Override public String getCommandName() { return APINAME.toLowerCase() + RESPONSE_SUFFIX; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProvidersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProvidersCmd.java index 44afb1bf7dbd..2b4b735be2ad 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProvidersCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProvidersCmd.java @@ -25,7 +25,6 @@ import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.api.BaseListCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.BackupProviderResponse; import org.apache.cloudstack.api.response.ListResponse; @@ -38,7 +37,7 @@ description = "Lists Backup and Recovery providers", responseObject = BackupProviderResponse.class, since = "4.14.0", authorized = {RoleType.Admin}) -public class ListBackupProvidersCmd extends BaseListCmd { +public class ListBackupProvidersCmd extends BaseCmd { public static final String APINAME = "listBackupProviders"; @Inject diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVMCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVMCmdByAdmin.java deleted file mode 100644 index 54c0cbd25d7e..000000000000 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVMCmdByAdmin.java +++ /dev/null @@ -1,103 +0,0 @@ -// 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.admin.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.BaseAsyncCmd; -import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.api.Parameter; -import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.SuccessResponse; -import org.apache.cloudstack.api.response.ZoneResponse; -import org.apache.cloudstack.backup.BackupManager; -import org.apache.cloudstack.context.CallContext; - -import com.cloud.exception.ConcurrentOperationException; -import com.cloud.exception.InsufficientCapacityException; -import com.cloud.exception.NetworkRuleConflictException; -import com.cloud.exception.ResourceAllocationException; -import com.cloud.exception.ResourceUnavailableException; -import com.cloud.hypervisor.Hypervisor; - -@APICommand(name = ImportVMCmdByAdmin.APINAME, - description = "Imports an un-managed external VM to CloudStack", - responseObject = SuccessResponse.class, - since = "4.14.0", - authorized = {RoleType.Admin}) -public class ImportVMCmdByAdmin extends BaseAsyncCmd { - public static final String APINAME = "importVirtualMachine"; - - @Inject - BackupManager backupManager; - - @Parameter(name = ApiConstants.NAME, - type = CommandType.STRING, - required = true, - description = "The internal VM name as on the hypervisor") - private String vmInternalName; - - @Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class, - description = "The zone ID", required = true) - private Long zoneId; - - @Override - public String getEventType() { - return "IMPORT.VM"; - } - - @Override - public String getEventDescription() { - return "Importing VM"; - } - - @Override - public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { - long accountId = CallContext.current().getCallingAccountId(); - long userId = CallContext.current().getCallingUserId(); - long domainId = CallContext.current().getCallingAccount().getDomainId(); - boolean result = backupManager.importVM(zoneId, domainId, accountId, userId, vmInternalName, Hypervisor.HypervisorType.VMware, null); - if (result) { - SuccessResponse response = new SuccessResponse(getCommandName()); - setResponseObject(response); - } else { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable to import VM"); - } - } - - @Override - public String getCommandName() { - return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; - } - - @Override - public long getEntityOwnerId() { - return CallContext.current().getCallingAccount().getId(); - } - - public String getVmInternalName() { - return vmInternalName; - } - - public Long getZoneId() { - return zoneId; - } -} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java index bfa0df8d22d3..2697d0c99f1d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java @@ -30,6 +30,7 @@ import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -39,9 +40,10 @@ import com.cloud.exception.NetworkRuleConflictException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.exception.CloudRuntimeException; @APICommand(name = AssignVirtualMachineToBackupOfferingCmd.APINAME, - description = "Assigns a backup offering to a VM", + description = "Assigns a VM to a backup offering", responseObject = BackupResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class AssignVirtualMachineToBackupOfferingCmd extends BaseAsyncCmd { @@ -87,7 +89,14 @@ public Long getOfferingId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - // TODO implement API + Backup backup = backupManager.assignVMToBackupOffering(getVmId(), getOfferingId()); + if (backup != null) { + BackupResponse response = _responseGenerator.createBackupResponse(backup); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new CloudRuntimeException("Error while creating backup of VM"); + } } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java index a862cd05e7a0..b6a31a766188 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java @@ -28,7 +28,7 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.BackupOfferingResponse; -import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -42,7 +42,7 @@ @APICommand(name = RemoveVirtualMachineFromBackupOfferingCmd.APINAME, description = "Removes a VM from a backup offering", - responseObject = BackupResponse.class, since = "4.14.0", + responseObject = SuccessResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class RemoveVirtualMachineFromBackupOfferingCmd extends BaseAsyncCmd { public static final String APINAME = "removeVirtualMachineFromBackupOffering"; @@ -87,7 +87,13 @@ public Long getOfferingId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - // TODO implement API + boolean result = backupManager.removeVMFromBackupOffering(getVmId(), getOfferingId()); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to remove VM from backup offering"); + } } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } @@ -110,6 +116,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Removing VM to backup offering ID: " + offeringId; + return "Removing VM from backup offering ID: " + offeringId; } } \ No newline at end of file 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 0e41ad109dcb..7693d6cf1441 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -23,7 +23,6 @@ import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; -import com.cloud.hypervisor.Hypervisor; import com.cloud.utils.component.Manager; import com.cloud.utils.component.PluggableService; @@ -46,6 +45,13 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer "backup.framework.sync.interval", "300", "The backup and recovery background sync task polling interval in seconds.", true); + + /** + * List backup provider offerings + * @param zoneId zone id + */ + List listBackupProviderOfferings(final Long zoneId); + /** * Add a new Backup and Recovery policy to CloudStack by mapping an existing external backup offering to a name and description * @param zoneId zone id @@ -62,29 +68,11 @@ BackupOffering importBackupOffering(final Long zoneId, final String offeringExte */ List listBackupOfferings(final ListBackupOfferingsCmd cmd); - /** - * List backup provider offerings - * @param zoneId zone id - */ - List listBackupProviderOfferings(final Long zoneId); - /** * Deletes a backup offering */ boolean deleteBackupOffering(final Long policyId); - /** - * List existing backups for a VM - */ - List listBackups(final Long id, final Long vmId); - - /** - * Lists restore points for a VM backup - * @param backupId - * @return - */ - List listBackupRestorePoints(final Long backupId); - /** * Assigns a VM to a backup offering * @param vmId @@ -109,10 +97,16 @@ BackupOffering importBackupOffering(final Long zoneId, final String offeringExte Backup createBackup(final Long vmId); /** - * Deletes a backup - * @return returns operation success + * List existing backups for a VM */ - boolean deleteBackup(final Long backupId); + List listBackups(final Long id, final Long vmId); + + /** + * Lists restore points for a VM backup + * @param backupId + * @return + */ + List listBackupRestorePoints(final Long backupId); /** * Restore a full VM from backup @@ -124,6 +118,9 @@ BackupOffering importBackupOffering(final Long zoneId, final String offeringExte */ boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long vmId, final Long backupId, final String restorePointId) throws Exception; - boolean importVM(long zoneId, long domainId, long accountId, long userId, - String vmInternalName, Hypervisor.HypervisorType hypervisorType, Backup backup); + /** + * Deletes a backup + * @return returns operation success + */ + boolean deleteBackup(final Long backupId); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java index ad64cd8bb8b1..d001de8b6c67 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java @@ -17,8 +17,6 @@ package org.apache.cloudstack.backup.dao; -import java.util.List; - import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.BackupOfferingVO; @@ -27,7 +25,6 @@ public interface BackupOfferingDao extends GenericDao { BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy); - List listByZone(Long zoneId); BackupOffering findByExternalId(String externalId, Long zoneId); BackupOffering findByName(String name, Long zoneId); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java index e3e200c12b27..10bff4f3db53 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java @@ -17,9 +17,6 @@ package org.apache.cloudstack.backup.dao; -import java.util.ArrayList; -import java.util.List; - import javax.annotation.PostConstruct; import javax.inject.Inject; @@ -70,15 +67,6 @@ public BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy) { return response; } - @Override - public List listByZone(Long zoneId) { - SearchCriteria sc = backupPoliciesSearch.create(); - if (zoneId != null) { - sc.setParameters("zone_id", zoneId); - } - return new ArrayList<>(listBy(sc)); - } - @Override public BackupOffering findByExternalId(String externalId, Long zoneId) { SearchCriteria sc = backupPoliciesSearch.create(); 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 38bcf44e0371..88b989c3a880 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -29,7 +29,6 @@ import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd; import org.apache.cloudstack.api.command.admin.backup.ListBackupProviderOfferingsCmd; import org.apache.cloudstack.api.command.admin.backup.ListBackupProvidersCmd; -import org.apache.cloudstack.api.command.admin.vm.ImportVMCmdByAdmin; import org.apache.cloudstack.api.command.user.backup.AssignVirtualMachineToBackupOfferingCmd; import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd; import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; @@ -119,6 +118,20 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { private static Map backupProvidersMap = new HashMap<>(); private List backupProviders; + @Override + public List listBackupProviderOfferings(final Long zoneId) { + if (zoneId == null || zoneId < 1) { + throw new CloudRuntimeException("Invalid zone ID passed"); + } + 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"); + } + final BackupProvider backupProvider = getBackupProvider(zoneId); + LOG.debug("Listing external backup offerings for the backup provider configured for zone ID " + zoneId); + return backupProvider.listBackupOfferings(zoneId); + } + @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_IMPORT_OFFERING, eventDescription = "importing backup offering", async = true) public BackupOffering importBackupOffering(final Long zoneId, final String offeringExternalId, @@ -178,50 +191,15 @@ public List listBackupOfferings(final ListBackupOfferingsCmd cmd } @Override - public List listBackupProviderOfferings(final Long zoneId) { - if (zoneId == null || zoneId < 1) { - throw new CloudRuntimeException("Invalid zone ID passed"); - } - 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"); - } - final BackupProvider backupProvider = getBackupProvider(zoneId); - LOG.debug("Listing external backup policies for the backup provider registered in zone " + zoneId); - return backupProvider.listBackupOfferings(zoneId); - } - - @Override - public List listBackups(final Long id, final Long vmId) { - final Account callerAccount = CallContext.current().getCallingAccount(); - final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); - if (vm == null) { - if (id != null) { - return Collections.singletonList(backupDao.findById(id)); - } - if (accountService.isRootAdmin(callerAccount.getId())) { - return new ArrayList<>(backupDao.listAll()); - } else { - return new ArrayList<>(backupDao.listByAccountId(callerAccount.getId())); - } - } - accountManager.checkAccess(callerAccount, null, true, vm); - return backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); - } - - @Override - public List listBackupRestorePoints(final Long backupId) { - BackupVO backup = backupDao.findById(backupId); - if (backup == null) { - throw new CloudRuntimeException("Could not find backup " + backupId); + public boolean deleteBackupOffering(final Long offeringId) { + if (!backupDao.listByOfferingId(offeringId).isEmpty()) { + throw new CloudRuntimeException("Cannot allow deletion of backup offering due to use in existing VM backups, please delete the VM backups using the offering first."); } - VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); - if (vm == null) { - throw new CloudRuntimeException("Could not find VM: " + backup.getVmId()); + final BackupOfferingVO offering = backupOfferingDao.findById(offeringId); + if (offering == null) { + throw new CloudRuntimeException("Could not find a backup offering with id: " + offeringId); } - accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); - BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); - return backupProvider.listBackupRestorePoints(backup.getUuid(), vm); + return backupOfferingDao.remove(offering.getId()); } @Override @@ -329,6 +307,40 @@ public Backup createBackup(final Long vmId) { throw new CloudRuntimeException("Failed to create VM backup"); } + + @Override + public List listBackups(final Long id, final Long vmId) { + final Account callerAccount = CallContext.current().getCallingAccount(); + final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + if (id != null) { + return Collections.singletonList(backupDao.findById(id)); + } + if (accountService.isRootAdmin(callerAccount.getId())) { + return new ArrayList<>(backupDao.listAll()); + } else { + return new ArrayList<>(backupDao.listByAccountId(callerAccount.getId())); + } + } + accountManager.checkAccess(callerAccount, null, true, vm); + return backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + } + + @Override + public List listBackupRestorePoints(final Long backupId) { + BackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Could not find backup " + backupId); + } + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); + if (vm == null) { + throw new CloudRuntimeException("Could not find VM: " + backup.getVmId()); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + return backupProvider.listBackupRestorePoints(backup.getUuid(), vm); + } + private void setBackupVolumes(BackupVO backup, VMInstanceVO vm) { List vmVolumes = volumeDao.findByInstance(vm.getId()); List volInfo = createVolumeInfoFromVolumes(vmVolumes); @@ -343,50 +355,20 @@ private List createVolumeInfoFromVolumes(List vmVol return list; } - @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_DELETE, eventDescription = "deleting VM backup", async = true) - public boolean deleteBackup(final Long backupId) { - final BackupVO backup = backupDao.findByIdIncludingRemoved(backupId); - if (backup == null) { - throw new CloudRuntimeException("Backup " + backupId + " does not exist"); - } - final Long vmId = backup.getVmId(); - final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); - if (vm == null) { - throw new CloudRuntimeException("VM " + vmId + " does not exist"); - } - accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); - final BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); - boolean result = backupProvider.deleteBackup(backup); - if (result) { - backup.setStatus(Backup.Status.Expunged); - if (backupDao.update(backup.getId(), backup)) { - backupDao.remove(backup.getId()); - return true; - } - } - // FIXME: fsm to deal with GC+deletion - backup.setStatus(Backup.Status.Removed); - if (backupDao.update(backup.getId(), backup)) { - backupDao.remove(backupId); - return true; - } - - return result; - } - - public boolean importVM(long zoneId, long domainId, long accountId, long userId, - String vmInternalName, Hypervisor.HypervisorType hypervisorType, Backup backup) { - //TODO: Remove it from the backup manager interface - //TODO: refactor with VM ingestion feature in future + public boolean importRestoredVM(long zoneId, long domainId, long accountId, long userId, + String vmInternalName, Hypervisor.HypervisorType hypervisorType, Backup backup) { + VirtualMachine vm = null; HypervisorGuru guru = hypervisorGuruManager.getGuru(hypervisorType); try { - guru.importVirtualMachine(zoneId, domainId, accountId, userId, vmInternalName, backup); - } catch (Exception e) { - LOG.error("Failed to import VM", e); - throw new CloudRuntimeException("Error during vm import: " + e.getMessage()); + vm = guru.importVirtualMachine(zoneId, domainId, accountId, userId, vmInternalName, backup); + } catch (final Exception e) { + LOG.error("Failed to import VM from backup restoration", e); + throw new CloudRuntimeException("Error during vm backup restoration and import: " + e.getMessage()); } - return true; + if (vm == null) { + LOG.error("Failed to import restored VM " + vmInternalName + " with hypervisor type " + hypervisorType + " for backup ID " + backup.getUuid()); + } + return vm != null; } @Override @@ -410,9 +392,8 @@ public boolean restoreBackup(final Long backupId, final String restorePointId) { if (!backupProvider.restoreVMFromBackup(vm, backup.getExternalId(), restorePointId)) { throw new CloudRuntimeException("Error restoring VM " + vm.getId() + " from backup " + backup.getId()); } - importVM(vm.getDataCenterId(), vm.getDomainId(), vm.getAccountId(), vm.getUserId(), + return importRestoredVM(vm.getDataCenterId(), vm.getDomainId(), vm.getAccountId(), vm.getUserId(), vm.getInstanceName(), vm.getHypervisorType(), backup); - return true; } private Backup.VolumeInfo getVolumeInfo(List backedUpVolumes, String volumeUuid) { @@ -464,6 +445,38 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, return true; } + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_DELETE, eventDescription = "deleting VM backup", async = true) + public boolean deleteBackup(final Long backupId) { + final BackupVO backup = backupDao.findByIdIncludingRemoved(backupId); + if (backup == null) { + throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + } + final Long vmId = backup.getVmId(); + final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM " + vmId + " does not exist"); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + final BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + boolean result = backupProvider.deleteBackup(backup); + if (result) { + backup.setStatus(Backup.Status.Expunged); + if (backupDao.update(backup.getId(), backup)) { + backupDao.remove(backup.getId()); + return true; + } + } + // FIXME: fsm to deal with GC+deletion + backup.setStatus(Backup.Status.Removed); + if (backupDao.update(backup.getId(), backup)) { + backupDao.remove(backupId); + return true; + } + + return result; + } + /** * Get the pair: hostIp, datastoreUuid in which to restore the volume, based on the VM to be attached information */ @@ -513,18 +526,6 @@ private boolean attachVolumeToVM(Long zoneId, String restoredVolumeLocation, Lis } } - @Override - public boolean deleteBackupOffering(final Long offeringId) { - if (!backupDao.listByOfferingId(offeringId).isEmpty()) { - throw new CloudRuntimeException("Cannot allow deletion of backup offering due to use in existing VM backups, please delete the VM backups first"); - } - BackupOfferingVO offering = backupOfferingDao.findById(offeringId); - if (offering == null) { - throw new CloudRuntimeException("Could not find a backup offering with id: " + offeringId); - } - return backupOfferingDao.remove(offering.getId()); - } - @Override public boolean configure(String name, Map params) throws ConfigurationException { super.configure(name, params); @@ -571,7 +572,6 @@ public List> getCommands() { cmdList.add(RestoreBackupCmd.class); cmdList.add(DeleteBackupCmd.class); cmdList.add(RestoreVolumeFromBackupAndAttachToVMCmd.class); - cmdList.add(ImportVMCmdByAdmin.class); // Schedule cmdList.add(CreateBackupScheduleCmd.class); cmdList.add(ListBackupSchedulesCmd.class); @@ -643,6 +643,8 @@ protected void runInContext() { backupManager.deleteBackup(backup.getId()); } + // TODO: Check and schedule backups per user-defined backup schedule + // Sync backup size usages final List backups = backupDao.listByZoneAndState(dataCenter.getId(), null); if (backups.isEmpty()) { From 33d580d28444e8876b7e48d5f8681bbce836bdc7 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Sun, 1 Dec 2019 16:48:15 +0530 Subject: [PATCH 12/81] minor fixes Signed-off-by: Rohit Yadav --- .../AssignVirtualMachineToBackupOfferingCmd.java | 2 +- .../cloudstack/backup/VeeamBackupProvider.java | 12 ++++-------- .../apache/cloudstack/backup/BackupManagerImpl.java | 13 +++++++++++-- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java index 2697d0c99f1d..c0587ef87aad 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java @@ -95,7 +95,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE response.setResponseName(getCommandName()); setResponseObject(response); } else { - throw new CloudRuntimeException("Error while creating backup of VM"); + throw new CloudRuntimeException("Error while assigning VM to backup offering"); } } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); 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 1693f59614be..6f78a4d2d602 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 @@ -56,19 +56,19 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, "The Veeam backup and recovery URL.", true, ConfigKey.Scope.Zone); private ConfigKey VeeamUsername = new ConfigKey<>("Advanced", String.class, - "backup.plugin.veeam.host.username", + "backup.plugin.veeam.username", "administrator", "The Veeam backup and recovery username.", true, ConfigKey.Scope.Zone); private ConfigKey VeeamPassword = new ConfigKey<>("Advanced", String.class, - "backup.plugin.veeam.host.password", + "backup.plugin.veeam.password", "P@ssword123", "The Veeam backup and recovery password.", true, ConfigKey.Scope.Zone); - private ConfigKey VeeamValidateSSLSecurity = new ConfigKey<>("Advanced", Boolean.class, "backup.plugin.veeam.validate.ssl", "true", + private ConfigKey VeeamValidateSSLSecurity = new ConfigKey<>("Advanced", Boolean.class, "backup.plugin.veeam.validate.ssl", "false", "When set to true, this will validate the SSL certificate when connecting to https/ssl enabled Veeam API service.", true, ConfigKey.Scope.Zone); - private ConfigKey VeeamApiRequestTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.request.timeout", "300", + private ConfigKey VeeamApiRequestTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.request.timeout", "600", "The Veeam B&R API request timeout in seconds.", true, ConfigKey.Scope.Zone); @Inject @@ -154,10 +154,6 @@ public Backup assignVMToBackupOffering(final VirtualMachine vm, final Backup bac vmBackup.setStatus(Backup.Status.BackedUp); vmBackup.setExternalId(job.getExternalId()); vmBackup.setCreated(new Date()); - if (!takeBackup(vmBackup)) { - vmBackup.setStatus(Backup.Status.Failed); - LOG.warn("Veeam provider failed to start backup job after creating a new backup for VM id: " + vm.getId()); - } return vmBackup; } } 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 88b989c3a880..9c2a3a072514 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -234,10 +234,19 @@ public Backup assignVMToBackupOffering(Long vmId, Long offeringId) { throw new CloudRuntimeException("Failed to persist VM backup object in database"); } - backup = (BackupVO) backupProvider.assignVMToBackupOffering(vm, backup, offering); + try { + backup = (BackupVO) backupProvider.assignVMToBackupOffering(vm, backup, offering); + } catch (Exception e) { + LOG.error("Exception caught while assigning VM to backup offering by the backup provider", e); + backup.setStatus(Backup.Status.Error); + backupDao.update(backup.getId(), backup); + throw e; + } + if (backup == null) { throw new CloudRuntimeException("Backup provider failed to assign VM to the backup offering, for VM: " + vm.getUuid()); } + if (backupDao.update(backup.getId(), backup)) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, vm.getAccountId(), vm.getDataCenterId(), backup.getId(), vm.getUuid(), backup.getOfferingId(), backup.getVmId(), null, @@ -258,7 +267,7 @@ public boolean removeVMFromBackupOffering(Long vmId, Long offeringId) { final Backup backup = backupDao.findByVmId(vmId); if (backup == null) { - LOG.debug("VM has no backups or backup offering configuration, skipping removal."); + LOG.debug("No backups or backup offering configuration found for the VM, skipping removal."); return true; } From 73f3d58a9c84859a7ed1001374ce34f59f7281ac Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Sun, 1 Dec 2019 17:07:14 +0530 Subject: [PATCH 13/81] fix deletion corner cases Signed-off-by: Rohit Yadav --- .../backup/VeeamBackupProvider.java | 2 +- .../cloudstack/backup/BackupManagerImpl.java | 21 ++++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) 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 6f78a4d2d602..cbbcee7384f1 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 @@ -193,7 +193,7 @@ public boolean takeBackup(final Backup backup) { @Override public boolean deleteBackup(Backup backup) { // Veeam does not support removal of a restore point or point-in-time backup - throw new CloudRuntimeException("Backend plugin does not allow removal of backup, to delete the backup chain remove VM from the backup offering"); + throw new CloudRuntimeException("Veeam B&R plugin does not allow removal of backup restore point, to delete the backup chain remove VM from the backup offering"); } @Override 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 9c2a3a072514..c6ff0e130533 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -285,9 +286,10 @@ public boolean removeVMFromBackupOffering(Long vmId, Long offeringId) { boolean result = backupProvider.removeVMFromBackupOffering(vm, backup); if (result) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), backup.getId(), - vm.getUuid(), backup.getOfferingId(), backup.getVmId(), null, - Backup.class.getSimpleName(), backup.getUuid()); - return true; + vm.getUuid(), backup.getOfferingId(), backup.getVmId(), null, Backup.class.getSimpleName(), backup.getUuid()); + ((BackupVO)backup).setStatus(Backup.Status.Expunged); + ((BackupVO)backup).setRemoved(new Date()); + return backupDao.update(backup.getId(), (BackupVO)backup); } return false; @@ -305,12 +307,11 @@ public Backup createBackup(final Long vmId) { final Backup backup = backupDao.findByVmId(vm.getId()); if (backup == null) { - throw new CloudRuntimeException("VM backup is not configured, please assign to an offering or define a custom schedule"); + throw new CloudRuntimeException("VM backup is not configured, please assign to an offering and/or define a custom schedule"); } final BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); if (backupProvider != null && backupProvider.takeBackup(backup)) { - // TODO: handle update etc? return backup; } throw new CloudRuntimeException("Failed to create VM backup"); @@ -476,14 +477,10 @@ public boolean deleteBackup(final Long backupId) { return true; } } - // FIXME: fsm to deal with GC+deletion + // Let GC task handle removal backup.setStatus(Backup.Status.Removed); - if (backupDao.update(backup.getId(), backup)) { - backupDao.remove(backupId); - return true; - } - - return result; + backup.setRemoved(new Date()); + return backupDao.update(backup.getId(), backup); } /** From 25b658c05915dacc43067cb8c0cc7cd4727f07d0 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Sun, 1 Dec 2019 17:31:56 +0530 Subject: [PATCH 14/81] handle case of force vm removal from offering to delete job/backups for veeam Signed-off-by: Rohit Yadav --- .../RemoveVirtualMachineFromBackupOfferingCmd.java | 11 ++++++++++- .../java/org/apache/cloudstack/backup/Backup.java | 1 + .../org/apache/cloudstack/backup/BackupManager.java | 2 +- .../org/apache/cloudstack/backup/BackupProvider.java | 6 ++++++ .../java/org/apache/cloudstack/backup/BackupVO.java | 5 +++++ .../apache/cloudstack/backup/DummyBackupProvider.java | 5 +++++ .../apache/cloudstack/backup/VeeamBackupProvider.java | 6 ++++++ .../apache/cloudstack/backup/veeam/VeeamBackup.java | 5 +++++ .../apache/cloudstack/backup/BackupManagerImpl.java | 10 +++++++--- 9 files changed, 46 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java index b6a31a766188..d78b6f2b4e11 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java @@ -68,6 +68,11 @@ public class RemoveVirtualMachineFromBackupOfferingCmd extends BaseAsyncCmd { description = "ID of the backup offering") private Long offeringId; + @Parameter(name = ApiConstants.FORCED, + type = CommandType.BOOLEAN, + description = "Whether to force remove VM from the backup offering that may also delete VM backups.") + private Boolean forced; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -80,6 +85,10 @@ public Long getOfferingId() { return offeringId; } + public boolean getForced() { + return forced == null ? false : forced; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -87,7 +96,7 @@ public Long getOfferingId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.removeVMFromBackupOffering(getVmId(), getOfferingId()); + boolean result = backupManager.removeVMFromBackupOffering(getVmId(), getOfferingId(), getForced()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); this.setResponseObject(response); 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 3e51ef3576d0..41bcb379305c 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -142,6 +142,7 @@ public String toString() { Long getSize(); Long getProtectedSize(); String getExternalId(); + Boolean hasUserDefinedSchedule(); DateUtil.IntervalType getScheduleType(); String getSchedule(); String getTimezone(); 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 7693d6cf1441..71b498f7b112 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -87,7 +87,7 @@ BackupOffering importBackupOffering(final Long zoneId, final String offeringExte * @param offeringId * @return */ - boolean removeVMFromBackupOffering(final Long vmId, final Long offeringId); + boolean removeVMFromBackupOffering(final Long vmId, final Long offeringId, final boolean forced); /** * Creates backup of a VM 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 c17887588e94..f733cded36b3 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -64,6 +64,12 @@ public interface BackupProvider { */ boolean removeVMFromBackupOffering(VirtualMachine vm, Backup backup); + /** + * Where removal of + * @return + */ + boolean willDeleteBackupsOnOfferingRemoval(); + /** * Starts and creates an adhoc backup process * for a previously registered VM backup 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 e30ddbbd23d0..2ea5c3a365b4 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 @@ -162,6 +162,11 @@ public Long getProtectedSize() { return protectedSize; } + @Override + public Boolean hasUserDefinedSchedule() { + return scheduleType != null; + } + @Override public DateUtil.IntervalType getScheduleType() { return scheduleType == null ? null : DateUtil.getIntervalType(scheduleType); 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 ff1a6fb22f1d..566b15e9a98b 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 @@ -115,6 +115,11 @@ public boolean removeVMFromBackupOffering(VirtualMachine vm, Backup vmBackup) { return false; } + @Override + public boolean willDeleteBackupsOnOfferingRemoval() { + return false; + } + @Override public boolean takeBackup(Backup backup) { s_logger.debug("Starting backup " + backup.getUuid() + " on Dummy provider"); 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 cbbcee7384f1..78c401dd46f8 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 @@ -180,10 +180,16 @@ public boolean removeVMFromBackupOffering(final VirtualMachine vm, final Backup final String clonedJobName = getGuestBackupName(vm.getInstanceName(), backup.getUuid()); if (!client.deleteJobAndBackup(clonedJobName)) { LOG.warn("Failed to remove Veeam job and backup for job: " + clonedJobName); + throw new CloudRuntimeException("Failed to delete Veeam B&R job and backup, an operation may be in progress. Please try again after some time."); } return client.listJob(clonedJobName) == null; } + @Override + public boolean willDeleteBackupsOnOfferingRemoval() { + return true; + } + @Override public boolean takeBackup(final Backup backup) { final VeeamClient client = getClient(backup.getZoneId()); diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java index 3bf7959ac563..ee4c764ac519 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java @@ -49,6 +49,11 @@ public String getExternalId() { return null; } + @Override + public Boolean hasUserDefinedSchedule() { + return null; + } + @Override public DateUtil.IntervalType getScheduleType() { return null; 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 c6ff0e130533..159af037d1bd 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -260,7 +260,7 @@ public Backup assignVMToBackupOffering(Long vmId, Long offeringId) { @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, eventDescription = "remove VM from backup offering", async = true) - public boolean removeVMFromBackupOffering(Long vmId, Long offeringId) { + public boolean removeVMFromBackupOffering(final Long vmId, final Long offeringId, final boolean forced) { final VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { throw new CloudRuntimeException("Did not find VM by provided ID"); @@ -268,8 +268,7 @@ public boolean removeVMFromBackupOffering(Long vmId, Long offeringId) { final Backup backup = backupDao.findByVmId(vmId); if (backup == null) { - LOG.debug("No backups or backup offering configuration found for the VM, skipping removal."); - return true; + throw new CloudRuntimeException("No backups or backup offering configuration found for the VM."); } if (!backup.getOfferingId().equals(offeringId)) { @@ -283,6 +282,11 @@ public boolean removeVMFromBackupOffering(Long vmId, Long offeringId) { accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + if (!forced && backupProvider.willDeleteBackupsOnOfferingRemoval()) { + throw new CloudRuntimeException("The backend provider will only allow removal of VM from the offering if forced:true is provided " + + "that will also delete the backups are removed."); + } + boolean result = backupProvider.removeVMFromBackupOffering(vm, backup); if (result) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), backup.getId(), From 83082416c3a470741325a5b5e5701d0366f44f8f Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Sun, 1 Dec 2019 18:22:40 +0530 Subject: [PATCH 15/81] backup scheduled crud apis Signed-off-by: Rohit Yadav --- .../cloudstack/api/ResponseGenerator.java | 3 + .../user/backup/CreateBackupScheduleCmd.java | 27 ++++-- .../command/user/backup/DeleteBackupCmd.java | 1 - .../user/backup/DeleteBackupScheduleCmd.java | 20 ++--- ...lesCmd.java => ListBackupScheduleCmd.java} | 33 ++++--- .../user/backup/UpdateBackupScheduleCmd.java | 37 ++++++-- .../cloudstack/backup/BackupManager.java | 10 +++ .../apache/cloudstack/backup/BackupVO.java | 2 +- .../cloudstack/backup/dao/BackupDao.java | 3 + .../cloudstack/backup/dao/BackupDaoImpl.java | 17 ++++ .../main/java/com/cloud/api/ApiDBUtils.java | 5 ++ .../java/com/cloud/api/ApiResponseHelper.java | 6 ++ .../cloudstack/backup/BackupManagerImpl.java | 86 ++++++++++++++++++- 13 files changed, 202 insertions(+), 48 deletions(-) rename api/src/main/java/org/apache/cloudstack/api/command/user/backup/{ListBackupSchedulesCmd.java => ListBackupScheduleCmd.java} (79%) 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 61d5a8735323..6fd612b32268 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -35,6 +35,7 @@ import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse; import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse; import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.api.response.CapacityResponse; import org.apache.cloudstack.api.response.ClusterResponse; import org.apache.cloudstack.api.response.ConditionResponse; @@ -471,6 +472,8 @@ List createTemplateResponses(ResponseView view, VirtualMachine BackupResponse createBackupResponse(Backup backup); + BackupScheduleResponse createBackupScheduleResponse(Backup backup); + BackupOfferingResponse createBackupOfferingResponse(BackupOffering policy); ManagementServerResponse createManagementResponse(ManagementServerHost mgmt); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java index 07fc56987710..e6e4cfb46f03 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java @@ -28,10 +28,14 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.BackupScheduleResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; import com.cloud.event.EventTypes; +import com.cloud.utils.exception.CloudRuntimeException; @APICommand(name = CreateBackupScheduleCmd.APINAME, description = "Create a user-defined VM backup schedule", @@ -47,12 +51,12 @@ public class CreateBackupScheduleCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.BACKUP_ID, + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, - entityType = BackupResponse.class, + entityType = UserVmResponse.class, required = true, - description = "id of the backup for which custom schedule is to be defined") - private Long backupId; + description = "ID of the VM for which schedule is to be defined") + private Long vmId; @Parameter(name = ApiConstants.INTERVAL_TYPE, type = CommandType.STRING, required = true, description = "valid values are HOURLY, DAILY, WEEKLY, and MONTHLY") private String intervalType; @@ -68,8 +72,8 @@ public class CreateBackupScheduleCmd extends BaseAsyncCmd { /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public Long getBackupId() { - return backupId; + public Long getVmId() { + return vmId; } ///////////////////////////////////////////////////// @@ -79,7 +83,14 @@ public Long getBackupId() { @Override public void execute() throws ServerApiException { try { - // TODO: ask service layer to do the magic + Backup backup = backupManager.createBackupSchedule(this); + if (backup != null) { + BackupScheduleResponse response = _responseGenerator.createBackupScheduleResponse(backup); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new CloudRuntimeException("Error while creating backup schedule of VM"); + } } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } @@ -102,6 +113,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Creating user-defined backup schedule for backup " + backupId; + return "Creating user-defined backup schedule for backup for VM ID " + vmId; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java index 7488d082a302..32344d8992b3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java @@ -77,7 +77,6 @@ public Long getId() { public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { boolean result = backupManager.deleteBackup(backupId); - // FIXME: the response type? if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java index e5ee2dac14f7..34a175fe583a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java @@ -27,8 +27,8 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -54,19 +54,19 @@ public class DeleteBackupScheduleCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.ID, + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, - entityType = BackupResponse.class, + entityType = UserVmResponse.class, required = true, - description = "ID of the backup whose schedule needs to be deleted") - private Long backupId; + description = "ID of the VM") + private Long vmId; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public Long getId() { - return backupId; + public Long getVmId() { + return vmId; } ///////////////////////////////////////////////////// @@ -76,13 +76,13 @@ public Long getId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = false; // TODO: use backupManager + boolean result = backupManager.deleteBackupSchedule(getVmId()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); setResponseObject(response); } else { - throw new CloudRuntimeException("Error while deleting backup of VM"); + throw new CloudRuntimeException("Error while deleting backup schedule of VM"); } } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); @@ -106,6 +106,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting backup schedule for backup ID " + backupId; + return "Deleting backup schedule for VM ID " + vmId; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupSchedulesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java similarity index 79% rename from api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupSchedulesCmd.java rename to api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java index 3e1837f55e65..7f015b19b9e7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupSchedulesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java @@ -27,9 +27,9 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -38,13 +38,14 @@ import com.cloud.exception.NetworkRuleConflictException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.exception.CloudRuntimeException; -@APICommand(name = ListBackupSchedulesCmd.APINAME, - description = "Lists backup schedules", +@APICommand(name = ListBackupScheduleCmd.APINAME, + description = "List backup schedule of a VM", responseObject = BackupScheduleResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class ListBackupSchedulesCmd extends BaseBackupListCmd { - public static final String APINAME = "listBackupSchedules"; +public class ListBackupScheduleCmd extends BaseBackupListCmd { + public static final String APINAME = "listBackupSchedule"; @Inject private BackupManager backupManager; @@ -53,26 +54,17 @@ public class ListBackupSchedulesCmd extends BaseBackupListCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.ID, - type = CommandType.UUID, - entityType = BackupResponse.class, - description = "id of the VM backup") - private Long id; - @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, - description = "id of the VM") + required = true, + description = "ID of the VM") private Long vmId; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public Long getId() { - return id; - } - public Long getVmId() { return vmId; } @@ -84,7 +76,14 @@ public Long getVmId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try{ - // TODO API stuff + Backup backup = backupManager.listBackupSchedule(getVmId()); + if (backup != null) { + BackupScheduleResponse response = _responseGenerator.createBackupScheduleResponse(backup); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new CloudRuntimeException("Error while listing backup schedule of VM"); + } } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java index cf927ac3321d..c583df2a4241 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java @@ -27,8 +27,9 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.BackupScheduleResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -38,6 +39,7 @@ import com.cloud.exception.NetworkRuleConflictException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.exception.CloudRuntimeException; @APICommand(name = UpdateBackupScheduleCmd.APINAME, description = "Updates backup schedule of a VM backup with an existing schedule", @@ -53,19 +55,29 @@ public class UpdateBackupScheduleCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.BACKUP_ID, + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, - entityType = BackupResponse.class, + entityType = UserVmResponse.class, required = true, - description = "ID of the backup") - private Long backupId; + description = "ID of the VM for which schedule is to be defined") + private Long vmId; + + @Parameter(name = ApiConstants.INTERVAL_TYPE, type = CommandType.STRING, required = true, description = "valid values are HOURLY, DAILY, WEEKLY, and MONTHLY") + private String intervalType; + + @Parameter(name = ApiConstants.MAX_BACKUPS, type = CommandType.INTEGER, required = true, description = "maximum number of backup restore points to keep") + private Integer maxBackups; + + @Parameter(name = ApiConstants.SCHEDULE, type = CommandType.STRING, required = true, description = "custom backup schedule, the format is:" + + "for HOURLY MM*, for DAILY MM:HH*, for WEEKLY MM:HH:DD (1-7)*, for MONTHLY MM:HH:DD (1-28)") + private String schedule; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public Long getBackupId() { - return backupId; + public Long getVmId() { + return vmId; } ///////////////////////////////////////////////////// @@ -75,7 +87,14 @@ public Long getBackupId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - // TODO implement API + Backup backup = backupManager.updateBackupSchedule(this); + if (backup != null) { + BackupScheduleResponse response = _responseGenerator.createBackupScheduleResponse(backup); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new CloudRuntimeException("Error while updating backup schedule of VM"); + } } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } @@ -98,6 +117,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Updating VM backup schedule for VM backup ID: " + backupId; + return "Updating VM backup schedule for VM ID: " + vmId; } } \ No newline at end of file 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 71b498f7b112..83f4676c4a27 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -19,7 +19,9 @@ import java.util.List; +import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; +import org.apache.cloudstack.api.command.user.backup.UpdateBackupScheduleCmd; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; @@ -89,6 +91,14 @@ BackupOffering importBackupOffering(final Long zoneId, final String offeringExte */ boolean removeVMFromBackupOffering(final Long vmId, final Long offeringId, final boolean forced); + Backup createBackupSchedule(CreateBackupScheduleCmd cmd); + + Backup updateBackupSchedule(UpdateBackupScheduleCmd cmd); + + Backup listBackupSchedule(Long vmId); + + boolean deleteBackupSchedule(Long vmId); + /** * Creates backup of a VM * @param vmId Virtual Machine ID 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 2ea5c3a365b4..9ea861a0dd4d 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 @@ -225,7 +225,7 @@ public void setProtectedSize(Long protectedSize) { this.protectedSize = protectedSize; } - public void setScheduleType(short scheduleType) { + public void setScheduleType(Short scheduleType) { this.scheduleType = scheduleType; } 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 c65b1853daaa..2d5872bf4900 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 @@ -20,6 +20,7 @@ import java.util.List; import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupVO; @@ -37,5 +38,7 @@ public interface BackupDao extends GenericDao { List listByZoneAndState(Long zoneId, Backup.Status state); BackupResponse newBackupResponse(Backup backup); + BackupScheduleResponse newBackupScheduleResponse(Backup backup); + BackupVO getBackupVO(Backup backup); } 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 59c5c4df6e35..ff4738f4ed54 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 @@ -24,6 +24,7 @@ import javax.inject.Inject; import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupVO; @@ -170,4 +171,20 @@ public BackupResponse newBackupResponse(Backup backup) { backupResponse.setObjectName("backup"); return backupResponse; } + + @Override + public BackupScheduleResponse newBackupScheduleResponse(Backup backup) { + AccountVO account = accountDao.findById(backup.getAccountId()); + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); + DataCenterVO zone = dataCenterDao.findById(backup.getZoneId()); + + BackupScheduleResponse response = new BackupScheduleResponse(); + response.setZoneId(zone.getUuid()); + response.setId(backup.getUuid()); + response.setAccountId(account.getUuid()); + response.setVmId(vm.getUuid()); + response.setVmName(vm.getHostName()); + response.setObjectName("backupschedule"); + return response; + } } diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index 1b683c189757..f31110729ef2 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -40,6 +40,7 @@ import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.AsyncJobResponse; import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.DomainRouterResponse; @@ -2052,6 +2053,10 @@ public static BackupResponse newBackupResponse(Backup backup) { return s_backupDao.newBackupResponse(backup); } + public static BackupScheduleResponse newBackupScheduleResponse(Backup backup) { + return s_backupDao.newBackupScheduleResponse(backup); + } + public static BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy) { return s_backupOfferingDao.newBackupOfferingResponse(policy); } diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 1932edbbaca6..8205c57f58bb 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -51,6 +51,7 @@ import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse; import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.api.response.CapabilityResponse; import org.apache.cloudstack.api.response.CapacityResponse; import org.apache.cloudstack.api.response.ClusterResponse; @@ -4235,6 +4236,11 @@ public BackupResponse createBackupResponse(Backup backup) { return ApiDBUtils.newBackupResponse(backup); } + @Override + public BackupScheduleResponse createBackupScheduleResponse(Backup backup) { + return ApiDBUtils.newBackupScheduleResponse(backup); + } + @Override public BackupOfferingResponse createBackupOfferingResponse(BackupOffering policy) { return ApiDBUtils.newBackupOfferingResponse(policy); 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 159af037d1bd..61e04e4ed62f 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -36,7 +36,7 @@ import org.apache.cloudstack.api.command.user.backup.DeleteBackupCmd; import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; -import org.apache.cloudstack.api.command.user.backup.ListBackupSchedulesCmd; +import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupsCmd; import org.apache.cloudstack.api.command.user.backup.RemoveVirtualMachineFromBackupOfferingCmd; import org.apache.cloudstack.api.command.user.backup.RestoreBackupCmd; @@ -299,6 +299,88 @@ public boolean removeVMFromBackupOffering(final Long vmId, final Long offeringId return false; } + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_CREATE, eventDescription = "creating VM backup schedule", async = true) + public Backup createBackupSchedule(CreateBackupScheduleCmd cmd) { + final VMInstanceVO vm = vmInstanceDao.findById(cmd.getVmId()); + if (vm == null) { + throw new CloudRuntimeException("Did not find VM by provided ID"); + } + + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + final Backup backup = backupDao.findByVmId(vm.getId()); + if (backup == null) { + throw new CloudRuntimeException("VM backup is not configured, please assign to an offering and/or define a custom schedule"); + } + + //TODO: handle create logic + + return backup; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_UPDATE, eventDescription = "updating VM backup schedule", async = true) + public Backup updateBackupSchedule(UpdateBackupScheduleCmd cmd) { + final VMInstanceVO vm = vmInstanceDao.findById(cmd.getVmId()); + if (vm == null) { + throw new CloudRuntimeException("Did not find VM by provided ID"); + } + + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + final Backup backup = backupDao.findByVmId(vm.getId()); + if (backup == null) { + throw new CloudRuntimeException("VM backup is not configured, please assign to an offering and/or define a custom schedule"); + } + + //TODO: handle update logic + + return backup; + } + + @Override + public Backup listBackupSchedule(final Long vmId) { + final VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("Did not find VM by provided ID"); + } + + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + final Backup backup = backupDao.findByVmId(vm.getId()); + if (backup == null) { + throw new CloudRuntimeException("VM backup is not configured, please assign to an offering and/or define a custom schedule"); + } + + return backup; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE, eventDescription = "deleting VM backup schedule", async = true) + public boolean deleteBackupSchedule(final Long vmId) { + final VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("Did not find VM by provided ID"); + } + + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + final BackupVO backup = (BackupVO) backupDao.findByVmId(vm.getId()); + if (backup == null) { + throw new CloudRuntimeException("VM backup is not configured, please assign to an offering and/or define a custom schedule."); + } + + if (!backup.hasUserDefinedSchedule()) { + throw new CloudRuntimeException("VM has no backup schedule defined, no need to delete anything."); + } + + backup.setSchedule(null); + backup.setScheduleType(null); + backup.setTimezone(null); + return backupDao.update(backup.getId(), backup); + } + @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_CREATE, eventDescription = "creating VM backup", async = true) public Backup createBackup(final Long vmId) { @@ -584,7 +666,7 @@ public List> getCommands() { cmdList.add(RestoreVolumeFromBackupAndAttachToVMCmd.class); // Schedule cmdList.add(CreateBackupScheduleCmd.class); - cmdList.add(ListBackupSchedulesCmd.class); + cmdList.add(ListBackupScheduleCmd.class); cmdList.add(UpdateBackupScheduleCmd.class); cmdList.add(DeleteBackupScheduleCmd.class); return cmdList; From 63b41d2d1d37152c24eed61b28e8916f582c3b1f Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Sun, 1 Dec 2019 18:23:40 +0530 Subject: [PATCH 16/81] apidocs comments Signed-off-by: Rohit Yadav --- .../cloudstack/backup/BackupManager.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) 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 83f4676c4a27..421ed17dd165 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -91,12 +91,32 @@ BackupOffering importBackupOffering(final Long zoneId, final String offeringExte */ boolean removeVMFromBackupOffering(final Long vmId, final Long offeringId, final boolean forced); + /** + * Creates a VM backup schedule + * @param cmd + * @return + */ Backup createBackupSchedule(CreateBackupScheduleCmd cmd); + /** + * Updates a VM backup schedule + * @param cmd + * @return + */ Backup updateBackupSchedule(UpdateBackupScheduleCmd cmd); + /** + * Lists VM backup schedule for a VM + * @param vmId + * @return + */ Backup listBackupSchedule(Long vmId); + /** + * Deletes VM backup schedule for a VM + * @param vmId + * @return + */ boolean deleteBackupSchedule(Long vmId); /** From aebb61af2c4fdff259eff572d4a088a7c280813b Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 5 Dec 2019 14:02:51 +0530 Subject: [PATCH 17/81] major refactorings per new spec updates Signed-off-by: Rohit Yadav --- .../java/com/cloud/vm/VirtualMachine.java | 19 +- .../apache/cloudstack/api/ApiConstants.java | 1 - .../cloudstack/api/ResponseGenerator.java | 3 +- ...signVirtualMachineToBackupOfferingCmd.java | 14 +- .../user/backup/CreateBackupScheduleCmd.java | 40 +++- .../user/backup/DeleteBackupScheduleCmd.java | 2 +- .../user/backup/ListBackupScheduleCmd.java | 8 +- ...veVirtualMachineFromBackupOfferingCmd.java | 16 +- .../user/backup/UpdateBackupScheduleCmd.java | 122 ------------ .../api/response/BackupResponse.java | 36 ++-- .../api/response/BackupScheduleResponse.java | 65 ++++--- .../org/apache/cloudstack/backup/Backup.java | 8 +- .../cloudstack/backup/BackupManager.java | 18 +- .../cloudstack/backup/BackupOffering.java | 3 +- .../cloudstack/backup/BackupProvider.java | 7 +- .../cloudstack/backup/BackupSchedule.java | 34 ++++ .../main/java/com/cloud/vm/VMInstanceVO.java | 52 +++-- .../cloudstack/backup/BackupOfferingVO.java | 20 +- .../cloudstack/backup/BackupScheduleVO.java | 134 +++++++++++++ .../apache/cloudstack/backup/BackupVO.java | 73 ------- .../cloudstack/backup/dao/BackupDao.java | 2 - .../cloudstack/backup/dao/BackupDaoImpl.java | 39 ++-- .../backup/dao/BackupOfferingDaoImpl.java | 3 +- .../backup/dao/BackupScheduleDao.java | 30 +++ .../backup/dao/BackupScheduleDaoImpl.java | 72 +++++++ .../cloud/entity/api/db/VMEntityVO.java | 41 ++-- ...spring-engine-schema-core-daos-context.xml | 1 + .../META-INF/db/schema-41300to41400.sql | 31 +-- .../backup/DummyBackupProvider.java | 23 +-- .../backup/VeeamBackupProvider.java | 28 ++- .../backup/veeam/VeeamBackupOffering.java | 5 + .../main/java/com/cloud/api/ApiDBUtils.java | 14 +- .../java/com/cloud/api/ApiResponseHelper.java | 5 +- .../cloud/storage/VolumeApiServiceImpl.java | 9 + .../cloudstack/backup/BackupManagerImpl.java | 183 ++++++++---------- 35 files changed, 638 insertions(+), 523 deletions(-) delete mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java diff --git a/api/src/main/java/com/cloud/vm/VirtualMachine.java b/api/src/main/java/com/cloud/vm/VirtualMachine.java index a46edd78f447..4bd12a922995 100644 --- a/api/src/main/java/com/cloud/vm/VirtualMachine.java +++ b/api/src/main/java/com/cloud/vm/VirtualMachine.java @@ -16,18 +16,19 @@ // under the License. package com.cloud.vm; +import java.util.Arrays; +import java.util.Date; +import java.util.Map; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.Displayable; +import org.apache.cloudstack.kernel.Partition; + import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.utils.fsm.StateMachine2; import com.cloud.utils.fsm.StateMachine2.Transition; import com.cloud.utils.fsm.StateMachine2.Transition.Impact; import com.cloud.utils.fsm.StateObject; -import org.apache.cloudstack.acl.ControlledEntity; -import org.apache.cloudstack.api.Displayable; -import org.apache.cloudstack.kernel.Partition; - -import java.util.Arrays; -import java.util.Date; -import java.util.Map; /** * VirtualMachine describes the properties held by a virtual machine @@ -319,6 +320,10 @@ public boolean isUsedBySystem() { Long getDiskOfferingId(); + Long getBackupOfferingId(); + + String getBackupExternalId(); + Type getType(); HypervisorType getHypervisorType(); 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 bff2fd1d9636..a0eaf9f2e35c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -212,7 +212,6 @@ public class ApiConstants { public static final String LBID = "lbruleid"; public static final String MAX = "max"; public static final String MAC_ADDRESS = "macaddress"; - public static final String MAX_BACKUPS = "maxbackups"; public static final String MAX_SNAPS = "maxsnaps"; public static final String MAX_CPU_NUMBER = "maxcpunumber"; public static final String MAX_MEMORY = "maxmemory"; 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 6fd612b32268..55b3a7d23641 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -122,6 +122,7 @@ import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.management.ManagementServerHost; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; @@ -472,7 +473,7 @@ List createTemplateResponses(ResponseView view, VirtualMachine BackupResponse createBackupResponse(Backup backup); - BackupScheduleResponse createBackupScheduleResponse(Backup backup); + BackupScheduleResponse createBackupScheduleResponse(BackupSchedule backup); BackupOfferingResponse createBackupOfferingResponse(BackupOffering policy); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java index c0587ef87aad..b5c09867b67e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java @@ -29,8 +29,8 @@ import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -40,7 +40,6 @@ import com.cloud.exception.NetworkRuleConflictException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; -import com.cloud.utils.exception.CloudRuntimeException; @APICommand(name = AssignVirtualMachineToBackupOfferingCmd.APINAME, description = "Assigns a VM to a backup offering", @@ -89,13 +88,12 @@ public Long getOfferingId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - Backup backup = backupManager.assignVMToBackupOffering(getVmId(), getOfferingId()); - if (backup != null) { - BackupResponse response = _responseGenerator.createBackupResponse(backup); - response.setResponseName(getCommandName()); - setResponseObject(response); + boolean result = backupManager.assignVMToBackupOffering(getVmId(), getOfferingId()); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); } else { - throw new CloudRuntimeException("Error while assigning VM to backup offering"); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add VM to backup offering"); } } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java index e6e4cfb46f03..c424df0418da 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java @@ -30,11 +30,12 @@ import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.context.CallContext; import com.cloud.event.EventTypes; +import com.cloud.utils.DateUtil; import com.cloud.utils.exception.CloudRuntimeException; @APICommand(name = CreateBackupScheduleCmd.APINAME, @@ -58,16 +59,25 @@ public class CreateBackupScheduleCmd extends BaseAsyncCmd { description = "ID of the VM for which schedule is to be defined") private Long vmId; - @Parameter(name = ApiConstants.INTERVAL_TYPE, type = CommandType.STRING, required = true, description = "valid values are HOURLY, DAILY, WEEKLY, and MONTHLY") + @Parameter(name = ApiConstants.INTERVAL_TYPE, + type = CommandType.STRING, + required = true, + description = "valid values are HOURLY, DAILY, WEEKLY, and MONTHLY") private String intervalType; - @Parameter(name = ApiConstants.MAX_BACKUPS, type = CommandType.INTEGER, required = true, description = "maximum number of backup restore points to keep") - private Integer maxBackups; - - @Parameter(name = ApiConstants.SCHEDULE, type = CommandType.STRING, required = true, description = "custom backup schedule, the format is:" + @Parameter(name = ApiConstants.SCHEDULE, + type = CommandType.STRING, + required = true, + description = "custom backup schedule, the format is:" + "for HOURLY MM*, for DAILY MM:HH*, for WEEKLY MM:HH:DD (1-7)*, for MONTHLY MM:HH:DD (1-28)") private String schedule; + @Parameter(name = ApiConstants.TIMEZONE, + type = CommandType.STRING, + required = true, + description = "Specifies a timezone for this command. For more information on the timezone parameter, see TimeZone Format.") + private String timezone; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -76,6 +86,18 @@ public Long getVmId() { return vmId; } + public DateUtil.IntervalType getIntervalType() { + return DateUtil.IntervalType.getIntervalType(intervalType); + } + + public String getSchedule() { + return schedule; + } + + public String getTimezone() { + return timezone; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -83,9 +105,9 @@ public Long getVmId() { @Override public void execute() throws ServerApiException { try { - Backup backup = backupManager.createBackupSchedule(this); - if (backup != null) { - BackupScheduleResponse response = _responseGenerator.createBackupScheduleResponse(backup); + BackupSchedule schedule = backupManager.createBackupSchedule(this); + if (schedule != null) { + BackupScheduleResponse response = _responseGenerator.createBackupScheduleResponse(schedule); response.setResponseName(getCommandName()); setResponseObject(response); } else { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java index 34a175fe583a..40938f857b98 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java @@ -41,7 +41,7 @@ import com.cloud.utils.exception.CloudRuntimeException; @APICommand(name = DeleteBackupScheduleCmd.APINAME, - description = "Deletes a VM backup schedule", + description = "Deletes the backup schedule of a VM", responseObject = SuccessResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class DeleteBackupScheduleCmd extends BaseAsyncCmd { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java index 7f015b19b9e7..5f275e2419a5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java @@ -29,8 +29,8 @@ import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.context.CallContext; import com.cloud.exception.ConcurrentOperationException; @@ -76,9 +76,9 @@ public Long getVmId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try{ - Backup backup = backupManager.listBackupSchedule(getVmId()); - if (backup != null) { - BackupScheduleResponse response = _responseGenerator.createBackupScheduleResponse(backup); + BackupSchedule schedule = backupManager.listBackupSchedule(getVmId()); + if (schedule != null) { + BackupScheduleResponse response = _responseGenerator.createBackupScheduleResponse(schedule); response.setResponseName(getCommandName()); setResponseObject(response); } else { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java index d78b6f2b4e11..8e915b05f167 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java @@ -27,7 +27,6 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.backup.BackupManager; @@ -61,13 +60,6 @@ public class RemoveVirtualMachineFromBackupOfferingCmd extends BaseAsyncCmd { description = "ID of the virtual machine") private Long vmId; - @Parameter(name = ApiConstants.BACKUP_OFFERING_ID, - type = CommandType.UUID, - entityType = BackupOfferingResponse.class, - required = true, - description = "ID of the backup offering") - private Long offeringId; - @Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, description = "Whether to force remove VM from the backup offering that may also delete VM backups.") @@ -81,10 +73,6 @@ public Long getVmId() { return vmId; } - public Long getOfferingId() { - return offeringId; - } - public boolean getForced() { return forced == null ? false : forced; } @@ -96,7 +84,7 @@ public boolean getForced() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.removeVMFromBackupOffering(getVmId(), getOfferingId(), getForced()); + boolean result = backupManager.removeVMFromBackupOffering(getVmId(), getForced()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); this.setResponseObject(response); @@ -125,6 +113,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Removing VM from backup offering ID: " + offeringId; + return "Removing VM ID" + vmId + " from backup offering"; } } \ No newline at end of file diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java deleted file mode 100644 index c583df2a4241..000000000000 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java +++ /dev/null @@ -1,122 +0,0 @@ -// 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.backup; - -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.BaseAsyncCmd; -import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.api.Parameter; -import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.BackupScheduleResponse; -import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.backup.Backup; -import org.apache.cloudstack.backup.BackupManager; -import org.apache.cloudstack.context.CallContext; - -import com.cloud.event.EventTypes; -import com.cloud.exception.ConcurrentOperationException; -import com.cloud.exception.InsufficientCapacityException; -import com.cloud.exception.NetworkRuleConflictException; -import com.cloud.exception.ResourceAllocationException; -import com.cloud.exception.ResourceUnavailableException; -import com.cloud.utils.exception.CloudRuntimeException; - -@APICommand(name = UpdateBackupScheduleCmd.APINAME, - description = "Updates backup schedule of a VM backup with an existing schedule", - responseObject = BackupScheduleResponse.class, since = "4.14.0", - authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class UpdateBackupScheduleCmd extends BaseAsyncCmd { - public static final String APINAME = "updateBackupSchedule"; - - @Inject - private BackupManager backupManager; - - ///////////////////////////////////////////////////// - //////////////// API parameters ///////////////////// - ///////////////////////////////////////////////////// - - @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, - type = CommandType.UUID, - entityType = UserVmResponse.class, - required = true, - description = "ID of the VM for which schedule is to be defined") - private Long vmId; - - @Parameter(name = ApiConstants.INTERVAL_TYPE, type = CommandType.STRING, required = true, description = "valid values are HOURLY, DAILY, WEEKLY, and MONTHLY") - private String intervalType; - - @Parameter(name = ApiConstants.MAX_BACKUPS, type = CommandType.INTEGER, required = true, description = "maximum number of backup restore points to keep") - private Integer maxBackups; - - @Parameter(name = ApiConstants.SCHEDULE, type = CommandType.STRING, required = true, description = "custom backup schedule, the format is:" - + "for HOURLY MM*, for DAILY MM:HH*, for WEEKLY MM:HH:DD (1-7)*, for MONTHLY MM:HH:DD (1-28)") - private String schedule; - - ///////////////////////////////////////////////////// - /////////////////// Accessors /////////////////////// - ///////////////////////////////////////////////////// - - public Long getVmId() { - return vmId; - } - - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// - - @Override - public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { - try { - Backup backup = backupManager.updateBackupSchedule(this); - if (backup != null) { - BackupScheduleResponse response = _responseGenerator.createBackupScheduleResponse(backup); - response.setResponseName(getCommandName()); - setResponseObject(response); - } else { - throw new CloudRuntimeException("Error while updating backup schedule of VM"); - } - } catch (Exception e) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); - } - } - - @Override - public String getCommandName() { - return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; - } - - @Override - public long getEntityOwnerId() { - return CallContext.current().getCallingAccount().getId(); - } - - @Override - public String getEventType() { - return EventTypes.EVENT_VM_BACKUP_SCHEDULE_UPDATE; - } - - @Override - public String getEventDescription() { - return "Updating VM backup schedule for VM ID: " + vmId; - } -} \ No newline at end of file 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 0a7e08b8e74b..878bc6089d37 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 @@ -32,17 +32,21 @@ public class BackupResponse extends BaseResponse { @SerializedName(ApiConstants.ID) - @Param(description = "internal id of the backup") + @Param(description = "ID of the VM (backup)") private String id; - @SerializedName(ApiConstants.VIRTUAL_MACHINE_NAME) + @SerializedName(ApiConstants.NAME) @Param(description = "name of the VM") - private String vmName; + private String name; @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) - @Param(description = "ID of the vm") + @Param(description = "ID of the VM") private String vmId; + @SerializedName(ApiConstants.VIRTUAL_MACHINE_NAME) + @Param(description = "name of the VM") + private String vmName; + @SerializedName(ApiConstants.ZONE_ID) @Param(description = "zone id") private String zoneId; @@ -87,6 +91,14 @@ public void setId(String id) { this.id = id; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public String getAccountId() { return accountId; } @@ -111,14 +123,6 @@ public void setExternalId(String externalId) { this.externalId = externalId; } - public String getVmName() { - return vmName; - } - - public void setVmName(String vmName) { - this.vmName = vmName; - } - public String getVmId() { return vmId; } @@ -127,6 +131,14 @@ public void setVmId(String vmId) { this.vmId = vmId; } + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + public Backup.Status getStatus() { return status; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java index e67f8e927ea4..de7997617c5e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java @@ -20,16 +20,17 @@ import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; -import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupSchedule; import com.cloud.serializer.Param; +import com.cloud.utils.DateUtil; import com.google.gson.annotations.SerializedName; -@EntityReference(value = Backup.class) +@EntityReference(value = BackupSchedule.class) public class BackupScheduleResponse extends BaseResponse { @SerializedName(ApiConstants.ID) - @Param(description = "internal id of the backup") + @Param(description = "ID of the VM backup schedule") private String id; @SerializedName(ApiConstants.VIRTUAL_MACHINE_NAME) @@ -37,18 +38,20 @@ public class BackupScheduleResponse extends BaseResponse { private String vmName; @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) - @Param(description = "ID of the vm") + @Param(description = "ID of the VM") private String vmId; - @SerializedName(ApiConstants.ZONE_ID) - @Param(description = "zone id") - private String zoneId; + @SerializedName("schedule") + @Param(description = "time the backup is scheduled to be taken.") + private String schedule; - @SerializedName(ApiConstants.ACCOUNT_ID) - @Param(description = "account id") - private String accountId; + @SerializedName("intervaltype") + @Param(description = "the interval type of the backup schedule") + private DateUtil.IntervalType intervalType; - // TODO: schedule related fields here + @SerializedName("timezone") + @Param(description = "the time zone of the backup schedule") + private String timezone; public String getId() { return id; @@ -58,22 +61,6 @@ public void setId(String id) { this.id = id; } - public String getAccountId() { - return accountId; - } - - public void setAccountId(String accountId) { - this.accountId = accountId; - } - - public String getZoneId() { - return zoneId; - } - - public void setZoneId(String zoneId) { - this.zoneId = zoneId; - } - public String getVmName() { return vmName; } @@ -89,4 +76,28 @@ public String getVmId() { public void setVmId(String vmId) { this.vmId = vmId; } + + public String getSchedule() { + return schedule; + } + + public void setSchedule(String schedule) { + this.schedule = schedule; + } + + public DateUtil.IntervalType getIntervalType() { + return intervalType; + } + + public void setIntervalType(DateUtil.IntervalType intervalType) { + this.intervalType = intervalType; + } + + public String getTimezone() { + return timezone; + } + + public void setTimezone(String timezone) { + this.timezone = timezone; + } } 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 41bcb379305c..f723535563db 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -24,7 +24,6 @@ import org.apache.cloudstack.api.InternalIdentity; import com.cloud.storage.Volume; -import com.cloud.utils.DateUtil; import com.cloud.utils.StringUtils; public interface Backup extends InternalIdentity, Identity { @@ -142,12 +141,7 @@ public String toString() { Long getSize(); Long getProtectedSize(); String getExternalId(); - Boolean hasUserDefinedSchedule(); - DateUtil.IntervalType getScheduleType(); - String getSchedule(); - String getTimezone(); - Date getScheduledTimestamp(); - Long getAsyncJobId(); + Long getAccountId(); Long getZoneId(); Date getCreated(); 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 421ed17dd165..c4b3423d7d1c 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -21,7 +21,6 @@ import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; -import org.apache.cloudstack.api.command.user.backup.UpdateBackupScheduleCmd; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; @@ -81,36 +80,29 @@ BackupOffering importBackupOffering(final Long zoneId, final String offeringExte * @param offeringId * @return */ - Backup assignVMToBackupOffering(final Long vmId, final Long offeringId); + boolean assignVMToBackupOffering(final Long vmId, final Long offeringId); /** * Removes a VM from a backup offering * @param vmId - * @param offeringId + * @param forced * @return */ - boolean removeVMFromBackupOffering(final Long vmId, final Long offeringId, final boolean forced); + boolean removeVMFromBackupOffering(final Long vmId, final boolean forced); /** * Creates a VM backup schedule * @param cmd * @return */ - Backup createBackupSchedule(CreateBackupScheduleCmd cmd); - - /** - * Updates a VM backup schedule - * @param cmd - * @return - */ - Backup updateBackupSchedule(UpdateBackupScheduleCmd cmd); + BackupSchedule createBackupSchedule(CreateBackupScheduleCmd cmd); /** * Lists VM backup schedule for a VM * @param vmId * @return */ - Backup listBackupSchedule(Long vmId); + BackupSchedule listBackupSchedule(Long vmId); /** * Deletes VM backup schedule for a VM diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupOffering.java b/api/src/main/java/org/apache/cloudstack/backup/BackupOffering.java index 92f336db6298..1d4a4b5c90f1 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupOffering.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupOffering.java @@ -20,11 +20,10 @@ import org.apache.cloudstack.api.InternalIdentity; public interface BackupOffering extends InternalIdentity, Identity { - String getExternalId(); String getName(); String getDescription(); boolean isImported(); long getZoneId(); - + String getProvider(); } 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 f733cded36b3..a404384aee05 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -54,15 +54,14 @@ public interface BackupProvider { * @param policy * @return */ - Backup assignVMToBackupOffering(VirtualMachine vm, Backup backup, BackupOffering backupOffering); + boolean assignVMToBackupOffering(VirtualMachine vm, BackupOffering backupOffering); /** - * Removes a VM backup + * Removes a VM from a backup offering or policy * @param vm - * @param backup * @return */ - boolean removeVMFromBackupOffering(VirtualMachine vm, Backup backup); + boolean removeVMFromBackupOffering(VirtualMachine vm); /** * Where removal of diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java b/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java new file mode 100644 index 000000000000..aedc40500509 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java @@ -0,0 +1,34 @@ +// 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 java.util.Date; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import com.cloud.utils.DateUtil; + +public interface BackupSchedule extends InternalIdentity, Identity { + Long getVmId(); + DateUtil.IntervalType getScheduleType(); + String getSchedule(); + String getTimezone(); + Date getScheduledTimestamp(); + Long getAsyncJobId(); +} diff --git a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java index d673b1e929bb..6411f505c8b6 100644 --- a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java @@ -16,14 +16,11 @@ // under the License. package com.cloud.vm; -import com.cloud.hypervisor.Hypervisor.HypervisorType; -import com.cloud.utils.db.Encrypt; -import com.cloud.utils.db.GenericDao; -import com.cloud.utils.db.StateMachine; -import com.cloud.utils.fsm.FiniteStateObject; -import com.cloud.vm.VirtualMachine.State; -import org.apache.commons.codec.binary.Base64; -import org.apache.log4j.Logger; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Date; +import java.util.Map; +import java.util.UUID; import javax.persistence.Column; import javax.persistence.DiscriminatorColumn; @@ -39,11 +36,16 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Transient; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Date; -import java.util.Map; -import java.util.UUID; + +import org.apache.commons.codec.binary.Base64; +import org.apache.log4j.Logger; + +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.utils.db.Encrypt; +import com.cloud.utils.db.GenericDao; +import com.cloud.utils.db.StateMachine; +import com.cloud.utils.fsm.FiniteStateObject; +import com.cloud.vm.VirtualMachine.State; @Entity @Table(name = "vm_instance") @@ -145,6 +147,12 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject { List listByZoneAndState(Long zoneId, Backup.Status state); BackupResponse newBackupResponse(Backup backup); - BackupScheduleResponse newBackupScheduleResponse(Backup backup); BackupVO getBackupVO(Backup backup); } 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 ff4738f4ed54..ba937b635496 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 @@ -24,7 +24,6 @@ import javax.inject.Inject; import org.apache.cloudstack.api.response.BackupResponse; -import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupVO; @@ -157,34 +156,20 @@ public BackupResponse newBackupResponse(Backup backup) { VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); DataCenterVO zone = dataCenterDao.findById(backup.getZoneId()); - BackupResponse backupResponse = new BackupResponse(); - backupResponse.setZoneId(zone.getUuid()); - backupResponse.setId(backup.getUuid()); - backupResponse.setAccountId(account.getUuid()); - backupResponse.setExternalId(backup.getExternalId()); - backupResponse.setVmId(vm.getUuid()); - backupResponse.setVolumes(backup.getVolumes()); - backupResponse.setStatus(backup.getStatus()); - backupResponse.setSize(backup.getSize()); - backupResponse.setProtectedSize(backup.getProtectedSize()); - backupResponse.setCreatedDate(backup.getCreated()); - backupResponse.setObjectName("backup"); - return backupResponse; - } - - @Override - public BackupScheduleResponse newBackupScheduleResponse(Backup backup) { - AccountVO account = accountDao.findById(backup.getAccountId()); - VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); - DataCenterVO zone = dataCenterDao.findById(backup.getZoneId()); - - BackupScheduleResponse response = new BackupScheduleResponse(); - response.setZoneId(zone.getUuid()); - response.setId(backup.getUuid()); - response.setAccountId(account.getUuid()); + BackupResponse response = new BackupResponse(); + response.setId(vm.getUuid()); response.setVmId(vm.getUuid()); + response.setName(vm.getHostName()); response.setVmName(vm.getHostName()); - response.setObjectName("backupschedule"); + response.setZoneId(zone.getUuid()); + response.setAccountId(account.getUuid()); + response.setExternalId(backup.getExternalId()); + response.setVolumes(backup.getVolumes()); + response.setStatus(backup.getStatus()); + response.setSize(backup.getSize()); + response.setProtectedSize(backup.getProtectedSize()); + response.setCreatedDate(backup.getCreated()); + response.setObjectName("backup"); return response; } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java index 10bff4f3db53..315dd1314073 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java @@ -82,5 +82,6 @@ public BackupOffering findByName(String name, Long zoneId) { SearchCriteria sc = backupPoliciesSearch.create(); sc.setParameters("name", name); sc.setParameters("zone_id", zoneId); - return findOneBy(sc); } + return findOneBy(sc); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java new file mode 100644 index 000000000000..47c43fbc3ffc --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java @@ -0,0 +1,30 @@ +// 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.api.response.BackupScheduleResponse; +import org.apache.cloudstack.backup.BackupSchedule; +import org.apache.cloudstack.backup.BackupScheduleVO; + +import com.cloud.utils.db.GenericDao; + +public interface BackupScheduleDao extends GenericDao { + BackupSchedule findByVM(Long vmId); + + BackupScheduleResponse newBackupScheduleResponse(BackupSchedule schedule); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java new file mode 100644 index 000000000000..04f30bc9dd7f --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java @@ -0,0 +1,72 @@ +// 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 javax.annotation.PostConstruct; +import javax.inject.Inject; + +import org.apache.cloudstack.api.response.BackupScheduleResponse; +import org.apache.cloudstack.backup.BackupSchedule; +import org.apache.cloudstack.backup.BackupScheduleVO; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.VMInstanceDao; + +public class BackupScheduleDaoImpl extends GenericDaoBase implements BackupScheduleDao { + + @Inject + VMInstanceDao vmInstanceDao; + + private SearchBuilder backupScheduleSearch; + + public BackupScheduleDaoImpl() { + } + + @PostConstruct + protected void init() { + backupScheduleSearch = createSearchBuilder(); + backupScheduleSearch.and("vm_id", backupScheduleSearch.entity().getVmId(), SearchCriteria.Op.EQ); + backupScheduleSearch.and("async_job_id", backupScheduleSearch.entity().getAsyncJobId(), SearchCriteria.Op.EQ); + backupScheduleSearch.done(); + } + + @Override + public BackupSchedule findByVM(Long vmId) { + SearchCriteria sc = backupScheduleSearch.create(); + sc.setParameters("vm_id", vmId); + return findOneBy(sc); + } + + @Override + public BackupScheduleResponse newBackupScheduleResponse(BackupSchedule schedule) { + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(schedule.getVmId()); + + BackupScheduleResponse response = new BackupScheduleResponse(); + response.setId(vm.getUuid()); + response.setVmId(vm.getUuid()); + response.setVmName(vm.getHostName()); + response.setIntervalType(schedule.getScheduleType()); + response.setSchedule(schedule.getSchedule()); + response.setTimezone(schedule.getTimezone()); + response.setObjectName("backupschedule"); + return response; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java index cdb80febdcf1..441b09d9528f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java @@ -16,13 +16,11 @@ // under the License. package org.apache.cloudstack.engine.cloud.entity.api.db; -import com.cloud.hypervisor.Hypervisor.HypervisorType; -import com.cloud.utils.db.Encrypt; -import com.cloud.utils.db.GenericDao; -import com.cloud.utils.db.StateMachine; -import com.cloud.utils.fsm.FiniteStateObject; -import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VirtualMachine.State; +import java.security.SecureRandom; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.UUID; import javax.persistence.Column; import javax.persistence.DiscriminatorColumn; @@ -38,11 +36,14 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Transient; -import java.security.SecureRandom; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.UUID; + +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.utils.db.Encrypt; +import com.cloud.utils.db.GenericDao; +import com.cloud.utils.db.StateMachine; +import com.cloud.utils.fsm.FiniteStateObject; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.State; @Entity @Table(name = "vm_instance") @@ -137,6 +138,12 @@ public class VMEntityVO implements VirtualMachine, FiniteStateObject + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql index 1767cd85db90..db21b427545e 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql @@ -26,11 +26,12 @@ UPDATE `cloud`.`hypervisor_capabilities` SET `storage_motion_supported` = 1 WHER CREATE TABLE IF NOT EXISTS `cloud`.`backup_offering` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) NOT NULL, + `uuid` varchar(40) NOT NULL UNIQUE, `name` varchar(255) NOT NULL COMMENT 'backup offering name', `description` varchar(255) NOT NULL COMMENT 'backup offering description', - `external_id` varchar(80) NOT NULL COMMENT 'external ID on provider side', + `external_id` varchar(255) NOT NULL COMMENT 'external ID on provider side', `zone_id` bigint(20) unsigned NOT NULL COMMENT 'zone id', + `provider` varchar(255) NOT NULL COMMENT 'backup provider', `created` datetime DEFAULT NULL, `removed` datetime DEFAULT NULL, PRIMARY KEY (`id`), @@ -38,28 +39,32 @@ CREATE TABLE IF NOT EXISTS `cloud`.`backup_offering` ( CONSTRAINT `fk_backup_offering__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +ALTER TABLE `cloud`.`vm_instance` ADD COLUMN `backup_offering_id` bigint unsigned DEFAULT NULL COMMENT 'ID of backup offering'; +ALTER TABLE `cloud`.`vm_instance` ADD COLUMN `external_backup_id` varchar(255) DEFAULT NULL COMMENT 'ID of external backup job or container if any'; + CREATE TABLE IF NOT EXISTS `cloud`.`backups` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) NOT NULL, + `uuid` varchar(40) NOT NULL UNIQUE, `vm_id` bigint(20) unsigned NOT NULL, - `offering_id` bigint(20) unsigned NOT NULL, - `external_id` varchar(80) COMMENT 'backup ID on provider side', - `volumes` text, - `status` varchar(20) NOT NULL, + `external_id` varchar(255) NOT NULL COMMENT 'external ID', + `type` varchar(255) NOT NULL COMMENT 'backup type', `size` bigint(20) DEFAULT 0, `protected_size` bigint(20) DEFAULT 0, + `volumes` text + PRIMARY KEY (`id`), + CONSTRAINT `fk_backup__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud`.`backup_schedule` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `uuid` varchar(40) NOT NULL UNIQUE, + `vm_id` bigint(20) unsigned NOT NULL UNIQUE, `schedule_type` int(4) DEFAULT NULL COMMENT 'backup schedulet type e.g. hourly, daily, etc.', `schedule` varchar(100) DEFAULT NULL COMMENT 'schedule time of execution', `timezone` varchar(100) DEFAULT NULL COMMENT 'the timezone in which the schedule time is specified', `scheduled_timestamp` datetime DEFAULT NULL COMMENT 'Time at which the backup was scheduled for execution', `async_job_id` bigint(20) unsigned DEFAULT NULL COMMENT 'If this schedule is being executed, it is the id of the create aysnc_job. Before that it is null', - `account_id` bigint(20) unsigned NOT NULL, - `zone_id` bigint(20) unsigned NOT NULL, - `created` datetime DEFAULT NULL, - `removed` datetime DEFAULT NULL, PRIMARY KEY (`id`), - CONSTRAINT `fk_backup__account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE, - CONSTRAINT `fk_backup__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE, CONSTRAINT `fk_backup__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 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 566b15e9a98b..c232dd3c89b2 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 @@ -17,7 +17,6 @@ package org.apache.cloudstack.backup; import java.util.Arrays; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -51,8 +50,8 @@ public String getDescription() { @Override public List listBackupOfferings(Long zoneId) { s_logger.debug("Listing backup policies on Dummy B&R Plugin"); - BackupOffering policy1 = new BackupOfferingVO(1, "aaaa-aaaa", "Golden Policy", "Gold description"); - BackupOffering policy2 = new BackupOfferingVO(1, "bbbb-bbbb", "Silver Policy", "Silver description"); + BackupOffering policy1 = new BackupOfferingVO(1, "aaaa-aaaa", "dummy", "Golden Policy", "Gold description"); + BackupOffering policy2 = new BackupOfferingVO(1, "bbbb-bbbb", "dummy", "Silver Policy", "Silver description"); return Arrays.asList(policy1, policy2); } @@ -63,15 +62,9 @@ public boolean isBackupOffering(Long zoneId, String uuid) { } @Override - public Backup assignVMToBackupOffering(VirtualMachine vm, Backup backup, BackupOffering backupOffering) { + public boolean assignVMToBackupOffering(VirtualMachine vm, BackupOffering backupOffering) { s_logger.debug("Creating VM backup for VM " + vm.getInstanceName() + " from backup offering " + backupOffering.getName()); - - List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); - BackupVO dummyBackup = (BackupVO) backup; - dummyBackup.setStatus(Backup.Status.BackedUp); - dummyBackup.setCreated(new Date()); - backups.add(dummyBackup); - return dummyBackup; + return true; } @Override @@ -104,11 +97,11 @@ public Map getBackupMetrics(Long zoneId, List bac } @Override - public boolean removeVMFromBackupOffering(VirtualMachine vm, Backup vmBackup) { - s_logger.debug("Removing VM backup " + vmBackup.getUuid() + " for VM " + vm.getInstanceName() + " on the Dummy Backup Provider"); + public boolean removeVMFromBackupOffering(VirtualMachine vm) { + s_logger.debug("Removing VM ID " + vm.getUuid() + " from backup offering by the Dummy Backup Provider"); final List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); for (final Backup backup : backups) { - if (backup.getExternalId().equals(vmBackup.getExternalId())) { + if (backup.getExternalId().equals(vm.getBackupExternalId())) { return true; } } @@ -122,7 +115,7 @@ public boolean willDeleteBackupsOnOfferingRemoval() { @Override public boolean takeBackup(Backup backup) { - s_logger.debug("Starting backup " + backup.getUuid() + " on Dummy provider"); + s_logger.debug("Starting backup for VM ID " + backup.getVmId() + " on Dummy provider"); return true; } 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 78c401dd46f8..c880fff85347 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 @@ -21,7 +21,6 @@ import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,6 +42,7 @@ import com.cloud.utils.Pair; import com.cloud.utils.component.AdapterBase; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; public class VeeamBackupProvider extends AdapterBase implements BackupProvider, Configurable { @@ -137,10 +137,10 @@ private String getGuestBackupName(final String instanceName, final String uuid) } @Override - public Backup assignVMToBackupOffering(final VirtualMachine vm, final Backup backup, final BackupOffering backupOffering) { + public boolean assignVMToBackupOffering(final VirtualMachine vm, final BackupOffering backupOffering) { final VeeamClient client = getClient(vm.getDataCenterId()); final Job parentJob = client.listJob(backupOffering.getExternalId()); - final String clonedJobName = getGuestBackupName(vm.getInstanceName(), backup.getUuid()); + final String clonedJobName = getGuestBackupName(vm.getInstanceName(), vm.getUuid()); if (client.cloneVeeamJob(parentJob, clonedJobName)) { for (BackupOffering job : client.listJobs()) { if (job.getName().equals(clonedJobName)) { @@ -150,34 +150,30 @@ public Backup assignVMToBackupOffering(final VirtualMachine vm, final Backup bac } final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm); if (client.addVMToVeeamJob(job.getExternalId(), vm.getInstanceName(), vmwareDC.getVcenterHost())) { - BackupVO vmBackup = ((BackupVO) backup); - vmBackup.setStatus(Backup.Status.BackedUp); - vmBackup.setExternalId(job.getExternalId()); - vmBackup.setCreated(new Date()); - return vmBackup; + ((VMInstanceVO) vm).setBackupExternalId(job.getExternalId()); + return true; } } } } else { LOG.error("Failed to clone pre-defined Veeam job (backup offering) for backup offering ID: " + backupOffering.getExternalId()); } - ((BackupVO) backup).setStatus(Backup.Status.Error); - return backup; + return false; } @Override - public boolean removeVMFromBackupOffering(final VirtualMachine vm, final Backup backup) { + public boolean removeVMFromBackupOffering(final VirtualMachine vm) { final VeeamClient client = getClient(vm.getDataCenterId()); final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm); try { - if (!client.removeVMFromVeeamJob(backup.getExternalId(), vm.getInstanceName(), vmwareDC.getVcenterHost())) { - LOG.warn("Failed to remove VM from Veeam Job id: " + backup.getExternalId()); + if (!client.removeVMFromVeeamJob(vm.getBackupExternalId(), vm.getInstanceName(), vmwareDC.getVcenterHost())) { + LOG.warn("Failed to remove VM from Veeam Job id: " + vm.getBackupExternalId()); } } catch (CloudRuntimeException e) { LOG.debug("VM was removed from the job so could not remove again, trying to delete the veeam job now.", e); } - final String clonedJobName = getGuestBackupName(vm.getInstanceName(), backup.getUuid()); + final String clonedJobName = getGuestBackupName(vm.getInstanceName(), vm.getUuid()); if (!client.deleteJobAndBackup(clonedJobName)) { LOG.warn("Failed to remove Veeam job and backup for job: " + clonedJobName); throw new CloudRuntimeException("Failed to delete Veeam B&R job and backup, an operation may be in progress. Please try again after some time."); @@ -225,10 +221,10 @@ public Map getBackupMetrics(final Long zoneId, final List final Map metrics = new HashMap<>(); final Map backendMetrics = getClient(zoneId).getBackupMetrics(); for (final Backup backup : backupList) { - if (!backendMetrics.containsKey(backup.getUuid())) { + if (!backendMetrics.containsKey(backup.getVmId())) { continue; } - metrics.put(backup, backendMetrics.get(backup.getUuid())); + metrics.put(backup, backendMetrics.get(backup.getVmId())); } return metrics; } diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackupOffering.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackupOffering.java index 8ea62a99a1ac..831ade68fd2f 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackupOffering.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackupOffering.java @@ -54,6 +54,11 @@ public long getZoneId() { return -1; } + @Override + public String getProvider() { + return "veeam"; + } + @Override public String getUuid() { return uid; diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index f31110729ef2..2db4dc2ac7ac 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -40,6 +40,7 @@ import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.AsyncJobResponse; import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; @@ -62,14 +63,15 @@ import org.apache.cloudstack.api.response.TemplateResponse; import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.VpcOfferingResponse; import org.apache.cloudstack.api.response.ZoneResponse; -import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; +import org.apache.cloudstack.backup.dao.BackupScheduleDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; @@ -454,6 +456,7 @@ public class ApiDBUtils { static HostGpuGroupsDao s_hostGpuGroupsDao; static VGPUTypesDao s_vgpuTypesDao; static BackupDao s_backupDao; + static BackupScheduleDao s_backupScheduleDao; static BackupOfferingDao s_backupOfferingDao; @Inject @@ -697,6 +700,8 @@ public class ApiDBUtils { private BackupDao backupDao; @Inject private BackupOfferingDao backupOfferingDao; + @Inject + private BackupScheduleDao backupScheduleDao; @PostConstruct void init() { @@ -820,6 +825,7 @@ void init() { s_hostGpuGroupsDao = hostGpuGroupsDao; s_vgpuTypesDao = vgpuTypesDao; s_backupDao = backupDao; + s_backupScheduleDao = backupScheduleDao; s_backupOfferingDao = backupOfferingDao; } @@ -2053,8 +2059,8 @@ public static BackupResponse newBackupResponse(Backup backup) { return s_backupDao.newBackupResponse(backup); } - public static BackupScheduleResponse newBackupScheduleResponse(Backup backup) { - return s_backupDao.newBackupScheduleResponse(backup); + public static BackupScheduleResponse newBackupScheduleResponse(BackupSchedule schedule) { + return s_backupScheduleDao.newBackupScheduleResponse(schedule); } public static BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy) { diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 8205c57f58bb..00fa603cf475 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -145,6 +145,7 @@ import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.config.Configuration; @@ -4237,8 +4238,8 @@ public BackupResponse createBackupResponse(Backup backup) { } @Override - public BackupScheduleResponse createBackupScheduleResponse(Backup backup) { - return ApiDBUtils.newBackupScheduleResponse(backup); + public BackupScheduleResponse createBackupScheduleResponse(BackupSchedule schedule) { + return ApiDBUtils.newBackupScheduleResponse(schedule); } @Override diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index ddd84137ad9b..6bb2bcda37e0 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -1558,6 +1558,11 @@ public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId) { throw new InvalidParameterValueException("Unable to attach volume, please specify a VM that does not have VM snapshots"); } + // if target VM has backups + if (vm.getBackupOfferingId() != null) { + throw new InvalidParameterValueException("Unable to attach volume, please specify a VM that does not have any backups"); + } + // permission check _accountMgr.checkAccess(caller, null, true, volumeToAttach, vm); @@ -1798,6 +1803,10 @@ public Volume detachVolumeFromVM(DetachVolumeCmd cmmd) { throw new InvalidParameterValueException("Unable to detach volume, please specify a VM that does not have VM snapshots"); } + if (vm.getBackupOfferingId() != null) { + throw new InvalidParameterValueException("Unable to detach volume, cannot detach volume from a VM that has backups. First remove the VM from the backup offering."); + } + AsyncJobExecutionContext asyncExecutionContext = AsyncJobExecutionContext.getCurrentExecutionContext(); if (asyncExecutionContext != null) { AsyncJob job = asyncExecutionContext.getJob(); 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 61e04e4ed62f..df8b1c7fb343 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TimeZone; import javax.inject.Inject; import javax.naming.ConfigurationException; @@ -41,9 +42,9 @@ import org.apache.cloudstack.api.command.user.backup.RemoveVirtualMachineFromBackupOfferingCmd; import org.apache.cloudstack.api.command.user.backup.RestoreBackupCmd; import org.apache.cloudstack.api.command.user.backup.RestoreVolumeFromBackupAndAttachToVMCmd; -import org.apache.cloudstack.api.command.user.backup.UpdateBackupScheduleCmd; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; +import org.apache.cloudstack.backup.dao.BackupScheduleDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.managed.context.ManagedContextRunnable; @@ -59,6 +60,7 @@ import com.cloud.event.ActionEvent; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; @@ -74,6 +76,7 @@ import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountService; +import com.cloud.utils.DateUtil; import com.cloud.utils.Pair; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.Filter; @@ -89,6 +92,10 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { private static final Logger LOG = Logger.getLogger(BackupManagerImpl.class); + @Inject + private BackupDao backupDao; + @Inject + private BackupScheduleDao backupScheduleDao; @Inject private BackupOfferingDao backupOfferingDao; @Inject @@ -98,8 +105,6 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { @Inject private AccountManager accountManager; @Inject - private BackupDao backupDao; - @Inject private UsageBackupDao usageBackupDao; @Inject private VolumeDao volumeDao; @@ -150,7 +155,7 @@ public BackupOffering importBackupOffering(final Long zoneId, final String offer throw new CloudRuntimeException("Backup offering '" + offeringExternalId + "' does not exist on provider " + provider.getName() + " on zone " + zoneId); } - final BackupOfferingVO offering = new BackupOfferingVO(zoneId, offeringExternalId, offeringName, offeringDescription); + final BackupOfferingVO offering = new BackupOfferingVO(zoneId, offeringExternalId, provider.getName(), offeringName, offeringDescription); final BackupOfferingVO savedOffering = backupOfferingDao.persist(offering); if (savedOffering == null) { throw new CloudRuntimeException("Unable to create backup offering: " + offeringExternalId + ", name: " + offeringName); @@ -205,155 +210,125 @@ public boolean deleteBackupOffering(final Long offeringId) { @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, eventDescription = "assign VM to backup offering", async = true) - public Backup assignVMToBackupOffering(Long vmId, Long offeringId) { + public boolean assignVMToBackupOffering(Long vmId, Long offeringId) { final VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { throw new CloudRuntimeException("Did not find VM by provided ID"); } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + final BackupOfferingVO offering = backupOfferingDao.findById(offeringId); if (offering == null) { throw new CloudRuntimeException("Provided backup offering does not exist"); } - final BackupProvider backupProvider = getBackupProvider(vm.getDataCenterId()); + final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); if (backupProvider == null) { throw new CloudRuntimeException("Failed to get the backup provider for the zone, please contact the administrator"); } - accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); - - final Backup vmBackup = backupDao.findByVmId(vm.getId()); - if (vmBackup != null) { - throw new CloudRuntimeException("VM is already assigned to a backup offering, please remove the previous assignment"); - } - - BackupVO backup = new BackupVO(vmId, offeringId, Backup.Status.Allocated, vm.getAccountId(), vm.getDataCenterId()); - setBackupVolumes(backup, vm); - backup = backupDao.persist(backup); - if (backup == null) { - throw new CloudRuntimeException("Failed to persist VM backup object in database"); - } - + boolean result = false; try { - backup = (BackupVO) backupProvider.assignVMToBackupOffering(vm, backup, offering); + vm.setBackupOfferingId(offering.getId()); + result = backupProvider.assignVMToBackupOffering(vm, offering); } catch (Exception e) { LOG.error("Exception caught while assigning VM to backup offering by the backup provider", e); - backup.setStatus(Backup.Status.Error); - backupDao.update(backup.getId(), backup); throw e; } - if (backup == null) { - throw new CloudRuntimeException("Backup provider failed to assign VM to the backup offering, for VM: " + vm.getUuid()); - } - - if (backupDao.update(backup.getId(), backup)) { - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, vm.getAccountId(), vm.getDataCenterId(), backup.getId(), - vm.getUuid(), backup.getOfferingId(), backup.getVmId(), null, - Backup.class.getSimpleName(), backup.getUuid()); - } else { - throw new CloudRuntimeException("Failed to update VM backup in the database, please try again"); + if (result && vmInstanceDao.update(vm.getId(), vm)) { + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), + vm.getUuid(), vm.getBackupOfferingId(), vm.getId(), null, + Backup.class.getSimpleName(), vm.getUuid()); + return true; } - return backup; + throw new CloudRuntimeException("Failed to update VM backup in the database, please try again"); } @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, eventDescription = "remove VM from backup offering", async = true) - public boolean removeVMFromBackupOffering(final Long vmId, final Long offeringId, final boolean forced) { + public boolean removeVMFromBackupOffering(final Long vmId, final boolean forced) { final VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { throw new CloudRuntimeException("Did not find VM by provided ID"); } - final Backup backup = backupDao.findByVmId(vmId); - if (backup == null) { - throw new CloudRuntimeException("No backups or backup offering configuration found for the VM."); - } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); - if (!backup.getOfferingId().equals(offeringId)) { - throw new CloudRuntimeException("Current VM offering assignment does not match provided backup offering ID"); + final BackupOfferingVO offering = backupOfferingDao.findById(vm.getBackupOfferingId()); + if (offering == null) { + throw new CloudRuntimeException("No previously configured backup offering found for the VM"); } - final BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); if (backupProvider == null) { throw new CloudRuntimeException("Failed to get the backup provider for the zone, please contact the administrator"); } - accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); - if (!forced && backupProvider.willDeleteBackupsOnOfferingRemoval()) { throw new CloudRuntimeException("The backend provider will only allow removal of VM from the offering if forced:true is provided " + - "that will also delete the backups are removed."); + "that will also delete the backups."); } - boolean result = backupProvider.removeVMFromBackupOffering(vm, backup); - if (result) { - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), backup.getId(), - vm.getUuid(), backup.getOfferingId(), backup.getVmId(), null, Backup.class.getSimpleName(), backup.getUuid()); - ((BackupVO)backup).setStatus(Backup.Status.Expunged); - ((BackupVO)backup).setRemoved(new Date()); - return backupDao.update(backup.getId(), (BackupVO)backup); + vm.setBackupOfferingId(null); + vm.setBackupExternalId(null); + boolean result = backupProvider.removeVMFromBackupOffering(vm); + if (result && vmInstanceDao.update(vm.getId(), vm)) { + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), + vm.getUuid(), vm.getBackupOfferingId(), vm.getId(), null, + Backup.class.getSimpleName(), vm.getUuid()); + return true; } - return false; } @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_CREATE, eventDescription = "creating VM backup schedule", async = true) - public Backup createBackupSchedule(CreateBackupScheduleCmd cmd) { - final VMInstanceVO vm = vmInstanceDao.findById(cmd.getVmId()); - if (vm == null) { - throw new CloudRuntimeException("Did not find VM by provided ID"); - } + public BackupSchedule createBackupSchedule(CreateBackupScheduleCmd cmd) { + final Long vmId = cmd.getVmId(); + final DateUtil.IntervalType intervalType = cmd.getIntervalType(); + final String scheduleString = cmd.getSchedule(); + final TimeZone timeZone = TimeZone.getTimeZone(cmd.getTimezone()); - accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); - - final Backup backup = backupDao.findByVmId(vm.getId()); - if (backup == null) { - throw new CloudRuntimeException("VM backup is not configured, please assign to an offering and/or define a custom schedule"); + if (intervalType == null) { + throw new CloudRuntimeException("Invalid interval type provided"); } - //TODO: handle create logic - - return backup; - } - - @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_UPDATE, eventDescription = "updating VM backup schedule", async = true) - public Backup updateBackupSchedule(UpdateBackupScheduleCmd cmd) { - final VMInstanceVO vm = vmInstanceDao.findById(cmd.getVmId()); + final VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { throw new CloudRuntimeException("Did not find VM by provided ID"); } - accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); - final Backup backup = backupDao.findByVmId(vm.getId()); - if (backup == null) { - throw new CloudRuntimeException("VM backup is not configured, please assign to an offering and/or define a custom schedule"); + final BackupSchedule oldSchedule = backupScheduleDao.findByVM(vmId); + if (oldSchedule != null) { + throw new CloudRuntimeException("VM already has a backup schedule"); + } + + String timezoneId = timeZone.getID(); + if (!timezoneId.equals(cmd.getTimezone())) { + LOG.warn("Using timezone: " + timezoneId + " for running this snapshot policy as an equivalent of " + cmd.getTimezone()); } - //TODO: handle update logic + try { + DateUtil.getNextRunTime(intervalType, cmd.getSchedule(), timezoneId, null); + } catch (Exception e) { + throw new InvalidParameterValueException("Invalid schedule: " + cmd.getSchedule() + " for interval type: " + cmd.getIntervalType()); + } - return backup; + return backupScheduleDao.persist(new BackupScheduleVO(vmId, intervalType, scheduleString, timezoneId)); } @Override - public Backup listBackupSchedule(final Long vmId) { + public BackupSchedule listBackupSchedule(final Long vmId) { final VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { throw new CloudRuntimeException("Did not find VM by provided ID"); } - accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); - final Backup backup = backupDao.findByVmId(vm.getId()); - if (backup == null) { - throw new CloudRuntimeException("VM backup is not configured, please assign to an offering and/or define a custom schedule"); - } - - return backup; + return backupScheduleDao.findByVM(vmId); } @Override @@ -366,19 +341,11 @@ public boolean deleteBackupSchedule(final Long vmId) { accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); - final BackupVO backup = (BackupVO) backupDao.findByVmId(vm.getId()); - if (backup == null) { - throw new CloudRuntimeException("VM backup is not configured, please assign to an offering and/or define a custom schedule."); - } - - if (!backup.hasUserDefinedSchedule()) { + final BackupSchedule schedule = backupScheduleDao.findByVM(vmId); + if (schedule == null) { throw new CloudRuntimeException("VM has no backup schedule defined, no need to delete anything."); } - - backup.setSchedule(null); - backup.setScheduleType(null); - backup.setTimezone(null); - return backupDao.update(backup.getId(), backup); + return backupScheduleDao.remove(schedule.getId()); } @Override @@ -396,7 +363,12 @@ public Backup createBackup(final Long vmId) { throw new CloudRuntimeException("VM backup is not configured, please assign to an offering and/or define a custom schedule"); } - final BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + final BackupOfferingVO offering = backupOfferingDao.findById(backup.getOfferingId()); + if (offering == null) { + throw new CloudRuntimeException("Provided backup offering does not exist"); + } + + final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); if (backupProvider != null && backupProvider.takeBackup(backup)) { return backup; } @@ -434,7 +406,7 @@ public List listBackupRestorePoints(final Long backupId) { } accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); - return backupProvider.listBackupRestorePoints(backup.getUuid(), vm); + return backupProvider.listBackupRestorePoints(vm.getUuid(), vm); } private void setBackupVolumes(BackupVO backup, VMInstanceVO vm) { @@ -462,7 +434,7 @@ public boolean importRestoredVM(long zoneId, long domainId, long accountId, long throw new CloudRuntimeException("Error during vm backup restoration and import: " + e.getMessage()); } if (vm == null) { - LOG.error("Failed to import restored VM " + vmInternalName + " with hypervisor type " + hypervisorType + " for backup ID " + backup.getUuid()); + LOG.error("Failed to import restored VM " + vmInternalName + " with hypervisor type " + hypervisorType + " using backup of VM ID " + backup.getVmId()); } return vm != null; } @@ -529,7 +501,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, LOG.debug("Asking provider to restore volume " + backedUpVolumeUuid + " from backup " + backupId + " and restore point " + restorePointId + " and attach it to VM: " + vm.getUuid()); BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); - Pair result = backupProvider.restoreBackedUpVolume(backup.getZoneId(), backup.getUuid(), + Pair result = backupProvider.restoreBackedUpVolume(backup.getZoneId(), vmFromBackup.getUuid(), restorePointId, backedUpVolumeUuid, hostIp, datastoreUuid); if (!result.first()) { throw new CloudRuntimeException("Error restoring volume " + backedUpVolumeUuid); @@ -637,11 +609,15 @@ public List listBackupProviders() { @Override public BackupProvider getBackupProvider(final Long zoneId) { final String name = BackupProviderPlugin.valueIn(zoneId); + return getBackupProvider(name); + } + + public BackupProvider getBackupProvider(final String name) { if (Strings.isNullOrEmpty(name)) { - throw new CloudRuntimeException("Invalid backup provider name configured in zone id: " + zoneId); + throw new CloudRuntimeException("Invalid backup provider name provided"); } if (!backupProvidersMap.containsKey(name)) { - throw new CloudRuntimeException("Failed to find backup provider for zone id:" + zoneId); + throw new CloudRuntimeException("Failed to find backup provider by the name: " + name); } return backupProvidersMap.get(name); } @@ -667,7 +643,6 @@ public List> getCommands() { // Schedule cmdList.add(CreateBackupScheduleCmd.class); cmdList.add(ListBackupScheduleCmd.class); - cmdList.add(UpdateBackupScheduleCmd.class); cmdList.add(DeleteBackupScheduleCmd.class); return cmdList; } From 57d6c1f654c24ba7d216ef4e470256976ae47c8e Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 5 Dec 2019 16:20:06 +0530 Subject: [PATCH 18/81] more refactorings Signed-off-by: Rohit Yadav --- .../command/user/backup/CreateBackupCmd.java | 11 +- .../command/user/backup/ListBackupsCmd.java | 4 - .../org/apache/cloudstack/backup/Backup.java | 20 +-- .../cloudstack/backup/BackupManager.java | 9 +- .../cloudstack/backup/BackupProvider.java | 5 +- .../cloud/usage/dao/UsageBackupDaoImpl.java | 6 +- .../apache/cloudstack/backup/BackupVO.java | 133 ++---------------- .../cloudstack/backup/dao/BackupDaoImpl.java | 12 +- .../backup/DummyBackupProvider.java | 8 +- .../backup/VeeamBackupProvider.java | 8 +- .../cloudstack/backup/veeam/VeeamBackup.java | 77 +--------- .../java/com/cloud/api/ApiResponseHelper.java | 2 +- .../cloudstack/backup/BackupManagerImpl.java | 86 +++++------ .../storage/VolumeApiServiceImplTest.java | 3 +- 14 files changed, 83 insertions(+), 301 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java index 82c98fb551b4..59f36702b38f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java @@ -27,9 +27,8 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -43,7 +42,7 @@ @APICommand(name = CreateBackupCmd.APINAME, description = "Create VM backup", - responseObject = BackupResponse.class, since = "4.14.0", + responseObject = SuccessResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class CreateBackupCmd extends BaseAsyncCmd { public static final String APINAME = "createBackup"; @@ -77,9 +76,9 @@ public Long getVmId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - Backup backup = backupManager.createBackup(getVmId()); - if (backup != null) { - BackupResponse response = _responseGenerator.createBackupResponse(backup); + boolean result = backupManager.createBackup(getVmId()); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); setResponseObject(response); } else { 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 f04f5672bbd8..4337aa8d7a3a 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 @@ -90,10 +90,6 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE try{ List restorePoints = new ArrayList<>(); List backups = backupManager.listBackups(getId(), getVmId()); - if (backups.size() > 0 && getVmId() != null) { - Backup backup = backups.get(0); - restorePoints = backupManager.listBackupRestorePoints(backup.getId()); - } setupResponseBackupList(backups, restorePoints); } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); 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 f723535563db..0a58e4108da8 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -17,7 +17,6 @@ package org.apache.cloudstack.backup; -import java.util.Date; import java.util.List; import org.apache.cloudstack.api.Identity; @@ -133,17 +132,18 @@ public String toString() { } } + String getExternalId(); Long getVmId(); - Long getOfferingId(); - List getBackedUpVolumes(); - String getVolumes(); - Status getStatus(); Long getSize(); Long getProtectedSize(); - String getExternalId(); - Long getAccountId(); - Long getZoneId(); - Date getCreated(); - Date getRemoved(); + default List getBackedUpVolumes() { + return null; + } + default String getVolumes() { + return null; + } + default Status getStatus() { + return null; + } } 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 c4b3423d7d1c..945ba9c09058 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -116,20 +116,13 @@ BackupOffering importBackupOffering(final Long zoneId, final String offeringExte * @param vmId Virtual Machine ID * @return returns operation success */ - Backup createBackup(final Long vmId); + boolean createBackup(final Long vmId); /** * List existing backups for a VM */ List listBackups(final Long id, final Long vmId); - /** - * Lists restore points for a VM backup - * @param backupId - * @return - */ - List listBackupRestorePoints(final Long backupId); - /** * Restore a full VM from backup */ 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 a404384aee05..3d4de52301dd 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -75,7 +75,7 @@ public interface BackupProvider { * @param backup * @return */ - boolean takeBackup(Backup backup); + boolean takeBackup(VirtualMachine vm); /** * Delete an existing backup @@ -92,8 +92,7 @@ public interface BackupProvider { /** * Restore a volume from a backup */ - Pair restoreBackedUpVolume(long zoneId, String backupUuid, String restorePointId, String volumeUuid, - String hostIp, String dataStoreUuid); + Pair restoreBackedUpVolume(long zoneId, String restorePointId, String volumeUuid, String hostIp, String dataStoreUuid); /** * List VM Backups diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java index 6453f4ba20d9..12d591f08f3d 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java @@ -42,7 +42,7 @@ public class UsageBackupDaoImpl extends GenericDaoBase implements UsageBackupDao { public static final Logger LOGGER = Logger.getLogger(UsageBackupDaoImpl.class); protected static final String GET_USAGE_RECORDS_BY_ACCOUNT = "SELECT id, zone_id, account_id, domain_id, backup_id, vm_id, size, protected_size, created, removed FROM cloud_usage.usage_backup WHERE " + - " account_id = ? AND ((removed IS NULL AND created <= ?) OR (created BETWEEN ? AND ?) OR (removed BETWEEN ? AND ?) " + + " vm_id = ? AND ((removed IS NULL AND created <= ?) OR (created BETWEEN ? AND ?) OR (removed BETWEEN ? AND ?) " + " OR ((created <= ?) AND (removed >= ?)))"; @Override @@ -51,9 +51,7 @@ public void updateMetrics(final Backup backup) { @Override public Boolean doInTransaction(final TransactionStatus status) { final QueryBuilder qb = QueryBuilder.create(UsageBackupVO.class); - qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, backup.getAccountId()); - qb.and(qb.entity().getZoneId(), SearchCriteria.Op.EQ, backup.getZoneId()); - qb.and(qb.entity().getBackupId(), SearchCriteria.Op.EQ, backup.getId()); + qb.and(qb.entity().getVmId(), SearchCriteria.Op.EQ, backup.getVmId()); final UsageBackupVO entry = findOneBy(qb.create()); if (entry == null) { return false; 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 3b47697f3d7e..8f05224f6d9e 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 @@ -17,9 +17,7 @@ package org.apache.cloudstack.backup; -import java.util.Arrays; -import java.util.Date; -import java.util.List; +import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; @@ -27,11 +25,6 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; - -import com.cloud.utils.DateUtil; -import com.google.gson.Gson; @Entity @Table(name = "backups") @@ -44,17 +37,11 @@ public class BackupVO implements Backup { @Column(name = "uuid") private String uuid; - @Column(name = "vm_id") - private Long vmId; - - @Column(name = "offering_id") - private long offeringId; - @Column(name = "external_id") private String externalId; - @Column(name = "volumes", length = 65535) - private String volumes; + @Column(name = "vm_id") + private Long vmId; @Column(name = "size") private Long size; @@ -62,34 +49,14 @@ public class BackupVO implements Backup { @Column(name = "protected_size") private Long protectedSize; - @Column(name = "status") - private Status status; - - @Column(name = "account_id") - private long accountId; - - @Column(name = "zone_id") - private Long zoneId; - - @Column(name = "created") - @Temporal(value = TemporalType.TIMESTAMP) - private Date created; - - @Column(name = "removed") - @Temporal(value = TemporalType.TIMESTAMP) - private Date removed; - public BackupVO() { - this.created = new Date(); + this.uuid = UUID.randomUUID().toString(); } - public BackupVO(final Long vmId, final long offeringId, final Status status, final long accountId, final Long zoneId) { + public BackupVO(final Long vmId, final String externalId) { + this.uuid = UUID.randomUUID().toString(); this.vmId = vmId; - this.offeringId = offeringId; - this.status = status; - this.accountId = accountId; - this.zoneId = zoneId; - this.created = new Date(); + this.externalId = externalId; } @Override @@ -102,15 +69,6 @@ public String getUuid() { return uuid; } - @Override - public Long getZoneId() { - return zoneId; - } - - public void setZoneId(Long zoneId) { - this.zoneId = zoneId; - } - @Override public String getExternalId() { return externalId; @@ -120,93 +78,30 @@ public void setExternalId(String externalId) { this.externalId = externalId; } - @Override - public Long getAccountId() { - return accountId; - } - @Override public Long getVmId() { return vmId; } - @Override - public Status getStatus() { - return status; - } - - public Long getSize() { - return size; - } - - public Long getProtectedSize() { - return protectedSize; - } - - @Override - public Date getCreated() { - return created; - } - - public void setId(long id) { - this.id = id; - } - - public void setAccountId(long accountId) { - this.accountId = accountId; - } - public void setVmId(Long vmId) { this.vmId = vmId; } - public void setStatus(Status status) { - this.status = status; + @Override + public Long getSize() { + return size; } public void setSize(Long size) { this.size = size; } - public void setProtectedSize(Long protectedSize) { - this.protectedSize = protectedSize; - } - - public void setCreated(Date start) { - this.created = start; - } - - @Override - public List getBackedUpVolumes() { - return Arrays.asList(new Gson().fromJson(this.volumes, VolumeInfo[].class)); - } - - public void setBackedUpVolumes(List volumes) { - this.volumes = new Gson().toJson(volumes); - } - - public Date getRemoved() { - return removed; - } - - public void setRemoved(Date removed) { - this.removed = removed; - } - - public String getVolumes() { - return volumes; - } - - protected void setVolumes(String volumes) { - this.volumes = volumes; - } - @Override - public Long getOfferingId() { - return offeringId; + public Long getProtectedSize() { + return protectedSize; } - public void setOfferingId(long offeringId) { - this.offeringId = offeringId; + public void setProtectedSize(Long protectedSize) { + this.protectedSize = protectedSize; } } 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 ba937b635496..a68ad433eff0 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 @@ -61,10 +61,7 @@ public BackupDaoImpl() { protected void init() { backupSearch = createSearchBuilder(); backupSearch.and("vm_id", backupSearch.entity().getVmId(), SearchCriteria.Op.EQ); - backupSearch.and("account_id", backupSearch.entity().getAccountId(), SearchCriteria.Op.EQ); - backupSearch.and("zone_id", backupSearch.entity().getZoneId(), SearchCriteria.Op.EQ); backupSearch.and("external_id", backupSearch.entity().getExternalId(), SearchCriteria.Op.EQ); - backupSearch.and("offering_id", backupSearch.entity().getOfferingId(), SearchCriteria.Op.EQ); backupSearch.and("status", backupSearch.entity().getStatus(), SearchCriteria.Op.EQ); backupSearch.done(); } @@ -114,12 +111,8 @@ private Backup findByExternalId(Long zoneId, String externalId) { public BackupVO getBackupVO(Backup backup) { BackupVO backupVO = new BackupVO(); - backupVO.setZoneId(backup.getZoneId()); - backupVO.setAccountId(backup.getAccountId()); backupVO.setExternalId(backup.getExternalId()); backupVO.setVmId(backup.getVmId()); - backupVO.setStatus(backup.getStatus()); - backupVO.setCreated(backup.getCreated()); return backupVO; } @@ -152,9 +145,9 @@ public List listByZoneAndState(Long zoneId, Backup.Status state) { @Override public BackupResponse newBackupResponse(Backup backup) { - AccountVO account = accountDao.findById(backup.getAccountId()); VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); - DataCenterVO zone = dataCenterDao.findById(backup.getZoneId()); + AccountVO account = accountDao.findById(vm.getAccountId()); + DataCenterVO zone = dataCenterDao.findById(vm.getDataCenterId()); BackupResponse response = new BackupResponse(); response.setId(vm.getUuid()); @@ -168,7 +161,6 @@ public BackupResponse newBackupResponse(Backup backup) { response.setStatus(backup.getStatus()); response.setSize(backup.getSize()); response.setProtectedSize(backup.getProtectedSize()); - response.setCreatedDate(backup.getCreated()); response.setObjectName("backup"); return response; } 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 c232dd3c89b2..0985ddb06ece 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 @@ -74,9 +74,9 @@ public boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String } @Override - public Pair restoreBackedUpVolume(long zoneId, String backupUuid, String restorePointId, String volumeUuid, + public Pair restoreBackedUpVolume(long zoneId, String restorePointId, String volumeUuid, String hostIp, String dataStoreUuid) { - s_logger.debug("Restoring volume " + volumeUuid + "from backup " + backupUuid + " on the Dummy Backup Provider"); + s_logger.debug("Restoring volume " + volumeUuid + "from backup " + restorePointId + " on the Dummy Backup Provider"); return new Pair<>(true, null); } @@ -114,8 +114,8 @@ public boolean willDeleteBackupsOnOfferingRemoval() { } @Override - public boolean takeBackup(Backup backup) { - s_logger.debug("Starting backup for VM ID " + backup.getVmId() + " on Dummy provider"); + public boolean takeBackup(VirtualMachine vm) { + s_logger.debug("Starting backup for VM ID " + vm.getUuid() + " on Dummy provider"); return true; } 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 c880fff85347..87fe7ae193c8 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 @@ -187,9 +187,9 @@ public boolean willDeleteBackupsOnOfferingRemoval() { } @Override - public boolean takeBackup(final Backup backup) { - final VeeamClient client = getClient(backup.getZoneId()); - return client.startBackupJob(backup.getExternalId()); + public boolean takeBackup(final VirtualMachine vm) { + final VeeamClient client = getClient(vm.getDataCenterId()); + return client.startBackupJob(vm.getBackupExternalId()); } @Override @@ -204,7 +204,7 @@ public boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String } @Override - public Pair restoreBackedUpVolume(long zoneId, String backupUuid, String restorePointId, + public Pair restoreBackedUpVolume(long zoneId, String restorePointId, String volumeUuid, String hostIp, String dataStoreUuid) { return getClient(zoneId).restoreVMToDifferentLocation(restorePointId, hostIp, dataStoreUuid); diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java index ee4c764ac519..6098ba2507af 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java @@ -17,13 +17,8 @@ package org.apache.cloudstack.backup.veeam; -import java.util.Date; -import java.util.List; - import org.apache.cloudstack.backup.Backup; -import com.cloud.utils.DateUtil; - public class VeeamBackup implements Backup { private String name; @@ -34,49 +29,9 @@ public VeeamBackup(String name, String uid) { this.uid = uid; } - @Override - public Long getZoneId() { - return null; - } - - @Override - public Long getAccountId() { - return null; - } - @Override public String getExternalId() { - return null; - } - - @Override - public Boolean hasUserDefinedSchedule() { - return null; - } - - @Override - public DateUtil.IntervalType getScheduleType() { - return null; - } - - @Override - public String getSchedule() { - return null; - } - - @Override - public String getTimezone() { - return null; - } - - @Override - public Date getScheduledTimestamp() { - return null; - } - - @Override - public Long getAsyncJobId() { - return null; + return uid; } @Override @@ -84,26 +39,6 @@ public Long getVmId() { return null; } - @Override - public Long getOfferingId() { - return null; - } - - @Override - public List getBackedUpVolumes() { - return null; - } - - @Override - public String getVolumes() { - return null; - } - - @Override - public Status getStatus() { - return null; - } - @Override public Long getSize() { return null; @@ -114,16 +49,6 @@ public Long getProtectedSize() { return null; } - @Override - public Date getCreated() { - return null; - } - - @Override - public Date getRemoved() { - return null; - } - @Override public String getUuid() { return uid; diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 00fa603cf475..509a5da96410 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -3670,7 +3670,7 @@ public UsageRecordResponse createUsageResponse(Usage usageRecord, Map listBackups(final Long id, final Long vmId) { return backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); } - @Override - public List listBackupRestorePoints(final Long backupId) { - BackupVO backup = backupDao.findById(backupId); - if (backup == null) { - throw new CloudRuntimeException("Could not find backup " + backupId); - } - VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); - if (vm == null) { - throw new CloudRuntimeException("Could not find VM: " + backup.getVmId()); - } - accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); - BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); - return backupProvider.listBackupRestorePoints(vm.getUuid(), vm); - } - - private void setBackupVolumes(BackupVO backup, VMInstanceVO vm) { - List vmVolumes = volumeDao.findByInstance(vm.getId()); - List volInfo = createVolumeInfoFromVolumes(vmVolumes); - backup.setBackedUpVolumes(volInfo); - } - private List createVolumeInfoFromVolumes(List vmVolumes) { List list = new ArrayList<>(); for (VolumeVO vol : vmVolumes) { @@ -442,21 +415,27 @@ public boolean importRestoredVM(long zoneId, long domainId, long accountId, long @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true) public boolean restoreBackup(final Long backupId, final String restorePointId) { - BackupVO backup = backupDao.findById(backupId); + final BackupVO backup = backupDao.findById(backupId); if (backup == null) { throw new CloudRuntimeException("Backup " + backupId + " does not exist"); } - Long vmId = backup.getVmId(); - BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); - VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + + final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); if (vm == null) { - throw new CloudRuntimeException("VM " + vmId + " couldn't be found on existing or removed VMs"); + throw new CloudRuntimeException("VM " + backup.getVmId() + " couldn't be found on existing or removed VMs"); } accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + if (vm.getRemoved() == null && !vm.getState().equals(VirtualMachine.State.Stopped) && !vm.getState().equals(VirtualMachine.State.Destroyed)) { throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup"); } + + final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(vm.getBackupOfferingId()); + if (offering == null) { + throw new CloudRuntimeException("Failed to find VM backup offering"); + } + final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); if (!backupProvider.restoreVMFromBackup(vm, backup.getExternalId(), restorePointId)) { throw new CloudRuntimeException("Error restoring VM " + vm.getId() + " from backup " + backup.getId()); } @@ -498,15 +477,21 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, String hostIp = restoreInfo.first(); String datastoreUuid = restoreInfo.second(); + LOG.debug("Asking provider to restore volume " + backedUpVolumeUuid + " from backup " + backupId + " and restore point " + restorePointId + " and attach it to VM: " + vm.getUuid()); - BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); - Pair result = backupProvider.restoreBackedUpVolume(backup.getZoneId(), vmFromBackup.getUuid(), - restorePointId, backedUpVolumeUuid, hostIp, datastoreUuid); + + final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(vm.getBackupOfferingId()); + if (offering == null) { + throw new CloudRuntimeException("Failed to find VM backup offering"); + } + + BackupProvider backupProvider = getBackupProvider(offering.getProvider()); + Pair result = backupProvider.restoreBackedUpVolume(vm.getDataCenterId(), restorePointId, backedUpVolumeUuid, hostIp, datastoreUuid); if (!result.first()) { throw new CloudRuntimeException("Error restoring volume " + backedUpVolumeUuid); } - if (!attachVolumeToVM(backup.getZoneId(), result.second(), backup.getBackedUpVolumes(), + if (!attachVolumeToVM(vmFromBackup.getDataCenterId(), result.second(), backup.getBackedUpVolumes(), backedUpVolumeUuid, vm, datastoreUuid, backup)) { throw new CloudRuntimeException("Error attaching volume " + backedUpVolumeUuid + " to VM " + vm.getUuid()); } @@ -526,18 +511,19 @@ public boolean deleteBackup(final Long backupId) { throw new CloudRuntimeException("VM " + vmId + " does not exist"); } accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); - final BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(vm.getBackupOfferingId()); + if (offering == null) { + throw new CloudRuntimeException("VM backup offering ID " + vm.getBackupOfferingId() + " does not exist"); + } + final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); boolean result = backupProvider.deleteBackup(backup); if (result) { - backup.setStatus(Backup.Status.Expunged); if (backupDao.update(backup.getId(), backup)) { backupDao.remove(backup.getId()); return true; } } - // Let GC task handle removal - backup.setStatus(Backup.Status.Removed); - backup.setRemoved(new Date()); + // Let GC task handle removal backup.setRemoved(new Date()); return backupDao.update(backup.getId(), backup); } @@ -705,13 +691,11 @@ protected void runInContext() { continue; } - // GC and expunge removed backups - for (final Backup backup : backupDao.listByZoneAndState(dataCenter.getId(), Backup.Status.Removed)) { - backupManager.deleteBackup(backup.getId()); - } - // TODO: Check and schedule backups per user-defined backup schedule + // TODO: sync backups backupProvider.listBackupRestorePoints(vm.getUuid(), vm); + + // Sync backup size usages final List backups = backupDao.listByZoneAndState(dataCenter.getId(), null); if (backups.isEmpty()) { diff --git a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java index da346536d5a3..7ec6fbe97985 100644 --- a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java +++ b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java @@ -33,7 +33,6 @@ import java.util.UUID; import java.util.concurrent.ExecutionException; -import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd; @@ -77,6 +76,7 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao; import com.cloud.org.Grouping; import com.cloud.serializer.GsonHelper; import com.cloud.server.TaggedResourceService; @@ -504,6 +504,7 @@ public void testResourceLimitCheckForUploadedVolume() throws NoSuchFieldExceptio when(vm.getType()).thenReturn(VirtualMachine.Type.User); when(vm.getState()).thenReturn(State.Running); when(vm.getDataCenterId()).thenReturn(34L); + when(vm.getBackupOfferingId()).thenReturn(null); when(volumeDaoMock.findByInstanceAndType(anyLong(), any(Volume.Type.class))).thenReturn(new ArrayList<>(10)); when(volumeDataFactoryMock.getVolume(9L)).thenReturn(volumeToAttach); when(volumeToAttach.getState()).thenReturn(Volume.State.Uploaded); From ad5d882ccf17843cf5c48a1799f9d2118b04f1a2 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 5 Dec 2019 17:52:28 +0530 Subject: [PATCH 19/81] more refactorings Signed-off-by: Rohit Yadav --- .../main/java/com/cloud/event/EventTypes.java | 1 - .../java/com/cloud/vm/VirtualMachine.java | 4 +++ .../org/apache/cloudstack/backup/Backup.java | 12 -------- .../main/java/com/cloud/vm/VMInstanceVO.java | 28 +++++++++++++++---- .../cloudstack/backup/dao/BackupDaoImpl.java | 5 ++-- .../cloud/entity/api/db/VMEntityVO.java | 24 ++++++++++++---- .../META-INF/db/schema-41300to41400.sql | 3 +- .../com/cloud/hypervisor/guru/VMwareGuru.java | 23 +++++++++++---- .../cloudstack/backup/BackupManagerImpl.java | 19 +++++++------ 9 files changed, 76 insertions(+), 43 deletions(-) diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index e2dcdd56cc06..c8905654c880 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -482,7 +482,6 @@ public class EventTypes { 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_CREATE = "BACKUP.SCHEDULE.CREATE"; - public static final String EVENT_VM_BACKUP_SCHEDULE_UPDATE = "BACKUP.SCHEDULE.UPDATE"; public static final String EVENT_VM_BACKUP_SCHEDULE_DELETE = "BACKUP.SCHEDULE.DELETE"; // external network device events diff --git a/api/src/main/java/com/cloud/vm/VirtualMachine.java b/api/src/main/java/com/cloud/vm/VirtualMachine.java index 4bd12a922995..ee4aed2466d1 100644 --- a/api/src/main/java/com/cloud/vm/VirtualMachine.java +++ b/api/src/main/java/com/cloud/vm/VirtualMachine.java @@ -18,10 +18,12 @@ import java.util.Arrays; import java.util.Date; +import java.util.List; import java.util.Map; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.api.Displayable; +import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.kernel.Partition; import com.cloud.hypervisor.Hypervisor.HypervisorType; @@ -324,6 +326,8 @@ public boolean isUsedBySystem() { String getBackupExternalId(); + List getBackupVolumes(); + Type getType(); HypervisorType getHypervisorType(); 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 0a58e4108da8..ebc0a7960afd 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -17,8 +17,6 @@ package org.apache.cloudstack.backup; -import java.util.List; - import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; @@ -136,14 +134,4 @@ public String toString() { Long getVmId(); Long getSize(); Long getProtectedSize(); - - default List getBackedUpVolumes() { - return null; - } - default String getVolumes() { - return null; - } - default Status getStatus() { - return null; - } } diff --git a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java index 6411f505c8b6..94dc87ebe8f3 100644 --- a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java @@ -18,7 +18,9 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Arrays; import java.util.Date; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -37,6 +39,7 @@ import javax.persistence.TemporalType; import javax.persistence.Transient; +import org.apache.cloudstack.backup.Backup; import org.apache.commons.codec.binary.Base64; import org.apache.log4j.Logger; @@ -46,6 +49,7 @@ import com.cloud.utils.db.StateMachine; import com.cloud.utils.fsm.FiniteStateObject; import com.cloud.vm.VirtualMachine.State; +import com.google.gson.Gson; @Entity @Table(name = "vm_instance") @@ -147,12 +151,6 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject getBackupVolumes() { + return Arrays.asList(new Gson().fromJson(this.backupVolumes, Backup.VolumeInfo[].class)); + } + + public void setBackupVolumes(List backupVolumes) { + this.backupVolumes = new Gson().toJson(backupVolumes); + } } 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 a68ad433eff0..a1ea2e3c9fc4 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 @@ -37,6 +37,7 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.VMInstanceDao; +import com.google.gson.Gson; public class BackupDaoImpl extends GenericDaoBase implements BackupDao { @@ -62,7 +63,6 @@ protected void init() { backupSearch = createSearchBuilder(); backupSearch.and("vm_id", backupSearch.entity().getVmId(), SearchCriteria.Op.EQ); backupSearch.and("external_id", backupSearch.entity().getExternalId(), SearchCriteria.Op.EQ); - backupSearch.and("status", backupSearch.entity().getStatus(), SearchCriteria.Op.EQ); backupSearch.done(); } @@ -157,8 +157,7 @@ public BackupResponse newBackupResponse(Backup backup) { response.setZoneId(zone.getUuid()); response.setAccountId(account.getUuid()); response.setExternalId(backup.getExternalId()); - response.setVolumes(backup.getVolumes()); - response.setStatus(backup.getStatus()); + response.setVolumes(new Gson().toJson(vm.getBackupVolumes())); response.setSize(backup.getSize()); response.setProtectedSize(backup.getProtectedSize()); response.setObjectName("backup"); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java index 441b09d9528f..c20f1848df14 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.engine.cloud.entity.api.db; import java.security.SecureRandom; +import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Map; @@ -37,6 +38,8 @@ import javax.persistence.TemporalType; import javax.persistence.Transient; +import org.apache.cloudstack.backup.Backup; + import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.utils.db.Encrypt; import com.cloud.utils.db.GenericDao; @@ -44,6 +47,7 @@ import com.cloud.utils.fsm.FiniteStateObject; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; +import com.google.gson.Gson; @Entity @Table(name = "vm_instance") @@ -138,12 +142,6 @@ public class VMEntityVO implements VirtualMachine, FiniteStateObject getBackupVolumes() { + return Arrays.asList(new Gson().fromJson(this.backupVolumes, Backup.VolumeInfo[].class)); + } } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql index db21b427545e..957348694b85 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql @@ -40,7 +40,8 @@ CREATE TABLE IF NOT EXISTS `cloud`.`backup_offering` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `cloud`.`vm_instance` ADD COLUMN `backup_offering_id` bigint unsigned DEFAULT NULL COMMENT 'ID of backup offering'; -ALTER TABLE `cloud`.`vm_instance` ADD COLUMN `external_backup_id` varchar(255) DEFAULT NULL COMMENT 'ID of external backup job or container if any'; +ALTER TABLE `cloud`.`vm_instance` ADD COLUMN `backup_external_id` varchar(255) DEFAULT NULL COMMENT 'ID of external backup job or container if any'; +ALTER TABLE `cloud`.`vm_instance` ADD COLUMN `backup_volumes` text DEFAULT NULL COMMENT 'details of backedup volumes'; CREATE TABLE IF NOT EXISTS `cloud`.`backups` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java index 142b68043f39..d1bc24d02579 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java @@ -849,7 +849,11 @@ private boolean isRootDisk(VirtualDisk disk, Map disksMap } VolumeVO volumeVO = disksMapping.get(disk); if (volumeVO == null) { - List backedUpVolumes = backup.getBackedUpVolumes(); + final VMInstanceVO vm = _vmDao.findByIdIncludingRemoved(backup.getVmId()); + if (vm == null) { + throw new CloudRuntimeException("Failed to find the volumes details from the VM backup"); + } + List backedUpVolumes = vm.getBackupVolumes(); for (Backup.VolumeInfo backedUpVolume : backedUpVolumes) { if (backedUpVolume.getSize().equals(disk.getCapacityInBytes())) { return backedUpVolume.getType().equals(Volume.Type.ROOT); @@ -1138,9 +1142,13 @@ private VirtualMachineDiskInfo getDiskInfo(VirtualMachineMO vmMo, Long poolId, S private VolumeVO createVolume(VirtualDisk disk, VirtualMachineMO vmToImport, long domainId, long zoneId, long accountId, long instanceId, Long poolId, long templateId, Backup backup, boolean isImport) throws Exception { - Long size = disk.getCapacityInBytes(); - List backedUpVolumes = backup.getBackedUpVolumes(); + VMInstanceVO vm = _vmDao.findByIdIncludingRemoved(backup.getVmId()); + if (vm == null) { + throw new CloudRuntimeException("Failed to find the backup volume information from the VM backup"); + } + List backedUpVolumes = vm.getBackupVolumes(); Volume.Type type = Volume.Type.DATADISK; + Long size = disk.getCapacityInBytes(); if (isImport) { for (Backup.VolumeInfo volumeInfo : backedUpVolumes) { if (volumeInfo.getSize().equals(disk.getCapacityInBytes())) { @@ -1303,9 +1311,14 @@ private void removeUnusedNics(List nics, VMInstanceVO vmInstanceVO) { } private Map getDisksMapping(Backup backup, List virtualDisks) { - Map map = new HashMap<>(); - List backedUpVolumes = backup.getBackedUpVolumes(); + final VMInstanceVO vm = _vmDao.findByIdIncludingRemoved(backup.getVmId()); + if (vm == null) { + throw new CloudRuntimeException("Failed to find the volumes details from the VM backup"); + } + List backedUpVolumes = vm.getBackupVolumes(); Map usedVols = new HashMap<>(); + Map map = new HashMap<>(); + for (Backup.VolumeInfo backedUpVol : backedUpVolumes) { for (VirtualDisk disk : virtualDisks) { if (!map.containsKey(disk) && backedUpVol.getSize().equals(disk.getCapacityInBytes()) 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 3fbd6b2aa8c7..401a0c4edc57 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -207,6 +207,14 @@ public boolean deleteBackupOffering(final Long offeringId) { return backupOfferingDao.remove(offering.getId()); } + private List createVolumeInfoFromVolumes(List vmVolumes) { + List list = new ArrayList<>(); + for (VolumeVO vol : vmVolumes) { + list.add(new Backup.VolumeInfo(vol.getUuid(), vol.getPath(), vol.getVolumeType(), vol.getSize())); + } + return list; + } + @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, eventDescription = "assign VM to backup offering", async = true) public boolean assignVMToBackupOffering(Long vmId, Long offeringId) { @@ -230,6 +238,7 @@ public boolean assignVMToBackupOffering(Long vmId, Long offeringId) { boolean result = false; try { vm.setBackupOfferingId(offering.getId()); + vm.setBackupVolumes(createVolumeInfoFromVolumes(volumeDao.findByInstance(vm.getId()))); result = backupProvider.assignVMToBackupOffering(vm, offering); } catch (Exception e) { LOG.error("Exception caught while assigning VM to backup offering by the backup provider", e); @@ -388,14 +397,6 @@ public List listBackups(final Long id, final Long vmId) { return backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); } - private List createVolumeInfoFromVolumes(List vmVolumes) { - List list = new ArrayList<>(); - for (VolumeVO vol : vmVolumes) { - list.add(new Backup.VolumeInfo(vol.getUuid(), vol.getPath(), vol.getVolumeType(), vol.getSize())); - } - return list; - } - public boolean importRestoredVM(long zoneId, long domainId, long accountId, long userId, String vmInternalName, Hypervisor.HypervisorType hypervisorType, Backup backup) { VirtualMachine vm = null; @@ -491,7 +492,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, if (!result.first()) { throw new CloudRuntimeException("Error restoring volume " + backedUpVolumeUuid); } - if (!attachVolumeToVM(vmFromBackup.getDataCenterId(), result.second(), backup.getBackedUpVolumes(), + if (!attachVolumeToVM(vmFromBackup.getDataCenterId(), result.second(), vmFromBackup.getBackupVolumes(), backedUpVolumeUuid, vm, datastoreUuid, backup)) { throw new CloudRuntimeException("Error attaching volume " + backedUpVolumeUuid + " to VM " + vm.getUuid()); } From 8c7d42f21ee5d1f783b7215989f3246e4bbf1e00 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 5 Dec 2019 22:07:53 +0530 Subject: [PATCH 20/81] api and schema refactorings, end to end Signed-off-by: Rohit Yadav --- .../main/java/com/cloud/event/EventTypes.java | 2 +- .../apache/cloudstack/api/ApiConstants.java | 3 +- .../cloudstack/api/BaseBackupListCmd.java | 32 --- .../user/backup/CreateBackupScheduleCmd.java | 10 +- .../user/backup/DeleteBackupScheduleCmd.java | 2 +- .../user/backup/ListBackupScheduleCmd.java | 3 +- .../command/user/backup/ListBackupsCmd.java | 39 +++- ...veVirtualMachineFromBackupOfferingCmd.java | 2 +- .../command/user/backup/RestoreBackupCmd.java | 14 +- ...storeVolumeFromBackupAndAttachToVMCmd.java | 12 +- .../user/backup/UpdateBackupScheduleCmd.java | 30 +++ .../api/response/BackupResponse.java | 187 +++++++++++------- .../api/response/BackupScheduleResponse.java | 12 -- .../org/apache/cloudstack/backup/Backup.java | 9 +- .../cloudstack/backup/BackupManager.java | 11 +- .../cloudstack/backup/BackupProvider.java | 11 +- .../cloudstack/backup/BackupSchedule.java | 3 +- .../java/com/cloud/usage/UsageBackupVO.java | 17 +- .../cloud/usage/dao/UsageBackupDaoImpl.java | 23 ++- .../main/java/com/cloud/vm/VMInstanceVO.java | 11 +- .../cloudstack/backup/BackupScheduleVO.java | 15 +- .../apache/cloudstack/backup/BackupVO.java | 102 +++++++++- .../cloudstack/backup/dao/BackupDao.java | 3 +- .../cloudstack/backup/dao/BackupDaoImpl.java | 27 ++- .../backup/dao/BackupScheduleDao.java | 2 +- .../backup/dao/BackupScheduleDaoImpl.java | 4 +- .../META-INF/db/schema-41300to41400.sql | 17 +- .../backup/DummyBackupProvider.java | 17 +- .../backup/VeeamBackupProvider.java | 19 +- .../cloud/storage/VolumeApiServiceImpl.java | 4 +- .../cloudstack/backup/BackupManagerImpl.java | 137 ++++++++----- .../storage/VolumeApiServiceImplTest.java | 2 + .../com/cloud/usage/UsageManagerImpl.java | 7 +- 33 files changed, 475 insertions(+), 314 deletions(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index c8905654c880..f7042964c7c9 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -481,7 +481,7 @@ public class EventTypes { public static final String EVENT_VM_BACKUP_RESTORE = "BACKUP.RESTORE"; 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_CREATE = "BACKUP.SCHEDULE.CREATE"; + public static final String EVENT_VM_BACKUP_SCHEDULE_CONFIGURE = "BACKUP.SCHEDULE.CONFIGURE"; public static final String EVENT_VM_BACKUP_SCHEDULE_DELETE = "BACKUP.SCHEDULE.DELETE"; // external network device events 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 a0eaf9f2e35c..ecd8cbed5575 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -33,6 +33,7 @@ public class ApiConstants { public static final String LIST_LB_VMIPS = "lbvmips"; public static final String AVAILABLE = "available"; public static final String BACKUP_ID = "backupid"; + public static final String BACKUP_OFFERING = "backupoffering"; public static final String BACKUP_OFFERING_ID = "backupofferingid"; public static final String BITS = "bits"; public static final String BOOTABLE = "bootable"; @@ -280,8 +281,6 @@ public class ApiConstants { public static final String RESOURCE_TYPE = "resourcetype"; public static final String RESOURCE_TYPE_NAME = "resourcetypename"; public static final String RESPONSE = "response"; - public static final String RESTORE_POINTS = "restorepoints"; - public static final String RESTORE_POINT_ID = "restorepointid"; public static final String REVERTABLE = "revertable"; public static final String REGISTERED = "registered"; public static final String QUALIFIERS = "qualifiers"; diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java index b5c5d4779e5a..2b4245e3a219 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java @@ -22,10 +22,7 @@ import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.api.response.ListResponse; -import org.apache.cloudstack.api.response.BackupResponse; -import org.apache.cloudstack.api.response.BackupRestorePointResponse; import org.apache.cloudstack.backup.BackupOffering; -import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.context.CallContext; public abstract class BaseBackupListCmd extends BaseListCmd { @@ -45,35 +42,6 @@ protected void setupResponseBackupOfferingsList(final List offer setResponseObject(response); } - protected void setupResponseBackupList(final List backups, final List restorePoints) { - final ListResponse response = new ListResponse<>(); - final List responses = new ArrayList<>(); - for (Backup backup : backups) { - if (backup == null) { - continue; - } - BackupResponse backupResponse = _responseGenerator.createBackupResponse(backup); - if (restorePoints != null && !restorePoints.isEmpty()) { - final List restorePointResponses = new ArrayList<>(); - for (Backup.RestorePoint rp : restorePoints) { - if (rp == null) { - continue; - } - BackupRestorePointResponse rpResponse = new BackupRestorePointResponse(); - rpResponse.setId(rp.getId()); - rpResponse.setCreated(rp.getCreated()); - rpResponse.setType(rp.getType()); - restorePointResponses.add(rpResponse); - } - backupResponse.setRestorePoints(restorePointResponses); - } - responses.add(backupResponse); - } - response.setResponses(responses); - response.setResponseName(getCommandName()); - setResponseObject(response); - } - @Override public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java index c424df0418da..c36fa6a354aa 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java @@ -39,10 +39,10 @@ import com.cloud.utils.exception.CloudRuntimeException; @APICommand(name = CreateBackupScheduleCmd.APINAME, - description = "Create a user-defined VM backup schedule", + description = "Creates a user-defined VM backup schedule", responseObject = BackupResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class CreateBackupScheduleCmd extends BaseAsyncCmd { +public class CreateBackupScheduleCmd extends BaseAsyncCmd { public static final String APINAME = "createBackupSchedule"; @Inject @@ -105,7 +105,7 @@ public String getTimezone() { @Override public void execute() throws ServerApiException { try { - BackupSchedule schedule = backupManager.createBackupSchedule(this); + BackupSchedule schedule = backupManager.configureBackupSchedule(this); if (schedule != null) { BackupScheduleResponse response = _responseGenerator.createBackupScheduleResponse(schedule); response.setResponseName(getCommandName()); @@ -130,11 +130,11 @@ public long getEntityOwnerId() { @Override public String getEventType() { - return EventTypes.EVENT_VM_BACKUP_SCHEDULE_CREATE; + return EventTypes.EVENT_VM_BACKUP_SCHEDULE_CONFIGURE; } @Override public String getEventDescription() { - return "Creating user-defined backup schedule for backup for VM ID " + vmId; + return "Configuring user-defined backup schedule for backup for VM ID " + vmId; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java index 40938f857b98..583b3e84f519 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java @@ -82,7 +82,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE response.setResponseName(getCommandName()); setResponseObject(response); } else { - throw new CloudRuntimeException("Error while deleting backup schedule of VM"); + throw new CloudRuntimeException("Failed to delete VM backup schedule"); } } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java index 5f275e2419a5..6ea2b0a2608f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java @@ -23,7 +23,6 @@ import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.BaseBackupListCmd; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; @@ -44,7 +43,7 @@ description = "List backup schedule of a VM", responseObject = BackupScheduleResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class ListBackupScheduleCmd extends BaseBackupListCmd { +public class ListBackupScheduleCmd extends BaseCmd { public static final String APINAME = "listBackupSchedule"; @Inject 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 4337aa8d7a3a..b4195e2b88f5 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 @@ -26,12 +26,14 @@ import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.BaseBackupListCmd; import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -46,7 +48,7 @@ description = "Lists VM backups", responseObject = BackupResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class ListBackupsCmd extends BaseBackupListCmd { +public class ListBackupsCmd extends BaseListProjectAndAccountResourcesCmd { public static final String APINAME = "listBackups"; @Inject @@ -59,7 +61,7 @@ public class ListBackupsCmd extends BaseBackupListCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = BackupResponse.class, - description = "id of the VM") + description = "id of the backup") private Long id; @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, @@ -68,11 +70,16 @@ public class ListBackupsCmd extends BaseBackupListCmd { description = "id of the VM") private Long vmId; + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.UUID, + entityType = ZoneResponse.class, + description = "list backups by zone id") + private Long zoneId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public Long getId() { return id; } @@ -81,16 +88,34 @@ public Long getVmId() { return vmId; } + public Long getZoneId() { + return zoneId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// + protected void setupResponseBackupList(final List backups) { + final List responses = new ArrayList<>(); + for (Backup backup : backups) { + if (backup == null) { + continue; + } + BackupResponse backupResponse = _responseGenerator.createBackupResponse(backup); + responses.add(backupResponse); + } + final ListResponse response = new ListResponse<>(); + response.setResponses(responses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try{ - List restorePoints = new ArrayList<>(); - List backups = backupManager.listBackups(getId(), getVmId()); - setupResponseBackupList(backups, restorePoints); + List backups = backupManager.listBackups(this); + setupResponseBackupList(backups); } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java index 8e915b05f167..28f03f8c8530 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java @@ -40,7 +40,7 @@ import com.cloud.exception.ResourceUnavailableException; @APICommand(name = RemoveVirtualMachineFromBackupOfferingCmd.APINAME, - description = "Removes a VM from a backup offering", + description = "Removes a VM from any existing backup offering", responseObject = SuccessResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class RemoveVirtualMachineFromBackupOfferingCmd extends BaseAsyncCmd { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java index 4a52bac6acaf..32beabeda93d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java @@ -61,12 +61,6 @@ public class RestoreBackupCmd extends BaseAsyncCmd { description = "id of the backup") private Long backupId; - @Parameter(name = ApiConstants.RESTORE_POINT_ID, - type = CommandType.STRING, - required = true, - description = "external id of the restore point") - private String restorePointId; - ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -75,10 +69,6 @@ public Long getBackupId() { return backupId; } - public String getRestorePointId() { - return restorePointId; - } - ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -86,7 +76,7 @@ public String getRestorePointId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.restoreBackup(backupId, restorePointId); + boolean result = backupManager.restoreBackup(backupId); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); @@ -116,6 +106,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Restoring VM from restore point: " + restorePointId + " on backup: " + backupId; + return "Restoring VM from backup: " + backupId; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java index c98e884675e3..fc8dc65751d4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java @@ -62,12 +62,6 @@ public class RestoreVolumeFromBackupAndAttachToVMCmd extends BaseAsyncCmd { description = "id of the backup") private Long backupId; - @Parameter(name = ApiConstants.RESTORE_POINT_ID, - type = CommandType.STRING, - required = true, - description = "id of the backup restore point") - private String restorePointId; - @Parameter(name = ApiConstants.VOLUME_ID, type = CommandType.STRING, required = true, @@ -97,10 +91,6 @@ public Long getBackupId() { return backupId; } - public String getRestorePointId() { - return restorePointId; - } - @Override public String getCommandName() { return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; @@ -118,7 +108,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.restoreBackupVolumeAndAttachToVM(volumeUuid, vmId, backupId, restorePointId); + boolean result = backupManager.restoreBackupVolumeAndAttachToVM(volumeUuid, backupId, vmId); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java new file mode 100644 index 000000000000..5a02cf9ac786 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/UpdateBackupScheduleCmd.java @@ -0,0 +1,30 @@ +// 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.backup; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.response.BackupResponse; + +@APICommand(name = UpdateBackupScheduleCmd.APINAME, + description = "Updates a user-defined VM backup schedule", + responseObject = BackupResponse.class, since = "4.14.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class UpdateBackupScheduleCmd extends CreateBackupScheduleCmd { + public static final String APINAME = "updateBackupSchedule"; +} 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 878bc6089d37..3dc1945c478c 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 @@ -17,9 +17,6 @@ package org.apache.cloudstack.api.response; -import java.util.Date; -import java.util.List; - import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; @@ -35,10 +32,6 @@ public class BackupResponse extends BaseResponse { @Param(description = "ID of the VM (backup)") private String id; - @SerializedName(ApiConstants.NAME) - @Param(description = "name of the VM") - private String name; - @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) @Param(description = "ID of the VM") private String vmId; @@ -47,25 +40,17 @@ public class BackupResponse extends BaseResponse { @Param(description = "name of the VM") private String vmName; - @SerializedName(ApiConstants.ZONE_ID) - @Param(description = "zone id") - private String zoneId; - - @SerializedName(ApiConstants.ACCOUNT_ID) - @Param(description = "account id") - private String accountId; - @SerializedName(ApiConstants.EXTERNAL_ID) @Param(description = "external backup id") private String externalId; - @SerializedName(ApiConstants.VOLUMES) - @Param(description = "backup volumes") - private String volumes; + @SerializedName(ApiConstants.TYPE) + @Param(description = "backup type") + private String type; - @SerializedName(ApiConstants.STATUS) - @Param(description = "backup volume ids") - private Backup.Status status; + @SerializedName(ApiConstants.CREATED) + @Param(description = "backup date") + private String date; @SerializedName(ApiConstants.SIZE) @Param(description = "backup size in bytes") @@ -75,13 +60,41 @@ public class BackupResponse extends BaseResponse { @Param(description = "backup protected (virtual) size in bytes") private Long protectedSize; - @SerializedName(ApiConstants.RESTORE_POINTS) - @Param(description = "list of backup restore points") - private List restorePoints; + @SerializedName(ApiConstants.STATUS) + @Param(description = "backup status") + private Backup.Status status; - @SerializedName(ApiConstants.CREATED) - @Param(description = "backup creation date") - private Date createdDate; + @SerializedName(ApiConstants.BACKUP_OFFERING_ID) + @Param(description = "backup offering id") + private String backupOfferingId; + + @SerializedName(ApiConstants.BACKUP_OFFERING_ID) + @Param(description = "backup offering id") + private String backupOfferingName; + + @SerializedName(ApiConstants.ACCOUNT_ID) + @Param(description = "account id") + private String accountId; + + @SerializedName(ApiConstants.ACCOUNT) + @Param(description = "account name") + private String account; + + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "domain id") + private String domainId; + + @SerializedName(ApiConstants.DOMAIN) + @Param(description = "domain name") + private String domain; + + @SerializedName(ApiConstants.ZONE_ID) + @Param(description = "zone id") + private String zoneId; + + @SerializedName(ApiConstants.ZONE) + @Param(description = "zone name") + private String zone; public String getId() { return id; @@ -91,28 +104,20 @@ public void setId(String id) { this.id = id; } - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getAccountId() { - return accountId; + public String getVmId() { + return vmId; } - public void setAccountId(String accountId) { - this.accountId = accountId; + public void setVmId(String vmId) { + this.vmId = vmId; } - public String getZoneId() { - return zoneId; + public String getVmName() { + return vmName; } - public void setZoneId(String zoneId) { - this.zoneId = zoneId; + public void setVmName(String vmName) { + this.vmName = vmName; } public String getExternalId() { @@ -123,28 +128,20 @@ public void setExternalId(String externalId) { this.externalId = externalId; } - public String getVmId() { - return vmId; - } - - public void setVmId(String vmId) { - this.vmId = vmId; + public String getType() { + return type; } - public String getVmName() { - return vmName; + public void setType(String type) { + this.type = type; } - public void setVmName(String vmName) { - this.vmName = vmName; - } - - public Backup.Status getStatus() { - return status; + public String getDate() { + return date; } - public void setStatus(Backup.Status status) { - this.status = status; + public void setDate(String date) { + this.date = date; } public Long getSize() { @@ -163,27 +160,75 @@ public void setProtectedSize(Long protectedSize) { this.protectedSize = protectedSize; } - public String getVolumes() { - return volumes; + public Backup.Status getStatus() { + return status; } - public void setVolumes(String volumes) { - this.volumes = volumes; + public void setStatus(Backup.Status status) { + this.status = status; } - public List getRestorePoints() { - return restorePoints; + public String getBackupOfferingId() { + return backupOfferingId; } - public void setRestorePoints(List restorePoints) { - this.restorePoints = restorePoints; + public void setBackupOfferingId(String backupOfferingId) { + this.backupOfferingId = backupOfferingId; + } + + public String getBackupOffering() { + return backupOfferingName; + } + + public void setBackupOffering(String backupOfferingName) { + this.backupOfferingName = backupOfferingName; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public String getDomainId() { + return domainId; + } + + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; } - public Date getCreatedDate() { - return createdDate; + public String getZone() { + return zone; } - public void setCreatedDate(Date createdDate) { - this.createdDate = createdDate; + public void setZone(String zone) { + this.zone = zone; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java index de7997617c5e..ba44f1e024f7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java @@ -29,10 +29,6 @@ @EntityReference(value = BackupSchedule.class) public class BackupScheduleResponse extends BaseResponse { - @SerializedName(ApiConstants.ID) - @Param(description = "ID of the VM backup schedule") - private String id; - @SerializedName(ApiConstants.VIRTUAL_MACHINE_NAME) @Param(description = "name of the VM") private String vmName; @@ -53,14 +49,6 @@ public class BackupScheduleResponse extends BaseResponse { @Param(description = "the time zone of the backup schedule") private String timezone; - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - public String getVmName() { return vmName; } 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 ebc0a7960afd..e6aa238ec9a3 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -17,13 +17,14 @@ package org.apache.cloudstack.backup; +import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; import com.cloud.storage.Volume; import com.cloud.utils.StringUtils; -public interface Backup extends InternalIdentity, Identity { +public interface Backup extends ControlledEntity, InternalIdentity, Identity { enum Status { Allocated, Queued, BackingUp, BackedUp, Error, Failed, Restoring, Removed, Expunged @@ -130,8 +131,12 @@ public String toString() { } } + long getVmId(); String getExternalId(); - Long getVmId(); + String getType(); + String getDate(); + Backup.Status getStatus(); Long getSize(); Long getProtectedSize(); + long getZoneId(); } 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 945ba9c09058..63f1cbea5217 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -21,6 +21,7 @@ import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; +import org.apache.cloudstack.api.command.user.backup.ListBackupsCmd; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; @@ -91,11 +92,11 @@ BackupOffering importBackupOffering(final Long zoneId, final String offeringExte boolean removeVMFromBackupOffering(final Long vmId, final boolean forced); /** - * Creates a VM backup schedule + * Creates or Updates a VM backup schedule * @param cmd * @return */ - BackupSchedule createBackupSchedule(CreateBackupScheduleCmd cmd); + BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd); /** * Lists VM backup schedule for a VM @@ -121,17 +122,17 @@ BackupOffering importBackupOffering(final Long zoneId, final String offeringExte /** * List existing backups for a VM */ - List listBackups(final Long id, final Long vmId); + List listBackups(final ListBackupsCmd cmd); /** * Restore a full VM from backup */ - boolean restoreBackup(final Long backupId, final String restorePointId); + boolean restoreBackup(final Long backupId); /** * Restore a backed up volume and attach it to a VM */ - boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long vmId, final Long backupId, final String restorePointId) throws Exception; + boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long backupId, final Long vmId) throws Exception; /** * Deletes a backup 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 3d4de52301dd..669dcc5f4347 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -87,19 +87,14 @@ public interface BackupProvider { /** * Restore VM from backup */ - boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String restorePointId); + boolean restoreVMFromBackup(VirtualMachine vm, Backup backup); /** * Restore a volume from a backup */ - Pair restoreBackedUpVolume(long zoneId, String restorePointId, String volumeUuid, String hostIp, String dataStoreUuid); - - /** - * List VM Backups - */ - List listBackups(Long zoneId, VirtualMachine vm); + Pair restoreBackedUpVolume(Backup backup, String volumeUuid, String hostIp, String dataStoreUuid); Map getBackupMetrics(Long zoneId, List backupList); - List listBackupRestorePoints(String backupUuid, VirtualMachine vm); + List listRestorePoints(VirtualMachine vm); } diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java b/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java index aedc40500509..d81dd731b1ff 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java @@ -19,12 +19,11 @@ import java.util.Date; -import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; import com.cloud.utils.DateUtil; -public interface BackupSchedule extends InternalIdentity, Identity { +public interface BackupSchedule extends InternalIdentity { Long getVmId(); DateUtil.IntervalType getScheduleType(); String getSchedule(); diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageBackupVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageBackupVO.java index 91b77bf0bb7a..85b02ad5b2e6 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageBackupVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageBackupVO.java @@ -48,9 +48,6 @@ public class UsageBackupVO implements InternalIdentity { @Column(name = "domain_id") private long domainId; - @Column(name = "backup_id") - private long backupId; - @Column(name = "vm_id") private long vmId; @@ -71,21 +68,19 @@ public class UsageBackupVO implements InternalIdentity { protected UsageBackupVO() { } - public UsageBackupVO(long zoneId, long accountId, long domainId, long backupId, long vmId, Date created) { + public UsageBackupVO(long zoneId, long accountId, long domainId, long vmId, Date created) { this.zoneId = zoneId; this.accountId = accountId; this.domainId = domainId; - this.backupId = backupId; this.vmId = vmId; this.created = created; } - public UsageBackupVO(long id, long zoneId, long accountId, long domainId, long backupId, long vmId, long size, long protectedSize, Date created, Date removed) { + public UsageBackupVO(long id, long zoneId, long accountId, long domainId, long vmId, long size, long protectedSize, Date created, Date removed) { this.id = id; this.zoneId = zoneId; this.accountId = accountId; this.domainId = domainId; - this.backupId = backupId; this.vmId = vmId; this.size = size; this.protectedSize = protectedSize; @@ -122,14 +117,6 @@ public void setDomainId(long domainId) { this.domainId = domainId; } - public long getBackupId() { - return backupId; - } - - public void setBackupId(long backupId) { - this.backupId = backupId; - } - public long getVmId() { return vmId; } diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java index 12d591f08f3d..e9d94d7e2dcf 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java @@ -41,8 +41,8 @@ @Component public class UsageBackupDaoImpl extends GenericDaoBase implements UsageBackupDao { public static final Logger LOGGER = Logger.getLogger(UsageBackupDaoImpl.class); - protected static final String GET_USAGE_RECORDS_BY_ACCOUNT = "SELECT id, zone_id, account_id, domain_id, backup_id, vm_id, size, protected_size, created, removed FROM cloud_usage.usage_backup WHERE " + - " vm_id = ? AND ((removed IS NULL AND created <= ?) OR (created BETWEEN ? AND ?) OR (removed BETWEEN ? AND ?) " + + protected static final String GET_USAGE_RECORDS_BY_ACCOUNT = "SELECT id, zone_id, account_id, domain_id, vm_id, size, protected_size, created, removed FROM cloud_usage.usage_backup WHERE " + + " account_id = ? AND ((removed IS NULL AND created <= ?) OR (created BETWEEN ? AND ?) OR (removed BETWEEN ? AND ?) " + " OR ((created <= ?) AND (removed >= ?)))"; @Override @@ -67,20 +67,20 @@ public Boolean doInTransaction(final TransactionStatus status) { } @Override - public void removeUsage(Long accountId, Long zoneId, Long backupId) { + public void removeUsage(Long accountId, Long zoneId, Long vmId) { boolean result = Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { @Override public Boolean doInTransaction(final TransactionStatus status) { final QueryBuilder qb = QueryBuilder.create(UsageBackupVO.class); qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); qb.and(qb.entity().getZoneId(), SearchCriteria.Op.EQ, zoneId); - qb.and(qb.entity().getBackupId(), SearchCriteria.Op.EQ, backupId); + qb.and(qb.entity().getVmId(), SearchCriteria.Op.EQ, vmId); final UsageBackupVO entry = findOneBy(qb.create()); return remove(qb.create()) > 0; } }); if (!result) { - LOGGER.warn("Failed to remove usage entry for backup id: " + backupId); + LOGGER.warn("Failed to remove usage entry for backup of VM ID: " + vmId); } } @@ -109,14 +109,13 @@ public List getUsageRecords(Long accountId, Date startDate, Date Long zoneId = Long.valueOf(rs.getLong(2)); Long acctId = Long.valueOf(rs.getLong(3)); Long domId = Long.valueOf(rs.getLong(4)); - Long backupId = Long.valueOf(rs.getLong(5)); - Long vmId = Long.valueOf(rs.getLong(6)); - Long size = Long.valueOf(rs.getLong(7)); - Long pSize = Long.valueOf(rs.getLong(8)); + Long vmId = Long.valueOf(rs.getLong(5)); + Long size = Long.valueOf(rs.getLong(6)); + Long pSize = Long.valueOf(rs.getLong(7)); Date createdDate = null; Date removedDate = null; - String createdTS = rs.getString(9); - String removedTS = rs.getString(10); + String createdTS = rs.getString(8); + String removedTS = rs.getString(9); if (createdTS != null) { createdDate = DateUtil.parseDateString(s_gmtTimeZone, createdTS); @@ -124,7 +123,7 @@ public List getUsageRecords(Long accountId, Date startDate, Date if (removedTS != null) { removedDate = DateUtil.parseDateString(s_gmtTimeZone, removedTS); } - usageRecords.add(new UsageBackupVO(id, zoneId, acctId, domId, backupId, vmId, size, pSize, createdDate, removedDate)); + usageRecords.add(new UsageBackupVO(id, zoneId, acctId, domId, vmId, size, pSize, createdDate, removedDate)); } } catch (Exception e) { txn.rollback(); diff --git a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java index 94dc87ebe8f3..469e4351905f 100644 --- a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java @@ -19,6 +19,7 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; @@ -49,6 +50,7 @@ import com.cloud.utils.db.StateMachine; import com.cloud.utils.fsm.FiniteStateObject; import com.cloud.vm.VirtualMachine.State; +import com.google.common.base.Strings; import com.google.gson.Gson; @Entity @@ -613,10 +615,17 @@ public void setBackupExternalId(String backupExternalId) { @Override public List getBackupVolumes() { + if (Strings.isNullOrEmpty(this.backupVolumes)) { + return Collections.emptyList(); + } return Arrays.asList(new Gson().fromJson(this.backupVolumes, Backup.VolumeInfo[].class)); } public void setBackupVolumes(List backupVolumes) { - this.backupVolumes = new Gson().toJson(backupVolumes); + if (backupVolumes == null) { + this.backupVolumes = null; + } else { + this.backupVolumes = new Gson().toJson(backupVolumes); + } } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java index 8ff7c76ed505..069a8e5b83d4 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java @@ -18,7 +18,6 @@ package org.apache.cloudstack.backup; import java.util.Date; -import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; @@ -39,9 +38,6 @@ public class BackupScheduleVO implements BackupSchedule { @Column(name = "id") private long id; - @Column(name = "uuid") - private String uuid; - @Column(name = "vm_id") private Long vmId; @@ -62,11 +58,9 @@ public class BackupScheduleVO implements BackupSchedule { Long asyncJobId; public BackupScheduleVO() { - this.uuid = UUID.randomUUID().toString(); } public BackupScheduleVO(Long vmId, DateUtil.IntervalType scheduleType, String schedule, String timezone) { - this.uuid = UUID.randomUUID().toString(); this.vmId = vmId; this.scheduleType = (short) scheduleType.ordinal(); this.schedule = schedule; @@ -78,11 +72,6 @@ public long getId() { return id; } - @Override - public String getUuid() { - return uuid; - } - public Long getVmId() { return vmId; } @@ -96,8 +85,8 @@ public DateUtil.IntervalType getScheduleType() { return scheduleType == null ? null : DateUtil.getIntervalType(scheduleType); } - public void setScheduleType(Short scheduleType) { - this.scheduleType = scheduleType; + public void setScheduleType(DateUtil.IntervalType intervalType) { + this.scheduleType = (short) intervalType.ordinal(); } public String getSchedule() { 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 8f05224f6d9e..78479d7ee824 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 @@ -21,6 +21,8 @@ import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @@ -37,11 +39,17 @@ public class BackupVO implements Backup { @Column(name = "uuid") private String uuid; + @Column(name = "vm_id") + private long vmId; + @Column(name = "external_id") private String externalId; - @Column(name = "vm_id") - private Long vmId; + @Column(name = "type") + private String backupType; + + @Column(name = "date") + private String date; @Column(name = "size") private Long size; @@ -49,6 +57,22 @@ public class BackupVO implements Backup { @Column(name = "protected_size") private Long protectedSize; + @Enumerated(value = EnumType.STRING) + @Column(name = "status") + private Backup.Status status; + + @Column(name = "backup_offering_id") + private long backupOfferingId; + + @Column(name = "account_id") + private long accountId; + + @Column(name = "domain_id") + private long domainId; + + @Column(name = "zone_id") + private long zoneId; + public BackupVO() { this.uuid = UUID.randomUUID().toString(); } @@ -57,6 +81,7 @@ public BackupVO(final Long vmId, final String externalId) { this.uuid = UUID.randomUUID().toString(); this.vmId = vmId; this.externalId = externalId; + this.status = Status.BackedUp; } @Override @@ -69,6 +94,15 @@ public String getUuid() { return uuid; } + @Override + public long getVmId() { + return vmId; + } + + public void setVmId(long vmId) { + this.vmId = vmId; + } + @Override public String getExternalId() { return externalId; @@ -78,13 +112,21 @@ public void setExternalId(String externalId) { this.externalId = externalId; } + public String getType() { + return backupType; + } + + public void setType(String type) { + this.backupType = type; + } + @Override - public Long getVmId() { - return vmId; + public String getDate() { + return date; } - public void setVmId(Long vmId) { - this.vmId = vmId; + public void setDate(String date) { + this.date = date; } @Override @@ -104,4 +146,52 @@ public Long getProtectedSize() { public void setProtectedSize(Long protectedSize) { this.protectedSize = protectedSize; } + + @Override + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public long getBackupOfferingId() { + return backupOfferingId; + } + + public void setBackupOfferingId(long backupOfferingId) { + this.backupOfferingId = backupOfferingId; + } + + @Override + public long getAccountId() { + return accountId; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + @Override + public long getDomainId() { + return domainId; + } + + public void setDomainId(long domainId) { + this.domainId = domainId; + } + + public long getZoneId() { + return zoneId; + } + + public void setZoneId(long zoneId) { + this.zoneId = zoneId; + } + + @Override + public Class getEntityType() { + return Backup.class; + } } 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 18b30cce30e1..6ac9befc353a 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,8 +35,7 @@ public interface BackupDao extends GenericDao { List listByOfferingId(Long offeringId); List syncBackups(Long zoneId, Long vmId, List externalBackups); List listByZoneAndState(Long zoneId, Backup.Status state); + BackupVO getBackupVO(Backup backup); BackupResponse newBackupResponse(Backup backup); - - BackupVO getBackupVO(Backup backup); } 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 a1ea2e3c9fc4..f888b2cb4c4e 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 @@ -25,11 +25,13 @@ import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.BackupVO; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; -import com.cloud.storage.dao.VolumeDao; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; import com.cloud.user.AccountVO; import com.cloud.user.dao.AccountDao; import com.cloud.utils.db.GenericDaoBase; @@ -37,13 +39,15 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.VMInstanceDao; -import com.google.gson.Gson; public class BackupDaoImpl extends GenericDaoBase implements BackupDao { @Inject AccountDao accountDao; + @Inject + DomainDao domainDao; + @Inject DataCenterDao dataCenterDao; @@ -51,7 +55,7 @@ public class BackupDaoImpl extends GenericDaoBase implements Bac VMInstanceDao vmInstanceDao; @Inject - VolumeDao volumeDao; + BackupOfferingDao backupOfferingDao; private SearchBuilder backupSearch; @@ -147,19 +151,28 @@ public List listByZoneAndState(Long zoneId, Backup.Status state) { public BackupResponse newBackupResponse(Backup backup) { VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); AccountVO account = accountDao.findById(vm.getAccountId()); + DomainVO domain = domainDao.findById(vm.getDomainId()); DataCenterVO zone = dataCenterDao.findById(vm.getDataCenterId()); + BackupOffering offering = backupOfferingDao.findById(vm.getBackupOfferingId()); BackupResponse response = new BackupResponse(); response.setId(vm.getUuid()); response.setVmId(vm.getUuid()); - response.setName(vm.getHostName()); response.setVmName(vm.getHostName()); - response.setZoneId(zone.getUuid()); - response.setAccountId(account.getUuid()); response.setExternalId(backup.getExternalId()); - response.setVolumes(new Gson().toJson(vm.getBackupVolumes())); + response.setType(backup.getType()); + response.setDate(backup.getDate()); response.setSize(backup.getSize()); response.setProtectedSize(backup.getProtectedSize()); + response.setStatus(backup.getStatus()); + response.setBackupOfferingId(offering.getUuid()); + response.setBackupOffering(offering.getName()); + response.setAccountId(account.getUuid()); + response.setAccount(account.getAccountName()); + response.setDomainId(domain.getUuid()); + response.setDomain(domain.getName()); + response.setZoneId(zone.getUuid()); + response.setZone(zone.getName()); response.setObjectName("backup"); return response; } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java index 47c43fbc3ffc..9d81e46adcc4 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java @@ -24,7 +24,7 @@ import com.cloud.utils.db.GenericDao; public interface BackupScheduleDao extends GenericDao { - BackupSchedule findByVM(Long vmId); + BackupScheduleVO findByVM(Long vmId); BackupScheduleResponse newBackupScheduleResponse(BackupSchedule schedule); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java index 04f30bc9dd7f..d8207a813095 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java @@ -49,7 +49,7 @@ protected void init() { } @Override - public BackupSchedule findByVM(Long vmId) { + public BackupScheduleVO findByVM(Long vmId) { SearchCriteria sc = backupScheduleSearch.create(); sc.setParameters("vm_id", vmId); return findOneBy(sc); @@ -58,9 +58,7 @@ public BackupSchedule findByVM(Long vmId) { @Override public BackupScheduleResponse newBackupScheduleResponse(BackupSchedule schedule) { VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(schedule.getVmId()); - BackupScheduleResponse response = new BackupScheduleResponse(); - response.setId(vm.getUuid()); response.setVmId(vm.getUuid()); response.setVmName(vm.getHostName()); response.setIntervalType(schedule.getScheduleType()); diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql index 957348694b85..51f6e9a99cf9 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql @@ -49,16 +49,22 @@ CREATE TABLE IF NOT EXISTS `cloud`.`backups` ( `vm_id` bigint(20) unsigned NOT NULL, `external_id` varchar(255) NOT NULL COMMENT 'external ID', `type` varchar(255) NOT NULL COMMENT 'backup type', - `size` bigint(20) DEFAULT 0, + `date` varchar(255) NOT NULL COMMENT 'backup date', + `size` bigint(20) DEFAULT 0 COMMENT 'size of the backup', `protected_size` bigint(20) DEFAULT 0, - `volumes` text + `status` varchar(32) DEFAULT NULL COMMENT 'backup status', + `backup_offering_id` bigint(20) unsigned NOT NULL COMMENT, + `account_id` bigint(20) unsigned NOT NULL COMMENT, + `domain_id` bigint(20) unsigned NOT NULL COMMENT, + `zone_id` bigint(20) unsigned NOT NULL, + `removed` datetime DEFAULT NULL COMMENT 'Date removed. not null if removed', PRIMARY KEY (`id`), - CONSTRAINT `fk_backup__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance` (`id`) ON DELETE CASCADE + CONSTRAINT `fk_backup__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_backup__account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `cloud`.`backup_schedule` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) NOT NULL UNIQUE, `vm_id` bigint(20) unsigned NOT NULL UNIQUE, `schedule_type` int(4) DEFAULT NULL COMMENT 'backup schedulet type e.g. hourly, daily, etc.', `schedule` varchar(100) DEFAULT NULL COMMENT 'schedule time of execution', @@ -74,12 +80,11 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`usage_backup` ( `zone_id` bigint(20) unsigned NOT NULL, `account_id` bigint(20) unsigned NOT NULL, `domain_id` bigint(20) unsigned NOT NULL, - `backup_id` bigint(20) unsigned NOT NULL, `vm_id` bigint(20) unsigned NOT NULL, `size` bigint(20) DEFAULT 0, `protected_size` bigint(20) DEFAULT 0, `created` datetime NOT NULL, `removed` datetime, PRIMARY KEY (`id`), - INDEX `i_usage_backup` (`zone_id`,`account_id`,`backup_id`,`vm_id`,`created`) + INDEX `i_usage_backup` (`zone_id`,`account_id`,`vm_id`,`created`) ) ENGINE=InnoDB CHARSET=utf8; 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 0985ddb06ece..8ccd7ba9d542 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 @@ -68,24 +68,17 @@ public boolean assignVMToBackupOffering(VirtualMachine vm, BackupOffering backup } @Override - public boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String restorePointId) { - s_logger.debug("Restoring vm " + vm.getUuid() + "from backup " + backupUuid + " on the Dummy Backup Provider"); + public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { + s_logger.debug("Restoring vm " + vm.getUuid() + "from backup " + backup.getUuid() + " on the Dummy Backup Provider"); return true; } @Override - public Pair restoreBackedUpVolume(long zoneId, String restorePointId, String volumeUuid, - String hostIp, String dataStoreUuid) { - s_logger.debug("Restoring volume " + volumeUuid + "from backup " + restorePointId + " on the Dummy Backup Provider"); + public Pair restoreBackedUpVolume(Backup backup, String volumeUuid, String hostIp, String dataStoreUuid) { + s_logger.debug("Restoring volume " + volumeUuid + "from backup " + backup.getUuid() + " on the Dummy Backup Provider"); return new Pair<>(true, null); } - @Override - public List listBackups(Long zoneId, VirtualMachine vm) { - s_logger.debug("Listing VM " + vm.getInstanceName() + "backups on the Dummy Backup Provider"); - return backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); - } - @Override public Map getBackupMetrics(Long zoneId, List backupList) { final Map metrics = new HashMap<>(); @@ -125,7 +118,7 @@ public boolean deleteBackup(Backup backup) { } @Override - public List listBackupRestorePoints(String backupUuid, VirtualMachine vm) { + public List listRestorePoints(VirtualMachine vm) { return Arrays.asList( new Backup.RestorePoint("aaaaaaaa", "22/08/2017", "Full"), new Backup.RestorePoint("bbbbbbbb", "23/08/2017", "Incremental")); 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 87fe7ae193c8..03988fec7063 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 @@ -199,23 +199,18 @@ public boolean deleteBackup(Backup backup) { } @Override - public boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String restorePointId) { + public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { + String restorePointId = backup.getExternalId(); return getClient(vm.getDataCenterId()).restoreFullVM(vm.getInstanceName(), restorePointId); } @Override - public Pair restoreBackedUpVolume(long zoneId, String restorePointId, - String volumeUuid, String hostIp, String dataStoreUuid) { - + public Pair restoreBackedUpVolume(Backup backup, String volumeUuid, String hostIp, String dataStoreUuid) { + Long zoneId = backup.getZoneId(); + String restorePointId = backup.getExternalId(); return getClient(zoneId).restoreVMToDifferentLocation(restorePointId, hostIp, dataStoreUuid); } - @Override - public List listBackups(Long zoneId, VirtualMachine vm) { - //return getClient(zoneId).listAllBackups(); - return null; - } - @Override public Map getBackupMetrics(final Long zoneId, final List backupList) { final Map metrics = new HashMap<>(); @@ -230,8 +225,8 @@ public Map getBackupMetrics(final Long zoneId, final List } @Override - public List listBackupRestorePoints(String backupUuid, VirtualMachine vm) { - String backupName = getGuestBackupName(vm.getInstanceName(), backupUuid); + public List listRestorePoints(VirtualMachine vm) { + String backupName = getGuestBackupName(vm.getInstanceName(), vm.getUuid()); return getClient(vm.getDataCenterId()).listRestorePoints(backupName, vm.getInstanceName()); } diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 6bb2bcda37e0..4fd5cc067390 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -1559,7 +1559,7 @@ public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId) { } // if target VM has backups - if (vm.getBackupOfferingId() != null) { + if (vm.getBackupOfferingId() != null || vm.getBackupVolumes().size() > 0) { throw new InvalidParameterValueException("Unable to attach volume, please specify a VM that does not have any backups"); } @@ -1803,7 +1803,7 @@ public Volume detachVolumeFromVM(DetachVolumeCmd cmmd) { throw new InvalidParameterValueException("Unable to detach volume, please specify a VM that does not have VM snapshots"); } - if (vm.getBackupOfferingId() != null) { + if (vm.getBackupOfferingId() != null || vm.getBackupVolumes().size() > 0) { throw new InvalidParameterValueException("Unable to detach volume, cannot detach volume from a VM that has backups. First remove the VM from the backup offering."); } 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 401a0c4edc57..6bc4f6cd4349 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -41,6 +41,7 @@ import org.apache.cloudstack.api.command.user.backup.RemoveVirtualMachineFromBackupOfferingCmd; import org.apache.cloudstack.api.command.user.backup.RestoreBackupCmd; import org.apache.cloudstack.api.command.user.backup.RestoreVolumeFromBackupAndAttachToVMCmd; +import org.apache.cloudstack.api.command.user.backup.UpdateBackupScheduleCmd; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao; @@ -66,7 +67,9 @@ import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.HypervisorGuru; import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.projects.Project; import com.cloud.storage.ScopeType; +import com.cloud.storage.SnapshotVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.DiskOfferingDao; @@ -77,6 +80,7 @@ import com.cloud.user.AccountService; import com.cloud.utils.DateUtil; import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.Filter; import com.cloud.utils.db.SearchBuilder; @@ -281,6 +285,7 @@ public boolean removeVMFromBackupOffering(final Long vmId, final boolean forced) vm.setBackupOfferingId(null); vm.setBackupExternalId(null); + vm.setBackupVolumes(null); boolean result = backupProvider.removeVMFromBackupOffering(vm); if (result && vmInstanceDao.update(vm.getId(), vm)) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), @@ -292,8 +297,8 @@ public boolean removeVMFromBackupOffering(final Long vmId, final boolean forced) } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_CREATE, eventDescription = "creating VM backup schedule", async = true) - public BackupSchedule createBackupSchedule(CreateBackupScheduleCmd cmd) { + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_CONFIGURE, eventDescription = "configuring VM backup schedule", async = true) + public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) { final Long vmId = cmd.getVmId(); final DateUtil.IntervalType intervalType = cmd.getIntervalType(); final String scheduleString = cmd.getSchedule(); @@ -309,12 +314,7 @@ public BackupSchedule createBackupSchedule(CreateBackupScheduleCmd cmd) { } accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); - final BackupSchedule oldSchedule = backupScheduleDao.findByVM(vmId); - if (oldSchedule != null) { - throw new CloudRuntimeException("VM already has a backup schedule"); - } - - String timezoneId = timeZone.getID(); + final String timezoneId = timeZone.getID(); if (!timezoneId.equals(cmd.getTimezone())) { LOG.warn("Using timezone: " + timezoneId + " for running this snapshot policy as an equivalent of " + cmd.getTimezone()); } @@ -325,7 +325,16 @@ public BackupSchedule createBackupSchedule(CreateBackupScheduleCmd cmd) { throw new InvalidParameterValueException("Invalid schedule: " + cmd.getSchedule() + " for interval type: " + cmd.getIntervalType()); } - return backupScheduleDao.persist(new BackupScheduleVO(vmId, intervalType, scheduleString, timezoneId)); + final BackupScheduleVO schedule = backupScheduleDao.findByVM(vmId); + if (schedule == null) { + return backupScheduleDao.persist(new BackupScheduleVO(vmId, intervalType, scheduleString, timezoneId)); + } + + schedule.setScheduleType(intervalType); + schedule.setSchedule(scheduleString); + schedule.setTimezone(timezoneId); + backupScheduleDao.update(schedule.getId(), schedule); + return backupScheduleDao.findByVM(vmId); } @Override @@ -366,6 +375,10 @@ public boolean createBackup(final Long vmId) { accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + if (vm.getBackupOfferingId() == null) { + throw new CloudRuntimeException("VM has not backup offering configured, cannot create backup before assigning it to a backup offering"); + } + final BackupOffering offering = backupOfferingDao.findById(vm.getBackupOfferingId()); if (offering == null) { throw new CloudRuntimeException("VM backup offering not found"); @@ -378,23 +391,54 @@ public boolean createBackup(final Long vmId) { throw new CloudRuntimeException("Failed to create VM backup"); } - @Override - public List listBackups(final Long id, final Long vmId) { - final Account callerAccount = CallContext.current().getCallingAccount(); - final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); - if (vm == null) { - if (id != null) { - return Collections.singletonList(backupDao.findById(id)); - } - if (accountService.isRootAdmin(callerAccount.getId())) { - return new ArrayList<>(backupDao.listAll()); - } else { - return new ArrayList<>(backupDao.listByAccountId(callerAccount.getId())); + public List listBackups(final ListBackupsCmd cmd) { + final Long id = cmd.getId(); + final Long vmId = cmd.getVmId(); + final Long zoneId = cmd.getZoneId(); + final Account caller = CallContext.current().getCallingAccount(); + List permittedAccounts = new ArrayList(); + + if (vmId != null) { + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm != null) { + accountManager.checkAccess(caller, null, true, vm); } } - accountManager.checkAccess(callerAccount, null, true, vm); - return backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + + Ternary domainIdRecursiveListProject = new Ternary(cmd.getDomainId(), + cmd.isRecursive(), null); + accountManager.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false); + Long domainId = domainIdRecursiveListProject.first(); + Boolean isRecursive = domainIdRecursiveListProject.second(); + Project.ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third(); + + Filter searchFilter = new Filter(SnapshotVO.class, "created", false, cmd.getStartIndex(), cmd.getPageSizeVal()); + SearchBuilder sb = backupDao.createSearchBuilder(); + accountManager.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); + + sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); + sb.and("idIN", sb.entity().getId(), SearchCriteria.Op.IN); + sb.and("vmId", sb.entity().getVmId(), SearchCriteria.Op.EQ); + sb.and("zoneId", sb.entity().getZoneId(), SearchCriteria.Op.EQ); + + SearchCriteria sc = sb.create(); + accountManager.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); + + if (id != null) { + sc.setParameters("id", id); + } + + if (vmId != null) { + sc.setParameters("vmId", vmId); + } + + if (zoneId != null) { + sc.setParameters("zoneId", zoneId); + } + + Pair, Integer> result = backupDao.searchAndCount(sc, searchFilter); + return new ArrayList<>(result.first()); } public boolean importRestoredVM(long zoneId, long domainId, long accountId, long userId, @@ -415,7 +459,7 @@ public boolean importRestoredVM(long zoneId, long domainId, long accountId, long @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true) - public boolean restoreBackup(final Long backupId, final String restorePointId) { + public boolean restoreBackup(final Long backupId) { final BackupVO backup = backupDao.findById(backupId); if (backup == null) { throw new CloudRuntimeException("Backup " + backupId + " does not exist"); @@ -437,7 +481,7 @@ public boolean restoreBackup(final Long backupId, final String restorePointId) { throw new CloudRuntimeException("Failed to find VM backup offering"); } final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); - if (!backupProvider.restoreVMFromBackup(vm, backup.getExternalId(), restorePointId)) { + if (!backupProvider.restoreVMFromBackup(vm, backup)) { throw new CloudRuntimeException("Error restoring VM " + vm.getId() + " from backup " + backup.getId()); } return importRestoredVM(vm.getDataCenterId(), vm.getDomainId(), vm.getAccountId(), vm.getUserId(), @@ -455,32 +499,37 @@ private Backup.VolumeInfo getVolumeInfo(List backedUpVolumes, @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true) - public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long vmId, final Long backupId, final String restorePointId) throws Exception { + public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long backupId, final Long vmId) throws Exception { if (Strings.isNullOrEmpty(backedUpVolumeUuid)) { throw new CloudRuntimeException("Invalid volume ID passed"); } - BackupVO backup = backupDao.findById(backupId); + final BackupVO backup = backupDao.findById(backupId); if (backup == null) { - throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + throw new CloudRuntimeException("Provided backup not found"); } - VMInstanceVO vm = vmInstanceDao.findById(vmId); + + final VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { - throw new CloudRuntimeException("VM " + vmId + " does not exist"); + throw new CloudRuntimeException("Provided VM not found"); } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); - VMInstanceVO vmFromBackup = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); - if (vmFromBackup != null) { - accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vmFromBackup); + if (backup.getZoneId() != vm.getDataCenterId()) { + throw new CloudRuntimeException("Cross zone backup restoration of volume is not allowed"); } - accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + final VMInstanceVO vmFromBackup = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); + if (vmFromBackup == null) { + throw new CloudRuntimeException("VM reference for the provided VM backup not found"); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vmFromBackup); Pair restoreInfo = getRestoreVolumeHostAndDatastore(vm); String hostIp = restoreInfo.first(); String datastoreUuid = restoreInfo.second(); - LOG.debug("Asking provider to restore volume " + backedUpVolumeUuid + " from backup " + backupId + - " and restore point " + restorePointId + " and attach it to VM: " + vm.getUuid()); + " (with external ID " + backup.getExternalId() + ") and attach it to VM: " + vm.getUuid()); final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(vm.getBackupOfferingId()); if (offering == null) { @@ -488,11 +537,11 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, } BackupProvider backupProvider = getBackupProvider(offering.getProvider()); - Pair result = backupProvider.restoreBackedUpVolume(vm.getDataCenterId(), restorePointId, backedUpVolumeUuid, hostIp, datastoreUuid); + Pair result = backupProvider.restoreBackedUpVolume(backup, backedUpVolumeUuid, hostIp, datastoreUuid); if (!result.first()) { throw new CloudRuntimeException("Error restoring volume " + backedUpVolumeUuid); } - if (!attachVolumeToVM(vmFromBackup.getDataCenterId(), result.second(), vmFromBackup.getBackupVolumes(), + if (!attachVolumeToVM(vm.getDataCenterId(), result.second(), vmFromBackup.getBackupVolumes(), backedUpVolumeUuid, vm, datastoreUuid, backup)) { throw new CloudRuntimeException("Error attaching volume " + backedUpVolumeUuid + " to VM " + vm.getUuid()); } @@ -524,8 +573,7 @@ public boolean deleteBackup(final Long backupId) { return true; } } - // Let GC task handle removal backup.setRemoved(new Date()); - return backupDao.update(backup.getId(), backup); + throw new CloudRuntimeException("Failed to delete the backup"); } /** @@ -621,16 +669,17 @@ public List> getCommands() { // Assignment cmdList.add(AssignVirtualMachineToBackupOfferingCmd.class); cmdList.add(RemoveVirtualMachineFromBackupOfferingCmd.class); + // Schedule + cmdList.add(CreateBackupScheduleCmd.class); + cmdList.add(UpdateBackupScheduleCmd.class); + cmdList.add(ListBackupScheduleCmd.class); + cmdList.add(DeleteBackupScheduleCmd.class); // Operations cmdList.add(CreateBackupCmd.class); cmdList.add(ListBackupsCmd.class); cmdList.add(RestoreBackupCmd.class); cmdList.add(DeleteBackupCmd.class); cmdList.add(RestoreVolumeFromBackupAndAttachToVMCmd.class); - // Schedule - cmdList.add(CreateBackupScheduleCmd.class); - cmdList.add(ListBackupScheduleCmd.class); - cmdList.add(DeleteBackupScheduleCmd.class); return cmdList; } diff --git a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java index 7ec6fbe97985..29e23bafd0dc 100644 --- a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java +++ b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java @@ -29,6 +29,7 @@ import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.concurrent.ExecutionException; @@ -505,6 +506,7 @@ public void testResourceLimitCheckForUploadedVolume() throws NoSuchFieldExceptio when(vm.getState()).thenReturn(State.Running); when(vm.getDataCenterId()).thenReturn(34L); when(vm.getBackupOfferingId()).thenReturn(null); + when(vm.getBackupVolumes()).thenReturn(Collections.emptyList()); when(volumeDaoMock.findByInstanceAndType(anyLong(), any(Volume.Type.class))).thenReturn(new ArrayList<>(10)); when(volumeDataFactoryMock.getVolume(9L)).thenReturn(volumeToAttach); when(volumeToAttach.getState()).thenReturn(Volume.State.Uploaded); diff --git a/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java b/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java index fd55faf81003..ad7aa179dce0 100644 --- a/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java +++ b/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java @@ -1901,8 +1901,7 @@ private void createVmSnapshotOnPrimaryEvent(UsageEventVO event) { } private void createBackupEvent(final UsageEventVO event) { - Long backupId = event.getResourceId(); - Long vmId = event.getTemplateId(); + Long vmId = event.getResourceId(); Long zoneId = event.getZoneId(); Long accountId = event.getAccountId(); Date created = event.getCreateDate(); @@ -1910,10 +1909,10 @@ private void createBackupEvent(final UsageEventVO event) { Long domainId = account.getDomainId(); if (EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN.equals(event.getType())) { - final UsageBackupVO backupVO = new UsageBackupVO(zoneId, accountId, domainId, backupId, vmId, created); + final UsageBackupVO backupVO = new UsageBackupVO(zoneId, accountId, domainId, vmId, created); usageBackupDao.persist(backupVO); } else if (EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE.equals(event.getType())) { - usageBackupDao.removeUsage(accountId, zoneId, backupId); + usageBackupDao.removeUsage(accountId, zoneId, vmId); } } From a57ae370039d41d3591b7d81215bdaeb1fe8b5e5 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 5 Dec 2019 22:30:10 +0530 Subject: [PATCH 21/81] cleanup: remove unused class Signed-off-by: Rohit Yadav --- .../cloudstack/backup/veeam/VeeamBackup.java | 61 ------------------- .../cloudstack/backup/veeam/VeeamClient.java | 7 +-- 2 files changed, 2 insertions(+), 66 deletions(-) delete mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java deleted file mode 100644 index 6098ba2507af..000000000000 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java +++ /dev/null @@ -1,61 +0,0 @@ -// 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.veeam; - -import org.apache.cloudstack.backup.Backup; - -public class VeeamBackup implements Backup { - - private String name; - private String uid; - - public VeeamBackup(String name, String uid) { - this.name = name; - this.uid = uid; - } - - @Override - public String getExternalId() { - return uid; - } - - @Override - public Long getVmId() { - return null; - } - - @Override - public Long getSize() { - return null; - } - - @Override - public Long getProtectedSize() { - return null; - } - - @Override - public String getUuid() { - return uid; - } - - @Override - public long getId() { - return -1; - } -} 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 fdd3a04d399e..09de58645f78 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 @@ -357,23 +357,20 @@ public Ref listBackupRepository(final String backupServerId) { return null; } - public List listAllBackups() { + public void listAllBackups() { LOG.debug("Trying to list Veeam backups"); try { final HttpResponse response = get("/backups"); checkResponseOK(response); final ObjectMapper objectMapper = new XmlMapper(); final EntityReferences entityReferences = objectMapper.readValue(response.getEntity().getContent(), EntityReferences.class); - final List backups = new ArrayList<>(); for (final Ref ref : entityReferences.getRefs()) { - backups.add(new VeeamBackup(ref.getName(), ref.getUid())); + LOG.debug("Veeam Backup found, name: " + ref.getName() + ", uid: " + ref.getUid() + ", type: " + ref.getType()); } - return backups; } catch (final IOException e) { LOG.error("Failed to list Veeam backups due to:", e); checkResponseTimeOut(e); } - return new ArrayList<>(); } public List listJobs() { From 8b0fa1be19aae54c24c1b7f48a5d8003878ce573 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 6 Dec 2019 02:09:54 +0530 Subject: [PATCH 22/81] fix both metrics and backup sync in the thread Signed-off-by: Rohit Yadav --- .../cloudstack/backup/BackupProvider.java | 4 +- .../com/cloud/usage/dao/UsageBackupDao.java | 3 +- .../cloud/usage/dao/UsageBackupDaoImpl.java | 11 ++-- .../java/com/cloud/vm/dao/VMInstanceDao.java | 2 + .../com/cloud/vm/dao/VMInstanceDaoImpl.java | 13 ++++ .../apache/cloudstack/backup/BackupVO.java | 7 --- .../cloudstack/backup/dao/BackupDao.java | 1 - .../cloudstack/backup/dao/BackupDaoImpl.java | 15 +---- .../META-INF/db/schema-41300to41400.sql | 13 ++-- .../backup/DummyBackupProvider.java | 28 +++++++-- .../backup/VeeamBackupProvider.java | 61 ++++++++++++++++-- .../cloudstack/backup/BackupManagerImpl.java | 63 ++++++++++++------- 12 files changed, 154 insertions(+), 67 deletions(-) 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 669dcc5f4347..5bb1d663554a 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -94,7 +94,9 @@ public interface BackupProvider { */ Pair restoreBackedUpVolume(Backup backup, String volumeUuid, String hostIp, String dataStoreUuid); - Map getBackupMetrics(Long zoneId, List backupList); + Map getBackupMetrics(Long zoneId, List vms); List listRestorePoints(VirtualMachine vm); + + void syncBackups(VirtualMachine vm, Backup.Metric metric); } diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDao.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDao.java index b171f22628d1..fb93c01a1226 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDao.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDao.java @@ -24,9 +24,10 @@ import com.cloud.usage.UsageBackupVO; import com.cloud.utils.db.GenericDao; +import com.cloud.vm.VirtualMachine; public interface UsageBackupDao extends GenericDao { - void updateMetrics(Backup backup); + void updateMetrics(VirtualMachine vm, Backup.Metric metric); void removeUsage(Long accountId, Long zoneId, Long backupId); List getUsageRecords(Long accountId, Date startDate, Date endDate); } diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java index e9d94d7e2dcf..13859ed302e5 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java @@ -37,6 +37,7 @@ import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.TransactionStatus; +import com.cloud.vm.VirtualMachine; @Component public class UsageBackupDaoImpl extends GenericDaoBase implements UsageBackupDao { @@ -46,23 +47,23 @@ public class UsageBackupDaoImpl extends GenericDaoBase impl " OR ((created <= ?) AND (removed >= ?)))"; @Override - public void updateMetrics(final Backup backup) { + public void updateMetrics(final VirtualMachine vm, Backup.Metric metric) { boolean result = Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { @Override public Boolean doInTransaction(final TransactionStatus status) { final QueryBuilder qb = QueryBuilder.create(UsageBackupVO.class); - qb.and(qb.entity().getVmId(), SearchCriteria.Op.EQ, backup.getVmId()); + qb.and(qb.entity().getVmId(), SearchCriteria.Op.EQ, vm.getId()); final UsageBackupVO entry = findOneBy(qb.create()); if (entry == null) { return false; } - entry.setSize(backup.getSize()); - entry.setProtectedSize(backup.getProtectedSize()); + entry.setSize(metric.getBackupSize()); + entry.setProtectedSize(metric.getDataSize()); return update(entry.getId(), entry); } }); if (!result) { - LOGGER.warn("Failed to update VM Backup metrics for backup id: " + backup.getId()); + LOGGER.warn("Failed to update Backup metrics for VM ID: " + vm.getId()); } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java index ce4d46abc250..01da445dc77f 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java @@ -112,6 +112,8 @@ public interface VMInstanceDao extends GenericDao, StateDao< List listVmsMigratingFromHost(Long hostId); + List listByZoneWithBackups(Long zoneId); + public Long countActiveByHostId(long hostId); Pair, Map> listClusterIdsInZoneByVmCount(long zoneId, long accountId); diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java index 405e023f10a0..06cf4992c950 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -94,6 +94,7 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem protected SearchBuilder HostAndStateSearch; protected SearchBuilder StartingWithNoHostSearch; protected SearchBuilder NotMigratingSearch; + protected SearchBuilder BackupSearch; @Inject ResourceTagDao _tagsDao; @@ -286,6 +287,11 @@ protected void init() { NotMigratingSearch.and("lastHost", NotMigratingSearch.entity().getLastHostId(), Op.EQ); NotMigratingSearch.and("state", NotMigratingSearch.entity().getState(), Op.NEQ); NotMigratingSearch.done(); + + BackupSearch = createSearchBuilder(); + BackupSearch.and("zone_id", BackupSearch.entity().getDataCenterId(), Op.EQ); + BackupSearch.and("backup_offering_id", BackupSearch.entity().getBackupOfferingId(), Op.NNULL); + BackupSearch.done(); } @Override @@ -577,6 +583,13 @@ public List listVmsMigratingFromHost(Long hostId) { return listBy(sc); } + @Override + public List listByZoneWithBackups(Long zoneId) { + SearchCriteria sc = BackupSearch.create(); + sc.setParameters("zone_id", zoneId); + return listBy(sc); + } + @Override public Long countActiveByHostId(long hostId) { SearchCriteria sc = CountActiveByHost.create(); 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 78479d7ee824..e56f55cbf597 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 @@ -77,13 +77,6 @@ public BackupVO() { this.uuid = UUID.randomUUID().toString(); } - public BackupVO(final Long vmId, final String externalId) { - this.uuid = UUID.randomUUID().toString(); - this.vmId = vmId; - this.externalId = externalId; - this.status = Status.BackedUp; - } - @Override public long getId() { return id; 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 6ac9befc353a..5d2f5ac64d61 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 @@ -34,7 +34,6 @@ public interface BackupDao extends GenericDao { List listByAccountId(Long accountId); List listByOfferingId(Long offeringId); List syncBackups(Long zoneId, Long vmId, List externalBackups); - List listByZoneAndState(Long zoneId, Backup.Status state); BackupVO getBackupVO(Backup backup); BackupResponse newBackupResponse(Backup backup); 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 f888b2cb4c4e..92fe3c886408 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 @@ -95,7 +95,9 @@ public Backup findByVmIdIncludingRemoved(Long vmId) { public List listByVmId(Long zoneId, Long vmId) { SearchCriteria sc = backupSearch.create(); sc.setParameters("vm_id", vmId); - sc.setParameters("zone_id", zoneId); + if (zoneId != null) { + sc.setParameters("zone_id", zoneId); + } return new ArrayList<>(listBy(sc)); } @@ -136,17 +138,6 @@ public List syncBackups(Long zoneId, Long vmId, List externalBac return listByVmId(zoneId, vmId); } - @Override - public List listByZoneAndState(Long zoneId, Backup.Status state) { - SearchCriteria sc = backupSearch.create(); - sc.setParameters("zone_id", zoneId); - if (state != null) { - sc.setParameters("status", state); - return new ArrayList<>(listIncludingRemovedBy(sc)); - } - return new ArrayList<>(listBy(sc)); - } - @Override public BackupResponse newBackupResponse(Backup backup) { VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql index 51f6e9a99cf9..1e1695768595 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql @@ -35,7 +35,6 @@ CREATE TABLE IF NOT EXISTS `cloud`.`backup_offering` ( `created` datetime DEFAULT NULL, `removed` datetime DEFAULT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), CONSTRAINT `fk_backup_offering__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -52,12 +51,12 @@ CREATE TABLE IF NOT EXISTS `cloud`.`backups` ( `date` varchar(255) NOT NULL COMMENT 'backup date', `size` bigint(20) DEFAULT 0 COMMENT 'size of the backup', `protected_size` bigint(20) DEFAULT 0, - `status` varchar(32) DEFAULT NULL COMMENT 'backup status', - `backup_offering_id` bigint(20) unsigned NOT NULL COMMENT, - `account_id` bigint(20) unsigned NOT NULL COMMENT, - `domain_id` bigint(20) unsigned NOT NULL COMMENT, + `status` varchar(32) DEFAULT NULL, + `backup_offering_id` bigint(20) unsigned NOT NULL, + `account_id` bigint(20) unsigned NOT NULL, + `domain_id` bigint(20) unsigned NOT NULL, `zone_id` bigint(20) unsigned NOT NULL, - `removed` datetime DEFAULT NULL COMMENT 'Date removed. not null if removed', + `removed` datetime DEFAULT NULL, PRIMARY KEY (`id`), CONSTRAINT `fk_backup__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance` (`id`) ON DELETE CASCADE, CONSTRAINT `fk_backup__account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE @@ -72,7 +71,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`backup_schedule` ( `scheduled_timestamp` datetime DEFAULT NULL COMMENT 'Time at which the backup was scheduled for execution', `async_job_id` bigint(20) unsigned DEFAULT NULL COMMENT 'If this schedule is being executed, it is the id of the create aysnc_job. Before that it is null', PRIMARY KEY (`id`), - CONSTRAINT `fk_backup__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance` (`id`) ON DELETE CASCADE + CONSTRAINT `fk_backup_schedule__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `cloud_usage`.`usage_backup` ( 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 8ccd7ba9d542..7fd7b5e0ff8b 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 @@ -17,6 +17,7 @@ package org.apache.cloudstack.backup; import java.util.Arrays; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -80,11 +81,11 @@ public Pair restoreBackedUpVolume(Backup backup, String volumeU } @Override - public Map getBackupMetrics(Long zoneId, List backupList) { - final Map metrics = new HashMap<>(); + public Map getBackupMetrics(Long zoneId, List vms) { + final Map metrics = new HashMap<>(); final Backup.Metric metric = new Backup.Metric(1000L, 100L); - for (Backup backup : backupList) { - metrics.put(backup, metric); + for (VirtualMachine vm : vms) { + metrics.put(vm, metric); } return metrics; } @@ -109,7 +110,19 @@ public boolean willDeleteBackupsOnOfferingRemoval() { @Override public boolean takeBackup(VirtualMachine vm) { s_logger.debug("Starting backup for VM ID " + vm.getUuid() + " on Dummy provider"); - return true; + + BackupVO backup = new BackupVO(); + backup.setVmId(vm.getId()); + backup.setType("FULL"); + backup.setDate(new Date().toString()); + backup.setStatus(Backup.Status.BackedUp); + backup.setSize(1024L); + backup.setProtectedSize(1024000L); + backup.setBackupOfferingId(vm.getBackupOfferingId()); + backup.setAccountId(vm.getAccountId()); + backup.setDomainId(vm.getDomainId()); + backup.setZoneId(vm.getDataCenterId()); + return backupDao.persist(backup) != null; } @Override @@ -123,4 +136,9 @@ public List listRestorePoints(VirtualMachine vm) { new Backup.RestorePoint("aaaaaaaa", "22/08/2017", "Full"), new Backup.RestorePoint("bbbbbbbb", "23/08/2017", "Incremental")); } + + @Override + public void syncBackups(VirtualMachine vm, Backup.Metric metric) { + + } } 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 03988fec7063..717e5ff84ac5 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 @@ -24,9 +24,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.inject.Inject; +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.veeam.VeeamClient; import org.apache.cloudstack.backup.veeam.api.Job; import org.apache.cloudstack.framework.config.ConfigKey; @@ -41,6 +44,9 @@ import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao; import com.cloud.utils.Pair; import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; @@ -75,6 +81,8 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, private VmwareDatacenterZoneMapDao vmwareDatacenterZoneMapDao; @Inject private VmwareDatacenterDao vmwareDatacenterDao; + @Inject + private BackupDao backupDao; private VeeamClient getClient(final Long zoneId) { try { @@ -212,14 +220,14 @@ public Pair restoreBackedUpVolume(Backup backup, String volumeU } @Override - public Map getBackupMetrics(final Long zoneId, final List backupList) { - final Map metrics = new HashMap<>(); + public Map getBackupMetrics(final Long zoneId, final List vms) { + final Map metrics = new HashMap<>(); final Map backendMetrics = getClient(zoneId).getBackupMetrics(); - for (final Backup backup : backupList) { - if (!backendMetrics.containsKey(backup.getVmId())) { + for (final VirtualMachine vm : vms) { + if (!backendMetrics.containsKey(vm.getUuid())) { continue; } - metrics.put(backup, backendMetrics.get(backup.getVmId())); + metrics.put(vm, backendMetrics.get(vm.getUuid())); } return metrics; } @@ -230,6 +238,49 @@ public List listRestorePoints(VirtualMachine vm) { return getClient(vm.getDataCenterId()).listRestorePoints(backupName, vm.getInstanceName()); } + @Override + public void syncBackups(VirtualMachine vm, Backup.Metric metric) { + List restorePoints = listRestorePoints(vm); + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + final List backupsInDb = backupDao.listByVmId(null, vm.getId()); + final List removeList = backupsInDb.stream().map(InternalIdentity::getId).collect(Collectors.toList()); + for (final Backup.RestorePoint restorePoint : restorePoints) { + boolean backupExists = false; + for (final Backup backup : backupsInDb) { + if (restorePoint.getId().equals(backup.getExternalId())) { + backupExists = true; + removeList.remove(backup.getId()); + break; + } + } + if (backupExists) { + continue; + } + BackupVO backup = new BackupVO(); + backup.setVmId(vm.getId()); + backup.setExternalId(restorePoint.getId()); + backup.setType(restorePoint.getType()); + backup.setDate(restorePoint.getCreated()); + backup.setStatus(Backup.Status.BackedUp); + if (metric != null) { + backup.setSize(metric.getBackupSize()); + backup.setProtectedSize(metric.getDataSize()); + } + backup.setBackupOfferingId(vm.getBackupOfferingId()); + backup.setAccountId(vm.getAccountId()); + backup.setDomainId(vm.getDomainId()); + backup.setZoneId(vm.getDataCenterId()); + backupDao.persist(backup); + } + for (final Long backupIdToRemove : removeList) { + backupDao.remove(backupIdToRemove); + } + } + }); + } + @Override public String getConfigComponentName() { return BackupService.class.getSimpleName(); 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 6bc4f6cd4349..569f9a74097f 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -67,6 +67,7 @@ import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.HypervisorGuru; import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.org.Grouping; import com.cloud.projects.Project; import com.cloud.storage.ScopeType; import com.cloud.storage.SnapshotVO; @@ -83,6 +84,7 @@ import com.cloud.utils.Ternary; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GlobalLock; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.exception.CloudRuntimeException; @@ -568,10 +570,7 @@ public boolean deleteBackup(final Long backupId) { final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); boolean result = backupProvider.deleteBackup(backup); if (result) { - if (backupDao.update(backup.getId(), backup)) { - backupDao.remove(backup.getId()); - return true; - } + return backupDao.remove(backup.getId()); } throw new CloudRuntimeException("Failed to delete the backup"); } @@ -632,8 +631,8 @@ public boolean configure(String name, Map params) throws Configu return true; } - public boolean isEnabled(final Long zoneId) { - return BackupFrameworkEnabled.valueIn(zoneId); + public boolean isDisabled(final DataCenter zone) { + return zone == null || Grouping.AllocationState.Disabled.equals(zone.getAllocationState()) || !BackupFrameworkEnabled.valueIn(zone.getId()); } @Override @@ -732,34 +731,52 @@ public BackupSyncTask(final BackupManager backupManager) { @Override protected void runInContext() { + final int SYNC_INTERVAL = BackupSyncPollingInterval.value().intValue(); try { if (LOG.isTraceEnabled()) { LOG.trace("Backup sync background task is running..."); } for (final DataCenter dataCenter : dataCenterDao.listAllZones()) { - if (dataCenter == null || !isEnabled(dataCenter.getId())) { + if (isDisabled(dataCenter)) { continue; } - // TODO: Check and schedule backups per user-defined backup schedule - - // TODO: sync backups backupProvider.listBackupRestorePoints(vm.getUuid(), vm); - + final BackupProvider backupProvider = getBackupProvider(dataCenter.getId()); + if (backupProvider == null) { + LOG.warn("Backup provider not available or configured for zone ID " + dataCenter.getId()); + continue; + } - // Sync backup size usages - final List backups = backupDao.listByZoneAndState(dataCenter.getId(), null); - if (backups.isEmpty()) { + List vms = vmInstanceDao.listByZoneWithBackups(dataCenter.getId()); + if (vms == null || vms.isEmpty()) { continue; } - final BackupProvider backupProvider = getBackupProvider(dataCenter.getId()); - final Map metrics = backupProvider.getBackupMetrics(dataCenter.getId(), backups); - for (final Backup backup : metrics.keySet()) { - final Backup.Metric metric = metrics.get(backup); - final BackupVO backupVO = (BackupVO) backup; - backupVO.setSize(metric.getBackupSize()); - backupVO.setProtectedSize(metric.getDataSize()); - if (backupDao.update(backupVO.getId(), backupVO)) { - usageBackupDao.updateMetrics(backup); + + // Sync backup usage metrics + final Map metrics = backupProvider.getBackupMetrics(dataCenter.getId(), new ArrayList<>(vms)); + final GlobalLock syncBackupMetricsLock = GlobalLock.getInternLock("BackupSyncTask_metrics_zone_" + dataCenter.getId()); + if (syncBackupMetricsLock.lock(SYNC_INTERVAL)) { + try { + for (final VirtualMachine vm : metrics.keySet()) { + final Backup.Metric metric = metrics.get(vm); + if (metric != null) { + usageBackupDao.updateMetrics(vm, metric); + } + } + } finally { + syncBackupMetricsLock.unlock(); + } + } + + // Sync out-of-band backups + for (final VirtualMachine vm : vms) { + final GlobalLock syncBackupsLock = GlobalLock.getInternLock("BackupSyncTask_backup_vm_" + vm.getId()); + if (syncBackupsLock.lock(SYNC_INTERVAL)) { + try { + backupProvider.syncBackups(vm, metrics.get(vm)); + } finally { + syncBackupsLock.unlock(); + } } } } From b66e8d74dd1a7513c15a4538d16d03965b71ebf0 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 6 Dec 2019 04:14:20 +0530 Subject: [PATCH 23/81] misc fixes Signed-off-by: Rohit Yadav --- .../org/apache/cloudstack/api/BaseBackupListCmd.java | 4 ++-- .../admin/backup/ListBackupProviderOfferingsCmd.java | 2 +- .../command/user/backup/ListBackupOfferingsCmd.java | 5 +++-- .../api/command/user/backup/ListBackupsCmd.java | 9 +++++---- .../org/apache/cloudstack/backup/BackupManager.java | 5 +++-- .../src/main/java/com/cloud/vm/VMInstanceVO.java | 6 +++--- .../apache/cloudstack/backup/BackupManagerImpl.java | 10 +++++----- 7 files changed, 22 insertions(+), 19 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java index 2b4245e3a219..0aa8366bcd5c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java @@ -27,7 +27,7 @@ public abstract class BaseBackupListCmd extends BaseListCmd { - protected void setupResponseBackupOfferingsList(final List offerings) { + protected void setupResponseBackupOfferingsList(final List offerings, final Integer count) { final ListResponse response = new ListResponse<>(); final List responses = new ArrayList<>(); for (final BackupOffering offering : offerings) { @@ -37,7 +37,7 @@ protected void setupResponseBackupOfferingsList(final List offer BackupOfferingResponse backupOfferingResponse = _responseGenerator.createBackupOfferingResponse(offering); responses.add(backupOfferingResponse); } - response.setResponses(responses); + response.setResponses(responses, count); response.setResponseName(getCommandName()); setResponseObject(response); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java index 5df57cae6ddc..2e5657d7d79f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java @@ -79,7 +79,7 @@ public void execute() throws ResourceUnavailableException, ServerApiException, C validateParameters(); try { final List backupOfferings = backupManager.listBackupProviderOfferings(getZoneId()); - setupResponseBackupOfferingsList(backupOfferings); + setupResponseBackupOfferingsList(backupOfferings, backupOfferings.size()); } catch (InvalidParameterValueException e) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage()); } catch (CloudRuntimeException e) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java index bd4d8ebc18f9..e745a6ba66dc 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java @@ -36,6 +36,7 @@ import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; @APICommand(name = ListBackupOfferingsCmd.APINAME, @@ -79,8 +80,8 @@ public Long getOfferingId() { @Override public void execute() throws ResourceUnavailableException, ServerApiException, ConcurrentOperationException { try { - final List backupOfferings = backupManager.listBackupOfferings(this); - setupResponseBackupOfferingsList(backupOfferings); + Pair, Integer> result = backupManager.listBackupOfferings(this); + setupResponseBackupOfferingsList(result.first(), result.second()); } catch (InvalidParameterValueException e) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage()); } catch (CloudRuntimeException e) { 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 b4195e2b88f5..1e1e7312897d 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 @@ -43,6 +43,7 @@ import com.cloud.exception.NetworkRuleConflictException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.Pair; @APICommand(name = ListBackupsCmd.APINAME, description = "Lists VM backups", @@ -96,7 +97,7 @@ public Long getZoneId() { /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// - protected void setupResponseBackupList(final List backups) { + protected void setupResponseBackupList(final List backups, final Integer count) { final List responses = new ArrayList<>(); for (Backup backup : backups) { if (backup == null) { @@ -106,7 +107,7 @@ protected void setupResponseBackupList(final List backups) { responses.add(backupResponse); } final ListResponse response = new ListResponse<>(); - response.setResponses(responses); + response.setResponses(responses, count); response.setResponseName(getCommandName()); setResponseObject(response); } @@ -114,8 +115,8 @@ protected void setupResponseBackupList(final List backups) { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try{ - List backups = backupManager.listBackups(this); - setupResponseBackupList(backups); + Pair, Integer> result = backupManager.listBackups(this); + setupResponseBackupList(result.first(), result.second()); } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } 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 63f1cbea5217..2fb343f72ff5 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -25,6 +25,7 @@ import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; +import com.cloud.utils.Pair; import com.cloud.utils.component.Manager; import com.cloud.utils.component.PluggableService; @@ -68,7 +69,7 @@ BackupOffering importBackupOffering(final Long zoneId, final String offeringExte * List backup offerings * @param ListBackupOfferingsCmd API cmd */ - List listBackupOfferings(final ListBackupOfferingsCmd cmd); + Pair, Integer> listBackupOfferings(final ListBackupOfferingsCmd cmd); /** * Deletes a backup offering @@ -122,7 +123,7 @@ BackupOffering importBackupOffering(final Long zoneId, final String offeringExte /** * List existing backups for a VM */ - List listBackups(final ListBackupsCmd cmd); + Pair, Integer> listBackups(final ListBackupsCmd cmd); /** * Restore a full VM from backup diff --git a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java index 469e4351905f..0e2ce2e73f82 100644 --- a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java @@ -198,10 +198,10 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject getBackupVolumes() { } public void setBackupVolumes(List backupVolumes) { - if (backupVolumes == null) { + if (backupVolumes == null || backupVolumes.isEmpty()) { this.backupVolumes = null; } else { this.backupVolumes = new Gson().toJson(backupVolumes); 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 569f9a74097f..77e427ffff2d 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -170,7 +170,7 @@ public BackupOffering importBackupOffering(final Long zoneId, final String offer } @Override - public List listBackupOfferings(final ListBackupOfferingsCmd cmd) { + public Pair, Integer> listBackupOfferings(final ListBackupOfferingsCmd cmd) { final Long offeringId = cmd.getOfferingId(); final Long zoneId = cmd.getZoneId(); final String keyword = cmd.getKeyword(); @@ -180,7 +180,7 @@ public List listBackupOfferings(final ListBackupOfferingsCmd cmd if (offering == null) { throw new CloudRuntimeException("Offering ID " + offeringId + " does not exist"); } - return Collections.singletonList(offering); + return new Pair<>(Collections.singletonList(offering), 1); } final Filter searchFilter = new Filter(BackupOfferingVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal()); @@ -198,7 +198,7 @@ public List listBackupOfferings(final ListBackupOfferingsCmd cmd sc.setParameters("name", "%" + keyword + "%"); } Pair, Integer> result = backupOfferingDao.searchAndCount(sc, searchFilter); - return new ArrayList<>(result.first()); + return new Pair<>(new ArrayList<>(result.first()), result.second()); } @Override @@ -394,7 +394,7 @@ public boolean createBackup(final Long vmId) { } @Override - public List listBackups(final ListBackupsCmd cmd) { + public Pair, Integer> listBackups(final ListBackupsCmd cmd) { final Long id = cmd.getId(); final Long vmId = cmd.getVmId(); final Long zoneId = cmd.getZoneId(); @@ -440,7 +440,7 @@ public List listBackups(final ListBackupsCmd cmd) { } Pair, Integer> result = backupDao.searchAndCount(sc, searchFilter); - return new ArrayList<>(result.first()); + return new Pair<>(new ArrayList<>(result.first()), result.second()); } public boolean importRestoredVM(long zoneId, long domainId, long accountId, long userId, From 00c87b8efd9df91f30766a7883009153c32abe14 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 6 Dec 2019 04:54:27 +0530 Subject: [PATCH 24/81] fixes Signed-off-by: Rohit Yadav --- .../schema/src/main/java/com/cloud/vm/VMInstanceVO.java | 8 ++------ .../org/apache/cloudstack/backup/VeeamBackupProvider.java | 2 +- .../org/apache/cloudstack/backup/BackupManagerImpl.java | 5 +++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java index 0e2ce2e73f82..38b33169b75d 100644 --- a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java @@ -621,11 +621,7 @@ public List getBackupVolumes() { return Arrays.asList(new Gson().fromJson(this.backupVolumes, Backup.VolumeInfo[].class)); } - public void setBackupVolumes(List backupVolumes) { - if (backupVolumes == null || backupVolumes.isEmpty()) { - this.backupVolumes = null; - } else { - this.backupVolumes = new Gson().toJson(backupVolumes); - } + public void setBackupVolumes(String backupVolumes) { + this.backupVolumes = backupVolumes; } } 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 717e5ff84ac5..14124bde6805 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 @@ -186,7 +186,7 @@ public boolean removeVMFromBackupOffering(final VirtualMachine vm) { LOG.warn("Failed to remove Veeam job and backup for job: " + clonedJobName); throw new CloudRuntimeException("Failed to delete Veeam B&R job and backup, an operation may be in progress. Please try again after some time."); } - return client.listJob(clonedJobName) == null; + return true; } @Override 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 77e427ffff2d..ab9b6dd5510e 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -92,6 +92,7 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.dao.VMInstanceDao; import com.google.common.base.Strings; +import com.google.gson.Gson; @Component public class BackupManagerImpl extends ManagerBase implements BackupManager { @@ -213,12 +214,12 @@ public boolean deleteBackupOffering(final Long offeringId) { return backupOfferingDao.remove(offering.getId()); } - private List createVolumeInfoFromVolumes(List vmVolumes) { + private String createVolumeInfoFromVolumes(List vmVolumes) { List list = new ArrayList<>(); for (VolumeVO vol : vmVolumes) { list.add(new Backup.VolumeInfo(vol.getUuid(), vol.getPath(), vol.getVolumeType(), vol.getSize())); } - return list; + return new Gson().toJson(list.toArray(), Backup.VolumeInfo[].class); } @Override From 4746d053a30c0612cd5801f1dca35d490b586795 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 6 Dec 2019 05:19:19 +0530 Subject: [PATCH 25/81] fix bugz Signed-off-by: Rohit Yadav --- .../apache/cloudstack/backup/VeeamBackupProvider.java | 3 +++ .../apache/cloudstack/backup/veeam/VeeamClient.java | 5 +++-- .../apache/cloudstack/backup/BackupManagerImpl.java | 11 +++++------ 3 files changed, 11 insertions(+), 8 deletions(-) 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 14124bde6805..40e46b354bcd 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 @@ -241,6 +241,9 @@ public List listRestorePoints(VirtualMachine vm) { @Override public void syncBackups(VirtualMachine vm, Backup.Metric metric) { List restorePoints = listRestorePoints(vm); + if (restorePoints == null || restorePoints.isEmpty()) { + return; + } Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { 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 09de58645f78..0d3452bd2c6e 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 @@ -627,10 +627,11 @@ public List listRestorePoints(String backupName, String vmI String.format("Get-VBRRestorePoint -Backup:$backup -Name \"%s\"", vmInternalName) ); Pair response = executePowerShellCommands(cmds); + final List restorePoints = new ArrayList<>(); if (response == null || !response.first()) { - throw new CloudRuntimeException("Failed to list restore points"); + LOG.debug("Veeam restore point listing failed due to: " + response.second()); + return restorePoints; } - final List restorePoints = new ArrayList<>(); for (final String block : response.second().split("\r\n\r\n")) { if (block.isEmpty()) { continue; 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 ab9b6dd5510e..14533ca04212 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -70,7 +70,6 @@ import com.cloud.org.Grouping; import com.cloud.projects.Project; import com.cloud.storage.ScopeType; -import com.cloud.storage.SnapshotVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.DiskOfferingDao; @@ -409,14 +408,14 @@ public Pair, Integer> listBackups(final ListBackupsCmd cmd) { } } - Ternary domainIdRecursiveListProject = new Ternary(cmd.getDomainId(), + final Ternary domainIdRecursiveListProject = new Ternary(cmd.getDomainId(), cmd.isRecursive(), null); accountManager.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false); - Long domainId = domainIdRecursiveListProject.first(); - Boolean isRecursive = domainIdRecursiveListProject.second(); - Project.ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third(); + final Long domainId = domainIdRecursiveListProject.first(); + final Boolean isRecursive = domainIdRecursiveListProject.second(); + final Project.ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third(); - Filter searchFilter = new Filter(SnapshotVO.class, "created", false, cmd.getStartIndex(), cmd.getPageSizeVal()); + final Filter searchFilter = new Filter(BackupVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal()); SearchBuilder sb = backupDao.createSearchBuilder(); accountManager.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); From 0d3934e4673f62d5af7518ff4714deea2d4d0c9e Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 6 Dec 2019 05:29:03 +0530 Subject: [PATCH 26/81] more fixes Signed-off-by: Rohit Yadav --- .../org/apache/cloudstack/api/response/BackupResponse.java | 4 ++-- .../java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java | 2 +- .../org/apache/cloudstack/backup/VeeamBackupProvider.java | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) 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 3dc1945c478c..957d48d18a83 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 @@ -29,7 +29,7 @@ public class BackupResponse extends BaseResponse { @SerializedName(ApiConstants.ID) - @Param(description = "ID of the VM (backup)") + @Param(description = "ID of the VM backup") private String id; @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) @@ -68,7 +68,7 @@ public class BackupResponse extends BaseResponse { @Param(description = "backup offering id") private String backupOfferingId; - @SerializedName(ApiConstants.BACKUP_OFFERING_ID) + @SerializedName(ApiConstants.BACKUP_OFFERING) @Param(description = "backup offering id") private String backupOfferingName; 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 92fe3c886408..05f52942a6c6 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 @@ -147,7 +147,7 @@ public BackupResponse newBackupResponse(Backup backup) { BackupOffering offering = backupOfferingDao.findById(vm.getBackupOfferingId()); BackupResponse response = new BackupResponse(); - response.setId(vm.getUuid()); + response.setId(backup.getUuid()); response.setVmId(vm.getUuid()); response.setVmName(vm.getHostName()); response.setExternalId(backup.getExternalId()); 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 40e46b354bcd..2e0b36224683 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 @@ -255,6 +255,11 @@ public void doInTransactionWithoutResult(TransactionStatus status) { if (restorePoint.getId().equals(backup.getExternalId())) { backupExists = true; removeList.remove(backup.getId()); + if (metric != null) { + ((BackupVO) backup).setSize(metric.getBackupSize()); + ((BackupVO) backup).setProtectedSize(metric.getDataSize()); + backupDao.update(backup.getId(), ((BackupVO) backup)); + } break; } } From 0c2ec5506b0657ecd1fe8f2f69a412b82dff6a5a Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Sun, 8 Dec 2019 02:00:26 +0530 Subject: [PATCH 27/81] refactorings and fixes Signed-off-by: Rohit Yadav --- .../com/cloud/hypervisor/HypervisorGuru.java | 4 ++-- .../main/java/com/cloud/vm/VirtualMachine.java | 4 +++- ...estoreVolumeFromBackupAndAttachToVMCmd.java | 4 ++-- .../api/response/BackupResponse.java | 12 ++++++++++++ .../main/java/com/cloud/vm/VMInstanceVO.java | 7 ++++++- .../cloudstack/backup/BackupScheduleVO.java | 4 ++-- .../cloudstack/backup/dao/BackupDaoImpl.java | 1 + .../engine/cloud/entity/api/db/VMEntityVO.java | 2 +- .../cloudstack/backup/VeeamBackupProvider.java | 6 +++--- .../com/cloud/hypervisor/guru/VMwareGuru.java | 10 +++++----- .../cloud/hypervisor/HypervisorGuruBase.java | 4 ++-- .../cloud/storage/VolumeApiServiceImpl.java | 4 ++-- .../cloudstack/backup/BackupManagerImpl.java | 18 +++++++++++++----- .../storage/VolumeApiServiceImplTest.java | 2 +- 14 files changed, 55 insertions(+), 27 deletions(-) diff --git a/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java b/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java index 97294da229b9..8a109649e969 100644 --- a/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java +++ b/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java @@ -87,8 +87,8 @@ public interface HypervisorGuru extends Adapter { Map getClusterSettings(long vmId); - VirtualMachine importVirtualMachine(long zoneId, long domainId, long accountId, long userId, - String vmInternalName, Backup backup) throws Exception; + VirtualMachine importVirtualMachineFromBackup(long zoneId, long domainId, long accountId, long userId, + String vmInternalName, Backup backup) throws Exception; boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location, Backup.VolumeInfo volumeInfo, VirtualMachine vm, long poolId, Backup backup) throws Exception; diff --git a/api/src/main/java/com/cloud/vm/VirtualMachine.java b/api/src/main/java/com/cloud/vm/VirtualMachine.java index ee4aed2466d1..246482135154 100644 --- a/api/src/main/java/com/cloud/vm/VirtualMachine.java +++ b/api/src/main/java/com/cloud/vm/VirtualMachine.java @@ -326,7 +326,9 @@ public boolean isUsedBySystem() { String getBackupExternalId(); - List getBackupVolumes(); + String getBackupVolumes(); + + List getBackupVolumeList(); Type getType(); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java index fc8dc65751d4..b5966a737df0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java @@ -59,13 +59,13 @@ public class RestoreVolumeFromBackupAndAttachToVMCmd extends BaseAsyncCmd { type = CommandType.UUID, entityType = BackupResponse.class, required = true, - description = "id of the backup") + description = "ID of the VM backup") private Long backupId; @Parameter(name = ApiConstants.VOLUME_ID, type = CommandType.STRING, required = true, - description = "id of the volume backed up") + description = "ID of the volume backed up") private String volumeUuid; @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, 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 957d48d18a83..624dfe8eb7e1 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 @@ -64,6 +64,10 @@ public class BackupResponse extends BaseResponse { @Param(description = "backup status") private Backup.Status status; + @SerializedName(ApiConstants.VOLUMES) + @Param(description = "backed up volumes") + private String volumes; + @SerializedName(ApiConstants.BACKUP_OFFERING_ID) @Param(description = "backup offering id") private String backupOfferingId; @@ -168,6 +172,14 @@ public void setStatus(Backup.Status status) { this.status = status; } + public String getVolumes() { + return volumes; + } + + public void setVolumes(String volumes) { + this.volumes = volumes; + } + public String getBackupOfferingId() { return backupOfferingId; } diff --git a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java index 38b33169b75d..2e737565f7db 100644 --- a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java @@ -614,7 +614,12 @@ public void setBackupExternalId(String backupExternalId) { } @Override - public List getBackupVolumes() { + public String getBackupVolumes() { + return this.backupVolumes; + } + + @Override + public List getBackupVolumeList() { if (Strings.isNullOrEmpty(this.backupVolumes)) { return Collections.emptyList(); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java index 069a8e5b83d4..d79787f2f785 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java @@ -85,8 +85,8 @@ public DateUtil.IntervalType getScheduleType() { return scheduleType == null ? null : DateUtil.getIntervalType(scheduleType); } - public void setScheduleType(DateUtil.IntervalType intervalType) { - this.scheduleType = (short) intervalType.ordinal(); + public void setScheduleType(Short intervalType) { + this.scheduleType = intervalType; } public String getSchedule() { 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 05f52942a6c6..5ea9d9378f8f 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 @@ -156,6 +156,7 @@ public BackupResponse newBackupResponse(Backup backup) { response.setSize(backup.getSize()); response.setProtectedSize(backup.getProtectedSize()); response.setStatus(backup.getStatus()); + response.setVolumes(vm.getBackupVolumes()); response.setBackupOfferingId(offering.getUuid()); response.setBackupOffering(offering.getName()); response.setAccountId(account.getUuid()); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java index c20f1848df14..4ab5f4275917 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java @@ -581,7 +581,7 @@ public String getBackupExternalId() { } @Override - public List getBackupVolumes() { + public List getBackupVolumeList() { return Arrays.asList(new Gson().fromJson(this.backupVolumes, Backup.VolumeInfo[].class)); } } 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 2e0b36224683..7879d0ed9577 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 @@ -208,14 +208,14 @@ public boolean deleteBackup(Backup backup) { @Override public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { - String restorePointId = backup.getExternalId(); + final String restorePointId = backup.getExternalId(); return getClient(vm.getDataCenterId()).restoreFullVM(vm.getInstanceName(), restorePointId); } @Override public Pair restoreBackedUpVolume(Backup backup, String volumeUuid, String hostIp, String dataStoreUuid) { - Long zoneId = backup.getZoneId(); - String restorePointId = backup.getExternalId(); + final Long zoneId = backup.getZoneId(); + final String restorePointId = backup.getExternalId(); return getClient(zoneId).restoreVMToDifferentLocation(restorePointId, hostIp, dataStoreUuid); } diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java index d1bc24d02579..3288c82e361f 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java @@ -853,7 +853,7 @@ private boolean isRootDisk(VirtualDisk disk, Map disksMap if (vm == null) { throw new CloudRuntimeException("Failed to find the volumes details from the VM backup"); } - List backedUpVolumes = vm.getBackupVolumes(); + List backedUpVolumes = vm.getBackupVolumeList(); for (Backup.VolumeInfo backedUpVolume : backedUpVolumes) { if (backedUpVolume.getSize().equals(disk.getCapacityInBytes())) { return backedUpVolume.getType().equals(Volume.Type.ROOT); @@ -1146,7 +1146,7 @@ private VolumeVO createVolume(VirtualDisk disk, VirtualMachineMO vmToImport, lon if (vm == null) { throw new CloudRuntimeException("Failed to find the backup volume information from the VM backup"); } - List backedUpVolumes = vm.getBackupVolumes(); + List backedUpVolumes = vm.getBackupVolumeList(); Volume.Type type = Volume.Type.DATADISK; Long size = disk.getCapacityInBytes(); if (isImport) { @@ -1315,7 +1315,7 @@ private Map getDisksMapping(Backup backup, List backedUpVolumes = vm.getBackupVolumes(); + List backedUpVolumes = vm.getBackupVolumeList(); Map usedVols = new HashMap<>(); Map map = new HashMap<>(); @@ -1391,8 +1391,8 @@ private ManagedObjectReference getDestStoreMor(VirtualMachineMO vmMo) throws Exc } @Override - public VirtualMachine importVirtualMachine(long zoneId, long domainId, long accountId, long userId, - String vmInternalName, Backup backup) throws Exception { + public VirtualMachine importVirtualMachineFromBackup(long zoneId, long domainId, long accountId, long userId, + String vmInternalName, Backup backup) throws Exception { DatacenterMO dcMo = getDatacenterMO(zoneId); VirtualMachineMO vmToImport = dcMo.findVm(vmInternalName); if (vmToImport == null) { diff --git a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java index df2be8318195..afa80fa066e4 100644 --- a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java +++ b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java @@ -261,8 +261,8 @@ public Map getClusterSettings(long vmId) { } @Override - public VirtualMachine importVirtualMachine(long zoneId, long domainId, long accountId, long userId, - String vmInternalName, Backup backup) throws Exception { + public VirtualMachine importVirtualMachineFromBackup(long zoneId, long domainId, long accountId, long userId, + String vmInternalName, Backup backup) throws Exception { return null; } diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 4fd5cc067390..f7787fdc46cf 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -1559,7 +1559,7 @@ public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId) { } // if target VM has backups - if (vm.getBackupOfferingId() != null || vm.getBackupVolumes().size() > 0) { + if (vm.getBackupOfferingId() != null || vm.getBackupVolumeList().size() > 0) { throw new InvalidParameterValueException("Unable to attach volume, please specify a VM that does not have any backups"); } @@ -1803,7 +1803,7 @@ public Volume detachVolumeFromVM(DetachVolumeCmd cmmd) { throw new InvalidParameterValueException("Unable to detach volume, please specify a VM that does not have VM snapshots"); } - if (vm.getBackupOfferingId() != null || vm.getBackupVolumes().size() > 0) { + if (vm.getBackupOfferingId() != null || vm.getBackupVolumeList().size() > 0) { throw new InvalidParameterValueException("Unable to detach volume, cannot detach volume from a VM that has backups. First remove the VM from the backup offering."); } 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 14533ca04212..89f590df1751 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -231,6 +231,10 @@ public boolean assignVMToBackupOffering(Long vmId, Long offeringId) { accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + if (vm.getBackupOfferingId() != null) { + throw new CloudRuntimeException("VM already is assigned to a backup offering, please remove the VM from its previous offering"); + } + final BackupOfferingVO offering = backupOfferingDao.findById(offeringId); if (offering == null) { throw new CloudRuntimeException("Provided backup offering does not exist"); @@ -316,6 +320,10 @@ public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) { } accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + if (vm.getBackupOfferingId() == null) { + throw new CloudRuntimeException("Cannot configure backup schedule for the VM without having any backup offering"); + } + final String timezoneId = timeZone.getID(); if (!timezoneId.equals(cmd.getTimezone())) { LOG.warn("Using timezone: " + timezoneId + " for running this snapshot policy as an equivalent of " + cmd.getTimezone()); @@ -332,7 +340,7 @@ public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) { return backupScheduleDao.persist(new BackupScheduleVO(vmId, intervalType, scheduleString, timezoneId)); } - schedule.setScheduleType(intervalType); + schedule.setScheduleType((short) intervalType.ordinal()); schedule.setSchedule(scheduleString); schedule.setTimezone(timezoneId); backupScheduleDao.update(schedule.getId(), schedule); @@ -448,7 +456,7 @@ public boolean importRestoredVM(long zoneId, long domainId, long accountId, long VirtualMachine vm = null; HypervisorGuru guru = hypervisorGuruManager.getGuru(hypervisorType); try { - vm = guru.importVirtualMachine(zoneId, domainId, accountId, userId, vmInternalName, backup); + vm = guru.importVirtualMachineFromBackup(zoneId, domainId, accountId, userId, vmInternalName, backup); } catch (final Exception e) { LOG.error("Failed to import VM from backup restoration", e); throw new CloudRuntimeException("Error during vm backup restoration and import: " + e.getMessage()); @@ -480,11 +488,11 @@ public boolean restoreBackup(final Long backupId) { final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(vm.getBackupOfferingId()); if (offering == null) { - throw new CloudRuntimeException("Failed to find VM backup offering"); + throw new CloudRuntimeException("Failed to find backup offering of the VM backup"); } final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); if (!backupProvider.restoreVMFromBackup(vm, backup)) { - throw new CloudRuntimeException("Error restoring VM " + vm.getId() + " from backup " + backup.getId()); + throw new CloudRuntimeException("Error restoring VM from backup ID " + backup.getId()); } return importRestoredVM(vm.getDataCenterId(), vm.getDomainId(), vm.getAccountId(), vm.getUserId(), vm.getInstanceName(), vm.getHypervisorType(), backup); @@ -543,7 +551,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, if (!result.first()) { throw new CloudRuntimeException("Error restoring volume " + backedUpVolumeUuid); } - if (!attachVolumeToVM(vm.getDataCenterId(), result.second(), vmFromBackup.getBackupVolumes(), + if (!attachVolumeToVM(vm.getDataCenterId(), result.second(), vmFromBackup.getBackupVolumeList(), backedUpVolumeUuid, vm, datastoreUuid, backup)) { throw new CloudRuntimeException("Error attaching volume " + backedUpVolumeUuid + " to VM " + vm.getUuid()); } diff --git a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java index 29e23bafd0dc..2a44b0662e5a 100644 --- a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java +++ b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java @@ -506,7 +506,7 @@ public void testResourceLimitCheckForUploadedVolume() throws NoSuchFieldExceptio when(vm.getState()).thenReturn(State.Running); when(vm.getDataCenterId()).thenReturn(34L); when(vm.getBackupOfferingId()).thenReturn(null); - when(vm.getBackupVolumes()).thenReturn(Collections.emptyList()); + when(vm.getBackupVolumeList()).thenReturn(Collections.emptyList()); when(volumeDaoMock.findByInstanceAndType(anyLong(), any(Volume.Type.class))).thenReturn(new ArrayList<>(10)); when(volumeDataFactoryMock.getVolume(9L)).thenReturn(volumeToAttach); when(volumeToAttach.getState()).thenReturn(Volume.State.Uploaded); From 14330aba3a7422044a058a3165c6c7d7bf334791 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Sun, 8 Dec 2019 02:03:33 +0530 Subject: [PATCH 28/81] missing declr. Signed-off-by: Rohit Yadav --- .../cloudstack/engine/cloud/entity/api/db/VMEntityVO.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java index 4ab5f4275917..48044948d2c0 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java @@ -580,6 +580,11 @@ public String getBackupExternalId() { return backupExternalId; } + @Override + public String getBackupVolumes() { + return this.backupVolumes; + } + @Override public List getBackupVolumeList() { return Arrays.asList(new Gson().fromJson(this.backupVolumes, Backup.VolumeInfo[].class)); From a2a543602206eb8a14e52611108a8571ecabba38 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Sun, 8 Dec 2019 02:21:31 +0530 Subject: [PATCH 29/81] minor fixes Signed-off-by: Rohit Yadav --- .../cloudstack/api/command/user/backup/RestoreBackupCmd.java | 2 +- .../java/org/apache/cloudstack/backup/veeam/VeeamClient.java | 2 +- .../java/org/apache/cloudstack/backup/BackupManagerImpl.java | 4 ++-- usage/src/main/java/com/cloud/usage/UsageManagerImpl.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java index 32beabeda93d..71fc018b0aab 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java @@ -41,7 +41,7 @@ import com.cloud.utils.exception.CloudRuntimeException; @APICommand(name = RestoreBackupCmd.APINAME, - description = "Restore a VM from a VM backup", + description = "Restores an existing stopped or deleted VM using a VM backup", responseObject = SuccessResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class RestoreBackupCmd extends BaseAsyncCmd { 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 0d3452bd2c6e..bdb701ffa2ae 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 @@ -624,7 +624,7 @@ private Backup.RestorePoint getRestorePointFromBlock(String[] parts) { public List listRestorePoints(String backupName, String vmInternalName) { final List cmds = Arrays.asList( String.format("$backup = Get-VBRBackup -Name \"%s\"", backupName), - String.format("Get-VBRRestorePoint -Backup:$backup -Name \"%s\"", vmInternalName) + String.format("if ($backup) { Get-VBRRestorePoint -Backup:$backup -Name \"%s\" }", vmInternalName) ); Pair response = executePowerShellCommands(cmds); final List restorePoints = new ArrayList<>(); 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 89f590df1751..633585ccdfff 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -257,7 +257,7 @@ public boolean assignVMToBackupOffering(Long vmId, Long offeringId) { if (result && vmInstanceDao.update(vm.getId(), vm)) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), - vm.getUuid(), vm.getBackupOfferingId(), vm.getId(), null, + "Backup-" + vm.getHostName() + "-" + vm.getUuid(), vm.getBackupOfferingId(), null, null, Backup.class.getSimpleName(), vm.getUuid()); return true; } @@ -295,7 +295,7 @@ public boolean removeVMFromBackupOffering(final Long vmId, final boolean forced) boolean result = backupProvider.removeVMFromBackupOffering(vm); if (result && vmInstanceDao.update(vm.getId(), vm)) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), - vm.getUuid(), vm.getBackupOfferingId(), vm.getId(), null, + "Backup-" + vm.getHostName() + "-" + vm.getUuid(), vm.getBackupOfferingId(), null, null, Backup.class.getSimpleName(), vm.getUuid()); return true; } diff --git a/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java b/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java index ad7aa179dce0..61e58e5e6e21 100644 --- a/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java +++ b/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java @@ -1904,9 +1904,9 @@ private void createBackupEvent(final UsageEventVO event) { Long vmId = event.getResourceId(); Long zoneId = event.getZoneId(); Long accountId = event.getAccountId(); - Date created = event.getCreateDate(); Account account = _accountDao.findByIdIncludingRemoved(event.getAccountId()); Long domainId = account.getDomainId(); + Date created = event.getCreateDate(); if (EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN.equals(event.getType())) { final UsageBackupVO backupVO = new UsageBackupVO(zoneId, accountId, domainId, vmId, created); From 0dbf44ab979e6fcadea80c77e99734b263961d7f Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Sun, 8 Dec 2019 23:10:00 +0530 Subject: [PATCH 30/81] implement backup schedule execution logic using timer and locks Signed-off-by: Rohit Yadav --- .../cloudstack/api/ApiCommandJobType.java | 1 + .../command/user/backup/CreateBackupCmd.java | 14 +- .../java/com/cloud/vm/dao/VMInstanceDao.java | 2 +- .../com/cloud/vm/dao/VMInstanceDaoImpl.java | 8 +- .../cloudstack/backup/BackupScheduleVO.java | 3 +- .../backup/dao/BackupScheduleDao.java | 5 + .../backup/dao/BackupScheduleDaoImpl.java | 16 ++ .../cloudstack/backup/BackupManagerImpl.java | 240 ++++++++++++++++-- .../spring-server-core-managers-context.xml | 1 + 9 files changed, 267 insertions(+), 23 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiCommandJobType.java b/api/src/main/java/org/apache/cloudstack/api/ApiCommandJobType.java index d35598b508da..1cac1daba19a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiCommandJobType.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiCommandJobType.java @@ -23,6 +23,7 @@ public enum ApiCommandJobType { Volume, ConsoleProxy, Snapshot, + Backup, Template, Iso, SystemVm, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java index 59f36702b38f..d93f3e657ebb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java @@ -21,9 +21,10 @@ import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandJobType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseAsyncCreateCmd; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; @@ -44,7 +45,7 @@ description = "Create VM backup", responseObject = SuccessResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class CreateBackupCmd extends BaseAsyncCmd { +public class CreateBackupCmd extends BaseAsyncCreateCmd { public static final String APINAME = "createBackup"; @Inject @@ -94,6 +95,11 @@ public String getCommandName() { return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; } + @Override + public ApiCommandJobType getInstanceType() { + return ApiCommandJobType.Backup; + } + @Override public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); @@ -108,4 +114,8 @@ public String getEventType() { public String getEventDescription() { return "Creating backup for VM " + vmId; } + + @Override + public void create() throws ResourceAllocationException { + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java index 01da445dc77f..95c28e3bf35a 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java @@ -112,7 +112,7 @@ public interface VMInstanceDao extends GenericDao, StateDao< List listVmsMigratingFromHost(Long hostId); - List listByZoneWithBackups(Long zoneId); + List listByZoneWithBackups(Long zoneId, Long backupOfferingId); public Long countActiveByHostId(long hostId); diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java index 06cf4992c950..19d1f7adc64d 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -290,7 +290,8 @@ protected void init() { BackupSearch = createSearchBuilder(); BackupSearch.and("zone_id", BackupSearch.entity().getDataCenterId(), Op.EQ); - BackupSearch.and("backup_offering_id", BackupSearch.entity().getBackupOfferingId(), Op.NNULL); + BackupSearch.and("backup_offering_not_null", BackupSearch.entity().getBackupOfferingId(), Op.NNULL); + BackupSearch.and("backup_offering_id", BackupSearch.entity().getBackupOfferingId(), Op.EQ); BackupSearch.done(); } @@ -584,9 +585,12 @@ public List listVmsMigratingFromHost(Long hostId) { } @Override - public List listByZoneWithBackups(Long zoneId) { + public List listByZoneWithBackups(Long zoneId, Long backupOfferingId) { SearchCriteria sc = BackupSearch.create(); sc.setParameters("zone_id", zoneId); + if (backupOfferingId != null) { + sc.setParameters("backup_offering_id", backupOfferingId); + } return listBy(sc); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java index d79787f2f785..ba31dc59d390 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java @@ -60,11 +60,12 @@ public class BackupScheduleVO implements BackupSchedule { public BackupScheduleVO() { } - public BackupScheduleVO(Long vmId, DateUtil.IntervalType scheduleType, String schedule, String timezone) { + public BackupScheduleVO(Long vmId, DateUtil.IntervalType scheduleType, String schedule, String timezone, Date scheduledTimestamp) { this.vmId = vmId; this.scheduleType = (short) scheduleType.ordinal(); this.schedule = schedule; this.timezone = timezone; + this.scheduledTimestamp = scheduledTimestamp; } @Override diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java index 9d81e46adcc4..516b0112c986 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java @@ -17,6 +17,9 @@ package org.apache.cloudstack.backup.dao; +import java.util.Date; +import java.util.List; + import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.backup.BackupScheduleVO; @@ -26,5 +29,7 @@ public interface BackupScheduleDao extends GenericDao { BackupScheduleVO findByVM(Long vmId); + List getSchedulesToExecute(Date currentTimestamp); + BackupScheduleResponse newBackupScheduleResponse(BackupSchedule schedule); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java index d8207a813095..7a58679e7e53 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java @@ -17,6 +17,9 @@ package org.apache.cloudstack.backup.dao; +import java.util.Date; +import java.util.List; + import javax.annotation.PostConstruct; import javax.inject.Inject; @@ -36,6 +39,7 @@ public class BackupScheduleDaoImpl extends GenericDaoBase backupScheduleSearch; + private SearchBuilder executableSchedulesSearch; public BackupScheduleDaoImpl() { } @@ -46,6 +50,11 @@ protected void init() { backupScheduleSearch.and("vm_id", backupScheduleSearch.entity().getVmId(), SearchCriteria.Op.EQ); backupScheduleSearch.and("async_job_id", backupScheduleSearch.entity().getAsyncJobId(), SearchCriteria.Op.EQ); backupScheduleSearch.done(); + + executableSchedulesSearch = createSearchBuilder(); + executableSchedulesSearch.and("scheduledTimestamp", executableSchedulesSearch.entity().getScheduledTimestamp(), SearchCriteria.Op.LT); + executableSchedulesSearch.and("asyncJobId", executableSchedulesSearch.entity().getAsyncJobId(), SearchCriteria.Op.NULL); + executableSchedulesSearch.done(); } @Override @@ -55,6 +64,13 @@ public BackupScheduleVO findByVM(Long vmId) { return findOneBy(sc); } + @Override + public List getSchedulesToExecute(Date currentTimestamp) { + SearchCriteria sc = executableSchedulesSearch.create(); + sc.setParameters("scheduledTimestamp", currentTimestamp); + return listBy(sc); + } + @Override public BackupScheduleResponse newBackupScheduleResponse(BackupSchedule schedule) { VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(schedule.getVmId()); 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 633585ccdfff..bdb133d66fe2 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -18,14 +18,18 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; +import java.util.Timer; +import java.util.TimerTask; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.backup.DeleteBackupOfferingCmd; import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd; import org.apache.cloudstack.api.command.admin.backup.ListBackupProviderOfferingsCmd; @@ -47,17 +51,23 @@ import org.apache.cloudstack.backup.dao.BackupScheduleDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher; +import org.apache.cloudstack.framework.jobs.AsyncJobManager; +import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.managed.context.ManagedContextTimerTask; import org.apache.cloudstack.poll.BackgroundPollManager; import org.apache.cloudstack.poll.BackgroundPollTask; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.log4j.Logger; -import org.springframework.stereotype.Component; +import com.cloud.api.ApiDispatcher; +import com.cloud.api.ApiGsonHelper; import com.cloud.dc.DataCenter; import com.cloud.dc.dao.DataCenterDao; import com.cloud.event.ActionEvent; +import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; import com.cloud.exception.InvalidParameterValueException; @@ -67,7 +77,6 @@ import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.HypervisorGuru; import com.cloud.hypervisor.HypervisorGuruManager; -import com.cloud.org.Grouping; import com.cloud.projects.Project; import com.cloud.storage.ScopeType; import com.cloud.storage.Volume; @@ -78,14 +87,20 @@ import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountService; +import com.cloud.user.User; import com.cloud.utils.DateUtil; import com.cloud.utils.Pair; import com.cloud.utils.Ternary; +import com.cloud.utils.component.ComponentContext; import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.DB; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GlobalLock; 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.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; @@ -93,7 +108,6 @@ import com.google.common.base.Strings; import com.google.gson.Gson; -@Component public class BackupManagerImpl extends ManagerBase implements BackupManager { private static final Logger LOG = Logger.getLogger(BackupManagerImpl.class); @@ -125,15 +139,32 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { private PrimaryDataStoreDao primaryDataStoreDao; @Inject private DiskOfferingDao diskOfferingDao; + @Inject + private ApiDispatcher apiDispatcher; + @Inject + private AsyncJobManager asyncJobManager; + + private AsyncJobDispatcher asyncJobDispatcher; + private Timer backupTimer; + private Date currentTimestamp; private static Map backupProvidersMap = new HashMap<>(); private List backupProviders; + public AsyncJobDispatcher getAsyncJobDispatcher() { + return asyncJobDispatcher; + } + + public void setAsyncJobDispatcher(final AsyncJobDispatcher dispatcher) { + asyncJobDispatcher = dispatcher; + } + @Override public List listBackupProviderOfferings(final Long zoneId) { if (zoneId == null || zoneId < 1) { throw new CloudRuntimeException("Invalid zone ID passed"); } + validateForZone(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"); @@ -147,6 +178,7 @@ public List listBackupProviderOfferings(final Long zoneId) { @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_IMPORT_OFFERING, eventDescription = "importing backup offering", async = true) public BackupOffering importBackupOffering(final Long zoneId, final String offeringExternalId, final String offeringName, final String offeringDescription) { + validateForZone(zoneId); final BackupOffering existingOffering = backupOfferingDao.findByExternalId(offeringExternalId, zoneId); if (existingOffering != null) { throw new CloudRuntimeException("A backup offering with external ID " + offeringExternalId + " already exists"); @@ -210,6 +242,12 @@ public boolean deleteBackupOffering(final Long offeringId) { if (offering == null) { throw new CloudRuntimeException("Could not find a backup offering with id: " + offeringId); } + + if (vmInstanceDao.listByZoneWithBackups(offering.getZoneId(), offering.getId()).size() > 0) { + throw new CloudRuntimeException("Backup offering is assigned to VMs, remove the assignment(s) in order to remove the offering."); + } + + validateForZone(offering.getZoneId()); return backupOfferingDao.remove(offering.getId()); } @@ -229,6 +267,8 @@ public boolean assignVMToBackupOffering(Long vmId, Long offeringId) { throw new CloudRuntimeException("Did not find VM by provided ID"); } + validateForZone(vm.getDataCenterId()); + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); if (vm.getBackupOfferingId() != null) { @@ -272,6 +312,7 @@ public boolean removeVMFromBackupOffering(final Long vmId, final boolean forced) throw new CloudRuntimeException("Did not find VM by provided ID"); } + validateForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); final BackupOfferingVO offering = backupOfferingDao.findById(vm.getBackupOfferingId()); @@ -318,6 +359,7 @@ public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) { if (vm == null) { throw new CloudRuntimeException("Did not find VM by provided ID"); } + validateForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); if (vm.getBackupOfferingId() == null) { @@ -329,20 +371,22 @@ public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) { LOG.warn("Using timezone: " + timezoneId + " for running this snapshot policy as an equivalent of " + cmd.getTimezone()); } + Date nextDateTime = null; try { - DateUtil.getNextRunTime(intervalType, cmd.getSchedule(), timezoneId, null); + nextDateTime = DateUtil.getNextRunTime(intervalType, cmd.getSchedule(), timezoneId, null); } catch (Exception e) { throw new InvalidParameterValueException("Invalid schedule: " + cmd.getSchedule() + " for interval type: " + cmd.getIntervalType()); } final BackupScheduleVO schedule = backupScheduleDao.findByVM(vmId); if (schedule == null) { - return backupScheduleDao.persist(new BackupScheduleVO(vmId, intervalType, scheduleString, timezoneId)); + return backupScheduleDao.persist(new BackupScheduleVO(vmId, intervalType, scheduleString, timezoneId, nextDateTime)); } schedule.setScheduleType((short) intervalType.ordinal()); schedule.setSchedule(scheduleString); schedule.setTimezone(timezoneId); + schedule.setScheduledTimestamp(nextDateTime); backupScheduleDao.update(schedule.getId(), schedule); return backupScheduleDao.findByVM(vmId); } @@ -353,6 +397,7 @@ public BackupSchedule listBackupSchedule(final Long vmId) { if (vm == null) { throw new CloudRuntimeException("Did not find VM by provided ID"); } + validateForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); return backupScheduleDao.findByVM(vmId); @@ -365,7 +410,7 @@ public boolean deleteBackupSchedule(final Long vmId) { if (vm == null) { throw new CloudRuntimeException("Did not find VM by provided ID"); } - + validateForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); final BackupSchedule schedule = backupScheduleDao.findByVM(vmId); @@ -382,7 +427,7 @@ public boolean createBackup(final Long vmId) { if (vm == null) { throw new CloudRuntimeException("Did not find VM by provided ID"); } - + validateForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); if (vm.getBackupOfferingId() == null) { @@ -474,6 +519,7 @@ public boolean restoreBackup(final Long backupId) { if (backup == null) { throw new CloudRuntimeException("Backup " + backupId + " does not exist"); } + validateForZone(backup.getZoneId()); final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); if (vm == null) { @@ -517,6 +563,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, if (backup == null) { throw new CloudRuntimeException("Provided backup not found"); } + validateForZone(backup.getZoneId()); final VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { @@ -570,6 +617,7 @@ public boolean deleteBackup(final Long backupId) { if (vm == null) { throw new CloudRuntimeException("VM " + vmId + " does not exist"); } + validateForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(vm.getBackupOfferingId()); if (offering == null) { @@ -639,8 +687,14 @@ public boolean configure(String name, Map params) throws Configu return true; } - public boolean isDisabled(final DataCenter zone) { - return zone == null || Grouping.AllocationState.Disabled.equals(zone.getAllocationState()) || !BackupFrameworkEnabled.valueIn(zone.getId()); + public boolean isDisabled(final Long zoneId) { + return !BackupFrameworkEnabled.valueIn(zoneId); + } + + private void validateForZone(final Long zoneId) { + if (zoneId == null || isDisabled(zoneId)) { + throw new CloudRuntimeException("Backup and Recovery feature is disabled for the zone"); + } } @Override @@ -708,12 +762,6 @@ public void setBackupProviders(final List backupProviders) { this.backupProviders = backupProviders; } - @Override - public boolean start() { - initializeBackupProviderMap(); - return true; - } - private void initializeBackupProviderMap() { if (backupProviders != null) { for (final BackupProvider backupProvider : backupProviders) { @@ -722,6 +770,164 @@ private void initializeBackupProviderMap() { } } + public void poll(final Date timestamp) { + currentTimestamp = timestamp; + GlobalLock scanLock = GlobalLock.getInternLock("backup.poll"); + try { + if (scanLock.lock(5)) { + try { + checkStatusOfCurrentlyExecutingBackups(); + } finally { + scanLock.unlock(); + } + } + } finally { + scanLock.releaseRef(); + } + + scanLock = GlobalLock.getInternLock("backup.poll"); + try { + if (scanLock.lock(5)) { + try { + scheduleBackups(); + } finally { + scanLock.unlock(); + } + } + } finally { + scanLock.releaseRef(); + } + } + + @DB + private Date scheduleNextBackupJob(final BackupScheduleVO backupSchedule) { + final Date nextTimestamp = DateUtil.getNextRunTime(backupSchedule.getScheduleType(), backupSchedule.getSchedule(), + backupSchedule.getTimezone(), currentTimestamp); + return Transaction.execute(new TransactionCallback() { + @Override + public Date doInTransaction(TransactionStatus status) { + backupSchedule.setScheduledTimestamp(nextTimestamp); + backupSchedule.setAsyncJobId(null); + backupScheduleDao.update(backupSchedule.getId(), backupSchedule); + return nextTimestamp; + } + }); + } + + private void checkStatusOfCurrentlyExecutingBackups() { + final SearchCriteria sc = backupScheduleDao.createSearchCriteria(); + sc.addAnd("asyncJobId", SearchCriteria.Op.NNULL); + final List backupSchedules = backupScheduleDao.search(sc, null); + for (final BackupScheduleVO backupSchedule : backupSchedules) { + final Long asyncJobId = backupSchedule.getAsyncJobId(); + final AsyncJobVO asyncJob = asyncJobManager.getAsyncJob(asyncJobId); + switch (asyncJob.getStatus()) { + case SUCCEEDED: + case FAILED: + final Date nextDateTime = scheduleNextBackupJob(backupSchedule); + final String nextScheduledTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, nextDateTime); + LOG.debug("Next backup scheduled time for VM ID " + backupSchedule.getVmId() + " is " + nextScheduledTime); + break; + } + } + } + + @DB + public void scheduleBackups() { + String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp); + LOG.debug("Backup backup.poll is being called at " + displayTime); + + final List backupsToBeExecuted = backupScheduleDao.getSchedulesToExecute(currentTimestamp); + for (final BackupScheduleVO backupSchedule: backupsToBeExecuted) { + final Long backupScheduleId = backupSchedule.getId(); + final Long vmId = backupSchedule.getVmId(); + + final VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null || vm.getBackupOfferingId() == null) { + backupScheduleDao.remove(backupScheduleId); + continue; + } + + if (isDisabled(vm.getDataCenterId())) { + continue; + } + + final Account backupAccount = accountService.getAccount(vm.getAccountId()); + if (backupAccount == null || backupAccount.getState() == Account.State.disabled) { + if (LOG.isDebugEnabled()) { + LOG.debug("Skip backup for VM " + vm.getUuid() + " since its account has been removed or disabled"); + } + continue; + } + + if (LOG.isDebugEnabled()) { + final Date scheduledTimestamp = backupSchedule.getScheduledTimestamp(); + displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, scheduledTimestamp); + LOG.debug("Scheduling 1 backup for VM ID " + vm.getId() + " (VM name:" + vm.getHostName() + + ") for backup schedule id: " + backupSchedule.getId() + " at " + displayTime); + } + + BackupScheduleVO tmpBackupScheduleVO = null; + + try { + tmpBackupScheduleVO = backupScheduleDao.acquireInLockTable(backupScheduleId); + + final Long eventId = ActionEventUtils.onScheduledActionEvent(User.UID_SYSTEM, vm.getAccountId(), + EventTypes.EVENT_VM_BACKUP_CREATE, "creating backup for VM ID:" + vm.getUuid(), true, 0); + final Map params = new HashMap(); + params.put(ApiConstants.VIRTUAL_MACHINE_ID, "" + vmId); + params.put("ctxUserId", "1"); + params.put("ctxAccountId", "" + vm.getAccountId()); + params.put("ctxStartEventId", String.valueOf(eventId)); + + final CreateBackupCmd cmd = new CreateBackupCmd(); + ComponentContext.inject(cmd); + apiDispatcher.dispatchCreateCmd(cmd, params); + params.put("id", "" + vmId); + params.put("ctxStartEventId", "1"); + + AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, vm.getAccountId(), CreateBackupCmd.class.getName(), + ApiGsonHelper.getBuilder().create().toJson(params), vmId, + cmd.getInstanceType() != null ? cmd.getInstanceType().toString() : null, null); + job.setDispatcher(asyncJobDispatcher.getName()); + + final long jobId = asyncJobManager.submitAsyncJob(job); + tmpBackupScheduleVO.setAsyncJobId(jobId); + backupScheduleDao.update(backupScheduleId, tmpBackupScheduleVO); + } catch (Exception e) { + LOG.warn("Scheduling backup failed due to ", e); + } finally { + if (tmpBackupScheduleVO != null) { + backupScheduleDao.releaseFromLockTable(backupScheduleId); + } + } + } + } + + @Override + public boolean start() { + initializeBackupProviderMap(); + + currentTimestamp = new Date(); + for (final BackupScheduleVO backupSchedule : backupScheduleDao.listAll()) { + scheduleNextBackupJob(backupSchedule); + } + final TimerTask backupPollTask = new ManagedContextTimerTask() { + @Override + protected void runInContext() { + try { + poll(new Date()); + } catch (final Throwable t) { + LOG.warn("Catch throwable in backup scheduler ", t); + } + } + }; + + backupTimer = new Timer("BackupPollTask"); + backupTimer.schedule(backupPollTask, BackupSyncPollingInterval.value() * 1000L, BackupSyncPollingInterval.value() * 1000L); + return true; + } + //////////////////////////////////////////////////// /////////////// Background Tasks /////////////////// //////////////////////////////////////////////////// @@ -745,7 +951,7 @@ protected void runInContext() { LOG.trace("Backup sync background task is running..."); } for (final DataCenter dataCenter : dataCenterDao.listAllZones()) { - if (isDisabled(dataCenter)) { + if (dataCenter == null || isDisabled(dataCenter.getId())) { continue; } @@ -755,7 +961,7 @@ protected void runInContext() { continue; } - List vms = vmInstanceDao.listByZoneWithBackups(dataCenter.getId()); + List vms = vmInstanceDao.listByZoneWithBackups(dataCenter.getId(), null); if (vms == null || vms.isEmpty()) { continue; } diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index 0c9cb7ee440f..af6dbe690865 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -303,6 +303,7 @@ + From 47c255be93407882c1be268c3adbe6ed2010ae1e Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Sun, 8 Dec 2019 23:57:14 +0530 Subject: [PATCH 31/81] return backupoffering name/id in list VMs api for UI Signed-off-by: Rohit Yadav --- .../apache/cloudstack/api/ApiConstants.java | 4 +- .../api/response/BackupResponse.java | 4 +- .../api/response/UserVmResponse.java | 24 +++ .../META-INF/db/schema-41300to41400.sql | 182 ++++++++++++++++++ .../cloudstack/quota/QuotaManagerImpl.java | 2 +- .../api/query/dao/UserVmJoinDaoImpl.java | 6 +- .../com/cloud/api/query/vo/UserVmJoinVO.java | 17 ++ 7 files changed, 233 insertions(+), 6 deletions(-) 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 ecd8cbed5575..d423338c112b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -33,7 +33,7 @@ public class ApiConstants { public static final String LIST_LB_VMIPS = "lbvmips"; public static final String AVAILABLE = "available"; public static final String BACKUP_ID = "backupid"; - public static final String BACKUP_OFFERING = "backupoffering"; + public static final String BACKUP_OFFERING_NAME = "backupofferingname"; public static final String BACKUP_OFFERING_ID = "backupofferingid"; public static final String BITS = "bits"; public static final String BOOTABLE = "bootable"; @@ -753,7 +753,7 @@ public enum HostDetails { } public enum VMDetails { - all, group, nics, stats, secgrp, tmpl, servoff, diskoff, iso, volume, min, affgrp; + all, group, nics, stats, secgrp, tmpl, servoff, diskoff, backoff, iso, volume, min, affgrp; } public enum DomainDetails { 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 624dfe8eb7e1..d0c8e5884503 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 @@ -72,8 +72,8 @@ public class BackupResponse extends BaseResponse { @Param(description = "backup offering id") private String backupOfferingId; - @SerializedName(ApiConstants.BACKUP_OFFERING) - @Param(description = "backup offering id") + @SerializedName(ApiConstants.BACKUP_OFFERING_NAME) + @Param(description = "backup offering name") private String backupOfferingName; @SerializedName(ApiConstants.ACCOUNT_ID) diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java index 8a2f1a169d64..ca3ca872943d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java @@ -156,6 +156,14 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @Param(description = "the name of the disk offering of the virtual machine", since = "4.4") private String diskOfferingName; + @SerializedName(ApiConstants.BACKUP_OFFERING_ID) + @Param(description = "the ID of the backup offering of the virtual machine", since = "4.14") + private String backupOfferingId; + + @SerializedName(ApiConstants.BACKUP_OFFERING_NAME) + @Param(description = "the name of the backup offering of the virtual machine", since = "4.14") + private String backupOfferingName; + @SerializedName("forvirtualnetwork") @Param(description = "the virtual network for the service offering") private Boolean forVirtualNetwork; @@ -439,6 +447,14 @@ public String getDiskOfferingName() { return diskOfferingName; } + public String getBackupOfferingId() { + return backupOfferingId; + } + + public String getBackupOfferingName() { + return backupOfferingName; + } + public Boolean getForVirtualNetwork() { return forVirtualNetwork; } @@ -697,6 +713,14 @@ public void setDiskOfferingName(String diskOfferingName) { this.diskOfferingName = diskOfferingName; } + public void setBackupOfferingId(String backupOfferingId) { + this.backupOfferingId = backupOfferingId; + } + + public void setBackupOfferingName(String backupOfferingName) { + this.backupOfferingName = backupOfferingName; + } + public void setCpuNumber(Integer cpuNumber) { this.cpuNumber = cpuNumber; } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql index 1e1695768595..00b232058c3a 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql @@ -87,3 +87,185 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`usage_backup` ( PRIMARY KEY (`id`), INDEX `i_usage_backup` (`zone_id`,`account_id`,`vm_id`,`created`) ) ENGINE=InnoDB CHARSET=utf8; + +DROP VIEW IF EXISTS `cloud`.`user_vm_view`; +CREATE +VIEW `user_vm_view` AS + SELECT + `vm_instance`.`id` AS `id`, + `vm_instance`.`name` AS `name`, + `user_vm`.`display_name` AS `display_name`, + `user_vm`.`user_data` AS `user_data`, + `account`.`id` AS `account_id`, + `account`.`uuid` AS `account_uuid`, + `account`.`account_name` AS `account_name`, + `account`.`type` AS `account_type`, + `domain`.`id` AS `domain_id`, + `domain`.`uuid` AS `domain_uuid`, + `domain`.`name` AS `domain_name`, + `domain`.`path` AS `domain_path`, + `projects`.`id` AS `project_id`, + `projects`.`uuid` AS `project_uuid`, + `projects`.`name` AS `project_name`, + `instance_group`.`id` AS `instance_group_id`, + `instance_group`.`uuid` AS `instance_group_uuid`, + `instance_group`.`name` AS `instance_group_name`, + `vm_instance`.`uuid` AS `uuid`, + `vm_instance`.`user_id` AS `user_id`, + `vm_instance`.`last_host_id` AS `last_host_id`, + `vm_instance`.`vm_type` AS `type`, + `vm_instance`.`limit_cpu_use` AS `limit_cpu_use`, + `vm_instance`.`created` AS `created`, + `vm_instance`.`state` AS `state`, + `vm_instance`.`removed` AS `removed`, + `vm_instance`.`ha_enabled` AS `ha_enabled`, + `vm_instance`.`hypervisor_type` AS `hypervisor_type`, + `vm_instance`.`instance_name` AS `instance_name`, + `vm_instance`.`guest_os_id` AS `guest_os_id`, + `vm_instance`.`display_vm` AS `display_vm`, + `guest_os`.`uuid` AS `guest_os_uuid`, + `vm_instance`.`pod_id` AS `pod_id`, + `host_pod_ref`.`uuid` AS `pod_uuid`, + `vm_instance`.`private_ip_address` AS `private_ip_address`, + `vm_instance`.`private_mac_address` AS `private_mac_address`, + `vm_instance`.`vm_type` AS `vm_type`, + `data_center`.`id` AS `data_center_id`, + `data_center`.`uuid` AS `data_center_uuid`, + `data_center`.`name` AS `data_center_name`, + `data_center`.`is_security_group_enabled` AS `security_group_enabled`, + `data_center`.`networktype` AS `data_center_type`, + `host`.`id` AS `host_id`, + `host`.`uuid` AS `host_uuid`, + `host`.`name` AS `host_name`, + `vm_template`.`id` AS `template_id`, + `vm_template`.`uuid` AS `template_uuid`, + `vm_template`.`name` AS `template_name`, + `vm_template`.`display_text` AS `template_display_text`, + `vm_template`.`enable_password` AS `password_enabled`, + `iso`.`id` AS `iso_id`, + `iso`.`uuid` AS `iso_uuid`, + `iso`.`name` AS `iso_name`, + `iso`.`display_text` AS `iso_display_text`, + `service_offering`.`id` AS `service_offering_id`, + `svc_disk_offering`.`uuid` AS `service_offering_uuid`, + `disk_offering`.`uuid` AS `disk_offering_uuid`, + `disk_offering`.`id` AS `disk_offering_id`, + (CASE + WHEN ISNULL(`service_offering`.`cpu`) THEN `custom_cpu`.`value` + ELSE `service_offering`.`cpu` + END) AS `cpu`, + (CASE + WHEN ISNULL(`service_offering`.`speed`) THEN `custom_speed`.`value` + ELSE `service_offering`.`speed` + END) AS `speed`, + (CASE + WHEN ISNULL(`service_offering`.`ram_size`) THEN `custom_ram_size`.`value` + ELSE `service_offering`.`ram_size` + END) AS `ram_size`, + `backup_offering`.`uuid` AS `backup_offering_uuid`, + `backup_offering`.`id` AS `backup_offering_id`, + `svc_disk_offering`.`name` AS `service_offering_name`, + `disk_offering`.`name` AS `disk_offering_name`, + `backup_offering`.`name` AS `backup_offering_name`, + `storage_pool`.`id` AS `pool_id`, + `storage_pool`.`uuid` AS `pool_uuid`, + `storage_pool`.`pool_type` AS `pool_type`, + `volumes`.`id` AS `volume_id`, + `volumes`.`uuid` AS `volume_uuid`, + `volumes`.`device_id` AS `volume_device_id`, + `volumes`.`volume_type` AS `volume_type`, + `security_group`.`id` AS `security_group_id`, + `security_group`.`uuid` AS `security_group_uuid`, + `security_group`.`name` AS `security_group_name`, + `security_group`.`description` AS `security_group_description`, + `nics`.`id` AS `nic_id`, + `nics`.`uuid` AS `nic_uuid`, + `nics`.`network_id` AS `network_id`, + `nics`.`ip4_address` AS `ip_address`, + `nics`.`ip6_address` AS `ip6_address`, + `nics`.`ip6_gateway` AS `ip6_gateway`, + `nics`.`ip6_cidr` AS `ip6_cidr`, + `nics`.`default_nic` AS `is_default_nic`, + `nics`.`gateway` AS `gateway`, + `nics`.`netmask` AS `netmask`, + `nics`.`mac_address` AS `mac_address`, + `nics`.`broadcast_uri` AS `broadcast_uri`, + `nics`.`isolation_uri` AS `isolation_uri`, + `vpc`.`id` AS `vpc_id`, + `vpc`.`uuid` AS `vpc_uuid`, + `networks`.`uuid` AS `network_uuid`, + `networks`.`name` AS `network_name`, + `networks`.`traffic_type` AS `traffic_type`, + `networks`.`guest_type` AS `guest_type`, + `user_ip_address`.`id` AS `public_ip_id`, + `user_ip_address`.`uuid` AS `public_ip_uuid`, + `user_ip_address`.`public_ip_address` AS `public_ip_address`, + `ssh_keypairs`.`keypair_name` AS `keypair_name`, + `resource_tags`.`id` AS `tag_id`, + `resource_tags`.`uuid` AS `tag_uuid`, + `resource_tags`.`key` AS `tag_key`, + `resource_tags`.`value` AS `tag_value`, + `resource_tags`.`domain_id` AS `tag_domain_id`, + `domain`.`uuid` AS `tag_domain_uuid`, + `domain`.`name` AS `tag_domain_name`, + `resource_tags`.`account_id` AS `tag_account_id`, + `account`.`account_name` AS `tag_account_name`, + `resource_tags`.`resource_id` AS `tag_resource_id`, + `resource_tags`.`resource_uuid` AS `tag_resource_uuid`, + `resource_tags`.`resource_type` AS `tag_resource_type`, + `resource_tags`.`customer` AS `tag_customer`, + `async_job`.`id` AS `job_id`, + `async_job`.`uuid` AS `job_uuid`, + `async_job`.`job_status` AS `job_status`, + `async_job`.`account_id` AS `job_account_id`, + `affinity_group`.`id` AS `affinity_group_id`, + `affinity_group`.`uuid` AS `affinity_group_uuid`, + `affinity_group`.`name` AS `affinity_group_name`, + `affinity_group`.`description` AS `affinity_group_description`, + `vm_instance`.`dynamically_scalable` AS `dynamically_scalable` + FROM + (((((((((((((((((((((((((((((((((`user_vm` + JOIN `vm_instance` ON (((`vm_instance`.`id` = `user_vm`.`id`) + AND ISNULL(`vm_instance`.`removed`)))) + JOIN `account` ON ((`vm_instance`.`account_id` = `account`.`id`))) + JOIN `domain` ON ((`vm_instance`.`domain_id` = `domain`.`id`))) + LEFT JOIN `guest_os` ON ((`vm_instance`.`guest_os_id` = `guest_os`.`id`))) + LEFT JOIN `host_pod_ref` ON ((`vm_instance`.`pod_id` = `host_pod_ref`.`id`))) + LEFT JOIN `projects` ON ((`projects`.`project_account_id` = `account`.`id`))) + LEFT JOIN `instance_group_vm_map` ON ((`vm_instance`.`id` = `instance_group_vm_map`.`instance_id`))) + LEFT JOIN `instance_group` ON ((`instance_group_vm_map`.`group_id` = `instance_group`.`id`))) + LEFT JOIN `data_center` ON ((`vm_instance`.`data_center_id` = `data_center`.`id`))) + LEFT JOIN `host` ON ((`vm_instance`.`host_id` = `host`.`id`))) + LEFT JOIN `vm_template` ON ((`vm_instance`.`vm_template_id` = `vm_template`.`id`))) + LEFT JOIN `vm_template` `iso` ON ((`iso`.`id` = `user_vm`.`iso_id`))) + LEFT JOIN `service_offering` ON ((`vm_instance`.`service_offering_id` = `service_offering`.`id`))) + LEFT JOIN `disk_offering` `svc_disk_offering` ON ((`vm_instance`.`service_offering_id` = `svc_disk_offering`.`id`))) + LEFT JOIN `disk_offering` ON ((`vm_instance`.`disk_offering_id` = `disk_offering`.`id`))) + LEFT JOIN `backup_offering` ON ((`vm_instance`.`backup_offering_id` = `backup_offering`.`id`))) + LEFT JOIN `volumes` ON ((`vm_instance`.`id` = `volumes`.`instance_id`))) + LEFT JOIN `storage_pool` ON ((`volumes`.`pool_id` = `storage_pool`.`id`))) + LEFT JOIN `security_group_vm_map` ON ((`vm_instance`.`id` = `security_group_vm_map`.`instance_id`))) + LEFT JOIN `security_group` ON ((`security_group_vm_map`.`security_group_id` = `security_group`.`id`))) + LEFT JOIN `nics` ON (((`vm_instance`.`id` = `nics`.`instance_id`) + AND ISNULL(`nics`.`removed`)))) + LEFT JOIN `networks` ON ((`nics`.`network_id` = `networks`.`id`))) + LEFT JOIN `vpc` ON (((`networks`.`vpc_id` = `vpc`.`id`) + AND ISNULL(`vpc`.`removed`)))) + LEFT JOIN `user_ip_address` ON ((`user_ip_address`.`vm_id` = `vm_instance`.`id`))) + LEFT JOIN `user_vm_details` `ssh_details` ON (((`ssh_details`.`vm_id` = `vm_instance`.`id`) + AND (`ssh_details`.`name` = 'SSH.PublicKey')))) + LEFT JOIN `ssh_keypairs` ON (((`ssh_keypairs`.`public_key` = `ssh_details`.`value`) + AND (`ssh_keypairs`.`account_id` = `account`.`id`)))) + LEFT JOIN `resource_tags` ON (((`resource_tags`.`resource_id` = `vm_instance`.`id`) + AND (`resource_tags`.`resource_type` = 'UserVm')))) + LEFT JOIN `async_job` ON (((`async_job`.`instance_id` = `vm_instance`.`id`) + AND (`async_job`.`instance_type` = 'VirtualMachine') + AND (`async_job`.`job_status` = 0)))) + LEFT JOIN `affinity_group_vm_map` ON ((`vm_instance`.`id` = `affinity_group_vm_map`.`instance_id`))) + LEFT JOIN `affinity_group` ON ((`affinity_group_vm_map`.`affinity_group_id` = `affinity_group`.`id`))) + LEFT JOIN `user_vm_details` `custom_cpu` ON (((`custom_cpu`.`vm_id` = `vm_instance`.`id`) + AND (`custom_cpu`.`name` = 'CpuNumber')))) + LEFT JOIN `user_vm_details` `custom_speed` ON (((`custom_speed`.`vm_id` = `vm_instance`.`id`) + AND (`custom_speed`.`name` = 'CpuSpeed')))) + LEFT JOIN `user_vm_details` `custom_ram_size` ON (((`custom_ram_size`.`vm_id` = `vm_instance`.`id`) + AND (`custom_ram_size`.`name` = 'memory')))); diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java index 581588f4ca8b..3eb949fca412 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java @@ -159,7 +159,7 @@ public List aggregatePendingQuotaRecordsForAccount(final AccountVO case QuotaTypes.VOLUME: case QuotaTypes.VM_SNAPSHOT: case QuotaTypes.BACKUP: - qu = updateQuotaDiskUsage(usageRecord, aggregationRatio, usageRecord.getUsageType()); + qu = updateQuotaDiskUsage(usageRecord, aggregationRatio, usageRecord.getUsageType()); if (qu != null) { quotaListForAccount.add(qu); } 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 4ccfce9edb48..a612829507e1 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 @@ -28,7 +28,6 @@ import javax.inject.Inject; -import com.cloud.vm.UserVmManager; import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants.VMDetails; @@ -57,6 +56,7 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.net.Dhcp; import com.cloud.vm.UserVmDetailVO; +import com.cloud.vm.UserVmManager; import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.VmStats; import com.cloud.vm.dao.NicExtraDhcpOptionDao; @@ -174,6 +174,10 @@ public UserVmResponse newUserVmResponse(ResponseView view, String objectName, Us userVmResponse.setDiskOfferingId(userVm.getDiskOfferingUuid()); userVmResponse.setDiskOfferingName(userVm.getDiskOfferingName()); } + if (details.contains(VMDetails.all) || details.contains(VMDetails.backoff)) { + userVmResponse.setBackupOfferingId(userVm.getBackupOfferingUuid()); + userVmResponse.setBackupOfferingName(userVm.getBackupOfferingName()); + } if (details.contains(VMDetails.all) || details.contains(VMDetails.servoff) || details.contains(VMDetails.stats)) { userVmResponse.setCpuNumber(userVm.getCpu()); userVmResponse.setCpuSpeed(userVm.getSpeed()); diff --git a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java index 7ff557c8fc85..6d48bec716be 100644 --- a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java @@ -211,6 +211,15 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro @Column(name = "service_offering_name") private String serviceOfferingName; + @Column(name = "backup_offering_id") + private Long backupOfferingId; + + @Column(name = "backup_offering_uuid") + private String backupOfferingUuid; + + @Column(name = "backup_offering_name") + private String backupOfferingName; + @Column(name = "cpu") private int cpu; @@ -599,6 +608,14 @@ public String getServiceOfferingName() { return serviceOfferingName; } + public String getBackupOfferingUuid() { + return backupOfferingUuid; + } + + public String getBackupOfferingName() { + return backupOfferingName; + } + public int getCpu() { return cpu; } From 6eee0eb57a0d5a9ddeb009194e66c677e875fbee Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 9 Dec 2019 01:50:09 +0530 Subject: [PATCH 32/81] offerings fixes Signed-off-by: Rohit Yadav --- .../api/response/BackupOfferingResponse.java | 10 ++ .../cloudstack/backup/BackupOffering.java | 3 + .../cloudstack/backup/BackupOfferingVO.java | 4 + .../backup/dao/BackupOfferingDaoImpl.java | 15 ++- .../backup/VeeamBackupProvider.java | 2 +- .../backup/veeam/VeeamBackupOffering.java | 7 + .../cloudstack/backup/BackupManagerImpl.java | 3 - ui/l10n/en.js | 1 + ui/scripts/configuration.js | 123 +++++++++++++++++- 9 files changed, 156 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java index a702f24fdd07..2777e5495d2a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.api.response; +import java.util.Date; + import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; @@ -47,6 +49,10 @@ public class BackupOfferingResponse extends BaseResponse { @Param(description = "zone ID") private String zoneId; + @SerializedName(ApiConstants.CREATED) + @Param(description = "the date this backup offering was created") + private Date created; + public void setId(String id) { this.id = id; } @@ -66,4 +72,8 @@ public void setDescription(String description) { public void setZoneId(String zoneId) { this.zoneId = zoneId; } + + public void setCreated(Date created) { + this.created = created; + } } diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupOffering.java b/api/src/main/java/org/apache/cloudstack/backup/BackupOffering.java index 1d4a4b5c90f1..5d5498ffd614 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupOffering.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupOffering.java @@ -16,6 +16,8 @@ //under the License. package org.apache.cloudstack.backup; +import java.util.Date; + import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; @@ -26,4 +28,5 @@ public interface BackupOffering extends InternalIdentity, Identity { boolean isImported(); long getZoneId(); String getProvider(); + Date getCreated(); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java index ab648b6ef0c7..1ad88accd51b 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java @@ -111,4 +111,8 @@ public String getProvider() { public String getDescription() { return description; } + + public Date getCreated() { + return created; + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java index 315dd1314073..2844bbee0002 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java @@ -50,19 +50,20 @@ protected void init() { } @Override - public BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy) { - DataCenterVO zone = dataCenterDao.findById(policy.getZoneId()); + public BackupOfferingResponse newBackupOfferingResponse(BackupOffering offering) { + DataCenterVO zone = dataCenterDao.findById(offering.getZoneId()); BackupOfferingResponse response = new BackupOfferingResponse(); - if (policy.isImported()) { - response.setId(policy.getUuid()); + if (offering.isImported()) { + response.setId(offering.getUuid()); if (zone != null) { response.setZoneId(zone.getUuid()); } } - response.setName(policy.getName()); - response.setDescription(policy.getDescription()); - response.setExternalId(policy.getExternalId()); + response.setName(offering.getName()); + response.setDescription(offering.getDescription()); + response.setExternalId(offering.getExternalId()); + response.setCreated(offering.getCreated()); response.setObjectName("backupoffering"); return response; } 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 7879d0ed9577..272d1cd3feae 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 @@ -74,7 +74,7 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, private ConfigKey VeeamValidateSSLSecurity = new ConfigKey<>("Advanced", Boolean.class, "backup.plugin.veeam.validate.ssl", "false", "When set to true, this will validate the SSL certificate when connecting to https/ssl enabled Veeam API service.", true, ConfigKey.Scope.Zone); - private ConfigKey VeeamApiRequestTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.request.timeout", "600", + private ConfigKey VeeamApiRequestTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.request.timeout", "300", "The Veeam B&R API request timeout in seconds.", true, ConfigKey.Scope.Zone); @Inject diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackupOffering.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackupOffering.java index 831ade68fd2f..181e63e56e3f 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackupOffering.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackupOffering.java @@ -17,6 +17,8 @@ package org.apache.cloudstack.backup.veeam; +import java.util.Date; + import org.apache.cloudstack.backup.BackupOffering; public class VeeamBackupOffering implements BackupOffering { @@ -59,6 +61,11 @@ public String getProvider() { return "veeam"; } + @Override + public Date getCreated() { + return null; + } + @Override public String getUuid() { return uid; 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 bdb133d66fe2..d8214e13c552 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -235,9 +235,6 @@ public Pair, Integer> listBackupOfferings(final ListBackupO @Override public boolean deleteBackupOffering(final Long offeringId) { - if (!backupDao.listByOfferingId(offeringId).isEmpty()) { - throw new CloudRuntimeException("Cannot allow deletion of backup offering due to use in existing VM backups, please delete the VM backups using the offering first."); - } final BackupOfferingVO offering = backupOfferingDao.findById(offeringId); if (offering == null) { throw new CloudRuntimeException("Could not find a backup offering with id: " + offeringId); diff --git a/ui/l10n/en.js b/ui/l10n/en.js index c9db38869027..017c3ce6c5ad 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -58,6 +58,7 @@ var dictionary = { "label.ESP.hash":"ESP Hash", "label.ESP.lifetime":"ESP Lifetime (second)", "label.ESP.policy":"ESP policy", +"label.import.backup.offering":"Import Backup Offering", "label.IKE.DH":"IKE DH", "label.IKE.encryption":"IKE Encryption", "label.IKE.hash":"IKE Hash", diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index e1348a1feb73..f8b38e600154 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -2940,12 +2940,133 @@ description: { label: 'label.description' }, - zoneid: { + zoneid: { label: 'label.zone', } }, actions: { + add: { + label: 'label.import.backup.offering', + createForm: { + title: 'label.import.backup.offering', + fields: { + name: { + label: 'label.name', + validation: { + required: true + } + }, + description: { + label: 'label.description', + validation: { + required: true + } + }, + zoneid: { + label: 'label.zone', + validation: { + required: true + }, + select: function(args) { + $.ajax({ + url: createURL("listZones"), + data: {available: 'true'}, + dataType: "json", + async: true, + success: function(json) { + var items = []; + var zoneObjs = json.listzonesresponse.zone; + $(zoneObjs).each(function() { + items.push({ + id: this.id, + description: this.name + }); + }); + items.sort(function(a, b) { + return a.description.localeCompare(b.description); + }); + items.unshift({ + id: -1, + description: '' + }); + args.response.success({ + data: items + }); + args.$select.change(function() { + var $form = $(this).closest('form'); + var zoneId = $form.find('select#label_zone').val(); + var extSelect = $form.find('select#label_external_id'); + extSelect.empty(); + if (zoneId === -1) { + return; + } + $.ajax({ + url: createURL("listBackupProviderOfferings"), + data: {zoneid: zoneId}, + dataType: "json", + success: function(json) { + var items = []; + var offerings = json.listbackupproviderofferingsresponse.backupoffering; + $(offerings).each(function() { + extSelect.append(new Option(this.name, this.externalid)) + }); + } + }); + }) + } + }); + } + }, + externalid: { + label: 'label.external.id', + select: function(args) { + args.response.success({ + data: [] + }); + } + } + }//end of fields + }, //end of createForm + + action: function(args) { + $.ajax({ + url: createURL('importBackupOffering'), + data: { + name: args.data.name, + description: args.data.description, + zoneid: args.data.zoneid, + externalid: args.data.externalid + }, + dataType: 'json', + success: function(json) { + var jid = json.importbackupofferingresponse.jobid; + args.response.success({ + _custom: { + jobId: jid, + getActionFilter: function() { + return backupOfferingActionfilter; + } + } + + }); + }, + error: function(data) { + args.response.error(parseXMLHttpResponse(data)); + } + }); + }, + + notification: { + poll: pollAsyncJobResult + }, + + messages: { + notification: function(args) { + return 'label.import.backup.offering'; + } + } + } }, dataProvider: function(args) { From 1a866b93ab84832a39f265ad8f9f5e5e152b2877 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 9 Dec 2019 02:32:19 +0530 Subject: [PATCH 33/81] ui fixes Signed-off-by: Rohit Yadav --- .../command/user/backup/CreateBackupCmd.java | 5 + ui/css/cloudstack3.css | 8 +- ui/l10n/en.js | 4 + ui/scripts/instances.js | 161 +++++++++++++----- 4 files changed, 136 insertions(+), 42 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java index d93f3e657ebb..db7a2763ef2f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java @@ -118,4 +118,9 @@ public String getEventDescription() { @Override public void create() throws ResourceAllocationException { } + + @Override + public Long getEntityId() { + return vmId; + } } diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 39345c2fffcc..f633556a5c6f 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -12498,6 +12498,7 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it .createTemplate .icon, .enableSwift .icon, .addVM .icon, +.assignToBackupOffering .icon, .dedicateZone .icon, .dedicate .icon { background-position: -69px -63px; @@ -12507,6 +12508,7 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it .createTemplate:hover .icon, .enableSwift:hover .icon, .addVM:hover .icon, +.assignToBackupOffering:hover .icon, .dedicateZone:hover .icon { background-position: -69px -645px; } @@ -12625,11 +12627,13 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it background-position: -138px -647px; } -.cancelMaintenanceMode .icon { +.cancelMaintenanceMode .icon, +.removeFromBackupOffering .icon { background-position: -138px -123px; } -.cancelMaintenanceMode:hover .icon { +.cancelMaintenanceMode:hover .icon, +.removeFromBackupOffering .icon { background-position: -138px -705px; } diff --git a/ui/l10n/en.js b/ui/l10n/en.js index 017c3ce6c5ad..bf09d7d46c3e 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -588,6 +588,7 @@ var dictionary = { "label.cpu.limits":"CPU limits", "label.cpu.mhz":"CPU (in MHz)", "label.cpu.utilized":"CPU Utilized", +"label.create.backup":"Create Backup", "label.create.VPN.connection":"Create VPN Connection", "label.create.nfs.secondary.staging.storage":"Create NFS Secondary Staging Store", "label.create.nfs.secondary.staging.store":"Create NFS secondary staging store", @@ -1828,6 +1829,9 @@ var dictionary = { "label.vmfs":"VMFS", "label.vms":"VMs", "label.backup":"Backups", +"label.backup.offering":"Backup Offering", +"label.backup.offering.assign":"Assign VM to backup offering", +"label.backup.offering.remove":"Remove VM from backup offering", "label.vmsnapshot":"VM Snapshots", "label.vmsnapshot.current":"isCurrent", "label.vmsnapshot.memory":"Snapshot memory", diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index 16c93d8ae0a0..6b74a12c7cef 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -1263,60 +1263,136 @@ createBackup: { messages: { + confirm: function(args) { + return 'label.create.backup'; + }, notification: function() { - return 'Create VM Backup'; + return 'label.create.backup'; } }, - label: 'Create VM Backup', + label: 'label.create.backup', + action: function(args) { + var data = { + virtualmachineid: args.context.instances[0].id + }; + $.ajax({ + url: createURL('createBackup'), + data: data, + dataType: 'json', + success: function(json) { + var jid = json.createbackupresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + + assignToBackupOffering: { + messages: { + confirm: function(args) { + return 'label.backup.offering.assign'; + }, + notification: function() { + return 'label.backup.offering.assign'; + } + }, + label: 'label.backup.offering.assign', createForm: { - title: 'Create VM Backup', - desc: 'Please select a backup offering for the VM backup with a name and description', - fields: { - name: { - label: 'label.name', - }, - description: { - label: 'label.description' - }, - policy: { - label: 'Backup Offering', - select: function(args) { - var items = []; - $.ajax({ - url: createURL('listBackupOfferings'), - dataType: 'json', - async: false, - success: function(json) { - var policies = json.listbackupofferingsresponse.backupoffering; - $(policies).each(function(index, policy) { - items.push({ - id: policy.id, - description: policy.name - }); - }); - args.response.success({ - data: items - }); - } - }); - } + title: 'label.backup.offering.assign', + fields: { + backupofferingid: { + label: 'label.backup.offering', + select: function(args) { + var data = { + zoneid: args.context.instances[0].zoneid + }; + $.ajax({ + url: createURL('listBackupOfferings'), + data: data, + async: false, + success: function(json) { + var offerings = json.listbackupofferingsresponse.backupoffering; + var items = [{ + id: -1, + description: '' + }]; + $(offerings).each(function() { + items.push({ + id: this.id, + description: this.name + }); + }); + args.response.success({ + data: items + }); + } + }); + } + } + } + }, + action: function(args) { + var data = { + virtualmachineid: args.context.instances[0].id, + backupofferingid: args.data.backupofferingid + }; + $.ajax({ + url: createURL('assignVirtualMachineToBackupOffering'), + data: data, + dataType: 'json', + success: function(json) { + var jid = json.assignvirtualmachinetobackupofferingresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + + removeFromBackupOffering: { + messages: { + confirm: function(args) { + return 'label.backup.offering.remove'; + }, + notification: function() { + return 'label.backup.offering.remove'; } }, + label: 'label.backup.offering.remove', + createForm: { + title: 'label.backup.offering.remove', + fields: { + forced: { + label: 'force.remove', + isBoolean: true, + isChecked: false + } + } + }, action: function(args) { var data = { virtualmachineid: args.context.instances[0].id, - name: args.data.name, - description: args.data.description, - policyid: args.data.policy + forced: args.data.forced === "on" }; $.ajax({ - url: createURL('createBackup'), + url: createURL('removeVirtualMachineFromBackupOffering'), data: data, dataType: 'json', - async: true, success: function(json) { - var jid = json.createbackupresponse.jobid; + var jid = json.removevirtualmachinefrombackupofferingresponse.jobid; args.response.success({ _custom: { jobId: jid @@ -3791,7 +3867,12 @@ allowedActions.push("expunge"); } } - allowedActions.push("createBackup"); + if (jsonObj.backupofferingid) { + allowedActions.push("createBackup"); + allowedActions.push("removeFromBackupOffering"); + } else { + allowedActions.push("assignToBackupOffering"); + } if (jsonObj.state == 'Starting' || jsonObj.state == 'Stopping' || jsonObj.state == 'Migrating') { allowedActions.push("viewConsole"); From cd5f9831344558a0d1b70a9a83035a32355be010 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 9 Dec 2019 02:42:53 +0530 Subject: [PATCH 34/81] fixes Signed-off-by: Rohit Yadav --- ui/l10n/en.js | 1 + ui/scripts/storage.js | 104 ++++++++++-------------------------------- 2 files changed, 24 insertions(+), 81 deletions(-) diff --git a/ui/l10n/en.js b/ui/l10n/en.js index bf09d7d46c3e..3a0a21c4b272 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -1832,6 +1832,7 @@ var dictionary = { "label.backup.offering":"Backup Offering", "label.backup.offering.assign":"Assign VM to backup offering", "label.backup.offering.remove":"Remove VM from backup offering", +"label.backup.restore":"Restore VM Backup", "label.vmsnapshot":"VM Snapshots", "label.vmsnapshot.current":"isCurrent", "label.vmsnapshot.memory":"Snapshot memory", diff --git a/ui/scripts/storage.js b/ui/scripts/storage.js index 75c09cbc610e..0e54b1f8f7c8 100644 --- a/ui/scripts/storage.js +++ b/ui/scripts/storage.js @@ -2659,8 +2659,8 @@ id: 'backups', isMaximized: true, fields: { - name: { - label: 'label.name' + virtualmachinename: { + label: 'label.vm.name' }, status: { label: 'label.state', @@ -2670,7 +2670,10 @@ 'Error': 'off' } }, - zoneid: { + account: { + label: 'label.account' + }, + zone: { label: 'label.zone' } }, @@ -2713,8 +2716,11 @@ id: { label: 'label.id' }, - name: { - label: 'label.name' + virtualmachinename: { + label: 'label.vm.name' + }, + virtualmachineid: { + label: 'label.vm.id' }, status: { label: 'label.state' @@ -2731,15 +2737,15 @@ volumes: { label: 'label.volumes' }, - virtualmachineid: { - label: 'label.vm.id' - }, - accountid: { - label: 'label.account' - }, - zoneid: { - label: 'label.zone' - }, + account: { + label: 'label.account' + }, + domain: { + label: 'label.domain' + }, + zone: { + label: 'label.zone' + }, created: { label: 'label.date', converter: cloudStack.converters.toLocalDate @@ -2797,82 +2803,18 @@ } }, - startBackup: { - label: 'Start Ad-hoc Backup', - messages: { - confirm: function(args) { - return 'Please confirm that you want to start an ad-hoc backup of the VM?'; - }, - notification: function(args) { - return 'Ad-hoc VM Backup'; - } - }, - action: function(args) { - $.ajax({ - url: createURL("startBackup&id=" + args.context.backups[0].id), - dataType: "json", - async: true, - success: function(json) { - var jid = json.startbackupresponse.jobid; - args.response.success({ - _custom: { - jobId: jid - } - }); - } - }); - }, - notification: { - poll: pollAsyncJobResult - } - }, - restoreBackup: { - label: 'Restore Backup', + label: 'label.backup.restore', messages: { confirm: function(args) { - return 'Please confirm that you want to restore the vm backup on the stopped VM?'; + return 'Please confirm that you want to restore the vm backup?'; }, notification: function(args) { - return 'Backup restored for the VM'; + return 'label.backup.restore'; } }, - createForm: { - title: 'Restore Backup', - desc: 'Please confirm that you want to restore the VM backup on the stopped VM?', - fields: { - restorepoint: { - label: 'Restore Point', - validation: { - required: true - }, - select: function(args) { - $.ajax({ - url: createURL("listRestorePoints&backupid=" + args.context.backups[0].id), - dataType: "json", - async: true, - success: function(json) { - var rps = json.listrestorepointsresponse.restorepoint; - var items = []; - $(rps).each(function() { - items.push({ - id: this.id, - description: this.created + " (" + this.type + ")" - }); - }); - args.response.success({ - data: items - }); - - } - }); - } - } - } - }, action: function(args) { var data = { - restorepointid: args.data.restorepoint, backupid: args.context.backups[0].id }; $.ajax({ From a6cf24cc6ff2001d74aa4c8e8f49ca029591c19e Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 9 Dec 2019 02:44:30 +0530 Subject: [PATCH 35/81] rename to id instead of backupid for consistency Signed-off-by: Rohit Yadav --- .../cloudstack/api/command/user/backup/RestoreBackupCmd.java | 4 ++-- ui/scripts/storage.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java index 71fc018b0aab..62fa9a17b774 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java @@ -54,11 +54,11 @@ public class RestoreBackupCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.BACKUP_ID, + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = BackupResponse.class, required = true, - description = "id of the backup") + description = "ID of the backup") private Long backupId; ///////////////////////////////////////////////////// diff --git a/ui/scripts/storage.js b/ui/scripts/storage.js index 0e54b1f8f7c8..0690137fda73 100644 --- a/ui/scripts/storage.js +++ b/ui/scripts/storage.js @@ -2815,7 +2815,7 @@ }, action: function(args) { var data = { - backupid: args.context.backups[0].id + id: args.context.backups[0].id }; $.ajax({ url: createURL("restoreBackup"), From 65f2d255e6909de57f46e584199d330d0322df76 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 9 Dec 2019 03:04:24 +0530 Subject: [PATCH 36/81] fix GC logic for veeam Signed-off-by: Rohit Yadav --- .../backup/VeeamBackupProvider.java | 10 +++++++ .../cloudstack/backup/BackupManagerImpl.java | 27 +++++++++++-------- 2 files changed, 26 insertions(+), 11 deletions(-) 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 272d1cd3feae..0c07fb79083c 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 @@ -30,6 +30,7 @@ import org.apache.cloudstack.api.InternalIdentity; import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.backup.dao.BackupScheduleDao; import org.apache.cloudstack.backup.veeam.VeeamClient; import org.apache.cloudstack.backup.veeam.api.Job; import org.apache.cloudstack.framework.config.ConfigKey; @@ -83,6 +84,8 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, private VmwareDatacenterDao vmwareDatacenterDao; @Inject private BackupDao backupDao; + @Inject + private BackupScheduleDao backupScheduleDao; private VeeamClient getClient(final Long zoneId) { try { @@ -186,6 +189,13 @@ public boolean removeVMFromBackupOffering(final VirtualMachine vm) { LOG.warn("Failed to remove Veeam job and backup for job: " + clonedJobName); throw new CloudRuntimeException("Failed to delete Veeam B&R job and backup, an operation may be in progress. Please try again after some time."); } + for (final Backup backup: backupDao.listByVmId(null, vm.getId())) { + backupDao.remove(backup.getId()); + } + final BackupSchedule backupSchedule = backupScheduleDao.findByVM(vm.getId()); + if (backupSchedule != null) { + backupScheduleDao.remove(backupSchedule.getId()); + } return true; } 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 d8214e13c552..ccd0b9adff9b 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -327,17 +327,22 @@ public boolean removeVMFromBackupOffering(final Long vmId, final boolean forced) "that will also delete the backups."); } - vm.setBackupOfferingId(null); - vm.setBackupExternalId(null); - vm.setBackupVolumes(null); - boolean result = backupProvider.removeVMFromBackupOffering(vm); - if (result && vmInstanceDao.update(vm.getId(), vm)) { - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), - "Backup-" + vm.getHostName() + "-" + vm.getUuid(), vm.getBackupOfferingId(), null, null, - Backup.class.getSimpleName(), vm.getUuid()); - return true; - } - return false; + return Transaction.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + vm.setBackupOfferingId(null); + vm.setBackupExternalId(null); + vm.setBackupVolumes(null); + boolean result = backupProvider.removeVMFromBackupOffering(vm); + if (result && vmInstanceDao.update(vm.getId(), vm)) { + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), + "Backup-" + vm.getHostName() + "-" + vm.getUuid(), vm.getBackupOfferingId(), null, null, + Backup.class.getSimpleName(), vm.getUuid()); + return true; + } + return false; + } + }); } @Override From a355c3385f819bec1a4ce234116ed8471727bda8 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 9 Dec 2019 14:00:19 +0530 Subject: [PATCH 37/81] minor fix Signed-off-by: Rohit Yadav --- .../org/apache/cloudstack/backup/VeeamBackupProvider.java | 7 ------- .../org/apache/cloudstack/backup/BackupManagerImpl.java | 4 ++++ 2 files changed, 4 insertions(+), 7 deletions(-) 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 0c07fb79083c..3dca9ed1209e 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 @@ -30,7 +30,6 @@ import org.apache.cloudstack.api.InternalIdentity; import org.apache.cloudstack.backup.dao.BackupDao; -import org.apache.cloudstack.backup.dao.BackupScheduleDao; import org.apache.cloudstack.backup.veeam.VeeamClient; import org.apache.cloudstack.backup.veeam.api.Job; import org.apache.cloudstack.framework.config.ConfigKey; @@ -84,8 +83,6 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, private VmwareDatacenterDao vmwareDatacenterDao; @Inject private BackupDao backupDao; - @Inject - private BackupScheduleDao backupScheduleDao; private VeeamClient getClient(final Long zoneId) { try { @@ -192,10 +189,6 @@ public boolean removeVMFromBackupOffering(final VirtualMachine vm) { for (final Backup backup: backupDao.listByVmId(null, vm.getId())) { backupDao.remove(backup.getId()); } - final BackupSchedule backupSchedule = backupScheduleDao.findByVM(vm.getId()); - if (backupSchedule != null) { - backupScheduleDao.remove(backupSchedule.getId()); - } return true; } 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 ccd0b9adff9b..2ca90c8adfaf 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -338,6 +338,10 @@ public Boolean doInTransaction(TransactionStatus status) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), "Backup-" + vm.getHostName() + "-" + vm.getUuid(), vm.getBackupOfferingId(), null, null, Backup.class.getSimpleName(), vm.getUuid()); + final BackupSchedule backupSchedule = backupScheduleDao.findByVM(vm.getId()); + if (backupSchedule != null) { + backupScheduleDao.remove(backupSchedule.getId()); + } return true; } return false; From 5003d243e91dddbc4ac57cc4f77b69ecc2e8ee6e Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 9 Dec 2019 17:39:23 +0530 Subject: [PATCH 38/81] minor fixes from self review Signed-off-by: Rohit Yadav --- .../user/backup/ListBackupScheduleCmd.java | 2 +- .../cloudstack/backup/BackupManagerImpl.java | 2 +- ui/scripts/instances.js | 3 +++ ui/scripts/storage.js | 15 ++++++++++----- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java index 6ea2b0a2608f..4068dc295dc0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java @@ -81,7 +81,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE response.setResponseName(getCommandName()); setResponseObject(response); } else { - throw new CloudRuntimeException("Error while listing backup schedule of VM"); + throw new CloudRuntimeException("No backup schedule exists for the VM"); } } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); 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 2ca90c8adfaf..491f64600d1c 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -594,7 +594,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, LOG.debug("Asking provider to restore volume " + backedUpVolumeUuid + " from backup " + backupId + " (with external ID " + backup.getExternalId() + ") and attach it to VM: " + vm.getUuid()); - final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(vm.getBackupOfferingId()); + final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(backup.getBackupOfferingId()); if (offering == null) { throw new CloudRuntimeException("Failed to find VM backup offering"); } diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index 6b74a12c7cef..72bf1aedbfeb 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -3033,6 +3033,9 @@ keypair: { label: 'label.ssh.key.pair' }, + backupofferingname: { + label: 'label.backup.offering' + }, domain: { label: 'label.domain' }, diff --git a/ui/scripts/storage.js b/ui/scripts/storage.js index 0690137fda73..3000bf62efd7 100644 --- a/ui/scripts/storage.js +++ b/ui/scripts/storage.js @@ -2670,6 +2670,12 @@ 'Error': 'off' } }, + type: { + label: 'label.type' + }, + created: { + label: 'label.date' + }, account: { label: 'label.account' }, @@ -2728,6 +2734,9 @@ externalid: { label: 'label.external.id' }, + type: { + label: 'label.type' + }, size: { label: 'label.size' }, @@ -2765,11 +2774,7 @@ }); } }); - }, - tags: cloudStack.api.tags({ - resourceType: 'Backup', - contextId: 'backups' - }) + } } }, actions: { From e3c500afc6e6261c20058e145c870c5df77b8366 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 9 Dec 2019 18:22:40 +0530 Subject: [PATCH 39/81] vmware: fix issue, find including removed for volume of an expunged VM Signed-off-by: Rohit Yadav --- .../src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java index 3288c82e361f..45be56275345 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java @@ -1324,7 +1324,7 @@ private Map getDisksMapping(Backup backup, List Date: Mon, 9 Dec 2019 21:08:34 +0530 Subject: [PATCH 40/81] bugfix - restore expunged vm case wip Signed-off-by: Rohit Yadav --- .../java/com/cloud/hypervisor/guru/VMwareGuru.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java index 45be56275345..4122580ce52d 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -1027,7 +1028,8 @@ private VMInstanceVO getVM(String vmInternalName, long templateId, long guestOsI existingVM.setTemplateId(templateId); existingVM.setGuestOSId(guestOsId); existingVM.setServiceOfferingId(serviceOfferingId); - return _vmDao.persist(existingVM); + _vmDao.update(existingVM.getId(), existingVM); + return _vmDao.findById(existingVM.getId()); } else { long id = userVmDao.getNextInSequence(Long.class, "id"); UserVmVO vmInstanceVO = new UserVmVO(id, vmInternalName, vmInternalName, templateId, HypervisorType.VMware, @@ -1087,13 +1089,17 @@ private long getDiskOfferingId(long size, Storage.ProvisioningType provisioningT diskOfferingDao.findByUniqueName("Cloud.Com-Custom").getId(); } - protected VolumeVO updateVolume(VirtualDisk disk, Map disksMapping, VirtualMachineMO vmToImport, Long poolId) throws Exception { + protected VolumeVO updateVolume(VirtualDisk disk, Map disksMapping, VirtualMachineMO vmToImport, Long poolId, Long instanceId) throws Exception { VolumeVO volumeVO = disksMapping.get(disk); String volumeName = getVolumeName(disk, vmToImport); volumeVO.setPath(volumeName); volumeVO.setPoolId(poolId); VirtualMachineDiskInfo diskInfo = getDiskInfo(vmToImport, poolId, volumeName); volumeVO.setChainInfo(new Gson().toJson(diskInfo)); + volumeVO.setState(Volume.State.Ready); + volumeVO.setInstanceId(instanceId); + volumeVO.setAttached(new Date()); + volumeVO.setRemoved(null); _volumeDao.update(volumeVO.getId(), volumeVO); return volumeVO; } @@ -1113,8 +1119,8 @@ private void importVMVolumes(VMInstanceVO vmInstanceVO, List virtua for (VirtualDisk disk : virtualDisks) { Long poolId = getPoolId(disk); VolumeVO volumeVO; - if (disksMapping.containsKey(disk) && disksMapping.get(disk) != null) { - volumeVO = updateVolume(disk, disksMapping, vmToImport, poolId); + if (disksMapping.containsKey(disk) && disksMapping.get(disk) != null && disksMapping.get(disk).getRemoved() == null) { + volumeVO = updateVolume(disk, disksMapping, vmToImport, poolId, instanceId); } else { volumeVO = createVolume(disk, vmToImport, domainId, zoneId, accountId, instanceId, poolId, templateId, backup, true); } From 4bbf1e5dbe2e30e5798803f03b838efb73b68d54 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 9 Dec 2019 21:52:44 +0530 Subject: [PATCH 41/81] fixes Signed-off-by: Rohit Yadav --- .../com/cloud/hypervisor/guru/VMwareGuru.java | 34 ++++++------------- .../cloudstack/backup/BackupManagerImpl.java | 2 +- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java index 4122580ce52d..89c8c7878162 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java @@ -1043,7 +1043,7 @@ private VMInstanceVO getVM(String vmInternalName, long templateId, long guestOsI /** * Create and persist volume */ - private VolumeVO createVolumeRecord(Volume.Type type, String volumeName, long zoneId, long domainId, + private void createVolumeRecord(Volume.Type type, String volumeName, long zoneId, long domainId, long accountId, long diskOfferingId, Storage.ProvisioningType provisioningType, Long size, long instanceId, Long poolId, long templateId, Integer unitNumber, VirtualMachineDiskInfo diskInfo) { VolumeVO volumeVO = new VolumeVO(type, volumeName, zoneId, domainId, accountId, diskOfferingId, @@ -1058,7 +1058,7 @@ private VolumeVO createVolumeRecord(Volume.Type type, String volumeName, long zo if (unitNumber != null) { volumeVO.setDeviceId(unitNumber.longValue()); } - return _volumeDao.persist(volumeVO); + _volumeDao.persist(volumeVO); } /** @@ -1089,7 +1089,7 @@ private long getDiskOfferingId(long size, Storage.ProvisioningType provisioningT diskOfferingDao.findByUniqueName("Cloud.Com-Custom").getId(); } - protected VolumeVO updateVolume(VirtualDisk disk, Map disksMapping, VirtualMachineMO vmToImport, Long poolId, Long instanceId) throws Exception { + protected void updateVolume(VirtualDisk disk, Map disksMapping, VirtualMachineMO vmToImport, Long poolId, Long instanceId) throws Exception { VolumeVO volumeVO = disksMapping.get(disk); String volumeName = getVolumeName(disk, vmToImport); volumeVO.setPath(volumeName); @@ -1101,7 +1101,6 @@ protected VolumeVO updateVolume(VirtualDisk disk, Map dis volumeVO.setAttached(new Date()); volumeVO.setRemoved(null); _volumeDao.update(volumeVO.getId(), volumeVO); - return volumeVO; } /** @@ -1115,27 +1114,16 @@ private void importVMVolumes(VMInstanceVO vmInstanceVO, List virtua long templateId = vmInstanceVO.getTemplateId(); long instanceId = vmInstanceVO.getId(); - List vols = new ArrayList<>(); + for (final VolumeVO existingVol : _volumeDao.findByInstance(vmInstanceVO.getId())) { + _volumeDao.remove(existingVol.getId()); + } + for (VirtualDisk disk : virtualDisks) { Long poolId = getPoolId(disk); - VolumeVO volumeVO; if (disksMapping.containsKey(disk) && disksMapping.get(disk) != null && disksMapping.get(disk).getRemoved() == null) { - volumeVO = updateVolume(disk, disksMapping, vmToImport, poolId, instanceId); + updateVolume(disk, disksMapping, vmToImport, poolId, instanceId); } else { - volumeVO = createVolume(disk, vmToImport, domainId, zoneId, accountId, instanceId, poolId, templateId, backup, true); - } - vols.add(volumeVO); - } - removeUnusedVolumes(vols, vmInstanceVO); - } - - private void removeUnusedVolumes(List vols, VMInstanceVO vmInstanceVO) { - List existingVolumes = _volumeDao.findByInstance(vmInstanceVO.getId()); - if (existingVolumes.size() != vols.size()) { - for (VolumeVO existingVol : existingVolumes) { - if (!vols.contains(existingVol)) { - _volumeDao.remove(existingVol.getId()); - } + createVolume(disk, vmToImport, domainId, zoneId, accountId, instanceId, poolId, templateId, backup, true); } } } @@ -1146,7 +1134,7 @@ private VirtualMachineDiskInfo getDiskInfo(VirtualMachineMO vmMo, Long poolId, S return diskInfoBuilder.getDiskInfoByBackingFileBaseName(volumeName, poolName); } - private VolumeVO createVolume(VirtualDisk disk, VirtualMachineMO vmToImport, long domainId, long zoneId, + private void createVolume(VirtualDisk disk, VirtualMachineMO vmToImport, long domainId, long zoneId, long accountId, long instanceId, Long poolId, long templateId, Backup backup, boolean isImport) throws Exception { VMInstanceVO vm = _vmDao.findByIdIncludingRemoved(backup.getVmId()); if (vm == null) { @@ -1170,7 +1158,7 @@ private VolumeVO createVolume(VirtualDisk disk, VirtualMachineMO vmToImport, lon long diskOfferingId = getDiskOfferingId(size, provisioningType); Integer unitNumber = disk.getUnitNumber(); VirtualMachineDiskInfo diskInfo = getDiskInfo(vmToImport, poolId, volumeName); - return createVolumeRecord(type, volumeName, zoneId, domainId, accountId, diskOfferingId, + createVolumeRecord(type, volumeName, zoneId, domainId, accountId, diskOfferingId, provisioningType, size, instanceId, poolId, templateId, unitNumber, diskInfo); } 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 491f64600d1c..a80af86d76c6 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -304,7 +304,7 @@ public boolean assignVMToBackupOffering(Long vmId, Long offeringId) { @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, eventDescription = "remove VM from backup offering", async = true) public boolean removeVMFromBackupOffering(final Long vmId, final boolean forced) { - final VMInstanceVO vm = vmInstanceDao.findById(vmId); + final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); if (vm == null) { throw new CloudRuntimeException("Did not find VM by provided ID"); } From b6a2db1bd5678da65938039874e976603565e9c2 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Tue, 10 Dec 2019 01:07:10 +0530 Subject: [PATCH 42/81] fixes fixes fixes Signed-off-by: Rohit Yadav --- .../com/cloud/hypervisor/guru/VMwareGuru.java | 48 +++++++++---------- .../cloudstack/backup/BackupManagerImpl.java | 2 +- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java index 89c8c7878162..623c1dd7e476 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java @@ -1043,7 +1043,7 @@ private VMInstanceVO getVM(String vmInternalName, long templateId, long guestOsI /** * Create and persist volume */ - private void createVolumeRecord(Volume.Type type, String volumeName, long zoneId, long domainId, + private VolumeVO createVolumeRecord(Volume.Type type, String volumeName, long zoneId, long domainId, long accountId, long diskOfferingId, Storage.ProvisioningType provisioningType, Long size, long instanceId, Long poolId, long templateId, Integer unitNumber, VirtualMachineDiskInfo diskInfo) { VolumeVO volumeVO = new VolumeVO(type, volumeName, zoneId, domainId, accountId, diskOfferingId, @@ -1054,11 +1054,13 @@ private void createVolumeRecord(Volume.Type type, String volumeName, long zoneId volumeVO.setInstanceId(instanceId); volumeVO.setPoolId(poolId); volumeVO.setTemplateId(templateId); + volumeVO.setAttached(new Date()); + volumeVO.setRemoved(null); volumeVO.setChainInfo(new Gson().toJson(diskInfo)); if (unitNumber != null) { volumeVO.setDeviceId(unitNumber.longValue()); } - _volumeDao.persist(volumeVO); + return _volumeDao.persist(volumeVO); } /** @@ -1089,7 +1091,7 @@ private long getDiskOfferingId(long size, Storage.ProvisioningType provisioningT diskOfferingDao.findByUniqueName("Cloud.Com-Custom").getId(); } - protected void updateVolume(VirtualDisk disk, Map disksMapping, VirtualMachineMO vmToImport, Long poolId, Long instanceId) throws Exception { + protected VolumeVO updateVolume(VirtualDisk disk, Map disksMapping, VirtualMachineMO vmToImport, Long poolId, Long instanceId) throws Exception { VolumeVO volumeVO = disksMapping.get(disk); String volumeName = getVolumeName(disk, vmToImport); volumeVO.setPath(volumeName); @@ -1099,8 +1101,8 @@ protected void updateVolume(VirtualDisk disk, Map disksMa volumeVO.setState(Volume.State.Ready); volumeVO.setInstanceId(instanceId); volumeVO.setAttached(new Date()); - volumeVO.setRemoved(null); _volumeDao.update(volumeVO.getId(), volumeVO); + return volumeVO; } /** @@ -1114,17 +1116,15 @@ private void importVMVolumes(VMInstanceVO vmInstanceVO, List virtua long templateId = vmInstanceVO.getTemplateId(); long instanceId = vmInstanceVO.getId(); - for (final VolumeVO existingVol : _volumeDao.findByInstance(vmInstanceVO.getId())) { - _volumeDao.remove(existingVol.getId()); - } - for (VirtualDisk disk : virtualDisks) { Long poolId = getPoolId(disk); + Volume volume = null; if (disksMapping.containsKey(disk) && disksMapping.get(disk) != null && disksMapping.get(disk).getRemoved() == null) { - updateVolume(disk, disksMapping, vmToImport, poolId, instanceId); + volume = updateVolume(disk, disksMapping, vmToImport, poolId, instanceId); } else { - createVolume(disk, vmToImport, domainId, zoneId, accountId, instanceId, poolId, templateId, backup, true); + volume = createVolume(disk, vmToImport, domainId, zoneId, accountId, instanceId, poolId, templateId, backup, true); } + s_logger.debug("VM backup restored (updated/created) volume id:" + volume.getId() + " for VM id:" + instanceId); } } @@ -1134,7 +1134,7 @@ private VirtualMachineDiskInfo getDiskInfo(VirtualMachineMO vmMo, Long poolId, S return diskInfoBuilder.getDiskInfoByBackingFileBaseName(volumeName, poolName); } - private void createVolume(VirtualDisk disk, VirtualMachineMO vmToImport, long domainId, long zoneId, + private VolumeVO createVolume(VirtualDisk disk, VirtualMachineMO vmToImport, long domainId, long zoneId, long accountId, long instanceId, Long poolId, long templateId, Backup backup, boolean isImport) throws Exception { VMInstanceVO vm = _vmDao.findByIdIncludingRemoved(backup.getVmId()); if (vm == null) { @@ -1158,7 +1158,7 @@ private void createVolume(VirtualDisk disk, VirtualMachineMO vmToImport, long do long diskOfferingId = getDiskOfferingId(size, provisioningType); Integer unitNumber = disk.getUnitNumber(); VirtualMachineDiskInfo diskInfo = getDiskInfo(vmToImport, poolId, volumeName); - createVolumeRecord(type, volumeName, zoneId, domainId, accountId, diskOfferingId, + return createVolumeRecord(type, volumeName, zoneId, domainId, accountId, diskOfferingId, provisioningType, size, instanceId, poolId, templateId, unitNumber, diskInfo); } @@ -1278,7 +1278,10 @@ private Pair getNicMacAddressAndNetworkName(VirtualDevice nicDev private void importVMNics(VirtualDevice[] nicDevices, DatacenterMO dcMo, Map networksMapping, VMInstanceVO vmInstanceVO) throws Exception { VmwareContext context = dcMo.getContext(); - List nics = new ArrayList<>(); + List existingNics = _nicDao.listByVmId(vmInstanceVO.getId()) + .stream() + .map(NicVO::getId) + .collect(Collectors.toList()); for (VirtualDevice nicDevice : nicDevices) { Pair pair = getNicMacAddressAndNetworkName(nicDevice, context); String macAddress = pair.first(); @@ -1288,20 +1291,13 @@ private void importVMNics(VirtualDevice[] nicDevices, DatacenterMO dcMo, Map nics, VMInstanceVO vmInstanceVO) { - List existingVMNics = _nicDao.listByVmId(vmInstanceVO.getId()); - if (nics.size() != existingVMNics.size()) { - for (NicVO existingNic : existingVMNics) { - if (!nics.contains(existingNic)) { - _nicDao.remove(existingNic.getId()); - } + if (nicVO != null) { + existingNics.remove(nicVO.getId()); } } + for (final Long unusedNic : existingNics) { + _nicDao.removeNicsForInstance(unusedNic); + } } private Map getDisksMapping(Backup backup, List virtualDisks) { @@ -1321,6 +1317,8 @@ private Map getDisksMapping(Backup backup, List Date: Tue, 10 Dec 2019 17:46:46 +0530 Subject: [PATCH 43/81] handle many edge cases around VM restoration of an expunged VM Signed-off-by: Rohit Yadav --- .../cloudstack/backup/BackupProvider.java | 2 - .../com/cloud/storage/dao/VolumeDaoImpl.java | 4 +- .../main/java/com/cloud/vm/VMInstanceVO.java | 4 + .../java/com/cloud/vm/dao/VMInstanceDao.java | 2 + .../com/cloud/vm/dao/VMInstanceDaoImpl.java | 7 + .../META-INF/db/schema-41300to41400.sql | 4 +- .../java/com/cloud/utils/db/GenericDao.java | 2 + .../com/cloud/utils/db/GenericDaoBase.java | 28 ++++ .../backup/DummyBackupProvider.java | 15 +-- .../backup/VeeamBackupProvider.java | 3 +- .../com/cloud/simulator/SimulatorGuru.java | 38 ++++++ .../com/cloud/hypervisor/guru/VMwareGuru.java | 122 ++++++++---------- .../com/cloud/user/MockUsageEventDao.java | 5 + 13 files changed, 151 insertions(+), 85 deletions(-) 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 5bb1d663554a..6d849f4b870e 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -96,7 +96,5 @@ public interface BackupProvider { Map getBackupMetrics(Long zoneId, List vms); - List listRestorePoints(VirtualMachine vm); - void syncBackups(VirtualMachine vm, Backup.Metric metric); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java index 368453c84c0d..fb051d4d427a 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java @@ -194,7 +194,9 @@ public List findByInstanceAndType(long id, Type vType) { public List findIncludingRemovedByInstanceAndType(long id, Type vType) { SearchCriteria sc = AllFieldsSearch.create(); sc.setParameters("instanceId", id); - sc.setParameters("vType", vType.toString()); + if (vType != null) { + sc.setParameters("vType", vType.toString()); + } return listIncludingRemovedBy(sc); } diff --git a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java index 2e737565f7db..c69e3cf1a1ee 100644 --- a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java @@ -500,6 +500,10 @@ public void setDetails(Map details) { this.details = details; } + public void setRemoved(Date removed) { + this.removed = removed; + } + transient String toString; @Override diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java index 95c28e3bf35a..cc0e7f9a8f82 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java @@ -86,6 +86,8 @@ public interface VMInstanceDao extends GenericDao, StateDao< VMInstanceVO findVMByInstanceName(String name); + VMInstanceVO findVMByInstanceNameIncludingRemoved(String name); + VMInstanceVO findVMByHostName(String hostName); void updateProxyId(long id, Long proxyId, Date time); diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java index 19d1f7adc64d..7b6fc683ebe8 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -457,6 +457,13 @@ public VMInstanceVO findVMByInstanceName(String name) { return findOneBy(sc); } + @Override + public VMInstanceVO findVMByInstanceNameIncludingRemoved(String name) { + SearchCriteria sc = InstanceNameSearch.create(); + sc.setParameters("instanceName", name); + return findOneIncludingRemovedBy(sc); + } + @Override public VMInstanceVO findVMByHostName(String hostName) { SearchCriteria sc = HostNameSearch.create(); diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql index 00b232058c3a..aa794bfb3289 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41300to41400.sql @@ -29,7 +29,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`backup_offering` ( `uuid` varchar(40) NOT NULL UNIQUE, `name` varchar(255) NOT NULL COMMENT 'backup offering name', `description` varchar(255) NOT NULL COMMENT 'backup offering description', - `external_id` varchar(255) NOT NULL COMMENT 'external ID on provider side', + `external_id` varchar(255) DEFAULT NULL COMMENT 'external ID on provider side', `zone_id` bigint(20) unsigned NOT NULL COMMENT 'zone id', `provider` varchar(255) NOT NULL COMMENT 'backup provider', `created` datetime DEFAULT NULL, @@ -46,7 +46,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`backups` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `uuid` varchar(40) NOT NULL UNIQUE, `vm_id` bigint(20) unsigned NOT NULL, - `external_id` varchar(255) NOT NULL COMMENT 'external ID', + `external_id` varchar(255) DEFAULT NULL COMMENT 'external ID', `type` varchar(255) NOT NULL COMMENT 'backup type', `date` varchar(255) NOT NULL COMMENT 'backup date', `size` bigint(20) DEFAULT 0 COMMENT 'size of the backup', diff --git a/framework/db/src/main/java/com/cloud/utils/db/GenericDao.java b/framework/db/src/main/java/com/cloud/utils/db/GenericDao.java index 63047e7c699b..f51b45b14241 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/GenericDao.java +++ b/framework/db/src/main/java/com/cloud/utils/db/GenericDao.java @@ -234,6 +234,8 @@ public interface GenericDao { */ void expunge(); + boolean unremove(ID id); + public K getNextInSequence(Class clazz, String name); /** diff --git a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java index fb923c6f0683..010c604034d3 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java +++ b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java @@ -1780,6 +1780,34 @@ public void expunge() { } } + @Override + public boolean unremove(ID id) { + if (_removed == null) { + return false; + } + + final TransactionLegacy txn = TransactionLegacy.currentTxn(); + PreparedStatement pstmt = null; + try { + txn.start(); + pstmt = txn.prepareAutoCloseStatement(_removeSql.first()); + final Attribute[] attrs = _removeSql.second(); + pstmt.setObject(1, null); + for (int i = 0; i < attrs.length - 1; i++) { + prepareAttribute(i + 2, pstmt, attrs[i], id); + } + + final int result = pstmt.executeUpdate(); + txn.commit(); + if (_cache != null) { + _cache.remove(id); + } + return result > 0; + } catch (final SQLException e) { + throw new CloudRuntimeException("DB Exception on: " + pstmt, e); + } + } + @DB() protected void setField(final Object entity, final ResultSet rs, ResultSetMetaData meta, final int index) throws SQLException { Attribute attr = _allColumns.get(new Pair(meta.getTableName(index), meta.getColumnName(index))); 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 7fd7b5e0ff8b..195ed3f6d43f 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 @@ -51,8 +51,8 @@ public String getDescription() { @Override public List listBackupOfferings(Long zoneId) { s_logger.debug("Listing backup policies on Dummy B&R Plugin"); - BackupOffering policy1 = new BackupOfferingVO(1, "aaaa-aaaa", "dummy", "Golden Policy", "Gold description"); - BackupOffering policy2 = new BackupOfferingVO(1, "bbbb-bbbb", "dummy", "Silver Policy", "Silver description"); + BackupOffering policy1 = new BackupOfferingVO(1, "gold-policy", "dummy", "Golden Policy", "Gold description"); + BackupOffering policy2 = new BackupOfferingVO(1, "silver-policy", "dummy", "Silver Policy", "Silver description"); return Arrays.asList(policy1, policy2); } @@ -113,11 +113,12 @@ public boolean takeBackup(VirtualMachine vm) { BackupVO backup = new BackupVO(); backup.setVmId(vm.getId()); + backup.setExternalId("dummy-external-id"); backup.setType("FULL"); backup.setDate(new Date().toString()); - backup.setStatus(Backup.Status.BackedUp); backup.setSize(1024L); backup.setProtectedSize(1024000L); + backup.setStatus(Backup.Status.BackedUp); backup.setBackupOfferingId(vm.getBackupOfferingId()); backup.setAccountId(vm.getAccountId()); backup.setDomainId(vm.getDomainId()); @@ -130,15 +131,7 @@ public boolean deleteBackup(Backup backup) { return true; } - @Override - public List listRestorePoints(VirtualMachine vm) { - return Arrays.asList( - new Backup.RestorePoint("aaaaaaaa", "22/08/2017", "Full"), - new Backup.RestorePoint("bbbbbbbb", "23/08/2017", "Incremental")); - } - @Override public void syncBackups(VirtualMachine vm, Backup.Metric metric) { - } } 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 3dca9ed1209e..403db3eb02d0 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 @@ -235,8 +235,7 @@ public Map getBackupMetrics(final Long zoneId, fi return metrics; } - @Override - public List listRestorePoints(VirtualMachine vm) { + private List listRestorePoints(VirtualMachine vm) { String backupName = getGuestBackupName(vm.getInstanceName(), vm.getUuid()); return getClient(vm.getDataCenterId()).listRestorePoints(backupName, vm.getInstanceName()); } diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/SimulatorGuru.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/SimulatorGuru.java index 4516c7c2fb8a..246d86d2712d 100644 --- a/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/SimulatorGuru.java +++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/SimulatorGuru.java @@ -16,22 +16,41 @@ // under the License. package com.cloud.simulator; +import java.util.Date; import java.util.Map; import javax.inject.Inject; +import org.apache.cloudstack.backup.Backup; + import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.HypervisorGuru; import com.cloud.hypervisor.HypervisorGuruBase; import com.cloud.storage.GuestOSVO; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.GuestOSDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.VMInstanceDao; public class SimulatorGuru extends HypervisorGuruBase implements HypervisorGuru { @Inject GuestOSDao _guestOsDao; + @Inject + VMInstanceDao instanceDao; + + @Inject + VolumeDao volumeDao; + + @Inject + NicDao nicDao; + protected SimulatorGuru() { super(); } @@ -52,6 +71,25 @@ public VirtualMachineTO implement(VirtualMachineProfile vm) { return to; } + @Override + public VirtualMachine importVirtualMachineFromBackup(long zoneId, long domainId, long accountId, long userId, + String vmInternalName, Backup backup) { + VMInstanceVO vm = instanceDao.findVMByInstanceNameIncludingRemoved(vmInternalName); + if (vm.getRemoved() != null) { + vm.setState(VirtualMachine.State.Stopped); + vm.setPowerState(VirtualMachine.PowerState.PowerOff); + instanceDao.update(vm.getId(), vm); + instanceDao.unremove(vm.getId()); + } + for (final VolumeVO volume : volumeDao.findIncludingRemovedByInstanceAndType(vm.getId(), null)) { + volume.setState(Volume.State.Ready); + volume.setAttached(new Date()); + volumeDao.update(volume.getId(), volume); + volumeDao.unremove(volume.getId()); + } + return vm; + } + @Override public boolean trackVmHostChange() { return false; diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java index 623c1dd7e476..36a94f32a73a 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java @@ -71,7 +71,10 @@ import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.agent.api.to.VolumeTO; import com.cloud.cluster.ClusterManager; +import com.cloud.configuration.Resource; import com.cloud.dc.ClusterDetailsDao; +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventUtils; import com.cloud.exception.InsufficientAddressCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.host.Host; @@ -133,6 +136,7 @@ import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.secondary.SecondaryStorageVmManager; import com.cloud.template.VirtualMachineTemplate.BootloaderType; +import com.cloud.user.ResourceLimitService; import com.cloud.utils.Pair; import com.cloud.utils.UuidUtils; import com.cloud.utils.db.DB; @@ -142,7 +146,6 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; import com.cloud.vm.DomainRouterVO; -import com.cloud.vm.Nic; import com.cloud.vm.NicProfile; import com.cloud.vm.NicVO; import com.cloud.vm.SecondaryStorageVmVO; @@ -150,6 +153,7 @@ import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.DomainRouterDao; @@ -200,10 +204,14 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co @Inject private VMInstanceDao _vmDao; @Inject + private VirtualMachineManager vmManager; + @Inject private ClusterManager _clusterMgr; @Inject VolumeDao _volumeDao; @Inject + ResourceLimitService _resourceLimitService; + @Inject PrimaryDataStoreDao _storagePoolDao; @Inject VolumeDataFactory _volFactory; @@ -1023,13 +1031,18 @@ private Long getImportingVMTemplate(List virtualDisks, DatacenterMO private VMInstanceVO getVM(String vmInternalName, long templateId, long guestOsId, long serviceOfferingId, long zoneId, long accountId, long userId, long domainId) { - VMInstanceVO existingVM = _vmDao.findVMByInstanceName(vmInternalName); - if (existingVM != null) { - existingVM.setTemplateId(templateId); - existingVM.setGuestOSId(guestOsId); - existingVM.setServiceOfferingId(serviceOfferingId); - _vmDao.update(existingVM.getId(), existingVM); - return _vmDao.findById(existingVM.getId()); + VMInstanceVO vm = _vmDao.findVMByInstanceNameIncludingRemoved(vmInternalName); + if (vm != null) { + vm.setState(VirtualMachine.State.Stopped); + vm.setPowerState(VirtualMachine.PowerState.PowerOff); + _vmDao.update(vm.getId(), vm); + if (vm.getRemoved() != null) { + _vmDao.unremove(vm.getId()); + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_CREATE, accountId, vm.getDataCenterId(), vm.getId(), + vm.getHostName(), vm.getServiceOfferingId(), vm.getTemplateId(), + vm.getHypervisorType().toString(), VirtualMachine.class.getName(), vm.getUuid(), vm.isDisplayVm()); + } + return _vmDao.findById(vm.getId()); } else { long id = userVmDao.getNextInSequence(Long.class, "id"); UserVmVO vmInstanceVO = new UserVmVO(id, vmInternalName, vmInternalName, templateId, HypervisorType.VMware, @@ -1091,25 +1104,35 @@ private long getDiskOfferingId(long size, Storage.ProvisioningType provisioningT diskOfferingDao.findByUniqueName("Cloud.Com-Custom").getId(); } - protected VolumeVO updateVolume(VirtualDisk disk, Map disksMapping, VirtualMachineMO vmToImport, Long poolId, Long instanceId) throws Exception { - VolumeVO volumeVO = disksMapping.get(disk); + protected VolumeVO updateVolume(VirtualDisk disk, Map disksMapping, VirtualMachineMO vmToImport, Long poolId, VirtualMachine vm) throws Exception { + VolumeVO volume = disksMapping.get(disk); String volumeName = getVolumeName(disk, vmToImport); - volumeVO.setPath(volumeName); - volumeVO.setPoolId(poolId); + volume.setPath(volumeName); + volume.setPoolId(poolId); VirtualMachineDiskInfo diskInfo = getDiskInfo(vmToImport, poolId, volumeName); - volumeVO.setChainInfo(new Gson().toJson(diskInfo)); - volumeVO.setState(Volume.State.Ready); - volumeVO.setInstanceId(instanceId); - volumeVO.setAttached(new Date()); - _volumeDao.update(volumeVO.getId(), volumeVO); - return volumeVO; + volume.setChainInfo(new Gson().toJson(diskInfo)); + volume.setInstanceId(vm.getId()); + volume.setState(Volume.State.Ready); + volume.setAttached(new Date()); + _volumeDao.update(volume.getId(), volume); + if (volume.getRemoved() != null) { + _volumeDao.unremove(volume.getId()); + if (vm.getType() == Type.User) { + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volume.getAccountId(), volume.getDataCenterId(), + volume.getId(), volume.getName(), volume.getDiskOfferingId(), null, volume.getSize(), + Volume.class.getName(), volume.getUuid(), volume.isDisplayVolume()); + _resourceLimitService.incrementResourceCount(vm.getAccountId(), Resource.ResourceType.volume, volume.isDisplayVolume()); + _resourceLimitService.incrementResourceCount(vm.getAccountId(), Resource.ResourceType.primary_storage, volume.isDisplayVolume(), volume.getSize()); + } + } + return volume; } /** * Get volumes for VM being imported */ - private void importVMVolumes(VMInstanceVO vmInstanceVO, List virtualDisks, - Map disksMapping, VirtualMachineMO vmToImport, Backup backup) throws Exception { + private void syncVMVolumes(VMInstanceVO vmInstanceVO, List virtualDisks, + Map disksMapping, VirtualMachineMO vmToImport, Backup backup) throws Exception { long zoneId = vmInstanceVO.getDataCenterId(); long accountId = vmInstanceVO.getAccountId(); long domainId = vmInstanceVO.getDomainId(); @@ -1119,8 +1142,8 @@ private void importVMVolumes(VMInstanceVO vmInstanceVO, List virtua for (VirtualDisk disk : virtualDisks) { Long poolId = getPoolId(disk); Volume volume = null; - if (disksMapping.containsKey(disk) && disksMapping.get(disk) != null && disksMapping.get(disk).getRemoved() == null) { - volume = updateVolume(disk, disksMapping, vmToImport, poolId, instanceId); + if (disksMapping.containsKey(disk) && disksMapping.get(disk) != null) { + volume = updateVolume(disk, disksMapping, vmToImport, poolId, vmInstanceVO); } else { volume = createVolume(disk, vmToImport, domainId, zoneId, accountId, instanceId, poolId, templateId, backup, true); } @@ -1239,34 +1262,6 @@ private NetworkMO getNetworkMO(VirtualE1000 nic, VmwareContext context) { return new NetworkMO(context, networkMor); } - /** - * Create and persist NIC - */ - private NicVO createNicRecord(String macAddress, Long networkId, VMInstanceVO vmInstanceVO) { - NicVO nicVO = new NicVO("ExternalGuestNetworkGuru", vmInstanceVO.getId(), networkId, vmInstanceVO.getType()); - nicVO.setMacAddress(macAddress); - nicVO.setAddressFormat(Networks.AddressFormat.Ip4); - nicVO.setReservationStrategy(Nic.ReservationStrategy.Start); - nicVO.setState(Nic.State.Reserved); - nicVO.setDefaultNic(true); - return _nicDao.persist(nicVO); - } - - /** - * Remove volumes and nics if the VM already existed on CloudStack. No action performed if not existing - */ - private Pair, List> cleanupExistingVMVolumesAndNics(String vmInternalName) { - VMInstanceVO existingVm = _vmDao.findVMByInstanceName(vmInternalName); - if (existingVm != null) { - List rootDisks = _volumeDao.findReadyRootVolumesByInstance(existingVm.getId()); - List datadisks = _volumeDao.findByInstanceAndType(existingVm.getId(), Volume.Type.DATADISK); - _volumeDao.deleteVolumesByInstance(existingVm.getId()); - _nicDao.removeNicsForInstance(existingVm.getId()); - return new Pair<>(rootDisks, datadisks); - } - return new Pair<>(null, null); - } - private Pair getNicMacAddressAndNetworkName(VirtualDevice nicDevice, VmwareContext context) throws Exception { VirtualE1000 nic = (VirtualE1000) nicDevice; String macAddress = nic.getMacAddress(); @@ -1275,28 +1270,22 @@ private Pair getNicMacAddressAndNetworkName(VirtualDevice nicDev return new Pair<>(macAddress, networkName); } - private void importVMNics(VirtualDevice[] nicDevices, DatacenterMO dcMo, Map networksMapping, - VMInstanceVO vmInstanceVO) throws Exception { + private void syncVMNics(VirtualDevice[] nicDevices, DatacenterMO dcMo, Map networksMapping, + VMInstanceVO vm) throws Exception { VmwareContext context = dcMo.getContext(); - List existingNics = _nicDao.listByVmId(vmInstanceVO.getId()) - .stream() - .map(NicVO::getId) - .collect(Collectors.toList()); + List allNics = _nicDao.listByVmId(vm.getId()); for (VirtualDevice nicDevice : nicDevices) { Pair pair = getNicMacAddressAndNetworkName(nicDevice, context); String macAddress = pair.first(); String networkName = pair.second(); NetworkVO networkVO = networksMapping.get(networkName); NicVO nicVO = _nicDao.findByNetworkIdAndMacAddress(networkVO.getId(), macAddress); - if (nicVO == null) { - nicVO = createNicRecord(macAddress, networkVO.getId(), vmInstanceVO); - } if (nicVO != null) { - existingNics.remove(nicVO.getId()); + allNics.remove(nicVO); } } - for (final Long unusedNic : existingNics) { - _nicDao.removeNicsForInstance(unusedNic); + for (final NicVO unMappedNic : allNics) { + vmManager.removeNicFromVm(vm, unMappedNic); } } @@ -1406,12 +1395,11 @@ public VMInstanceVO doInTransaction(TransactionStatus status) throws Exception { long serviceOfferingId = getImportingVMServiceOffering(configSummary, runtimeInfo); long templateId = getImportingVMTemplate(virtualDisks, dcMo, vmInternalName, guestOsId, accountId, disksMapping, backup); - VMInstanceVO vmInstanceVO = getVM(vmInternalName, templateId, guestOsId, + VMInstanceVO vm = getVM(vmInternalName, templateId, guestOsId, serviceOfferingId, zoneId, accountId, userId, domainId); - - importVMNics(nicDevices, dcMo, networksMapping, vmInstanceVO); - importVMVolumes(vmInstanceVO, virtualDisks, disksMapping, vmToImport, backup); - return vmInstanceVO; + syncVMVolumes(vm, virtualDisks, disksMapping, vmToImport, backup); + syncVMNics(nicDevices, dcMo, networksMapping, vm); + return vm; } }); } diff --git a/server/src/test/java/com/cloud/user/MockUsageEventDao.java b/server/src/test/java/com/cloud/user/MockUsageEventDao.java index 5d8ed6c45553..5683ca3aefd8 100644 --- a/server/src/test/java/com/cloud/user/MockUsageEventDao.java +++ b/server/src/test/java/com/cloud/user/MockUsageEventDao.java @@ -216,6 +216,11 @@ public void expunge() { } + @Override + public boolean unremove(Long id) { + return false; + } + @Override public K getNextInSequence(Class clazz, String name) { return null; From 8e1fe3a4824ec728c4f2b486be620c8323a88d81 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Tue, 10 Dec 2019 22:56:07 +0530 Subject: [PATCH 44/81] add ui button/forms Signed-off-by: Rohit Yadav --- .../backup/DummyBackupProvider.java | 13 +- ui/css/cloudstack3.css | 6 +- ui/scripts/storage.js | 129 +++++++++++++++++- 3 files changed, 138 insertions(+), 10 deletions(-) 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 195ed3f6d43f..7898edc4cbf6 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 @@ -29,6 +29,7 @@ import com.cloud.utils.Pair; import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachine; public class DummyBackupProvider extends AdapterBase implements BackupProvider { @@ -77,7 +78,7 @@ public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { @Override public Pair restoreBackedUpVolume(Backup backup, String volumeUuid, String hostIp, String dataStoreUuid) { s_logger.debug("Restoring volume " + volumeUuid + "from backup " + backup.getUuid() + " on the Dummy Backup Provider"); - return new Pair<>(true, null); + throw new CloudRuntimeException("Dummy plugin does not support this feature"); } @Override @@ -93,18 +94,16 @@ public Map getBackupMetrics(Long zoneId, List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + final List backups = backupDao.listByVmId(null, vm.getId()); for (final Backup backup : backups) { - if (backup.getExternalId().equals(vm.getBackupExternalId())) { - return true; - } + backupDao.remove(backup.getId()); } - return false; + return true; } @Override public boolean willDeleteBackupsOnOfferingRemoval() { - return false; + return true; } @Override diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index f633556a5c6f..95163f356559 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -12269,12 +12269,14 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it .stop .icon, .removeVmwareDc .icon, +.removeBackupChain .icon, .release .icon { background-position: 0 -31px; } .stop:hover .icon, .removeVmwareDc:hover .icon, +.removeBackupChain:hover .icon, .release:hover .icon { background-position: 0 -613px; } @@ -12348,14 +12350,16 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it .attach .icon, .attachISO .icon, .attachDisk .icon, +.restoreBackupVolume .icon, .associateProfileToBlade .icon { background-position: -104px -3px; } .attach:hover .icon, .attachISO:hover .icon, +.restoreBackupVolume:hover .icon, .attachDisk:hover .icon { - background-position: -101px -585px; + background-position: -104px -585px; } .detach .icon, diff --git a/ui/scripts/storage.js b/ui/scripts/storage.js index 3000bf62efd7..df5d4fa3a3fb 100644 --- a/ui/scripts/storage.js +++ b/ui/scripts/storage.js @@ -2756,8 +2756,7 @@ label: 'label.zone' }, created: { - label: 'label.date', - converter: cloudStack.converters.toLocalDate + label: 'label.date' } }, dataProvider: function(args) { @@ -2841,6 +2840,130 @@ notification: { poll: pollAsyncJobResult } + }, + + restoreBackupVolume: { + label: 'Restore and Attach Backup Volume', + messages: { + confirm: function(args) { + return 'Please confirm that you want to restore and attach the volume from the backup?'; + }, + notification: function(args) { + return 'Restore and Attach Backup Volume'; + } + }, + createForm: { + title: 'Restore and Attach Backup Volume', + desc: 'Please select the volume you want to restore and attach to the VM.', + fields: { + volume: { + label: 'label.volume', + validation: { + required: true + }, + select: function(args) { + var volumes = JSON.parse(args.context.backups[0].volumes); + var items = []; + $(volumes).each(function() { + items.push({ + id: this.uuid, + description: '(' + this.type + ') ' + this.uuid + }); + }); + args.response.success({ + data: items + }); + } + }, + virtualmachine: { + label: 'label.virtual.machine', + validation: { + required: true + }, + select: function(args) { + $.ajax({ + url: createURL("listVirtualMachines"), + dataType: "json", + async: true, + success: function(json) { + var vms = json.listvirtualmachinesresponse.virtualmachine; + var items = []; + $(vms).each(function() { + items.push({ + id: this.id, + description: this.name + }); + }); + args.response.success({ + data: items + }); + + } + }); + } + } + } + }, + action: function(args) { + console.log(args); + var data = { + backupid: args.context.backups[0].id, + volumeid: args.data.volume, + virtualmachineid: args.data.virtualmachine + }; + $.ajax({ + url: createURL("restoreVolumeFromBackupAndAttachToVM"), + data: data, + dataType: "json", + async: true, + success: function(json) { + var jid = json.restorevolumefrombackupandattachtovmresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + + }, + notification: { + poll: pollAsyncJobResult + } + }, + + removeBackupChain: { + label: 'Delete Backup Chain', + messages: { + confirm: function(args) { + return 'Are you sure you want to remove VM from backup offering and delete the backup chain?'; + }, + notification: function(args) { + return 'Delete Backup Chain'; + } + }, + action: function(args) { + $.ajax({ + url: createURL("removeVirtualMachineFromBackupOffering"), + data: { + virtualmachineid: args.context.backups[0].virtualmachineid, + forced: true + }, + dataType: "json", + async: true, + success: function(json) { + var jid = json.removevirtualmachinefrombackupofferingresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } } } } @@ -2973,6 +3096,8 @@ allowedActions.push("remove"); allowedActions.push("startBackup"); allowedActions.push("restoreBackup"); + allowedActions.push("restoreBackupVolume"); + allowedActions.push("removeBackupChain"); return allowedActions; }; From b77665030c25177180d00779d7fdcd4bed862c0e Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Wed, 11 Dec 2019 00:49:38 +0530 Subject: [PATCH 45/81] backup schedule ui finish testing+fixes Signed-off-by: Rohit Yadav --- .../user/backup/CreateBackupScheduleCmd.java | 14 +- .../user/backup/DeleteBackupScheduleCmd.java | 14 +- .../cloud/usage/dao/UsageBackupDaoImpl.java | 2 +- .../cloudstack/backup/BackupManagerImpl.java | 4 +- ui/css/cloudstack3.css | 6 +- ui/index.html | 1 + ui/scripts/instances.js | 271 ++++++++++++++++++ ui/scripts/ui-custom/backupSchedule.js | 181 ++++++++++++ 8 files changed, 462 insertions(+), 31 deletions(-) create mode 100644 ui/scripts/ui-custom/backupSchedule.js diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java index c36fa6a354aa..e10386de9878 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java @@ -23,7 +23,6 @@ import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; @@ -34,7 +33,6 @@ import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.context.CallContext; -import com.cloud.event.EventTypes; import com.cloud.utils.DateUtil; import com.cloud.utils.exception.CloudRuntimeException; @@ -42,7 +40,7 @@ description = "Creates a user-defined VM backup schedule", responseObject = BackupResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class CreateBackupScheduleCmd extends BaseAsyncCmd { +public class CreateBackupScheduleCmd extends BaseCmd { public static final String APINAME = "createBackupSchedule"; @Inject @@ -127,14 +125,4 @@ public String getCommandName() { public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); } - - @Override - public String getEventType() { - return EventTypes.EVENT_VM_BACKUP_SCHEDULE_CONFIGURE; - } - - @Override - public String getEventDescription() { - return "Configuring user-defined backup schedule for backup for VM ID " + vmId; - } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java index 583b3e84f519..1c7b65c8edcf 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java @@ -23,7 +23,6 @@ import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; @@ -32,7 +31,6 @@ import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; -import com.cloud.event.EventTypes; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.NetworkRuleConflictException; @@ -44,7 +42,7 @@ description = "Deletes the backup schedule of a VM", responseObject = SuccessResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class DeleteBackupScheduleCmd extends BaseAsyncCmd { +public class DeleteBackupScheduleCmd extends BaseCmd { public static final String APINAME = "deleteBackupSchedule"; @Inject @@ -98,14 +96,4 @@ public String getCommandName() { public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); } - - @Override - public String getEventType() { - return EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE; - } - - @Override - public String getEventDescription() { - return "Deleting backup schedule for VM ID " + vmId; - } } diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java index 13859ed302e5..060647a4af98 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java @@ -63,7 +63,7 @@ public Boolean doInTransaction(final TransactionStatus status) { } }); if (!result) { - LOGGER.warn("Failed to update Backup metrics for VM ID: " + vm.getId()); + LOGGER.trace("Failed to update backup metrics for VM ID: " + vm.getId()); } } 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 a411ed00f5db..878414047645 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -350,7 +350,7 @@ public Boolean doInTransaction(TransactionStatus status) { } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_CONFIGURE, eventDescription = "configuring VM backup schedule", async = true) + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_CONFIGURE, eventDescription = "configuring VM backup schedule") public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) { final Long vmId = cmd.getVmId(); final DateUtil.IntervalType intervalType = cmd.getIntervalType(); @@ -410,7 +410,7 @@ public BackupSchedule listBackupSchedule(final Long vmId) { } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE, eventDescription = "deleting VM backup schedule", async = true) + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE, eventDescription = "deleting VM backup schedule") public boolean deleteBackupSchedule(final Long vmId) { final VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 95163f356559..a7bea283c868 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -12418,11 +12418,13 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it background-position: -36px -673px; } -.recurringSnapshot .icon { +.recurringSnapshot .icon, +.configureBackupSchedule .icon { background-position: -69px -95px; } -.recurringSnapshot:hover .icon { +.recurringSnapshot:hover .icon, +.configureBackupSchedule:hover .icon { background-position: -69px -677px; } diff --git a/ui/index.html b/ui/index.html index 7c8aec3dced9..375cf4296343 100644 --- a/ui/index.html +++ b/ui/index.html @@ -1918,6 +1918,7 @@

+ diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index 72bf1aedbfeb..8739a681896a 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -1294,6 +1294,276 @@ } }, + configureBackupSchedule: { + label: 'Backup Schedule', + action: { + custom: cloudStack.uiCustom.backupSchedule({ + desc: 'Configure VM backup schedule', + dataProvider: function(args) { + $.ajax({ + url: createURL('listBackupSchedule'), + data: { + virtualmachineid: args.context.instances[0].id + }, + async: true, + dataType: 'json', + success: function(data) { + var schedule = {} + if (data && data.listbackupscheduleresponse && data.listbackupscheduleresponse.backupschedule) { + schedule = data.listbackupscheduleresponse.backupschedule; + schedule.id = schedule.virtualmachineid; + if (schedule.intervaltype == 'HOURLY') { + schedule.type = 0; + schedule.time = schedule.schedule; + } else if (schedule.intervaltype == 'DAILY') { + schedule.type = 1; + schedule.time = schedule.schedule.split(':')[1] + ':' + schedule.schedule.split(':')[0]; + } else if (schedule.intervaltype == 'WEEKLY') { + schedule.type = 2; + schedule.time = schedule.schedule.split(':')[1] + ':' + schedule.schedule.split(':')[0]; + schedule['day-of-week'] = schedule.schedule.split(':')[2]; + } else if (schedule.intervaltype == 'MONTHLY') { + schedule.type = 3; + schedule.time = schedule.schedule.split(':')[1] + ':' + schedule.schedule.split(':')[0]; + schedule['day-of-month'] = schedule.schedule.split(':')[2]; + } + schedule.time = '(' + schedule.intervaltype + ') ' + schedule.time + } + args.response.success({ + data: [schedule] + }); + }, + error: function(data) { + } + }); + }, + actions: { + add: function(args) { + var snap = args.snapshot; + + var data = { + virtualmachineid: args.context.instances[0].id, + intervaltype: snap['snapshot-type'], + timezone: snap.timezone + }; + + var convertTime = function(minute, hour, meridiem, extra) { + var convertedHour = meridiem == 'PM' ? + (hour != 12 ? parseInt(hour) + 12 : 12) : (hour != 12 ? hour : '00'); + var time = minute + ':' + convertedHour; + if (extra) time += ':' + extra; + + return time; + }; + + switch (snap['snapshot-type']) { + case 'hourly': // Hourly + $.extend(data, { + schedule: snap.schedule + }); + break; + + case 'daily': // Daily + $.extend(data, { + schedule: convertTime( + snap['time-minute'], + snap['time-hour'], + snap['time-meridiem'] + ) + }); + break; + + case 'weekly': // Weekly + $.extend(data, { + schedule: convertTime( + snap['time-minute'], + snap['time-hour'], + snap['time-meridiem'], + snap['day-of-week'] + ) + }); + break; + + case 'monthly': // Monthly + $.extend(data, { + schedule: convertTime( + snap['time-minute'], + snap['time-hour'], + snap['time-meridiem'], + snap['day-of-month'] + ) + }); + break; + } + + $.ajax({ + url: createURL('createBackupSchedule'), + data: data, + dataType: 'json', + async: true, + success: function(data) { + var schedule = {} + if (data && data.createbackupscheduleresponse && data.createbackupscheduleresponse.backupschedule) { + schedule = data.createbackupscheduleresponse.backupschedule; + schedule.id = schedule.virtualmachineid; + if (schedule.intervaltype == 'HOURLY') { + schedule.type = 0; + schedule.time = schedule.schedule; + } else if (schedule.intervaltype == 'DAILY') { + schedule.type = 1; + schedule.time = schedule.schedule.split(':')[1] + ':' + schedule.schedule.split(':')[0]; + } else if (schedule.intervaltype == 'WEEKLY') { + schedule.type = 2; + schedule.time = schedule.schedule.split(':')[1] + ':' + schedule.schedule.split(':')[0]; + schedule['day-of-week'] = schedule.schedule.split(':')[2]; + } else if (schedule.intervaltype == 'MONTHLY') { + schedule.type = 3; + schedule.time = schedule.schedule.split(':')[1] + ':' + schedule.schedule.split(':')[0]; + schedule['day-of-month'] = schedule.schedule.split(':')[2]; + } + schedule.time = schedule.time + ' (' + schedule.intervaltype + ')' + } + args.response.success({ + data: schedule + }); + } + }); + }, + remove: function(args) { + console.log(args); + $.ajax({ + url: createURL('deleteBackupSchedule'), + data: { + virtualmachineid: args.context.instances[0].id + }, + dataType: 'json', + async: true, + success: function(data) { + args.response.success(); + } + }); + } + }, + + // Select data + selects: { + schedule: function(args) { + var time = []; + + for (var i = 1; i <= 59; i++) { + time.push({ + id: i, + name: i + }); + } + + args.response.success({ + data: time + }); + }, + timezone: function(args) { + args.response.success({ + data: $.map(timezoneMap, function(value, key) { + return { + id: key, + name: value + }; + }) + }); + }, + 'day-of-week': function(args) { + args.response.success({ + data: [{ + id: 1, + name: 'label.sunday' + }, { + id: 2, + name: 'label.monday' + }, { + id: 3, + name: 'label.tuesday' + }, { + id: 4, + name: 'label.wednesday' + }, { + id: 5, + name: 'label.thursday' + }, { + id: 6, + name: 'label.friday' + }, { + id: 7, + name: 'label.saturday' + }] + }); + }, + + 'day-of-month': function(args) { + var time = []; + + for (var i = 1; i <= 28; i++) { + time.push({ + id: i, + name: i + }); + } + + args.response.success({ + data: time + }); + }, + + 'time-hour': function(args) { + var time = []; + + for (var i = 1; i <= 12; i++) { + time.push({ + id: i, + name: i + }); + } + + args.response.success({ + data: time + }); + }, + + 'time-minute': function(args) { + var time = []; + + for (var i = 0; i <= 59; i++) { + time.push({ + id: i < 10 ? '0' + i : i, + name: i < 10 ? '0' + i : i + }); + } + + args.response.success({ + data: time + }); + }, + + 'time-meridiem': function(args) { + args.response.success({ + data: [{ + id: 'AM', + name: 'AM' + }, { + id: 'PM', + name: 'PM' + }] + }); + } + } + }) + }, + messages: { + notification: function(args) { + return 'Backup Schedule'; + } + } + }, + assignToBackupOffering: { messages: { confirm: function(args) { @@ -3872,6 +4142,7 @@ } if (jsonObj.backupofferingid) { allowedActions.push("createBackup"); + allowedActions.push("configureBackupSchedule"); allowedActions.push("removeFromBackupOffering"); } else { allowedActions.push("assignToBackupOffering"); diff --git a/ui/scripts/ui-custom/backupSchedule.js b/ui/scripts/ui-custom/backupSchedule.js new file mode 100644 index 000000000000..56468989bd6b --- /dev/null +++ b/ui/scripts/ui-custom/backupSchedule.js @@ -0,0 +1,181 @@ +// 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. +(function(cloudStack, $) { + cloudStack.uiCustom.backupSchedule = function(args) { + var desc = args.desc; + var selects = args.selects; + var actions = args.actions; + var dataProvider = args.dataProvider; + + return function(args) { + var $backups = $('#template').find('.recurring-snapshots').clone(); + var context = args.context; + + // Update labels + $backups.find('.forms ul li.hourly a').html(_l('label.hourly')); + $backups.find('.forms ul li.daily a').html(_l('label.daily')); + $backups.find('.forms ul li.weekly a').html(_l('label.weekly')); + $backups.find('.forms ul li.monthly a').html(_l('label.monthly')); + $backups.find('.field.timezone .name').html(_l('label.timezone')); + $backups.find('.field.time .name').html(_l('label.time')); + $backups.find('.field.time .value label').html(_l('label.minute.past.hour')); + $backups.find('.field.maxsnaps').hide(); + $backups.find('.add-snapshot-action.add').html(_l('label.configure')); + + $backups.find('.desc').html(_l(desc)); + $backups.find('.forms').tabs(); + + $backups.find('form select').each(function() { + var $select = $(this); + var selectData = selects[$select.attr('name')]; + + if (selectData) { + selectData({ + response: { + success: function(args) { + $(args.data).each(function() { + var $option = $('