From 2fc49ebfeb9b83aaa1b7c9d91e190f67e6c0463f Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 29 Jun 2018 15:37:21 +0530 Subject: [PATCH 01/21] b&r: phase1 all commits melded Signed-off-by: Rohit Yadav --- .../main/java/com/cloud/event/EventTypes.java | 9 + .../apache/cloudstack/api/ApiConstants.java | 2 + .../cloudstack/api/BaseBackupListCmd.java | 83 ++++ .../cloudstack/api/ResponseGenerator.java | 12 + .../admin/backup/DeleteBackupPolicyCmd.java | 92 ++++ .../admin/backup/ImportBackupPolicyCmd.java | 137 ++++++ .../admin/backup/ListBackupProvidersCmd.java | 99 +++++ .../user/backup/AddVMToBackupPolicyCmd.java | 120 +++++ .../user/backup/CreateVMBackupCmd.java | 114 +++++ .../user/backup/DeleteVMBackupCmd.java | 100 +++++ .../user/backup/ListBackupPoliciesCmd.java | 111 +++++ .../backup/ListBackupPolicyVMMappingsCmd.java | 110 +++++ .../command/user/backup/ListVMBackupsCmd.java | 96 ++++ .../backup/RemoveVMFromBackupPolicyCmd.java | 121 ++++++ .../user/backup/RestoreVMFromBackupCmd.java | 111 +++++ ...storeVolumeFromBackupAndAttachToVMCmd.java | 138 ++++++ .../api/response/BackupPolicyResponse.java | 69 +++ .../response/BackupPolicyVMMapResponse.java | 66 +++ .../api/response/BackupProviderResponse.java | 53 +++ .../api/response/BackupResponse.java | 164 +++++++ .../org/apache/cloudstack/backup/Backup.java | 43 ++ .../cloudstack/backup/BackupManager.java | 107 +++++ .../cloudstack/backup/BackupPolicy.java | 30 ++ .../cloudstack/backup/BackupPolicyVMMap.java | 27 ++ .../cloudstack/backup/BackupProvider.java | 87 ++++ .../cloudstack/backup/BackupService.java | 37 ++ client/pom.xml | 10 + .../cloudstack/backup/module.properties | 21 + ...e-lifecycle-backup-context-inheritable.xml | 32 ++ .../spring-core-registry-core-context.xml | 4 + .../cloudstack/backup/BackupPolicyTO.java | 73 ++++ .../backup/BackupPolicyVMMapVO.java | 87 ++++ .../cloudstack/backup/BackupPolicyVO.java | 94 ++++ .../apache/cloudstack/backup/BackupTO.java | 176 ++++++++ .../apache/cloudstack/backup/BackupVO.java | 246 +++++++++++ .../cloudstack/backup/dao/BackupDao.java | 34 ++ .../cloudstack/backup/dao/BackupDaoImpl.java | 153 +++++++ .../backup/dao/BackupPolicyDao.java | 32 ++ .../backup/dao/BackupPolicyDaoImpl.java | 79 ++++ .../backup/dao/BackupPolicyVMMapDao.java | 37 ++ .../backup/dao/BackupPolicyVMMapDaoImpl.java | 128 ++++++ ...spring-engine-schema-core-daos-context.xml | 3 + .../META-INF/db/schema-41110to41200.sql | 48 +- .../cloudstack/backup/BackupVOTest.java | 78 ++++ plugins/backup/dummy/pom.xml | 29 ++ .../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 | 186 ++++++++ .../cloudstack/backup/veeam/VeeamBackup.java | 99 +++++ .../backup/veeam/VeeamBackupPolicy.java | 66 +++ .../cloudstack/backup/veeam/VeeamClient.java | 368 ++++++++++++++++ .../cloudstack/backup/veeam/VeeamObject.java | 30 ++ .../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/Link.java | 69 +++ .../backup/veeam/api/ObjectInJob.java | 94 ++++ .../backup/veeam/api/ObjectsInJob.java | 39 ++ .../cloudstack/backup/veeam/api/Ref.java | 83 ++++ .../cloudstack/backup/veeam/api/Result.java | 47 ++ .../cloudstack/backup/veeam/api/Task.java | 106 +++++ .../backup/veeam/api/VeeamObjectType.java | 35 ++ .../cloudstack/veeam/module.properties | 18 + .../veeam/spring-backup-veeam-context.xml | 27 ++ .../backup/veeam/VeeamClientTest.java | 85 ++++ pom.xml | 2 +- .../main/java/com/cloud/api/ApiDBUtils.java | 33 ++ .../java/com/cloud/api/ApiResponseHelper.java | 21 + .../cloudstack/backup/BackupManagerImpl.java | 410 ++++++++++++++++++ .../spring-server-core-managers-context.xml | 5 + .../integration/smoke/test_backup_recovery.py | 202 +++++++++ tools/apidoc/gen_toc.py | 3 +- tools/marvin/marvin/lib/base.py | 124 ++++++ 76 files changed, 6072 insertions(+), 3 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/DeleteBackupPolicyCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupPolicyCmd.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/user/backup/AddVMToBackupPolicyCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateVMBackupCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupPoliciesCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupPolicyVMMappingsCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupsCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVMFromBackupPolicyCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.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/BackupPolicyResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/BackupPolicyVMMapResponse.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/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/BackupPolicy.java create mode 100644 api/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMap.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/org/apache/cloudstack/backup/BackupPolicyTO.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMapVO.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVO.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/BackupTO.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/BackupPolicyDao.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDaoImpl.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDao.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDaoImpl.java create mode 100644 engine/schema/src/test/java/org/apache/cloudstack/backup/BackupVOTest.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/VeeamBackupPolicy.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/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/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/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/java/org/apache/cloudstack/backup/veeam/api/VeeamObjectType.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 diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 907b93eca103..a2baf7c7dd5c 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -584,6 +584,15 @@ public class EventTypes { public static final String EVENT_TEMPLATE_DIRECT_DOWNLOAD_FAILURE = "TEMPLATE.DIRECT.DOWNLOAD.FAILURE"; public static final String EVENT_ISO_DIRECT_DOWNLOAD_FAILURE = "ISO.DIRECT.DOWNLOAD.FAILURE"; + // Backup and Recovery events + public static final String EVENT_ADD_VM_TO_BACKUP_POLICY = "ADD.VM.TO.BACKUP.POLICY"; + public static final String EVENT_REMOVE_VM_FROM_BACKUP_POLICY = "REMOVE.VM.FROM.BACKUP.POLICY"; + public static final String EVENT_IMPORT_BACKUP_POLICY = "IMPORT.BACKUP.POLICY"; + public static final String EVENT_CREATE_VM_BACKUP = "CREATE.VM.BACKUP"; + public static final String EVENT_DELETE_VM_BACKUP = "DELETE.VM.BACKUP"; + public static final String EVENT_RESTORE_VM_FROM_BACKUP = "RESTORE.VM.FROM.BACKUP"; + public static final String EVENT_RESTORE_VOLUME_FROM_BACKUP_AND_ATTACH_TO_VM = "RESTORE.VOLUME.FROM.BACKUP.AND.ATTACH.TO.VM"; + static { // TODO: need a way to force author adding event types to declare the entity details as well, with out braking 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 f03ddc7465c3..123e066148db 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -121,6 +121,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"; @@ -336,6 +337,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 VOLUME_IDS = "volumeids"; public static final String ZONE_ID = "zoneid"; public static final String ZONE_NAME = "zonename"; public static final String NETWORK_TYPE = "networktype"; 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..42f4a21639e8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.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.api; + +import org.apache.cloudstack.api.response.BackupPolicyResponse; +import org.apache.cloudstack.api.response.BackupPolicyVMMapResponse; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.backup.BackupPolicyVMMap; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupPolicy; + +import java.util.ArrayList; +import java.util.List; + +public abstract class BaseBackupListCmd extends BaseListCmd { + + protected void setupResponseBackupPolicyList(final List policies) { + final ListResponse response = new ListResponse<>(); + final List responses = new ArrayList<>(); + for (final BackupPolicy policy : policies) { + if (policy == null) { + continue; + } + BackupPolicyResponse backupPolicyResponse = _responseGenerator.createBackupPolicyResponse(policy); + responses.add(backupPolicyResponse); + } + response.setResponses(responses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + protected void setupResponseBackupList(final List backups) { + final ListResponse response = new ListResponse<>(); + final List responses = new ArrayList<>(); + for (Backup backup : backups) { + if (backup == null) { + continue; + } + BackupResponse backupResponse = _responseGenerator.createBackupResponse(backup); + responses.add(backupResponse); + } + response.setResponses(responses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + protected void setupResponseBackupPolicyVMMappings(final List mappings) { + final ListResponse response = new ListResponse<>(); + final List responses = new ArrayList<>(); + for (BackupPolicyVMMap map : mappings) { + if (map == null) { + continue; + } + BackupPolicyVMMapResponse resp = _responseGenerator.createBackupPolicyVMMappingResponse(map); + responses.add(resp); + } + 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 4fb248cd1055..db702e5713a9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -34,6 +34,9 @@ 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.BackupPolicyResponse; +import org.apache.cloudstack.api.response.BackupPolicyVMMapResponse; +import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.CapacityResponse; import org.apache.cloudstack.api.response.ClusterResponse; import org.apache.cloudstack.api.response.ConditionResponse; @@ -116,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.BackupPolicy; +import org.apache.cloudstack.backup.BackupPolicyVMMap; import org.apache.cloudstack.config.Configuration; +import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; import org.apache.cloudstack.region.PortableIp; import org.apache.cloudstack.region.PortableIpRange; @@ -462,4 +468,10 @@ List createTemplateResponses(ResponseView view, VirtualMachine ListResponse createUpgradeRouterTemplateResponse(List jobIds); SSHKeyPairResponse createSSHKeyPairResponse(SSHKeyPair sshkeyPair, boolean privatekey); + + BackupResponse createBackupResponse(Backup backup); + + BackupPolicyResponse createBackupPolicyResponse(BackupPolicy policy); + + BackupPolicyVMMapResponse createBackupPolicyVMMappingResponse(BackupPolicyVMMap map); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteBackupPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteBackupPolicyCmd.java new file mode 100644 index 000000000000..f3dadafe0657 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteBackupPolicyCmd.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.BackupPolicyResponse; +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 = DeleteBackupPolicyCmd.APINAME, + description = "Deletes a backup policy", + responseObject = SuccessResponse.class, since = "4.12.0", + authorized = {RoleType.Admin}) +public class DeleteBackupPolicyCmd extends BaseCmd { + public static final String APINAME = "deleteBackupPolicy"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + //////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = BackupPolicyResponse.class, + required = true, + description = "The backup policy internal ID") + private Long id; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + if (backupManager.deleteBackupPolicy(getId())) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable to remove backup policy: " + 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/ImportBackupPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupPolicyCmd.java new file mode 100644 index 000000000000..c94b9e20c269 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupPolicyCmd.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 com.cloud.event.EventTypes; +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.BackupPolicyResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.backup.BackupPolicy; + +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 = ImportBackupPolicyCmd.APINAME, + description = "Imports a backup policy from the backup provider", + responseObject = BackupPolicyResponse.class, since = "4.12.0", + authorized = {RoleType.Admin}) +public class ImportBackupPolicyCmd extends BaseAsyncCmd { + public static final String APINAME = "importBackupPolicy"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + //////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, + description = "the name of the backup policy") + private String policyName; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = true, + description = "the description of the backup policy") + private String description; + + @Parameter(name = ApiConstants.EXTERNAL_ID, + type = CommandType.STRING, + required = true, + description = "The backup policy ID (on 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 { + BackupPolicy policy = backupManager.importBackupPolicy(getZoneId(), getPolicyExternalId(), getPolicyName(), getDescription()); + if (policy != null) { + BackupPolicyResponse response = _responseGenerator.createBackupPolicyResponse(policy); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add a Backup policy"); + } + } 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_IMPORT_BACKUP_POLICY; + } + + @Override + public String getEventDescription() { + return "Importing backup policy: " + policyName + " (externalId=" + policyExternalId + ") on zone " + zoneId ; + } +} 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..30e9905fcf19 --- /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.12.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/user/backup/AddVMToBackupPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AddVMToBackupPolicyCmd.java new file mode 100644 index 000000000000..3074188314a8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AddVMToBackupPolicyCmd.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.api.command.user.backup; + +import javax.inject.Inject; + +import com.cloud.event.EventTypes; +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.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupPolicyResponse; +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; + +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 = AddVMToBackupPolicyCmd.APINAME, + description = "Assigns a VM to an existing backup policy", + responseObject = SuccessResponse.class, since = "4.12.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class AddVMToBackupPolicyCmd extends BaseAsyncCmd { + public static final String APINAME = "addVMToBackupPolicy"; + + @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 to be assigned to the backup policy") + private Long vmId; + + @Parameter(name = ApiConstants.POLICY_ID, + type = CommandType.UUID, + entityType = BackupPolicyResponse.class, + required = true, + description = "id of the backup policy") + 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 { + boolean result = backupManager.addVMToBackupPolicy(getPolicyId(), getVmId()); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to assign VM to backup policy"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return AddVMToBackupPolicyCmd.APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_ADD_VM_TO_BACKUP_POLICY; + } + + @Override + public String getEventDescription() { + return "Adding VM " + vmId + " to backup policy " + policyId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateVMBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateVMBackupCmd.java new file mode 100644 index 000000000000..94a821abded6 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateVMBackupCmd.java @@ -0,0 +1,114 @@ +// 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 com.cloud.event.EventTypes; +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.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; +import com.cloud.utils.exception.CloudRuntimeException; + +@APICommand(name = CreateVMBackupCmd.APINAME, + description = "Create VM backup", + responseObject = BackupResponse.class, since = "4.12.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class CreateVMBackupCmd extends BaseAsyncCmd { + public static final String APINAME = "createVMBackup"; + + @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; + + //FIXME: add name, description etc.? + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getVmId() { + return vmId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + Backup backup = backupManager.createBackup(vmId); + 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_CREATE_VM_BACKUP; + } + + @Override + public String getEventDescription() { + return "Creating backup for VM " + vmId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java new file mode 100644 index 000000000000..fde8a9f7e6a1 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java @@ -0,0 +1,100 @@ +// 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.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.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 = DeleteVMBackupCmd.APINAME, + description = "Delete VM backup", + responseObject = SuccessResponse.class, since = "4.12.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class DeleteVMBackupCmd extends BaseCmd { + public static final String APINAME = "deleteVMBackup"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + required = true, + description = "id of 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(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupPoliciesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupPoliciesCmd.java new file mode 100644 index 000000000000..293bb33bf6c8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupPoliciesCmd.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 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.BackupPolicyResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupPolicy; +import org.apache.commons.lang.BooleanUtils; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.exception.CloudRuntimeException; + +@APICommand(name = ListBackupPoliciesCmd.APINAME, + description = "Lists backup policies", + responseObject = BackupPolicyResponse.class, since = "4.12.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListBackupPoliciesCmd extends BaseBackupListCmd { + public static final String APINAME = "listBackupPolicies"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = BaseCmd.CommandType.UUID, entityType = BackupPolicyResponse.class, + description = "The backup policy ID") + private Long policyId; + + @Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class, + description = "The zone ID") + private Long zoneId; + + @Parameter(name = ApiConstants.EXTERNAL, type = CommandType.BOOLEAN, + description = "True if list external backup policies (provider policies)", authorized = {RoleType.Admin}) + private Boolean external; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getZoneId() { + return zoneId; + } + + public boolean isExternal() { + return BooleanUtils.isTrue(external); + } + + public Long getPolicyId() { + return policyId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, ServerApiException, ConcurrentOperationException { + validateParameters(); + try { + final List backupPolicies = backupManager.listBackupPolicies(getZoneId(), isExternal(), getPolicyId()); + setupResponseBackupPolicyList(backupPolicies); + } 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 (isExternal() && getZoneId() == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please provide the zone id when external option is specified"); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupPolicyVMMappingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupPolicyVMMappingsCmd.java new file mode 100644 index 000000000000..00ed936c1d5e --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupPolicyVMMappingsCmd.java @@ -0,0 +1,110 @@ +// 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.BackupPolicyResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupPolicyVMMap; + +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 = ListBackupPolicyVMMappingsCmd.APINAME, + description = "Lists VMs mapped to a backup policy", + responseObject = BackupPolicyResponse.class, since = "4.12.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListBackupPolicyVMMappingsCmd extends BaseBackupListCmd { + public static final String APINAME = "listBackupPolicyVMMappings"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, + description = "The id of the VM") + private Long vmId; + + @Parameter(name = ApiConstants.POLICY_ID, type = BaseCmd.CommandType.UUID, entityType = BackupPolicyResponse.class, + description = "The backup policy 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 getVmId() { + return vmId; + } + + public Long getPolicyId() { + return policyId; + } + + public Long getZoneId() { + return zoneId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + List mappings = backupManager.listBackupPolicyVMMappings(getVmId(), getZoneId(), getPolicyId()); + setupResponseBackupPolicyVMMappings(mappings); + } catch (CloudRuntimeException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + private void validateParameters() { + if (zoneId == null && policyId == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please provide a zone id or a policy id"); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupsCmd.java new file mode 100644 index 000000000000..a9b183fd438f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupsCmd.java @@ -0,0 +1,96 @@ +// 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.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 = ListVMBackupsCmd.APINAME, + description = "Lists VM backups", + responseObject = BackupResponse.class, since = "4.12.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListVMBackupsCmd extends BaseBackupListCmd { + public static final String APINAME = "listVMBackups"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + description = "id of the VM") + private Long vmId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getVmId() { + return vmId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try{ + List backups = backupManager.listVMBackups(getVmId()); + setupResponseBackupList(backups); + } 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/RemoveVMFromBackupPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVMFromBackupPolicyCmd.java new file mode 100644 index 000000000000..e28e6708792a --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVMFromBackupPolicyCmd.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 com.cloud.event.EventTypes; +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.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupPolicyResponse; +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; + +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 = RemoveVMFromBackupPolicyCmd.APINAME, + description = "Removes a VM from an existing backup policy", + responseObject = SuccessResponse.class, since = "4.12.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class RemoveVMFromBackupPolicyCmd extends BaseAsyncCmd { + public static final String APINAME = "removeVMFromBackupPolicy"; + + @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 to be removed from the backup policy") + private Long vmId; + + @Parameter(name = ApiConstants.POLICY_ID, + type = CommandType.UUID, + entityType = BackupPolicyResponse.class, + required = true, + description = "id of the backup policy") + 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 { + boolean result = backupManager.removeVMFromBackupPolicy(getPolicyId(), getVmId()); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to remove VM from backup policy"); + } + } catch (Exception 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_REMOVE_VM_FROM_BACKUP_POLICY; + } + + @Override + public String getEventDescription() { + return "Removing VM " + vmId + " from backup policy " + policyId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java new file mode 100644 index 000000000000..019b535d4e80 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.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 com.cloud.event.EventTypes; +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.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 = RestoreVMFromBackupCmd.APINAME, + description = "Restore VM from backup", + responseObject = SuccessResponse.class, since = "4.12.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class RestoreVMFromBackupCmd extends BaseAsyncCmd { + public static final String APINAME = "restoreVMFromBackup"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.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 { + boolean result = backupManager.restoreVMFromBackup(backupId); + 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_RESTORE_VM_FROM_BACKUP; + } + + @Override + public String getEventDescription() { + 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 new file mode 100644 index 000000000000..1567e7f9ae11 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java @@ -0,0 +1,138 @@ +// 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 com.cloud.event.EventTypes; +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.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.VolumeResponse; +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.utils.exception.CloudRuntimeException; + +@APICommand(name = RestoreVolumeFromBackupAndAttachToVMCmd.APINAME, + description = "Restore and attach a backed up volume to VM", + responseObject = SuccessResponse.class, since = "4.12.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 ///////////////////// + ///////////////////////////////////////////////////// + + //FIXME: discuss on simplification + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + required = true, + description = "id of the backup") + private Long backupId; + + //FIXME: is this necessary when backup id is known? unless we want to restore to a different volume? + @Parameter(name = ApiConstants.VOLUME_ID, + type = CommandType.UUID, + entityType = VolumeResponse.class, + required = true, + description = "id of the volume to restore and to be attached to the vm") + private Long volumeId; + + //FIXME: is this necessary when backup id is known? unless we want to restore to a different VM? + @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 Long getVolumeId() { + return volumeId; + } + + public Long getVmId() { + return vmId; + } + + public Long getBackupId() { + return backupId; + } + + @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(volumeId, vmId, backupId); + 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_RESTORE_VOLUME_FROM_BACKUP_AND_ATTACH_TO_VM; + } + + @Override + public String getEventDescription() { + return "Restoring volume "+ volumeId + " from backup " + backupId + " and attaching it to VM " + vmId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupPolicyResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupPolicyResponse.java new file mode 100644 index 000000000000..9cabcd49449a --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupPolicyResponse.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.BackupPolicy; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = BackupPolicy.class) +public class BackupPolicyResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "internal id of the backup policy") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "internal name for the backup policy") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "internal description for the backup policy") + private String description; + + @SerializedName(ApiConstants.EXTERNAL_ID) + @Param(description = "policy 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/BackupPolicyVMMapResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupPolicyVMMapResponse.java new file mode 100644 index 000000000000..855ae51fd522 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupPolicyVMMapResponse.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.BackupPolicyVMMap; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = BackupPolicyVMMap.class) +public class BackupPolicyVMMapResponse extends BaseResponse { + + @SerializedName(ApiConstants.ZONE_ID) + @Param(description = "zone id") + private String zoneId; + + @SerializedName(ApiConstants.POLICY_ID) + @Param(description = "backup policy id") + private String backupPolicyId; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) + @Param(description = "virtual machine id") + private String vmId; + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + + public String getBackupPolicyId() { + return backupPolicyId; + } + + public void setBackupPolicyId(String backupPolicyId) { + this.backupPolicyId = backupPolicyId; + } + + public String getVmId() { + return vmId; + } + + public void setVmId(String vmId) { + this.vmId = vmId; + } +} 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..2cc58af3ba6a --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java @@ -0,0 +1,164 @@ +// 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 com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +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 java.util.Date; +import java.util.List; + +@EntityReference(value = Backup.class) +public class BackupResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "internal id of the backup") + private String id; + + @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.NAME) + @Param(description = "backup name") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "backup description") + private String description; + + @SerializedName(ApiConstants.PARENT_ID) + @Param(description = "backup parent id") + private String parentId; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) + @Param(description = "backup vm id") + private String vmId; + + @SerializedName(ApiConstants.VOLUME_IDS) + @Param(description = "backup volume ids") + private List volumeIds; + + @SerializedName(ApiConstants.STATUS) + @Param(description = "backup volume ids") + private Backup.Status status; + + @SerializedName(ApiConstants.START_DATE) + @Param(description = "backup start date") + private Date startDate; + + 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 getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } + + 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 List getVolumeIds() { + return volumeIds; + } + + public void setVolumeIds(List volumeIds) { + this.volumeIds = volumeIds; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } +} 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..79d462688e59 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -0,0 +1,43 @@ +//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; + +import java.util.Date; +import java.util.List; + +public interface Backup extends InternalIdentity, Identity { + + enum Status { + BackingUp, BackedUp, Failed, Queued, Restoring + } + + Long getZoneId(); + Long getAccountId(); + String getExternalId(); + String getName(); + String getDescription(); + Long getParentId(); + Long getVmId(); + List getVolumeIds(); + Status getStatus(); + Date getStartTime(); + 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..c25ef18cf364 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.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.backup; + +import java.util.List; + +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; + +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); + + /** + * Add a new Backup and Recovery policy to CloudStack by mapping an existing external backup policy to a name and description + * @param zoneId zone id + * @param policyExternalId backup policy external id + * @param policyName internal name for the backup policy + * @param policyDescription internal description for the backup policy + */ + BackupPolicy importBackupPolicy(Long zoneId, String policyExternalId, String policyName, String policyDescription); + + /** + * Assign VM to existing backup policy + */ + boolean addVMToBackupPolicy(Long policyId, Long virtualMachineId); + + /** + * Remove a VM from a backup policy + */ + boolean removeVMFromBackupPolicy(Long policyId, Long vmId); + + /** + * Return mappings between backup policy and VMs + */ + List listBackupPolicyVMMappings(Long vmId, Long zoneId, Long policyId); + + /** + * List existing backups for a VM + */ + List listVMBackups(Long vmId); + + /** + * List backup policies + * @param zoneId zone id + * @param external if true, only external backup policies are listed + * @param policyId if not null, only the policy with this id is listed + */ + List listBackupPolicies(Long zoneId, Boolean external, Long policyId); + + /** + * Creates backup of a VM + * @param vmId Virtual Machine ID + * @return returns operation success + */ + Backup createBackup(Long vmId); + + /** + * Deletes a backup + * @return returns operation success + */ + boolean deleteBackup(Long backupId); + + /** + * Restore a full VM from backup + */ + boolean restoreVMFromBackup(Long backupId); + + /** + * Restore a backed up volume and attach it to a VM + */ + boolean restoreBackupVolumeAndAttachToVM(Long volumeId, Long vmId, Long backupId); + + /** + * Deletes a backup policy + */ + boolean deleteBackupPolicy(Long policyId); +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupPolicy.java b/api/src/main/java/org/apache/cloudstack/backup/BackupPolicy.java new file mode 100644 index 000000000000..d447c4e9c670 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupPolicy.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 BackupPolicy extends InternalIdentity, Identity { + + String getExternalId(); + String getName(); + String getDescription(); + boolean isImported(); + long getZoneId(); + +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMap.java b/api/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMap.java new file mode 100644 index 000000000000..f71832e482cd --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMap.java @@ -0,0 +1,27 @@ +//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.InternalIdentity; + +public interface BackupPolicyVMMap extends InternalIdentity { + + long getPolicyId(); + long getVmId(); + 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..4e857ae0c66e --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.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 +//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 com.cloud.agent.api.to.VolumeTO; +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 listBackupPolicies(Long zoneId); + + /** + * True if policy with id uuid exists on the backup provider + */ + boolean isBackupPolicy(Long zoneId, String uuid); + + /** + * Assign VM to backup policy + * @return true if VM is successfully assigned, false if not + */ + boolean addVMToBackupPolicy(BackupPolicy policy, VirtualMachine vm); + + /** + * Remove a VM form a backup policy + */ + boolean removeVMFromBackupPolicy(BackupPolicy policy, VirtualMachine vm); + + /** + * Starts ad-hoc backup of a VM assigned to a policy + * @param policy + * @param vm + * @return true if backup successfully starts + */ + Backup createVMBackup(BackupPolicy policy, VirtualMachine vm); + + /** + * Restore VM from backup + */ + boolean restoreVMFromBackup(String vmUuid, String backupUuid); + + /** + * Restore a volume from a backup + */ + VolumeTO restoreVolumeFromBackup(String volumeUuid, String backupUuid); + + /** + * List VM Backups + */ + List listVMBackups(Long zoneId, VirtualMachine vm); + + /** + * Remove a VM backup + */ + boolean removeVMBackup(VirtualMachine vm, String backupId); +} 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/client/pom.xml b/client/pom.xml index baf49add1b4d..8b04386a65db 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -479,6 +479,16 @@ cloud-plugin-integrations-prometheus-exporter ${project.version} + + org.apache.cloudstack + cloud-plugin-backup-dummy + ${project.version} + + + org.apache.cloudstack + cloud-plugin-backup-veeam + ${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 1f70e5261473..007def7d8de7 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 @@ -323,4 +323,8 @@ class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry"> + + + diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyTO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyTO.java new file mode 100644 index 000000000000..6c71655f7922 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyTO.java @@ -0,0 +1,73 @@ +/* + * 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; + +public class BackupPolicyTO implements BackupPolicy { + + private String uuid; + private long id; + private String name; + private String description; + private String externalId; + private long zoneId; + + public BackupPolicyTO() { + } + + public BackupPolicyTO(final String externalId, final String name, final String description) { + this.name = name; + this.description = description; + this.externalId = externalId; + } + + @Override + public String getExternalId() { + return externalId; + } + + public String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public long getId() { + return id; + } + + @Override + public boolean isImported() { + return false; + } + + @Override + public long getZoneId() { + return zoneId; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMapVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMapVO.java new file mode 100644 index 000000000000..c88fda52891a --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMapVO.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; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "backup_policy_vm_map") +public class BackupPolicyVMMapVO implements BackupPolicyVMMap { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "policy_id") + private long policyId; + + @Column(name = "vm_id") + private long vmId; + + @Column(name = "zone_id") + private long zoneId; + + public BackupPolicyVMMapVO() { + } + + public BackupPolicyVMMapVO(long zoneId, long policyId, long vmId) { + this.policyId = policyId; + this.vmId = vmId; + this.zoneId = zoneId; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getPolicyId() { + return policyId; + } + + public void setPolicyId(long policyId) { + this.policyId = policyId; + } + + public long getVmId() { + return vmId; + } + + public void setVmId(long vmId) { + this.vmId = vmId; + } + + public long getZoneId() { + return zoneId; + } + + public void setZoneId(long zoneId) { + this.zoneId = zoneId; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVO.java new file mode 100644 index 000000000000..a1f8d4058e1f --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVO.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; + +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; + +@Entity +@Table(name = "backup_policy") +public class BackupPolicyVO implements BackupPolicy { + @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; + + public BackupPolicyVO() { + this.uuid = UUID.randomUUID().toString(); + } + + public BackupPolicyVO(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; + } + + + 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/BackupTO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupTO.java new file mode 100644 index 000000000000..6b3c24cf6226 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupTO.java @@ -0,0 +1,176 @@ +// 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.List; + +public class BackupTO implements Backup { + + private long id; + private String uuid; + private Long accountId; + private String name; + private String description; + private Long parentId; + private Long vmId; + private List volumeIds; + private Status status; + private Date startTime; + private Long zoneId; + private String externalId; + private String parentExternalId; + + public BackupTO() { + } + + public BackupTO(final Long zoneId, final Long accountId, final String externalId, final String name, final String description, + final String parentExternalId, final Long vmId, final List volumeIds, final Status status, final Date startTime) { + this.zoneId = zoneId; + this.accountId = accountId; + this.externalId = externalId; + this.name = name; + this.description = description; + this.parentExternalId = parentExternalId; + this.vmId = vmId; + this.volumeIds = volumeIds; + this.status = status; + this.startTime = startTime; + } + + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + @Override + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public Long getAccountId() { + return accountId; + } + + public void setAccountId(Long accountId) { + this.accountId = accountId; + } + + @Override + public Long getZoneId() { + return zoneId; + } + + public void setZoneId(Long zoneId) { + this.zoneId = zoneId; + } + + public String getParentExternalId() { + return parentExternalId; + } + + public void setParentExternalId(String parentExternalId) { + this.parentExternalId = parentExternalId; + } + + @Override + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public Long getParentId() { + return parentId; + } + + public void setParentId(Long parentId) { + this.parentId = parentId; + } + + public Long getVmId() { + return vmId; + } + + public void setVmId(Long vmId) { + this.vmId = vmId; + } + + @Override + public List getVolumeIds() { + return volumeIds; + } + + public void setVolumeIds(List volumeIds) { + this.volumeIds = volumeIds; + } + + @Override + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + @Override + public Date getStartTime() { + return startTime; + } + + @Override + public Date getRemoved() { + return null; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } +} 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..9a6f04cef38e --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java @@ -0,0 +1,246 @@ +//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.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; + +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 javax.persistence.Transient; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.StringJoiner; +import java.util.UUID; + +@Entity +@Table(name = "backup") +public class BackupVO implements Backup { + + public BackupVO() { + this.uuid = UUID.randomUUID().toString(); + volumeIds = new ArrayList<>(); + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "account_id") + private long accountId; + + @Column(name = "zone_id") + private Long zoneId; + + @Column(name = "external_id") + private String externalId; + + @Column(name = "name") + private String name; + + @Column(name = "description") + private String description; + + @Column(name = "parent_id") + private Long parentId; + + @Column(name = "vm_id") + private Long vmId; + + @Column(name = "volumes") + private String volumes; + + @Column(name = "status") + private Status status; + + @Column(name = "start") + @Temporal(value = TemporalType.TIMESTAMP) + private Date startTime; + + @Column(name = "removed") + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + + @Transient + private List volumeIds; + + @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 String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public Long getParentId() { + return parentId; + } + + @Override + public Long getVmId() { + return vmId; + } + + @Override + public Status getStatus() { + return status; + } + + @Override + public Date getStartTime() { + return startTime; + } + + 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 setName(String name) { + this.name = name; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setParentId(Long parentId) { + this.parentId = parentId; + } + + public void setVmId(Long vmId) { + this.vmId = vmId; + } + + public void setStatus(Status status) { + this.status = status; + } + + public void setStartTime(Date start) { + this.startTime = start; + } + + protected void convertVolumeStringToList() { + volumeIds = new ArrayList<>(); + if (StringUtils.isNotBlank(volumes)) { + String[] strIds = StringUtils.substringBetween(volumes,"[", "]").split(","); + for (String strId: strIds) { + if (StringUtils.isNotBlank(strId)) { + volumeIds.add(Long.valueOf(strId)); + } + } + } + } + + @Override + public List getVolumeIds() { + convertVolumeStringToList(); + return volumeIds; + } + + public void setVolumeIds(List volumes) { + if (CollectionUtils.isEmpty(volumes)) { + volumeIds = new ArrayList<>(); + } else { + volumeIds = new ArrayList<>(volumes); + } + convertVolumeIdsToString(); + } + + private void convertVolumeIdsToString() { + StringJoiner stringJoiner = new StringJoiner(",", "[", "]"); + if (CollectionUtils.isNotEmpty(volumeIds)) { + for (Long volId : volumeIds) { + stringJoiner.add(String.valueOf(volId)); + } + volumes = stringJoiner.toString(); + } else { + volumes = null; + } + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + protected String getVolumes() { + return volumes; + } + + protected void setVolumes(String volumes) { + this.volumes = volumes; + } +} 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..9bb8ea117723 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.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.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.backup.BackupVO; +import org.apache.cloudstack.backup.Backup; + +import java.util.List; + +public interface BackupDao extends GenericDao { + + List listByVmId(Long zoneId, Long vmId); + List syncVMBackups(Long zoneId, Long vmId, List externalBackups); + + 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..f10d2f9278ba --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java @@ -0,0 +1,153 @@ +// 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 com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.storage.VolumeVO; +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; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.backup.BackupTO; +import org.apache.cloudstack.backup.BackupVO; +import org.apache.cloudstack.backup.Backup; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; + +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("zone_id", backupSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + backupSearch.and("external_id", backupSearch.entity().getExternalId(), SearchCriteria.Op.EQ); + backupSearch.done(); + } + @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)); + } + + 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.setName(backup.getName()); + backupVO.setDescription(backup.getDescription()); + if (backup instanceof BackupTO) { + String parentExternalId = ((BackupTO) backup).getParentExternalId(); + if (StringUtils.isNotBlank(parentExternalId)) { + Backup parent = findByExternalId(backup.getZoneId(), parentExternalId); + backupVO.setParentId(parent.getId()); + } + } + backupVO.setVmId(backup.getVmId()); + backupVO.setVolumeIds(backup.getVolumeIds()); + backupVO.setStatus(backup.getStatus()); + backupVO.setStartTime(backup.getStartTime()); + return backupVO; + } + + public void removeExistingVMBackups(Long zoneId, Long vmId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters("vm_id", vmId); + sc.setParameters("zone_id", zoneId); + expunge(sc); + } + + @Override + public List syncVMBackups(Long zoneId, Long vmId, List externalBackups) { + for (Backup backup : externalBackups) { + BackupVO backupVO = getBackupVO(backup); + persist(backupVO); + } + return listByVmId(zoneId, vmId); + } + + @Override + public BackupResponse newBackupResponse(Backup backup) { + AccountVO account = accountDao.findById(backup.getAccountId()); + BackupVO parent = findById(backup.getParentId()); + VMInstanceVO vm = vmInstanceDao.findById(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.setName(backup.getName()); + backupResponse.setDescription(backup.getDescription()); + if (parent != null) { + backupResponse.setParentId(parent.getUuid()); + } + backupResponse.setVmId(vm.getUuid()); + if (CollectionUtils.isNotEmpty(backup.getVolumeIds())) { + List volIds = new ArrayList<>(); + for (Long volId : backup.getVolumeIds()) { + VolumeVO volume = volumeDao.findById(volId); + volIds.add(volume.getUuid()); + } + backupResponse.setVolumeIds(volIds); + } + backupResponse.setStatus(backup.getStatus()); + backupResponse.setStartDate(backup.getStartTime()); + backupResponse.setObjectName("backup"); + return backupResponse; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDao.java new file mode 100644 index 000000000000..42100d28fb85 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDao.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 org.apache.cloudstack.api.response.BackupPolicyResponse; +import org.apache.cloudstack.backup.BackupPolicy; +import org.apache.cloudstack.backup.BackupPolicyVO; + +import com.cloud.utils.db.GenericDao; + +import java.util.List; + +public interface BackupPolicyDao extends GenericDao { + + BackupPolicyResponse newBackupPolicyResponse(BackupPolicy policy); + List listByZone(Long zoneId); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDaoImpl.java new file mode 100644 index 000000000000..972672ea4766 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDaoImpl.java @@ -0,0 +1,79 @@ +// 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.BackupPolicyResponse; +import org.apache.cloudstack.backup.BackupPolicy; +import org.apache.cloudstack.backup.BackupPolicyVO; +import org.springframework.stereotype.Component; + +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; + +@Component +public class BackupPolicyDaoImpl extends GenericDaoBase implements BackupPolicyDao { + + @Inject + DataCenterDao dataCenterDao; + + private SearchBuilder backupPoliciesSearch; + + public BackupPolicyDaoImpl() { + } + + @PostConstruct + protected void init() { + backupPoliciesSearch = createSearchBuilder(); + backupPoliciesSearch.and("zone_id", backupPoliciesSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + backupPoliciesSearch.done(); + } + + @Override + public BackupPolicyResponse newBackupPolicyResponse(BackupPolicy policy) { + DataCenterVO zone = dataCenterDao.findById(policy.getZoneId()); + + BackupPolicyResponse response = new BackupPolicyResponse(); + if (policy.isImported()) { + response.setId(policy.getUuid()); + response.setZoneId(zone.getUuid()); + } + response.setName(policy.getName()); + response.setDescription(policy.getDescription()); + response.setExternalId(policy.getExternalId()); + response.setObjectName("backuppolicy"); + 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)); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDao.java new file mode 100644 index 000000000000..61b0fdf90430 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDao.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 + * 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.BackupPolicyVMMapResponse; +import org.apache.cloudstack.backup.BackupPolicyVMMap; +import org.apache.cloudstack.backup.BackupPolicyVMMapVO; + +import com.cloud.utils.db.GenericDao; + +public interface BackupPolicyVMMapDao extends GenericDao { + + BackupPolicyVMMapVO findByVMId(long vmId); + List listByPolicyId(long policyId); + List listByPolicyIdAndVMId(long policyId, long vmId); + List listByZoneId(Long zoneId); + BackupPolicyVMMapResponse newBackupPolicyVMMappingResponse(BackupPolicyVMMap map); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDaoImpl.java new file mode 100644 index 000000000000..a4b0a37dba57 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDaoImpl.java @@ -0,0 +1,128 @@ +/* + * 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 javax.annotation.PostConstruct; +import javax.inject.Inject; + +import org.apache.cloudstack.api.response.BackupPolicyVMMapResponse; +import org.apache.cloudstack.backup.BackupPolicyVMMap; +import org.apache.cloudstack.backup.BackupPolicyVMMapVO; +import org.apache.cloudstack.backup.BackupPolicyVO; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Component; + +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; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class BackupPolicyVMMapDaoImpl extends GenericDaoBase implements BackupPolicyVMMapDao { + + @Inject + private VMInstanceDao vmInstanceDao; + + @Inject + private BackupPolicyDao backupPolicyDao; + + @Inject + private DataCenterDao dataCenterDao; + + private SearchBuilder mapSearch; + + public BackupPolicyVMMapDaoImpl() { + } + + @PostConstruct + protected void init() { + mapSearch = createSearchBuilder(); + mapSearch.and("vm_id", mapSearch.entity().getVmId(), SearchCriteria.Op.EQ); + mapSearch.and("policy_id", mapSearch.entity().getPolicyId(), SearchCriteria.Op.EQ); + mapSearch.and("zone_id", mapSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + mapSearch.done(); + } + + @Override + public BackupPolicyVMMapVO findByVMId(long vmId) { + SearchCriteria sc = mapSearch.create(); + sc.setParameters("vm_id", vmId); + List maps = listBy(sc); + if (CollectionUtils.isNotEmpty(maps)) { + if (maps.size() > 1) { + throw new CloudRuntimeException("Error: Vm " + vmId + " is assigned to multiple policies"); + } + return maps.get(0); + } + return null; + } + + @Override + public List listByPolicyId(long policyId) { + SearchCriteria sc = mapSearch.create(); + sc.setParameters("policy_id", policyId); + return listBy(sc); + } + + @Override + public List listByPolicyIdAndVMId(long policyId, long vmId) { + SearchCriteria sc = mapSearch.create(); + sc.setParameters("policy_id", policyId); + sc.setParameters("vm_id", vmId); + return listBy(sc); + } + + @Override + public List listByZoneId(Long zoneId) { + SearchCriteria sc = mapSearch.create(); + if (zoneId != null) { + sc.setParameters("zone_id", zoneId); + } + return listBy(sc); + } + + @Override + public BackupPolicyVMMapResponse newBackupPolicyVMMappingResponse(BackupPolicyVMMap map) { + BackupPolicyVO policy = backupPolicyDao.findById(map.getPolicyId()); + if (policy == null) { + throw new CloudRuntimeException("Policy " + map.getPolicyId() + " does not exist"); + } + VMInstanceVO vm = vmInstanceDao.findById(map.getVmId()); + if (vm == null) { + throw new CloudRuntimeException("VM " + map.getVmId() + " does not exist"); + } + DataCenterVO zone = dataCenterDao.findById(map.getZoneId()); + if (zone == null) { + throw new CloudRuntimeException("Zone " + map.getZoneId() + " does not exist"); + } + BackupPolicyVMMapResponse response = new BackupPolicyVMMapResponse(); + response.setBackupPolicyId(policy.getUuid()); + response.setVmId(vm.getUuid()); + response.setZoneId(zone.getUuid()); + response.setObjectName("backuppolicyvmmap"); + return response; + } +} 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 84c27583925b..011075ae7c1a 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 @@ -356,4 +356,7 @@ + + + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql b/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql index de6865fab515..d01af4b85e04 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql @@ -34,4 +34,50 @@ INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'moveNetworkAclItem', 'ALLOW', 302) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'moveNetworkAclItem', 'ALLOW', 260) ON DUPLICATE KEY UPDATE rule=rule; -UPDATE `cloud`.`async_job` SET `removed` = now() WHERE `removed` IS NULL; \ No newline at end of file +UPDATE `cloud`.`async_job` SET `removed` = now() WHERE `removed` IS NULL; + +-- Backup and Recovery + +CREATE TABLE IF NOT EXISTS `cloud`.`backup_policy` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `uuid` varchar(40) NOT NULL, + `name` varchar(255) NOT NULL COMMENT 'backup policy name', + `description` varchar(255) NOT NULL COMMENT 'backup policy description', + `external_id` varchar(80) NOT NULL COMMENT 'backup policy ID on provider side', + `zone_id` bigint(20) unsigned NOT NULL COMMENT 'zone id', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + CONSTRAINT `fk_backup_policy__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud`.`backup_policy_vm_map` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `zone_id` bigint(20) unsigned NOT NULL, + `policy_id` bigint(20) unsigned NOT NULL, + `vm_id` bigint(20) unsigned NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_backup_policy_vm_map__policy_id` FOREIGN KEY (`policy_id`) REFERENCES `backup_policy` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_backup_policy_vm_map__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_backup_policy_vm_map__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud`.`backup` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `uuid` varchar(40) NOT NULL, + `account_id` bigint(20) unsigned NOT NULL, + `zone_id` bigint(20) unsigned NOT NULL, + `external_id` varchar(80) NOT NULL COMMENT 'backup ID on provider side', + `name` varchar(255) NOT NULL COMMENT 'backup name', + `description` varchar(255) COMMENT 'backup description', + `parent_id` bigint(20) unsigned COMMENT 'backup parent id', + `vm_id` bigint(20) unsigned NOT NULL, + `volumes` varchar(100), + `status` varchar(20) NOT NULL, + `start` 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__parent_id` FOREIGN KEY (`parent_id`) REFERENCES `backup` (`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/engine/schema/src/test/java/org/apache/cloudstack/backup/BackupVOTest.java b/engine/schema/src/test/java/org/apache/cloudstack/backup/BackupVOTest.java new file mode 100644 index 000000000000..33aca9682e25 --- /dev/null +++ b/engine/schema/src/test/java/org/apache/cloudstack/backup/BackupVOTest.java @@ -0,0 +1,78 @@ +//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.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@RunWith(JUnit4.class) +public class BackupVOTest { + + private BackupVO vo = new BackupVO(); + + private final List ids = Arrays.asList(1L, 2L, 3L, 4L); + + @Test + public void testVolumeIdsNotEmptyList() { + vo.setVolumeIds(ids); + Assert.assertEquals("[1,2,3,4]", vo.getVolumes()); + Assert.assertEquals(ids, vo.getVolumeIds()); + } + + @Test + public void testVolumeIdsEmptyList() { + vo.setVolumeIds(new ArrayList<>()); + Assert.assertEquals(null, vo.getVolumes()); + Assert.assertEquals(new ArrayList<>(), vo.getVolumeIds()); + } + + @Test + public void testVolumeIdsUnsetVolumeIds() { + Assert.assertEquals(null, vo.getVolumes()); + Assert.assertEquals(new ArrayList<>(), vo.getVolumeIds()); + } + + @Test + public void testDecodeVolumesStringNotEmptyList() { + vo.setVolumes("[1,2,3,4]"); + Assert.assertEquals(ids, vo.getVolumeIds()); + } + + @Test + public void testDecodeVolumesStringEmptyList() { + vo.setVolumes(null); + Assert.assertEquals(new ArrayList<>(), vo.getVolumeIds()); + } + + @Test + public void testSetVolumeIdsMultipleTimes() { + List list = Arrays.asList(1L, 2L); + vo.setVolumeIds(list); + Assert.assertEquals("[1,2]", vo.getVolumes()); + vo.setVolumeIds(new ArrayList<>()); + Assert.assertEquals(null, vo.getVolumes()); + vo.setVolumeIds(ids); + Assert.assertEquals("[1,2,3,4]", vo.getVolumes()); + } +} diff --git a/plugins/backup/dummy/pom.xml b/plugins/backup/dummy/pom.xml new file mode 100644 index 000000000000..0bdc3f94edc6 --- /dev/null +++ b/plugins/backup/dummy/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + cloud-plugin-backup-dummy + Apache CloudStack Plugin - Dummy Backup and Recovery Plugin + + cloudstack-plugins + org.apache.cloudstack + 4.12.0.0-SNAPSHOT + ../../pom.xml + + 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..0aacf9104dc1 --- /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.Comparator; +import java.util.Date; +import java.util.List; + +import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.log4j.Logger; + +import com.cloud.agent.api.to.VolumeTO; +import com.cloud.storage.Storage; +import com.cloud.storage.Volume; +import com.cloud.utils.component.AdapterBase; +import com.cloud.vm.VirtualMachine; + +import javax.inject.Inject; + +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 B&R Plugin"; + } + + @Override + public List listBackupPolicies(Long zoneId) { + s_logger.debug("Listing backup policies on Dummy B&R Plugin"); + BackupPolicy policy1 = new BackupPolicyTO("aaaa-aaaa", "Golden Policy", "Gold description"); + BackupPolicy policy2 = new BackupPolicyTO("bbbb-bbbb", "Silver Policy", "Silver description"); + return Arrays.asList(policy1, policy2); + } + + @Override + public boolean isBackupPolicy(Long zoneId, String uuid) { + s_logger.debug("Checking if backup policy exists on the Dummy Backup Provider"); + return true; + } + + @Override + public boolean addVMToBackupPolicy(BackupPolicy policy, VirtualMachine vm) { + s_logger.debug("Assigning VM " + vm.getInstanceName() + " to backup policy " + policy.getName()); + return true; + } + + @Override + public boolean removeVMFromBackupPolicy(BackupPolicy policy, VirtualMachine vm) { + s_logger.debug("Removing VM " + vm.getInstanceName() + " from backup policy " + policy.getName()); + return true; + } + + @Override + public Backup createVMBackup(BackupPolicy policy, VirtualMachine vm) { + s_logger.debug("Creating VM backup for VM " + vm.getInstanceName() + " from backup policy " + policy.getName()); + + List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + String backupNumber = String.valueOf(backups.size() + 1); + Backup lastBackup = null; + if (backups.size() > 0) { + backups.sort(Comparator.comparing(Backup::getStartTime)); + lastBackup = backups.get(backups.size() - 1); + } + BackupTO newBackup = new BackupTO(vm.getDataCenterId(), vm.getAccountId(), + "xxxx-xxxx-" + vm.getUuid() + "-" + backupNumber, "Backup-" + vm.getUuid() + backupNumber, + "VM-" + vm.getInstanceName() + "-backup-" + backupNumber, + lastBackup != null ? lastBackup.getExternalId() : null, vm.getId(), null, + Backup.Status.BackedUp, new Date()); + backups.add(newBackup); + + return newBackup; + } + + @Override + public boolean restoreVMFromBackup(String vmUuid, String backupUuid) { + s_logger.debug("Restoring vm " + vmUuid + "from backup " + backupUuid + " on the Dummy Backup Provider"); + return true; + } + + @Override + public VolumeTO restoreVolumeFromBackup(String volumeUuid, String backupUuid) { + s_logger.debug("Restoring volume " + volumeUuid + "from backup " + backupUuid + " on the Dummy Backup Provider"); + return new VolumeTO(0L, Volume.Type.DATADISK, Storage.StoragePoolType.NetworkFilesystem, "pool-aaaa", "volumeTest", + "/test", "volTest", 1024L, "", ""); + } + + @Override + public List listVMBackups(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 boolean removeVMBackup(VirtualMachine vm, String backupId) { + s_logger.debug("Removing VM backup " + backupId + " for VM " + vm.getInstanceName() + " on the Dummy Backup Provider"); + + List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + for (Backup backup : backups) { + if (backup.getExternalId().equals(backupId)) { + return true; + } + } + return false; + } +} 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..bb2fa1911b11 --- /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.12.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..84e442aa4cc4 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java @@ -0,0 +1,186 @@ +// 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.List; + +import javax.inject.Inject; + +import org.apache.cloudstack.backup.veeam.VeeamClient; +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.agent.api.to.VolumeTO; +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.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); + + private 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.username", + "administrator", + "The Veeam backup and recovery username.", true, ConfigKey.Scope.Zone); + + private ConfigKey VeeamPassword = new ConfigKey<>("Advanced", String.class, + "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", + "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 listBackupPolicies(final Long zoneId) { + return getClient(zoneId).listBackupPolicies(); + } + + @Override + public boolean isBackupPolicy(final Long zoneId, final String uuid) { + List policies = listBackupPolicies(zoneId); + if (CollectionUtils.isEmpty(policies)) { + return false; + } + for (final BackupPolicy 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; + } + + @Override + public boolean addVMToBackupPolicy(final BackupPolicy policy, final VirtualMachine vm) { + final VmwareDatacenter vmwareDatacenter = findVmwareDatacenterForVM(vm); + return getClient(vm.getDataCenterId()).addVMToVeeamJob(policy.getExternalId(), vm.getInstanceName(), vmwareDatacenter.getVcenterHost()); + } + + @Override + public boolean removeVMFromBackupPolicy(final BackupPolicy policy, final VirtualMachine vm) { + final VmwareDatacenter vmwareDatacenter = findVmwareDatacenterForVM(vm); + return getClient(vm.getDataCenterId()).removeVMFromVeeamJob(policy.getExternalId(), vm.getInstanceName(), vmwareDatacenter.getVcenterHost()); + } + + @Override + public Backup createVMBackup(BackupPolicy policy, VirtualMachine vm) { + //TODO: Return backup + getClient(vm.getDataCenterId()).startBackupJob(policy.getExternalId()); + return null; + } + + @Override + public boolean restoreVMFromBackup(String vmUuid, String backupUuid) { + //TODO + return false; + } + + @Override + public VolumeTO restoreVolumeFromBackup(String volumeUuid, String backupUuid) { + //TODO + return null; + } + + @Override + public List listVMBackups(Long zoneId, VirtualMachine vm) { + //return getClient(zoneId).listAllBackups(); + return null; + } + + @Override + public boolean removeVMBackup(VirtualMachine vm, String backupId) { + //TODO: Implement + return false; + } + + @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 B&R 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..285542a01baf --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.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.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 String getName() { + return name; + } + + @Override + public String getDescription() { + return "Veeam Backup"; + } + + @Override + public Long getParentId() { + return null; + } + + @Override + public Long getVmId() { + return null; + } + + @Override + public List getVolumeIds() { + return null; + } + + @Override + public Status getStatus() { + return null; + } + + @Override + public Date getStartTime() { + 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/VeeamBackupPolicy.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackupPolicy.java new file mode 100644 index 000000000000..bd29add689a8 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackupPolicy.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.BackupPolicy; + +public class VeeamBackupPolicy implements BackupPolicy { + + private String name; + private String uid; + + public VeeamBackupPolicy(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 Policy (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..16e402a23721 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java @@ -0,0 +1,368 @@ +// 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.veeam.api.VeeamObjectType.HierarchyRootReference; + +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.List; + +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.BackupPolicy; +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.ObjectInJob; +import org.apache.cloudstack.backup.veeam.api.ObjectsInJob; +import org.apache.cloudstack.backup.veeam.api.Ref; +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.exception.CloudRuntimeException; +import com.cloud.utils.nio.TrustAllManager; +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(); + + 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()); + } + } + + 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 boolean checkTaskStatus(final HttpResponse response) throws IOException { + final Task task = parseTaskResponse(response); + for (int i = 0; i < 20; 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")) { + return true; + } + throw new CloudRuntimeException("Failed to assign VM to backup policy 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; + } + + //////////////////////////////////////////////////////// + //////////////// Public Veeam APIs ///////////////////// + //////////////////////////////////////////////////////// + + 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 listBackupPolicies() { + 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 VeeamBackupPolicy(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 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 addVMToVeeamJob(final String jobId, final String vmwareInstanceName, final String vmwareDcName) { + LOG.debug("Trying to add VM to backup policy that is a 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 policy 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 policy 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); + 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); + } + } + throw new CloudRuntimeException("VM was not found to be assigned to backup policy"); + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + throw new CloudRuntimeException("Failed to remove VM from backup policy, please check veeam tasks"); + } + +} 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..b8f368d986c3 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObject.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.veeam; + +import java.util.List; + +import org.apache.cloudstack.backup.veeam.api.VeeamObjectType; + +public interface VeeamObject { + String getUuid(); + String getName(); + String getHref(); + VeeamObjectType getType(); + List getLinks(); +} 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/Link.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Link.java new file mode 100644 index 000000000000..f45ba4fe673a --- /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 VeeamObjectType 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 VeeamObjectType getType() { + return type; + } + + public void setType(String type) { + this.type = VeeamObjectType.valueOf(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..c21c554aebe8 --- /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 VeeamObjectType 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 VeeamObjectType getType() { + return type; + } + + public void setType(String type) { + this.type = VeeamObjectType.valueOf(type); + } +} 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/java/org/apache/cloudstack/backup/veeam/api/VeeamObjectType.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/VeeamObjectType.java new file mode 100644 index 000000000000..82aea34ebf9b --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/VeeamObjectType.java @@ -0,0 +1,35 @@ +// 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; + +public enum VeeamObjectType { + HierarchyRoot, + HierarchyRootReference, + + Job, + JobReference, + + Backup, + BackupReference, + BackupJobSessionReferenceList, + BackupServerReference, + BackupFileReferenceList, + + RestorePointReferenceList, + RepositoryReference, +} 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..1eeb7a240b2c --- /dev/null +++ b/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java @@ -0,0 +1,85 @@ +// 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 org.apache.cloudstack.backup.BackupPolicy; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +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.listBackupPolicies(); + verify(getRequestedFor(urlMatching(".*/jobs"))); + Assert.assertEquals(policies.size(), 1); + Assert.assertEquals(policies.get(0).getName(), "ZONE1-GOLD"); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7f8d3e0d7477..d2913247f6ac 100644 --- a/pom.xml +++ b/pom.xml @@ -130,7 +130,7 @@ 23.6-jre 4.5.4 4.4.8 - 2.9.2 + 2.9.5 1.9.2 0.16 3.22.0-GA diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index 22ed2437d4de..d07e37666b9c 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -39,6 +39,9 @@ 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.BackupPolicyResponse; +import org.apache.cloudstack.api.response.BackupPolicyVMMapResponse; +import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.DomainRouterResponse; @@ -61,9 +64,15 @@ import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupPolicy; +import org.apache.cloudstack.backup.BackupPolicyVMMap; +import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.backup.dao.BackupPolicyDao; +import org.apache.cloudstack.backup.dao.BackupPolicyVMMapDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.jobs.AsyncJob; import org.apache.cloudstack.framework.jobs.AsyncJobManager; @@ -434,6 +443,9 @@ public class ApiDBUtils { static ResourceMetaDataService s_resourceDetailsService; static HostGpuGroupsDao s_hostGpuGroupsDao; static VGPUTypesDao s_vgpuTypesDao; + static BackupDao s_backupDao; + static BackupPolicyDao s_backupPolicyDao; + static BackupPolicyVMMapDao s_backupPolicyVMMapDao; @Inject private ManagementServer ms; @@ -666,6 +678,12 @@ public class ApiDBUtils { private HostGpuGroupsDao hostGpuGroupsDao; @Inject private VGPUTypesDao vgpuTypesDao; + @Inject + private BackupDao backupDao; + @Inject + private BackupPolicyDao backupPolicyDao; + @Inject + private BackupPolicyVMMapDao backupPolicyVMMapDao; @PostConstruct void init() { @@ -785,6 +803,9 @@ void init() { s_resourceDetailsService = resourceDetailsService; s_hostGpuGroupsDao = hostGpuGroupsDao; s_vgpuTypesDao = vgpuTypesDao; + s_backupDao = backupDao; + s_backupPolicyDao = backupPolicyDao; + s_backupPolicyVMMapDao = backupPolicyVMMapDao; } // /////////////////////////////////////////////////////////// @@ -2000,4 +2021,16 @@ 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 BackupPolicyResponse newBackupPolicyResponse(BackupPolicy policy) { + return s_backupPolicyDao.newBackupPolicyResponse(policy); + } + + public static BackupPolicyVMMapResponse newBackupPolicyVMMappingResponse(BackupPolicyVMMap map) { + return s_backupPolicyVMMapDao.newBackupPolicyVMMappingResponse(map); + } } diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index ba3d5c3e143f..2231282aea6f 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -199,6 +199,9 @@ 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.BackupPolicyResponse; +import org.apache.cloudstack.api.response.BackupPolicyVMMapResponse; +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; @@ -289,6 +292,8 @@ 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.BackupPolicy; +import org.apache.cloudstack.backup.BackupPolicyVMMap; import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; @@ -296,6 +301,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.framework.jobs.AsyncJob; import org.apache.cloudstack.framework.jobs.AsyncJobManager; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; @@ -3964,4 +3970,19 @@ public SSHKeyPairResponse createSSHKeyPairResponse(SSHKeyPair sshkeyPair, boolea response.setDomainName(domain.getName()); return response; } + + @Override + public BackupResponse createBackupResponse(Backup backup) { + return ApiDBUtils.newBackupResponse(backup); + } + + @Override + public BackupPolicyResponse createBackupPolicyResponse(BackupPolicy policy) { + return ApiDBUtils.newBackupPolicyResponse(policy); + } + + @Override + public BackupPolicyVMMapResponse createBackupPolicyVMMappingResponse(BackupPolicyVMMap map) { + return ApiDBUtils.newBackupPolicyVMMappingResponse(map); + } } 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..b57ab16d0100 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -0,0 +1,410 @@ +// 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 com.cloud.event.ActionEvent; +import com.cloud.event.EventTypes; +import org.apache.cloudstack.api.command.admin.backup.DeleteBackupPolicyCmd; +import org.apache.cloudstack.api.command.admin.backup.ImportBackupPolicyCmd; +import org.apache.cloudstack.api.command.admin.backup.ListBackupProvidersCmd; +import org.apache.cloudstack.api.command.user.backup.AddVMToBackupPolicyCmd; +import org.apache.cloudstack.api.command.user.backup.CreateVMBackupCmd; +import org.apache.cloudstack.api.command.user.backup.DeleteVMBackupCmd; +import org.apache.cloudstack.api.command.user.backup.ListBackupPoliciesCmd; +import org.apache.cloudstack.api.command.user.backup.ListBackupPolicyVMMappingsCmd; +import org.apache.cloudstack.api.command.user.backup.ListVMBackupsCmd; +import org.apache.cloudstack.api.command.user.backup.RemoveVMFromBackupPolicyCmd; +import org.apache.cloudstack.api.command.user.backup.RestoreVolumeFromBackupAndAttachToVMCmd; +import org.apache.cloudstack.api.command.user.backup.RestoreVMFromBackupCmd; +import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.backup.dao.BackupPolicyDao; +import org.apache.cloudstack.backup.dao.BackupPolicyVMMapDao; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.commons.lang.BooleanUtils; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.api.to.VolumeTO; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.storage.VolumeApiService; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +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 BackupPolicyDao backupPolicyDao; + + @Inject + private VMInstanceDao vmInstanceDao; + + @Inject + private AccountService accountService; + + @Inject + private BackupPolicyVMMapDao backupPolicyVMMapDao; + + @Inject + private BackupDao backupDao; + + @Inject + private VolumeDao volumeDao; + + @Inject + private VolumeApiService volumeApiService; + + private static Map backupProvidersMap = new HashMap<>(); + private List backupProviders; + + @Override + @ActionEvent(eventType = EventTypes.EVENT_IMPORT_BACKUP_POLICY, eventDescription = "importing backup policy", async = true) + public BackupPolicy importBackupPolicy(Long zoneId, String policyExternalId, String policyName, String policyDescription) { + final BackupProvider provider = getBackupProvider(zoneId); + if (!provider.isBackupPolicy(zoneId, policyExternalId)) { + throw new CloudRuntimeException("Policy " + policyExternalId + " does not exist on provider " + provider.getName() + " on zone " + zoneId); + } + + final BackupPolicyVO policy = new BackupPolicyVO(zoneId, policyExternalId, policyName, policyDescription); + final BackupPolicyVO savedPolicy = backupPolicyDao.persist(policy); + if (savedPolicy == null) { + throw new CloudRuntimeException("Unable to create backup policy: " + policyExternalId + ", name: " + policyName); + } + LOG.debug("Successfully created backup policy " + policyName + " mapped to backup provider policy " + policyExternalId); + return savedPolicy; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_ADD_VM_TO_BACKUP_POLICY, eventDescription = "adding VM to backup policy", async = true) + public boolean addVMToBackupPolicy(Long policyId, Long virtualMachineId) { + VMInstanceVO vm = vmInstanceDao.findById(virtualMachineId); + if (vm == null) { + throw new CloudRuntimeException("VM " + virtualMachineId + " does not exist"); + } + // FIXME: check if VM is already assigned to a policy? + BackupPolicyVO policy = backupPolicyDao.findById(policyId); + if (policy == null) { + throw new CloudRuntimeException("Policy " + policy + " does not exist"); + } + BackupProvider backupProvider = getBackupProvider(vm.getDataCenterId()); + boolean result = backupProvider.addVMToBackupPolicy(policy, vm); + if (result) { + BackupPolicyVMMapVO map = backupPolicyVMMapDao.findByVMId(virtualMachineId); + if (map != null) { + backupPolicyVMMapDao.expunge(map.getId()); + } + map = new BackupPolicyVMMapVO(vm.getDataCenterId(), policy.getId(), virtualMachineId); + backupPolicyVMMapDao.persist(map); + LOG.debug("Successfully assigned VM " + virtualMachineId + " to backup policy " + policy.getName()); + } else { + LOG.debug("Could not assign VM " + virtualMachineId + " to backup policy " + policyId); + } + return result; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_REMOVE_VM_FROM_BACKUP_POLICY, eventDescription = "removing VM from backup policy", async = true) + public boolean removeVMFromBackupPolicy(Long policyId, Long vmId) { + BackupPolicyVO policy = backupPolicyDao.findById(policyId); + if (policy == null) { + throw new CloudRuntimeException("Policy " + policyId + " does not exist"); + } + VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM " + vmId + " does not exist"); + } + BackupProvider backupProvider = getBackupProvider(vm.getDataCenterId()); + boolean result = backupProvider.removeVMFromBackupPolicy(policy, vm); + if (result) { + List map = backupPolicyVMMapDao.listByPolicyIdAndVMId(policyId, vmId); + if (map.size() > 1) { + throw new CloudRuntimeException("More than one mapping between VM " + vmId + " and policy " + policyId); + } + backupPolicyVMMapDao.expunge(map.get(0).getId()); + LOG.debug("Successfully removed VM " + vmId + " from backup policy " + policy.getName()); + } else { + LOG.debug("Could not remove VM " + vmId + " from backup policy " + policyId); + } + return result; + } + + @Override + public List listBackupPolicyVMMappings(Long vmId, Long zoneId, Long policyId) { + if (vmId != null) { + return Collections.singletonList(backupPolicyVMMapDao.findByVMId(vmId)); + } + if (zoneId != null) { + return new ArrayList<>(backupPolicyVMMapDao.listByZoneId(zoneId)); + } + if (policyId != null) { + return new ArrayList<>(backupPolicyVMMapDao.listByPolicyId(policyId)); + } + return new ArrayList<>(backupPolicyVMMapDao.listAll()); + } + + @Override + //TODO: Add background job to sync VM backups from the provider + public List listVMBackups(Long vmId) { + VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM " + vmId + " does not exist"); + } + Long zoneId = vm.getDataCenterId(); + BackupProvider backupProvider = getBackupProvider(zoneId); + return backupDao.listByVmId(zoneId, vmId); + } + + /** + * List external backup policies for the Backup and Recovery provider registered in the zone zoneId + */ + private List listExternalPolicies(Long zoneId) { + 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"); + } + BackupProvider backupProvider = getBackupProvider(zoneId); + LOG.debug("Listing external backup policies for the backup provider registered in zone " + zoneId); + return backupProvider.listBackupPolicies(zoneId); + } + + /** + * List imported backup policies in the zone zoneId + */ + private List listInternalPolicies(Long zoneId) { + LOG.debug("Listing imported backup policies on zone " + zoneId); + return backupPolicyDao.listByZone(zoneId); + } + + /** + * List imported backup policy with id policyId + */ + private List listInternalPolicyById(Long policyId) { + BackupPolicyVO policy = backupPolicyDao.findById(policyId); + if (policy == null) { + throw new CloudRuntimeException("Policy " + policyId + " does not exist"); + } + LOG.debug("Listing imported backup policy with id: " + policyId); + return Collections.singletonList(policy); + } + + @Override + public List listBackupPolicies(Long zoneId, Boolean external, Long policyId) { + if (policyId != null) { + return listInternalPolicyById(policyId); + } else { + return BooleanUtils.isTrue(external) ? listExternalPolicies(zoneId) : listInternalPolicies(zoneId); + } + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_CREATE_VM_BACKUP, eventDescription = "creating VM backup", async = true) + public Backup createBackup(Long vmId) { + VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM does not exist"); + } + BackupPolicyVMMap vmMap = backupPolicyVMMapDao.findByVMId(vmId); + if (vmMap == null) { + throw new CloudRuntimeException("VM " + vmId + " is not assigned to any backup policy"); + } + BackupPolicyVO policy = backupPolicyDao.findById(vmMap.getPolicyId()); + if (policy == null) { + throw new CloudRuntimeException("Policy does not exist"); + } + BackupProvider backupProvider = getBackupProvider(vm.getDataCenterId()); + + Backup vmBackup = backupProvider.createVMBackup(policy, vm); + if (vmBackup == null) { + return null; + } + BackupVO backupVO = backupDao.getBackupVO(vmBackup); + return backupDao.persist(backupVO); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_DELETE_VM_BACKUP, eventDescription = "deleting VM backup", async = true) + public boolean deleteBackup(Long backupId) { + BackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + } + Long zoneId = backup.getZoneId(); + Long vmId = backup.getVmId(); + VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM " + vmId + " does not exist"); + } + BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + boolean result = backupProvider.removeVMBackup(vm, backup.getExternalId()); + if (result) { + backupDao.remove(backupId); + } + return result; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_RESTORE_VM_FROM_BACKUP, eventDescription = "restoring VM from backup", async = true) + public boolean restoreVMFromBackup(Long backupId) { + 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.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM " + vmId + " does not exist"); + } + return backupProvider.restoreVMFromBackup(vm.getUuid(), backup.getUuid()); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_RESTORE_VM_FROM_BACKUP, eventDescription = "restoring VM from backup", async = true) + public boolean restoreBackupVolumeAndAttachToVM(Long volumeId, Long vmId, Long backupId) { + BackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + } + BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + + VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM " + vmId + " does not exist"); + } + VolumeVO volume = volumeDao.findByIdIncludingRemoved(volumeId); + if (volume == null) { + throw new CloudRuntimeException("Volume " + volumeId + " could not be found"); + } + LOG.debug("Asking provider to restore volume " + volumeId + " from backup " + backupId); + VolumeTO restoredVolume = backupProvider.restoreVolumeFromBackup(backup.getUuid(), volume.getUuid()); + attachVolumeToVM(restoredVolume, vm); + return false; + } + + /** + * Attach volume to VM + */ + private void attachVolumeToVM(VolumeTO restoredVolume, VMInstanceVO vm) { + LOG.debug("Attaching the restored volume to VM " + vm.getId()); + volumeDao.attachVolume(restoredVolume.getId(), vm.getId(), restoredVolume.getDeviceId()); + } + + @Override + public boolean deleteBackupPolicy(Long policyId) { + BackupPolicyVO policy = backupPolicyDao.findById(policyId); + if (policy == null) { + throw new CloudRuntimeException("Could not find a backup policy with id: " + policyId); + } + return backupPolicyDao.expunge(policy.getId()); + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + 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>(); + + // Backup Policy APIs + cmdList.add(ListBackupProvidersCmd.class); + cmdList.add(ListBackupPoliciesCmd.class); + cmdList.add(ImportBackupPolicyCmd.class); + cmdList.add(DeleteBackupPolicyCmd.class); + cmdList.add(AddVMToBackupPolicyCmd.class); + cmdList.add(RemoveVMFromBackupPolicyCmd.class); + cmdList.add(ListBackupPolicyVMMappingsCmd.class); + + // Backup and Restore APIs + cmdList.add(ListVMBackupsCmd.class); + cmdList.add(CreateVMBackupCmd.class); + cmdList.add(DeleteVMBackupCmd.class); + cmdList.add(RestoreVMFromBackupCmd.class); + cmdList.add(RestoreVolumeFromBackupAndAttachToVMCmd.class); + + return cmdList; + } + + @Override + public String getConfigComponentName() { + return BackupService.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[]{BackupFrameworkEnabled, BackupProviderPlugin}; + } + + 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); + } + } + } +} 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/test/integration/smoke/test_backup_recovery.py b/test/integration/smoke/test_backup_recovery.py new file mode 100644 index 000000000000..f9a00b95568a --- /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, BackupPolicy, Configurations, VMBackup) +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 policy to use on tests + + cls.external_policies = BackupPolicy.listExternal(cls.api_client, cls.zone.id) + cls.debug("Importing backup policy %s - %s" % (cls.external_policies[0].externalid, cls.external_policies[0].name)) + cls.policy = BackupPolicy.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 policy from the dummy provider + # 2. List internal backup policies, policy id should be listed + # 3. Delete backup policy + # 4. List internal backup policies, policy id should not be listed + + # Import backup policy + ext_policy = self.external_policies[1] + self.debug("Importing backup policy %s - %s" % (ext_policy.externalid, ext_policy.name)) + policy = BackupPolicy.importExisting(self.apiclient, self.zone.id, ext_policy.externalid, + ext_policy.name, ext_policy.description) + + # Verify policy is listed + imported_policies = BackupPolicy.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 policy + self.debug("Deleting backup policy %s" % policy.id) + policy.delete(self.apiclient) + + # Verify policy is not listed + imported_policies = BackupPolicy.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 policy + """ + + # Validate the following: + # 1. Add VM to backup policy + # 2. Verify a mapping between the VM and the backup policy exists + # 3. Remove VM from backup policy + # 4. Verify there is no mapping between the VM and the backup policy + + # Add VM to backup policy + self.debug("Adding VM %s to backup policy %s" % (self.vm.id, self.policy.id)) + self.policy.addVM(self.apiclient, self.vm.id) + + # Verify a mapping between backup policy and VM is created on DB + mappings = BackupPolicy.listVMMappings(self.apiclient, self.policy.id, self.vm.id, self.zone.id) + self.assertNotEqual(len(mappings), 0, "A mapping between VM and backup policy should exist") + self.assertNotEqual(mappings[0], None, "A mapping between VM and backup policy should exist") + + # Remove VM from backup policy + self.debug("Removing VM %s from backup policy %s" % (self.vm.id, self.policy.id)) + self.policy.removeVM(self.apiclient, self.vm.id) + + # Verify mapping is removed + zone_mappings = BackupPolicy.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 policy 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 = VMBackup.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) + VMBackup.create(self.apiclient, self.vm.id) + + # Verify backup is created for the VM + backups = VMBackup.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 + VMBackup.delete(self.apiclient, backup.id) + + # Verify backup is deleted + backups = VMBackup.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) \ No newline at end of file diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index f45f030ac403..49cf6e158e5c 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -191,7 +191,8 @@ 'listElastistorInterface': 'Misc', 'cloudian': 'Cloudian', 'Sioc' : 'Sioc', - 'Diagnostics': 'Diagnostics' + 'Diagnostics': 'Diagnostics', + 'Backup' : 'Backup' } diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index fdfed774f84f..56eaab2f10bf 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -5375,3 +5375,127 @@ def delete(self, apiclient, resourceid, resourcetype): cmd.resourceid = resourceid cmd.resourcetype = resourcetype return (apiclient.removeResourceDetail(cmd)) + +# Backup and Recovery + +class BackupPolicy: + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def importExisting(self, apiclient, zoneid, externalid, name, description): + """Import existing backup policy from the provider""" + + cmd = importBackupPolicy.importBackupPolicyCmd() + cmd.zoneid = zoneid + cmd.externalid = externalid + cmd.name = name + cmd.description = description + return BackupPolicy(apiclient.importBackupPolicy(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 policy""" + + cmd = deleteBackupPolicy.deleteBackupPolicyCmd() + cmd.id = self.id + return (apiclient.deleteBackupPolicy(cmd)) + + def addVM(self, apiclient, vmid): + """Add a VM to a backup policy""" + + cmd = addVMToBackupPolicy.addVMToBackupPolicyCmd() + cmd.policyid = self.id + cmd.virtualmachineid = vmid + return (apiclient.addVMToBackupPolicy(cmd)) + + def removeVM(self, apiclient, vmid): + """Remove a VM from a backup policy""" + + cmd = removeVMFromBackupPolicy.removeVMFromBackupPolicyCmd() + cmd.policyid = self.id + cmd.virtualmachineid = vmid + return (apiclient.removeVMFromBackupPolicy(cmd)) + + @classmethod + def listVMMappings(self, apiclient, policyid=None, vmid=None, zoneid=None): + """List VM - Backup policies mappings""" + + cmd = listBackupPolicyVMMappings.listBackupPolicyVMMappingsCmd() + if vmid: + cmd.virtualmachineid = vmid + if zoneid: + cmd.zoneid = zoneid + if policyid: + cmd.policyid = policyid + return (apiclient.listBackupPolicyVMMappings(cmd)) + +class VMBackup: + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(self, apiclient, vmid): + """Create VM backup""" + + cmd = createVMBackup.createVMBackupCmd() + cmd.virtualmachineid = vmid + return (apiclient.createVMBackup(cmd)) + + @classmethod + def delete(self, apiclient, id): + """Delete VM backup""" + + cmd = deleteVMBackup.deleteVMBackupCmd() + cmd.id = id + return (apiclient.deleteVMBackup(cmd)) + + @classmethod + def list(self, apiclient, vmid): + """List VM backups""" + + cmd = listVMBackups.listVMBackupsCmd() + cmd.virtualmachineid = vmid + return (apiclient.listVMBackups(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)) \ No newline at end of file From b62eacba37ad3a966d77049a971b9334a036a971 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 2 Jul 2018 14:56:56 +0530 Subject: [PATCH 02/21] B&R: Phase 2 commits melded Signed-off-by: Rohit Yadav --- .../main/java/com/cloud/event/EventTypes.java | 30 +- .../com/cloud/hypervisor/HypervisorGuru.java | 7 + .../java/com/cloud/server/ResourceTag.java | 1 + .../apache/cloudstack/api/ApiConstants.java | 2 + .../cloudstack/api/BaseBackupListCmd.java | 43 +- .../cloudstack/api/ResponseGenerator.java | 10 +- .../admin/backup/ImportBackupPolicyCmd.java | 2 +- .../command/admin/vm/ImportVMCmdByAdmin.java | 103 +++ .../user/backup/CreateVMBackupCmd.java | 46 +- .../user/backup/DeleteVMBackupCmd.java | 8 +- ...md.java => ListVMBackupRestorePoints.java} | 77 +- .../command/user/backup/ListVMBackupsCmd.java | 8 +- .../backup/RemoveVMFromBackupPolicyCmd.java | 121 --- .../user/backup/RestoreVMFromBackupCmd.java | 20 +- ...storeVolumeFromBackupAndAttachToVMCmd.java | 37 +- ...upPolicyCmd.java => StartVMBackupCmd.java} | 46 +- ...kupResponse.java => VMBackupResponse.java} | 71 +- ...java => VMBackupRestorePointResponse.java} | 53 +- .../org/apache/cloudstack/backup/Backup.java | 43 -- .../cloudstack/backup/BackupManager.java | 55 +- .../cloudstack/backup/BackupPolicyVMMap.java | 27 - .../cloudstack/backup/BackupProvider.java | 42 +- .../apache/cloudstack/backup/VMBackup.java | 143 ++++ .../apache/cloudstack/usage/UsageTypes.java | 2 + client/pom.xml | 26 +- .../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 | 44 ++ .../cloud/storage/dao/VMTemplatePoolDao.java | 4 + .../storage/dao/VMTemplatePoolDaoImpl.java | 24 + .../java/com/cloud/usage/UsageVMBackupVO.java | 172 +++++ .../cloud/usage/dao/UsageVMBackupDao.java} | 20 +- .../cloud/usage/dao/UsageVMBackupDaoImpl.java | 137 ++++ .../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/BackupPolicyTO.java | 73 -- .../backup/BackupPolicyVMMapVO.java | 87 --- .../cloudstack/backup/BackupPolicyVO.java | 18 + .../apache/cloudstack/backup/BackupTO.java | 176 ----- .../backup/{BackupVO.java => VMBackupVO.java} | 160 ++-- .../backup/dao/BackupPolicyVMMapDao.java | 37 - .../backup/dao/BackupPolicyVMMapDaoImpl.java | 128 ---- .../cloudstack/backup/dao/VMBackupDao.java | 27 +- ...ackupDaoImpl.java => VMBackupDaoImpl.java} | 89 ++- ...spring-engine-schema-core-daos-context.xml | 4 +- .../META-INF/db/schema-41110to41200.sql | 45 +- .../storage/dao/DiskOfferingDaoImplTest.java | 56 ++ .../cloudstack/backup/BackupVOTest.java | 78 -- .../cloudstack/quota/QuotaManagerImpl.java | 3 +- .../cloudstack/quota/constant/QuotaTypes.java | 1 + .../backup/DummyBackupProvider.java | 102 ++- plugins/backup/veeam/pom.xml | 16 + .../backup/VeeamBackupProvider.java | 111 ++- .../cloudstack/backup/veeam/VeeamBackup.java | 19 +- .../cloudstack/backup/veeam/VeeamClient.java | 274 ++++++- .../cloudstack/backup/veeam/VeeamObject.java | 4 +- .../backup/veeam/api/BackupJobCloneInfo.java | 58 ++ .../cloudstack/backup/veeam/api/Job.java | 163 ++++ .../backup/veeam/api/JobCloneSpec.java | 41 + .../cloudstack/backup/veeam/api/Link.java | 6 +- .../cloudstack/backup/veeam/api/Ref.java | 6 +- .../backup/veeam/api/RestoreSession.java | 120 +++ .../backup/veeam/VeeamClientTest.java | 22 +- .../com/cloud/hypervisor/guru/VMwareGuru.java | 722 ++++++++++++++++++ .../vmware/manager/VmwareManagerImpl.java | 4 +- plugins/pom.xml | 15 +- pom.xml | 3 +- .../main/java/com/cloud/api/ApiDBUtils.java | 27 +- .../java/com/cloud/api/ApiResponseHelper.java | 326 ++++---- .../cloud/hypervisor/HypervisorGuruBase.java | 12 + .../cloudstack/backup/BackupManagerImpl.java | 434 +++++++---- .../com/cloud/vpc/dao/MockNetworkDaoImpl.java | 5 + .../com/cloud/usage/UsageManagerImpl.java | 36 +- .../usage/parser/VMBackupUsageParser.java | 124 +++ .../main/java/com/cloud/utils/UuidUtils.java | 22 + .../hypervisor/vmware/mo/DatastoreMO.java | 2 +- .../cloud/hypervisor/vmware/mo/NetworkMO.java | 9 + 81 files changed, 3486 insertions(+), 1648 deletions(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVMCmdByAdmin.java rename api/src/main/java/org/apache/cloudstack/api/command/user/backup/{ListBackupPolicyVMMappingsCmd.java => ListVMBackupRestorePoints.java} (59%) delete mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVMFromBackupPolicyCmd.java rename api/src/main/java/org/apache/cloudstack/api/command/user/backup/{AddVMToBackupPolicyCmd.java => StartVMBackupCmd.java} (73%) rename api/src/main/java/org/apache/cloudstack/api/response/{BackupResponse.java => VMBackupResponse.java} (75%) rename api/src/main/java/org/apache/cloudstack/api/response/{BackupPolicyVMMapResponse.java => VMBackupRestorePointResponse.java} (54%) delete mode 100644 api/src/main/java/org/apache/cloudstack/backup/Backup.java delete mode 100644 api/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMap.java create mode 100644 api/src/main/java/org/apache/cloudstack/backup/VMBackup.java create mode 100644 engine/schema/src/main/java/com/cloud/usage/UsageVMBackupVO.java rename engine/schema/src/main/java/{org/apache/cloudstack/backup/dao/BackupDao.java => com/cloud/usage/dao/UsageVMBackupDao.java} (64%) create mode 100644 engine/schema/src/main/java/com/cloud/usage/dao/UsageVMBackupDaoImpl.java delete mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyTO.java delete mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMapVO.java delete mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/BackupTO.java rename engine/schema/src/main/java/org/apache/cloudstack/backup/{BackupVO.java => VMBackupVO.java} (63%) delete mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDao.java delete mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDaoImpl.java rename plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/VeeamObjectType.java => engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDao.java (55%) rename engine/schema/src/main/java/org/apache/cloudstack/backup/dao/{BackupDaoImpl.java => VMBackupDaoImpl.java} (65%) create mode 100644 engine/schema/src/test/java/com/cloud/storage/dao/DiskOfferingDaoImplTest.java delete mode 100644 engine/schema/src/test/java/org/apache/cloudstack/backup/BackupVOTest.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/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/RestoreSession.java create mode 100644 usage/src/main/java/com/cloud/usage/parser/VMBackupUsageParser.java diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index a2baf7c7dd5c..54af76a8e319 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 { @@ -469,6 +470,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"; + // VM VMBackup and Recovery events + public static final String EVENT_VM_BACKUP_IMPORT_POLICY = "VMBACKUP.IMPORT.POLICY"; + public static final String EVENT_VM_BACKUP_CREATE = "VMBACKUP.CREATE"; + public static final String EVENT_VM_BACKUP_START = "VMBACKUP.START"; + public static final String EVENT_VM_BACKUP_RESTORE = "VMBACKUP.RESTORE"; + public static final String EVENT_VM_BACKUP_DELETE = "VMBACKUP.DELETE"; + public static final String EVENT_VM_BACKUP_RESTORE_VOLUME_TO_VM = "VMBACKUP.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"; @@ -584,15 +593,6 @@ public class EventTypes { public static final String EVENT_TEMPLATE_DIRECT_DOWNLOAD_FAILURE = "TEMPLATE.DIRECT.DOWNLOAD.FAILURE"; public static final String EVENT_ISO_DIRECT_DOWNLOAD_FAILURE = "ISO.DIRECT.DOWNLOAD.FAILURE"; - // Backup and Recovery events - public static final String EVENT_ADD_VM_TO_BACKUP_POLICY = "ADD.VM.TO.BACKUP.POLICY"; - public static final String EVENT_REMOVE_VM_FROM_BACKUP_POLICY = "REMOVE.VM.FROM.BACKUP.POLICY"; - public static final String EVENT_IMPORT_BACKUP_POLICY = "IMPORT.BACKUP.POLICY"; - public static final String EVENT_CREATE_VM_BACKUP = "CREATE.VM.BACKUP"; - public static final String EVENT_DELETE_VM_BACKUP = "DELETE.VM.BACKUP"; - public static final String EVENT_RESTORE_VM_FROM_BACKUP = "RESTORE.VM.FROM.BACKUP"; - public static final String EVENT_RESTORE_VOLUME_FROM_BACKUP_AND_ATTACH_TO_VM = "RESTORE.VOLUME.FROM.BACKUP.AND.ATTACH.TO.VM"; - static { // TODO: need a way to force author adding event types to declare the entity details as well, with out braking diff --git a/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java b/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java index 45e19ee2674b..095c41790fca 100644 --- a/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java +++ b/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Map; +import org.apache.cloudstack.backup.VMBackup; import org.apache.cloudstack.framework.config.ConfigKey; import com.cloud.agent.api.Command; @@ -84,4 +85,10 @@ public interface HypervisorGuru extends Adapter { List finalizeExpungeVolumes(VirtualMachine vm); Map getClusterSettings(long vmId); + + VirtualMachine importVirtualMachine(long zoneId, long domainId, long accountId, long userId, + String vmInternalName, VMBackup backup) throws Exception; + + boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location, VMBackup.VolumeInfo volumeInfo, + VirtualMachine vm, long poolId) throws Exception; } diff --git a/api/src/main/java/com/cloud/server/ResourceTag.java b/api/src/main/java/com/cloud/server/ResourceTag.java index 0bd5d734e30e..5a147cfdd6d3 100644 --- a/api/src/main/java/com/cloud/server/ResourceTag.java +++ b/api/src/main/java/com/cloud/server/ResourceTag.java @@ -43,6 +43,7 @@ public enum ResourceObjectType { NetworkACL(true, true), StaticRoute(true, false), VMSnapshot(true, false), + VMBackup(true, false), RemoteAccessVpn(true, true), Zone(false, true), ServiceOffering(false, 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 123e066148db..2ac84f9e96c2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -254,6 +254,7 @@ 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_POINT_ID = "restorepointid"; public static final String REVERTABLE = "revertable"; public static final String REGISTERED = "registered"; public static final String QUERY_FILTER = "queryfilter"; @@ -615,6 +616,7 @@ public class ApiConstants { public static final String EXCLUSIVE_GSLB_PROVIDER = "isexclusivegslbprovider"; public static final String GSLB_PROVIDER_PUBLIC_IP = "gslbproviderpublicip"; public static final String GSLB_PROVIDER_PRIVATE_IP = "gslbproviderprivateip"; + public static final String VM_BACKUP_ID = "vmbackupid"; public static final String VM_SNAPSHOT_DESCRIPTION = "description"; public static final String VM_SNAPSHOT_DISPLAYNAME = "name"; public static final String VM_SNAPSHOT_ID = "vmsnapshotid"; 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 42f4a21639e8..d36dea37f7ee 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java @@ -17,17 +17,16 @@ package org.apache.cloudstack.api; +import java.util.ArrayList; +import java.util.List; + import org.apache.cloudstack.api.response.BackupPolicyResponse; -import org.apache.cloudstack.api.response.BackupPolicyVMMapResponse; -import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.ListResponse; -import org.apache.cloudstack.backup.BackupPolicyVMMap; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.api.response.VMBackupResponse; +import org.apache.cloudstack.api.response.VMBackupRestorePointResponse; import org.apache.cloudstack.backup.BackupPolicy; - -import java.util.ArrayList; -import java.util.List; +import org.apache.cloudstack.backup.VMBackup; +import org.apache.cloudstack.context.CallContext; public abstract class BaseBackupListCmd extends BaseListCmd { @@ -46,14 +45,14 @@ protected void setupResponseBackupPolicyList(final List policies) setResponseObject(response); } - protected void setupResponseBackupList(final List backups) { - final ListResponse response = new ListResponse<>(); - final List responses = new ArrayList<>(); - for (Backup backup : backups) { + protected void setupResponseBackupList(final List backups) { + final ListResponse response = new ListResponse<>(); + final List responses = new ArrayList<>(); + for (VMBackup backup : backups) { if (backup == null) { continue; } - BackupResponse backupResponse = _responseGenerator.createBackupResponse(backup); + VMBackupResponse backupResponse = _responseGenerator.createBackupResponse(backup); responses.add(backupResponse); } response.setResponses(responses); @@ -61,15 +60,19 @@ protected void setupResponseBackupList(final List backups) { setResponseObject(response); } - protected void setupResponseBackupPolicyVMMappings(final List mappings) { - final ListResponse response = new ListResponse<>(); - final List responses = new ArrayList<>(); - for (BackupPolicyVMMap map : mappings) { - if (map == null) { + protected void setupResponseRestorePointsList(final List restorePoints) { + final ListResponse response = new ListResponse<>(); + final List responses = new ArrayList<>(); + for (VMBackup.RestorePoint rp : restorePoints) { + if (rp == null) { continue; } - BackupPolicyVMMapResponse resp = _responseGenerator.createBackupPolicyVMMappingResponse(map); - responses.add(resp); + VMBackupRestorePointResponse rpResponse = new VMBackupRestorePointResponse(); + rpResponse.setId(rp.getId()); + rpResponse.setCreated(rp.getCreated()); + rpResponse.setType(rp.getType()); + rpResponse.setObjectName("vmbackuprestorepoint"); + responses.add(rpResponse); } response.setResponses(responses); response.setResponseName(getCommandName()); 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 db702e5713a9..64b9ed598a1f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -35,8 +35,6 @@ import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse; import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse; import org.apache.cloudstack.api.response.BackupPolicyResponse; -import org.apache.cloudstack.api.response.BackupPolicyVMMapResponse; -import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.CapacityResponse; import org.apache.cloudstack.api.response.ClusterResponse; import org.apache.cloudstack.api.response.ConditionResponse; @@ -111,6 +109,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.VMBackupResponse; import org.apache.cloudstack.api.response.VMSnapshotResponse; import org.apache.cloudstack.api.response.VirtualRouterProviderResponse; import org.apache.cloudstack.api.response.VlanIpRangeResponse; @@ -120,9 +119,8 @@ import org.apache.cloudstack.api.response.VpnUsersResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.backup.BackupPolicy; -import org.apache.cloudstack.backup.BackupPolicyVMMap; +import org.apache.cloudstack.backup.VMBackup; import org.apache.cloudstack.config.Configuration; -import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; import org.apache.cloudstack.region.PortableIp; import org.apache.cloudstack.region.PortableIpRange; @@ -469,9 +467,7 @@ List createTemplateResponses(ResponseView view, VirtualMachine SSHKeyPairResponse createSSHKeyPairResponse(SSHKeyPair sshkeyPair, boolean privatekey); - BackupResponse createBackupResponse(Backup backup); + VMBackupResponse createBackupResponse(VMBackup backup); BackupPolicyResponse createBackupPolicyResponse(BackupPolicy policy); - - BackupPolicyVMMapResponse createBackupPolicyVMMappingResponse(BackupPolicyVMMap map); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupPolicyCmd.java index c94b9e20c269..a9cb41f61466 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupPolicyCmd.java @@ -127,7 +127,7 @@ public long getEntityOwnerId() { @Override public String getEventType() { - return EventTypes.EVENT_IMPORT_BACKUP_POLICY; + return EventTypes.EVENT_VM_BACKUP_IMPORT_POLICY; } @Override 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..9f5604d64f5b --- /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 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; +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 javax.inject.Inject; + +@APICommand(name = ImportVMCmdByAdmin.APINAME, + description = "Import VM", + responseObject = SuccessResponse.class, + since = "4.12.0", + authorized = {RoleType.Admin}) +public class ImportVMCmdByAdmin extends BaseAsyncCmd { + public static final String APINAME = "importVM"; + + @Inject + BackupManager backupManager; + + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + required = true, + description = "VM Internal name") + 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/CreateVMBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateVMBackupCmd.java index 94a821abded6..494943eb9c9f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateVMBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateVMBackupCmd.java @@ -19,7 +19,6 @@ import javax.inject.Inject; -import com.cloud.event.EventTypes; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -28,12 +27,14 @@ 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.BackupPolicyResponse; +import org.apache.cloudstack.api.response.VMBackupResponse; import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.VMBackup; 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; @@ -43,7 +44,7 @@ @APICommand(name = CreateVMBackupCmd.APINAME, description = "Create VM backup", - responseObject = BackupResponse.class, since = "4.12.0", + responseObject = VMBackupResponse.class, since = "4.12.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class CreateVMBackupCmd extends BaseAsyncCmd { public static final String APINAME = "createVMBackup"; @@ -55,6 +56,17 @@ public class CreateVMBackupCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + required = true, + description = "Name of the VM Backup") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, + type = CommandType.STRING, + description = "The description of the snapshot") + private String description; + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, @@ -62,16 +74,34 @@ public class CreateVMBackupCmd extends BaseAsyncCmd { description = "id of the VM") private Long vmId; - //FIXME: add name, description etc.? + @Parameter(name = ApiConstants.POLICY_ID, + type = CommandType.UUID, + entityType = BackupPolicyResponse.class, + required = true, + description = "id of the backup policy") + private Long policyId; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + public Long getVmId() { return vmId; } + public Long getPolicyId() { + return policyId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -79,9 +109,9 @@ public Long getVmId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - Backup backup = backupManager.createBackup(vmId); + VMBackup backup = backupManager.createBackup(name, description, vmId, policyId); if (backup != null) { - BackupResponse response = _responseGenerator.createBackupResponse(backup); + VMBackupResponse response = _responseGenerator.createBackupResponse(backup); response.setResponseName(getCommandName()); setResponseObject(response); } else { @@ -104,7 +134,7 @@ public long getEntityOwnerId() { @Override public String getEventType() { - return EventTypes.EVENT_CREATE_VM_BACKUP; + return EventTypes.EVENT_VM_BACKUP_CREATE; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java index fde8a9f7e6a1..778353bbf7b3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java @@ -26,7 +26,7 @@ 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.VMBackupResponse; import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -54,9 +54,9 @@ public class DeleteVMBackupCmd extends BaseCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, - entityType = BackupResponse.class, + entityType = VMBackupResponse.class, required = true, - description = "id of backup") + description = "id of the VM backup") private Long backupId; ///////////////////////////////////////////////////// @@ -75,7 +75,7 @@ public Long getId() { public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { boolean result = backupManager.deleteBackup(backupId); - // FIXME: the response type + // 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/ListBackupPolicyVMMappingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupRestorePoints.java similarity index 59% rename from api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupPolicyVMMappingsCmd.java rename to api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupRestorePoints.java index 00ed936c1d5e..44aa57f190e2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupPolicyVMMappingsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupRestorePoints.java @@ -14,12 +14,14 @@ // 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; +package org.apache.cloudstack.api.command.user.backup; +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 org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -28,25 +30,19 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.BackupPolicyResponse; -import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.api.response.VMBackupResponse; import org.apache.cloudstack.backup.BackupManager; -import org.apache.cloudstack.backup.BackupPolicyVMMap; +import org.apache.cloudstack.backup.VMBackup; -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; +import javax.inject.Inject; +import java.util.List; -@APICommand(name = ListBackupPolicyVMMappingsCmd.APINAME, - description = "Lists VMs mapped to a backup policy", - responseObject = BackupPolicyResponse.class, since = "4.12.0", +@APICommand(name = ListVMBackupRestorePoints.APINAME, + description = "Lists VM backup restore points", + responseObject = VMBackupResponse.class, since = "4.12.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class ListBackupPolicyVMMappingsCmd extends BaseBackupListCmd { - public static final String APINAME = "listBackupPolicyVMMappings"; +public class ListVMBackupRestorePoints extends BaseBackupListCmd { + public static final String APINAME = "listVMBackupRestorePoints"; @Inject private BackupManager backupManager; @@ -55,32 +51,19 @@ public class ListBackupPolicyVMMappingsCmd extends BaseBackupListCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, - description = "The id of the VM") - private Long vmId; - - @Parameter(name = ApiConstants.POLICY_ID, type = BaseCmd.CommandType.UUID, entityType = BackupPolicyResponse.class, - description = "The backup policy ID") - private Long policyId; - - @Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class, - description = "The zone ID") - private Long zoneId; + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = VMBackupResponse.class, + required = true, + description = "id of the VM backup") + private Long backupId; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public Long getVmId() { - return vmId; - } - - public Long getPolicyId() { - return policyId; - } - - public Long getZoneId() { - return zoneId; + public Long getBackupId() { + return backupId; } ///////////////////////////////////////////////////// @@ -90,21 +73,15 @@ public Long getZoneId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - List mappings = backupManager.listBackupPolicyVMMappings(getVmId(), getZoneId(), getPolicyId()); - setupResponseBackupPolicyVMMappings(mappings); - } catch (CloudRuntimeException e) { + List restorePoints = backupManager.listVMBackupRestorePoints(backupId); + setupResponseRestorePointsList(restorePoints); + } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } } - private void validateParameters() { - if (zoneId == null && policyId == null) { - throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please provide a zone id or a policy id"); - } - } - @Override public String getCommandName() { - return APINAME.toLowerCase() + RESPONSE_SUFFIX; + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupsCmd.java index a9b183fd438f..d93e1bdb37e8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupsCmd.java @@ -29,9 +29,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.VMBackupResponse; import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.VMBackup; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -43,7 +43,7 @@ @APICommand(name = ListVMBackupsCmd.APINAME, description = "Lists VM backups", - responseObject = BackupResponse.class, since = "4.12.0", + responseObject = VMBackupResponse.class, since = "4.12.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class ListVMBackupsCmd extends BaseBackupListCmd { public static final String APINAME = "listVMBackups"; @@ -76,7 +76,7 @@ public Long getVmId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try{ - List backups = backupManager.listVMBackups(getVmId()); + List backups = backupManager.listVMBackups(getVmId()); 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/RemoveVMFromBackupPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVMFromBackupPolicyCmd.java deleted file mode 100644 index e28e6708792a..000000000000 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVMFromBackupPolicyCmd.java +++ /dev/null @@ -1,121 +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 com.cloud.event.EventTypes; -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.Parameter; -import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.BackupPolicyResponse; -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; - -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 = RemoveVMFromBackupPolicyCmd.APINAME, - description = "Removes a VM from an existing backup policy", - responseObject = SuccessResponse.class, since = "4.12.0", - authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class RemoveVMFromBackupPolicyCmd extends BaseAsyncCmd { - public static final String APINAME = "removeVMFromBackupPolicy"; - - @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 to be removed from the backup policy") - private Long vmId; - - @Parameter(name = ApiConstants.POLICY_ID, - type = CommandType.UUID, - entityType = BackupPolicyResponse.class, - required = true, - description = "id of the backup policy") - 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 { - boolean result = backupManager.removeVMFromBackupPolicy(getPolicyId(), getVmId()); - if (result) { - SuccessResponse response = new SuccessResponse(getCommandName()); - response.setResponseName(getCommandName()); - setResponseObject(response); - } else { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to remove VM from backup policy"); - } - } catch (Exception 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_REMOVE_VM_FROM_BACKUP_POLICY; - } - - @Override - public String getEventDescription() { - return "Removing VM " + vmId + " from backup policy " + policyId; - } -} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java index 019b535d4e80..daad3fb1257f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java @@ -28,7 +28,7 @@ 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.VMBackupResponse; import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -56,11 +56,17 @@ public class RestoreVMFromBackupCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, - entityType = BackupResponse.class, + entityType = VMBackupResponse.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 /////////////////////// ///////////////////////////////////////////////////// @@ -69,6 +75,10 @@ public Long getBackupId() { return backupId; } + public String getRestorePointId() { + return restorePointId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -76,7 +86,7 @@ public Long getBackupId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.restoreVMFromBackup(backupId); + boolean result = backupManager.restoreVMFromBackup(backupId, restorePointId); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); @@ -101,11 +111,11 @@ public long getEntityOwnerId() { @Override public String getEventType() { - return EventTypes.EVENT_RESTORE_VM_FROM_BACKUP; + return EventTypes.EVENT_VM_BACKUP_RESTORE; } @Override public String getEventDescription() { - return "Restoring VM from backup " + backupId; + 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 index 1567e7f9ae11..7058a9015eb6 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 @@ -28,10 +28,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.VMBackupResponse; import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -56,23 +55,25 @@ public class RestoreVolumeFromBackupAndAttachToVMCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - //FIXME: discuss on simplification - @Parameter(name = ApiConstants.ID, + @Parameter(name = ApiConstants.VM_BACKUP_ID, type = CommandType.UUID, - entityType = BackupResponse.class, + entityType = VMBackupResponse.class, required = true, description = "id of the backup") private Long backupId; - //FIXME: is this necessary when backup id is known? unless we want to restore to a different volume? + @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.UUID, - entityType = VolumeResponse.class, + type = CommandType.STRING, required = true, - description = "id of the volume to restore and to be attached to the vm") - private Long volumeId; + description = "id of the volume backed up") + private String volumeUuid; - //FIXME: is this necessary when backup id is known? unless we want to restore to a different VM? @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, @@ -84,8 +85,8 @@ public class RestoreVolumeFromBackupAndAttachToVMCmd extends BaseAsyncCmd { /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public Long getVolumeId() { - return volumeId; + public String getVolumeUuid() { + return volumeUuid; } public Long getVmId() { @@ -96,6 +97,10 @@ public Long getBackupId() { return backupId; } + public String getRestorePointId() { + return restorePointId; + } + @Override public String getCommandName() { return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; @@ -113,7 +118,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.restoreBackupVolumeAndAttachToVM(volumeId, vmId, backupId); + boolean result = backupManager.restoreBackupVolumeAndAttachToVM(volumeUuid, vmId, backupId, restorePointId); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); @@ -128,11 +133,11 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE @Override public String getEventType() { - return EventTypes.EVENT_RESTORE_VOLUME_FROM_BACKUP_AND_ATTACH_TO_VM; + return EventTypes.EVENT_VM_BACKUP_RESTORE_VOLUME_TO_VM; } @Override public String getEventDescription() { - return "Restoring volume "+ volumeId + " from backup " + backupId + " and attaching it to VM " + vmId; + return "Restoring volume "+ volumeUuid + " from backup " + backupId + " and attaching it to VM " + vmId; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AddVMToBackupPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/StartVMBackupCmd.java similarity index 73% rename from api/src/main/java/org/apache/cloudstack/api/command/user/backup/AddVMToBackupPolicyCmd.java rename to api/src/main/java/org/apache/cloudstack/api/command/user/backup/StartVMBackupCmd.java index 3074188314a8..1518a5f15b9a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AddVMToBackupPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/StartVMBackupCmd.java @@ -18,7 +18,6 @@ import javax.inject.Inject; -import com.cloud.event.EventTypes; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -26,24 +25,24 @@ import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.BackupPolicyResponse; import org.apache.cloudstack.api.response.SuccessResponse; -import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.VMBackupResponse; 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 = AddVMToBackupPolicyCmd.APINAME, - description = "Assigns a VM to an existing backup policy", +@APICommand(name = StartVMBackupCmd.APINAME, + description = "Starts backup of a previously registered VM VMBackup", responseObject = SuccessResponse.class, since = "4.12.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class AddVMToBackupPolicyCmd extends BaseAsyncCmd { - public static final String APINAME = "addVMToBackupPolicy"; +public class StartVMBackupCmd extends BaseAsyncCmd { + public static final String APINAME = "startVMBackup"; @Inject private BackupManager backupManager; @@ -52,30 +51,19 @@ public class AddVMToBackupPolicyCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, - entityType = UserVmResponse.class, + entityType = VMBackupResponse.class, required = true, - description = "id of the VM to be assigned to the backup policy") - private Long vmId; - - @Parameter(name = ApiConstants.POLICY_ID, - type = CommandType.UUID, - entityType = BackupPolicyResponse.class, - required = true, - description = "id of the backup policy") - private Long policyId; + description = "id of the VM backup for which ad-hoc backup needs to be started") + private Long backupId; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public Long getVmId() { - return vmId; - } - - public Long getPolicyId() { - return policyId; + public Long getBackupId() { + return backupId; } ///////////////////////////////////////////////////// @@ -85,13 +73,13 @@ public Long getPolicyId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.addVMToBackupPolicy(getPolicyId(), getVmId()); + boolean result = backupManager.startVMBackup(getBackupId()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); setResponseObject(response); } else { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to assign VM to backup policy"); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to start VM backup"); } } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); @@ -100,7 +88,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE @Override public String getCommandName() { - return AddVMToBackupPolicyCmd.APINAME.toLowerCase() + RESPONSE_SUFFIX; + return StartVMBackupCmd.APINAME.toLowerCase() + RESPONSE_SUFFIX; } @Override @@ -110,11 +98,11 @@ public long getEntityOwnerId() { @Override public String getEventType() { - return EventTypes.EVENT_ADD_VM_TO_BACKUP_POLICY; + return EventTypes.EVENT_VM_BACKUP_START; } @Override public String getEventDescription() { - return "Adding VM " + vmId + " to backup policy " + policyId; + return "Starting VM backup for backup id " + backupId; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VMBackupResponse.java similarity index 75% rename from api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java rename to api/src/main/java/org/apache/cloudstack/api/response/VMBackupResponse.java index 2cc58af3ba6a..28b8ce49fd76 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VMBackupResponse.java @@ -17,18 +17,19 @@ package org.apache.cloudstack.api.response; -import com.cloud.serializer.Param; -import com.google.gson.annotations.SerializedName; +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 org.apache.cloudstack.backup.VMBackup; -import java.util.Date; -import java.util.List; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; -@EntityReference(value = Backup.class) -public class BackupResponse extends BaseResponse { +@EntityReference(value = VMBackup.class) +public class VMBackupResponse extends BaseResponse { @SerializedName(ApiConstants.ID) @Param(description = "internal id of the backup") @@ -54,10 +55,6 @@ public class BackupResponse extends BaseResponse { @Param(description = "backup description") private String description; - @SerializedName(ApiConstants.PARENT_ID) - @Param(description = "backup parent id") - private String parentId; - @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) @Param(description = "backup vm id") private String vmId; @@ -68,11 +65,19 @@ public class BackupResponse extends BaseResponse { @SerializedName(ApiConstants.STATUS) @Param(description = "backup volume ids") - private Backup.Status status; + private VMBackup.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.START_DATE) - @Param(description = "backup start date") - private Date startDate; + @SerializedName(ApiConstants.CREATED) + @Param(description = "backup creation date") + private Date createdDate; public String getId() { return id; @@ -122,14 +127,6 @@ public void setDescription(String description) { this.description = description; } - public String getParentId() { - return parentId; - } - - public void setParentId(String parentId) { - this.parentId = parentId; - } - public String getVmId() { return vmId; } @@ -138,14 +135,30 @@ public void setVmId(String vmId) { this.vmId = vmId; } - public Backup.Status getStatus() { + public VMBackup.Status getStatus() { return status; } - public void setStatus(Backup.Status status) { + public void setStatus(VMBackup.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 List getVolumeIds() { return volumeIds; } @@ -154,11 +167,11 @@ public void setVolumeIds(List volumeIds) { this.volumeIds = volumeIds; } - public Date getStartDate() { - return startDate; + public Date getCreatedDate() { + return createdDate; } - public void setStartDate(Date startDate) { - this.startDate = startDate; + public void setCreatedDate(Date createdDate) { + this.createdDate = createdDate; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupPolicyVMMapResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VMBackupRestorePointResponse.java similarity index 54% rename from api/src/main/java/org/apache/cloudstack/api/response/BackupPolicyVMMapResponse.java rename to api/src/main/java/org/apache/cloudstack/api/response/VMBackupRestorePointResponse.java index 855ae51fd522..c9d07e74faea 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupPolicyVMMapResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VMBackupRestorePointResponse.java @@ -17,50 +17,49 @@ package org.apache.cloudstack.api.response; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; -import org.apache.cloudstack.backup.BackupPolicyVMMap; - -import com.cloud.serializer.Param; -import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.backup.VMBackup; -@EntityReference(value = BackupPolicyVMMap.class) -public class BackupPolicyVMMapResponse extends BaseResponse { +@EntityReference(value = VMBackup.RestorePoint.class) +public class VMBackupRestorePointResponse extends BaseResponse { - @SerializedName(ApiConstants.ZONE_ID) - @Param(description = "zone id") - private String zoneId; + @SerializedName(ApiConstants.ID) + @Param(description = "external id of the restore point") + private String id; - @SerializedName(ApiConstants.POLICY_ID) - @Param(description = "backup policy id") - private String backupPolicyId; + @SerializedName(ApiConstants.CREATED) + @Param(description = "created time") + private String created; - @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) - @Param(description = "virtual machine id") - private String vmId; + @SerializedName(ApiConstants.TYPE) + @Param(description = "restore point type") + private String type; - public String getZoneId() { - return zoneId; + public String getId() { + return id; } - public void setZoneId(String zoneId) { - this.zoneId = zoneId; + public void setId(String id) { + this.id = id; } - public String getBackupPolicyId() { - return backupPolicyId; + public String getCreated() { + return created; } - public void setBackupPolicyId(String backupPolicyId) { - this.backupPolicyId = backupPolicyId; + public void setCreated(String created) { + this.created = created; } - public String getVmId() { - return vmId; + public String getType() { + return type; } - public void setVmId(String vmId) { - this.vmId = vmId; + 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 deleted file mode 100644 index 79d462688e59..000000000000 --- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java +++ /dev/null @@ -1,43 +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 -//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; - -import java.util.Date; -import java.util.List; - -public interface Backup extends InternalIdentity, Identity { - - enum Status { - BackingUp, BackedUp, Failed, Queued, Restoring - } - - Long getZoneId(); - Long getAccountId(); - String getExternalId(); - String getName(); - String getDescription(); - Long getParentId(); - Long getVmId(); - List getVolumeIds(); - Status getStatus(); - Date getStartTime(); - 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 index c25ef18cf364..a15979646450 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 com.cloud.hypervisor.Hypervisor; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; @@ -40,6 +41,10 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer "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 policy to a name and description * @param zoneId zone id @@ -47,61 +52,57 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer * @param policyName internal name for the backup policy * @param policyDescription internal description for the backup policy */ - BackupPolicy importBackupPolicy(Long zoneId, String policyExternalId, String policyName, String policyDescription); + BackupPolicy importBackupPolicy(final Long zoneId, final String policyExternalId, + final String policyName, final String policyDescription); /** - * Assign VM to existing backup policy - */ - boolean addVMToBackupPolicy(Long policyId, Long virtualMachineId); - - /** - * Remove a VM from a backup policy + * List backup policies + * @param zoneId zone id + * @param external if true, only external backup policies are listed + * @param policyId if not null, only the policy with this id is listed */ - boolean removeVMFromBackupPolicy(Long policyId, Long vmId); + List listBackupPolicies(final Long zoneId, final Boolean external, final Long policyId); /** - * Return mappings between backup policy and VMs + * Deletes a backup policy */ - List listBackupPolicyVMMappings(Long vmId, Long zoneId, Long policyId); + boolean deleteBackupPolicy(final Long policyId); /** * List existing backups for a VM */ - List listVMBackups(Long vmId); - - /** - * List backup policies - * @param zoneId zone id - * @param external if true, only external backup policies are listed - * @param policyId if not null, only the policy with this id is listed - */ - List listBackupPolicies(Long zoneId, Boolean external, Long policyId); + List listVMBackups(final Long vmId); /** * Creates backup of a VM * @param vmId Virtual Machine ID * @return returns operation success */ - Backup createBackup(Long vmId); + VMBackup createBackup(final String name, final String description, final Long vmId, final Long policyId); + + /** + * Assign VM to existing backup policy + */ + boolean startVMBackup(final Long vmBackupId); /** * Deletes a backup * @return returns operation success */ - boolean deleteBackup(Long backupId); + boolean deleteBackup(final Long backupId); /** * Restore a full VM from backup */ - boolean restoreVMFromBackup(Long backupId); + boolean restoreVMFromBackup(final Long backupId, final String restorePointId); /** * Restore a backed up volume and attach it to a VM */ - boolean restoreBackupVolumeAndAttachToVM(Long volumeId, Long vmId, Long backupId); + boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long vmId, final Long backupId, final String restorePointId) throws Exception; - /** - * Deletes a backup policy - */ - boolean deleteBackupPolicy(Long policyId); + boolean importVM(long zoneId, long domainId, long accountId, long userId, + String vmInternalName, Hypervisor.HypervisorType hypervisorType, VMBackup backup); + + List listVMBackupRestorePoints(final Long backupId); } diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMap.java b/api/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMap.java deleted file mode 100644 index f71832e482cd..000000000000 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMap.java +++ /dev/null @@ -1,27 +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 -//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.InternalIdentity; - -public interface BackupPolicyVMMap extends InternalIdentity { - - long getPolicyId(); - long getVmId(); - 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 index 4e857ae0c66e..69e7cf90aa70 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -17,8 +17,9 @@ package org.apache.cloudstack.backup; import java.util.List; +import java.util.Map; -import com.cloud.agent.api.to.VolumeTO; +import com.cloud.utils.Pair; import com.cloud.vm.VirtualMachine; public interface BackupProvider { @@ -47,41 +48,46 @@ public interface BackupProvider { boolean isBackupPolicy(Long zoneId, String uuid); /** - * Assign VM to backup policy - * @return true if VM is successfully assigned, false if not + * Creates backup of a VM assigned to a policy + * @param policy + * @param vm + * @return true if backup successfully starts */ - boolean addVMToBackupPolicy(BackupPolicy policy, VirtualMachine vm); + VMBackup createVMBackup(BackupPolicy policy, VirtualMachine vm, VMBackup backup); /** - * Remove a VM form a backup policy + * Removes a VM backup + * @param vm + * @param backup + * @return */ - boolean removeVMFromBackupPolicy(BackupPolicy policy, VirtualMachine vm); + boolean removeVMBackup(VirtualMachine vm, VMBackup backup); /** - * Starts ad-hoc backup of a VM assigned to a policy - * @param policy - * @param vm - * @return true if backup successfully starts + * Starts and creates an adhoc backup process + * for a previously registered VM backup + * @param vmBackup + * @return */ - Backup createVMBackup(BackupPolicy policy, VirtualMachine vm); + boolean startBackup(VMBackup vmBackup); /** * Restore VM from backup */ - boolean restoreVMFromBackup(String vmUuid, String backupUuid); + boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String restorePointId); /** * Restore a volume from a backup */ - VolumeTO restoreVolumeFromBackup(String volumeUuid, String backupUuid); + Pair restoreBackedUpVolume(long zoneId, String backupUuid, String restorePointId, String volumeUuid, + String hostIp, String dataStoreUuid); /** * List VM Backups */ - List listVMBackups(Long zoneId, VirtualMachine vm); + List listVMBackups(Long zoneId, VirtualMachine vm); - /** - * Remove a VM backup - */ - boolean removeVMBackup(VirtualMachine vm, String backupId); + Map getBackupMetrics(Long zoneId, List backupList); + + List listVMBackupRestorePoints(String backupUuid, VirtualMachine vm); } diff --git a/api/src/main/java/org/apache/cloudstack/backup/VMBackup.java b/api/src/main/java/org/apache/cloudstack/backup/VMBackup.java new file mode 100644 index 000000000000..16a69bc287d5 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/VMBackup.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 +//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 com.cloud.storage.Volume; +import com.cloud.utils.StringUtils; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface VMBackup 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 String getPath() { + return path; + } + + public Long getSize() { + return size; + } + + @Override + public String toString() { + return StringUtils.join(":", uuid, path, type, size); + } + } + + Long getZoneId(); + Long getAccountId(); + String getExternalId(); + String getName(); + String getDescription(); + Long getVmId(); + List getBackedUpVolumes(); + Status getStatus(); + Long getSize(); + Long getProtectedSize(); + Date getCreated(); + Date getRemoved(); +} 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 f03d9a9f7333..44f78dd1d34d 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 VM_BACKUP = 28; public static List listUsageTypes() { List responseList = new ArrayList(); @@ -67,6 +68,7 @@ public static List listUsageTypes() { responseList.add(new UsageTypeResponse(VM_DISK_BYTES_WRITE, "VM Disk usage(Bytes Write)")); responseList.add(new UsageTypeResponse(VM_SNAPSHOT, "VM Snapshot storage usage")); responseList.add(new UsageTypeResponse(VM_SNAPSHOT_ON_PRIMARY, "VM Snapshot on primary storage usage")); + responseList.add(new UsageTypeResponse(VM_BACKUP, "VM Backup storage usage")); return responseList; } } diff --git a/client/pom.xml b/client/pom.xml index 8b04386a65db..0613bdc888f1 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -484,11 +484,6 @@ cloud-plugin-backup-dummy ${project.version} - - org.apache.cloudstack - cloud-plugin-backup-veeam - ${project.version} - @@ -1157,25 +1152,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 @@ -1184,7 +1174,7 @@ org.apache.cloudstack - cloud-plugin-api-vmware-sioc + cloud-plugin-database-mysqlha ${project.version} 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 1e33b6ac535f..67817d82b7ae 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 aae61a120943..d395af43c561 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 @@ -57,4 +57,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 f54c55863ca9..35ba06406898 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 @@ -54,6 +54,7 @@ public class ServiceOfferingDaoImpl extends GenericDaoBase SystemServiceOffering; protected final SearchBuilder ServiceOfferingsByKeywordSearch; protected final SearchBuilder PublicServiceOfferingSearch; + protected final SearchBuilder PublicCpuRamSearch; public ServiceOfferingDaoImpl() { super(); @@ -84,6 +85,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().getSystemUse(), SearchCriteria.Op.EQ); + PublicCpuRamSearch.done(); } @Override @@ -289,4 +296,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 0a348f55ae87..4e49d4187436 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 { @@ -32,4 +33,6 @@ public interface DiskOfferingDao extends GenericDao { DiskOfferingVO persistDeafultDiskOffering(DiskOfferingVO offering); + List listAllBySizeAndProvisioningType(long size, Storage.ProvisioningType provisioningType, long domainId); + } 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 41993b6b903b..a69ee14fc498 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,11 +16,18 @@ // 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; import javax.persistence.EntityExistsException; +import com.cloud.storage.Storage; +import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.exception.CloudRuntimeException; import org.springframework.stereotype.Component; import com.cloud.storage.DiskOfferingVO; @@ -38,7 +45,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 (domain_id IS NULL OR domain_id = ?) AND removed IS NULL"; + private final Attribute _typeAttr; + protected final static long GB_UNIT_BYTES = 1024 * 1024 * 1024; protected DiskOfferingDaoImpl() { DomainIdSearch = createSearchBuilder(); @@ -138,6 +149,39 @@ 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, long domainId) { + 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()); + pstmt.setLong(3, domainId); + 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 520416e34e7f..5b4108455ef2 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 @@ -46,4 +46,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 d2c4a99e6aeb..81cbc9de07a6 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 @@ -56,6 +56,7 @@ public class VMTemplatePoolDaoImpl extends GenericDaoBase TemplatePoolStatusSearch; protected final SearchBuilder TemplateStatesSearch; 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 = ?"; @@ -106,6 +107,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 @@ -244,6 +251,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/usage/UsageVMBackupVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageVMBackupVO.java new file mode 100644 index 000000000000..3bee1c5a190c --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/usage/UsageVMBackupVO.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_vm_backup") +public class UsageVMBackupVO 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 UsageVMBackupVO() { + } + + public UsageVMBackupVO(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 UsageVMBackupVO(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/org/apache/cloudstack/backup/dao/BackupDao.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMBackupDao.java similarity index 64% rename from engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java rename to engine/schema/src/main/java/com/cloud/usage/dao/UsageVMBackupDao.java index 9bb8ea117723..5b01e57ae72e 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMBackupDao.java @@ -15,20 +15,18 @@ // specific language governing permissions and limitations // under the License. -package org.apache.cloudstack.backup.dao; - -import com.cloud.utils.db.GenericDao; -import org.apache.cloudstack.api.response.BackupResponse; -import org.apache.cloudstack.backup.BackupVO; -import org.apache.cloudstack.backup.Backup; +package com.cloud.usage.dao; +import java.util.Date; import java.util.List; -public interface BackupDao extends GenericDao { +import org.apache.cloudstack.backup.VMBackup; - List listByVmId(Long zoneId, Long vmId); - List syncVMBackups(Long zoneId, Long vmId, List externalBackups); +import com.cloud.usage.UsageVMBackupVO; +import com.cloud.utils.db.GenericDao; - BackupResponse newBackupResponse(Backup backup); - BackupVO getBackupVO(Backup backup); +public interface UsageVMBackupDao extends GenericDao { + void updateMetrics(VMBackup 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/UsageVMBackupDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMBackupDaoImpl.java new file mode 100644 index 000000000000..a1ab1c289541 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMBackupDaoImpl.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 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.VMBackup; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.usage.UsageVMBackupVO; +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 UsageVMBackupDaoImpl extends GenericDaoBase implements UsageVMBackupDao { + public static final Logger LOGGER = Logger.getLogger(UsageVMBackupDaoImpl.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_vm_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 VMBackup backup) { + boolean result = Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public Boolean doInTransaction(final TransactionStatus status) { + final QueryBuilder qb = QueryBuilder.create(UsageVMBackupVO.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 UsageVMBackupVO entry = findOneBy(qb.create()); + 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(UsageVMBackupVO.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 UsageVMBackupVO 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 UsageVMBackupVO(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 0d86964974a5..6698e8b68729 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 @@ -82,4 +82,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 f953a4c1f93e..805cb84f6c35 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 @@ -348,4 +348,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/BackupPolicyTO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyTO.java deleted file mode 100644 index 6c71655f7922..000000000000 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyTO.java +++ /dev/null @@ -1,73 +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; - -public class BackupPolicyTO implements BackupPolicy { - - private String uuid; - private long id; - private String name; - private String description; - private String externalId; - private long zoneId; - - public BackupPolicyTO() { - } - - public BackupPolicyTO(final String externalId, final String name, final String description) { - this.name = name; - this.description = description; - this.externalId = externalId; - } - - @Override - public String getExternalId() { - return externalId; - } - - public String getName() { - return name; - } - - @Override - public String getDescription() { - return description; - } - - @Override - public String getUuid() { - return uuid; - } - - @Override - public long getId() { - return id; - } - - @Override - public boolean isImported() { - return false; - } - - @Override - public long getZoneId() { - return zoneId; - } -} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMapVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMapVO.java deleted file mode 100644 index c88fda52891a..000000000000 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVMMapVO.java +++ /dev/null @@ -1,87 +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; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; - -@Entity -@Table(name = "backup_policy_vm_map") -public class BackupPolicyVMMapVO implements BackupPolicyVMMap { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private long id; - - @Column(name = "policy_id") - private long policyId; - - @Column(name = "vm_id") - private long vmId; - - @Column(name = "zone_id") - private long zoneId; - - public BackupPolicyVMMapVO() { - } - - public BackupPolicyVMMapVO(long zoneId, long policyId, long vmId) { - this.policyId = policyId; - this.vmId = vmId; - this.zoneId = zoneId; - } - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public long getPolicyId() { - return policyId; - } - - public void setPolicyId(long policyId) { - this.policyId = policyId; - } - - public long getVmId() { - return vmId; - } - - public void setVmId(long vmId) { - this.vmId = vmId; - } - - public long getZoneId() { - return zoneId; - } - - public void setZoneId(long zoneId) { - this.zoneId = zoneId; - } -} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVO.java index a1f8d4058e1f..245f1693cbc0 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVO.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.backup; +import java.util.Date; import java.util.UUID; import javax.persistence.Column; @@ -25,6 +26,8 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; @Entity @Table(name = "backup_policy") @@ -49,6 +52,14 @@ public class BackupPolicyVO implements BackupPolicy { @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 BackupPolicyVO() { this.uuid = UUID.randomUUID().toString(); } @@ -59,8 +70,15 @@ public BackupPolicyVO(final long zoneId, final String externalId, final String n this.name = name; this.description = description; this.externalId = externalId; + this.created = new Date(); } + public BackupPolicyVO(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; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupTO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupTO.java deleted file mode 100644 index 6b3c24cf6226..000000000000 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupTO.java +++ /dev/null @@ -1,176 +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; - -import java.util.Date; -import java.util.List; - -public class BackupTO implements Backup { - - private long id; - private String uuid; - private Long accountId; - private String name; - private String description; - private Long parentId; - private Long vmId; - private List volumeIds; - private Status status; - private Date startTime; - private Long zoneId; - private String externalId; - private String parentExternalId; - - public BackupTO() { - } - - public BackupTO(final Long zoneId, final Long accountId, final String externalId, final String name, final String description, - final String parentExternalId, final Long vmId, final List volumeIds, final Status status, final Date startTime) { - this.zoneId = zoneId; - this.accountId = accountId; - this.externalId = externalId; - this.name = name; - this.description = description; - this.parentExternalId = parentExternalId; - this.vmId = vmId; - this.volumeIds = volumeIds; - this.status = status; - this.startTime = startTime; - } - - @Override - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - @Override - public String getUuid() { - return uuid; - } - - public void setUuid(String uuid) { - this.uuid = uuid; - } - - @Override - public Long getAccountId() { - return accountId; - } - - public void setAccountId(Long accountId) { - this.accountId = accountId; - } - - @Override - public Long getZoneId() { - return zoneId; - } - - public void setZoneId(Long zoneId) { - this.zoneId = zoneId; - } - - public String getParentExternalId() { - return parentExternalId; - } - - public void setParentExternalId(String parentExternalId) { - this.parentExternalId = parentExternalId; - } - - @Override - - public String getExternalId() { - return externalId; - } - - public void setExternalId(String externalId) { - this.externalId = externalId; - } - - @Override - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Override - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - @Override - public Long getParentId() { - return parentId; - } - - public void setParentId(Long parentId) { - this.parentId = parentId; - } - - public Long getVmId() { - return vmId; - } - - public void setVmId(Long vmId) { - this.vmId = vmId; - } - - @Override - public List getVolumeIds() { - return volumeIds; - } - - public void setVolumeIds(List volumeIds) { - this.volumeIds = volumeIds; - } - - @Override - public Status getStatus() { - return status; - } - - public void setStatus(Status status) { - this.status = status; - } - - @Override - public Date getStartTime() { - return startTime; - } - - @Override - public Date getRemoved() { - return null; - } - - public void setStartTime(Date startTime) { - this.startTime = startTime; - } -} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/VMBackupVO.java similarity index 63% rename from engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java rename to engine/schema/src/main/java/org/apache/cloudstack/backup/VMBackupVO.java index 9a6f04cef38e..c42ff799ca56 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/VMBackupVO.java @@ -17,8 +17,11 @@ package org.apache.cloudstack.backup; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang.StringUtils; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.StringJoiner; +import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; @@ -29,21 +32,14 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Transient; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.StringJoiner; -import java.util.UUID; -@Entity -@Table(name = "backup") -public class BackupVO implements Backup { - - public BackupVO() { - this.uuid = UUID.randomUUID().toString(); - volumeIds = new ArrayList<>(); - } +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import org.apache.commons.lang.StringUtils; +@Entity +@Table(name = "vm_backup") +public class VMBackupVO implements VMBackup { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") @@ -52,23 +48,17 @@ public BackupVO() { @Column(name = "uuid") private String uuid; - @Column(name = "account_id") - private long accountId; - - @Column(name = "zone_id") - private Long zoneId; - - @Column(name = "external_id") - private String externalId; - @Column(name = "name") private String name; @Column(name = "description") private String description; - @Column(name = "parent_id") - private Long parentId; + @Column(name = "external_id") + private String externalId; + + @Column(name = "policy_id") + private long policyId; @Column(name = "vm_id") private Long vmId; @@ -76,12 +66,24 @@ public BackupVO() { @Column(name = "volumes") private String volumes; + @Column(name = "size") + private Long size; + + @Column(name = "protected_size") + private Long protectedSize; + @Column(name = "status") private Status status; - @Column(name = "start") + @Column(name = "account_id") + private long accountId; + + @Column(name = "zone_id") + private Long zoneId; + + @Column(name = "created") @Temporal(value = TemporalType.TIMESTAMP) - private Date startTime; + private Date created; @Column(name = "removed") @Temporal(value = TemporalType.TIMESTAMP) @@ -90,6 +92,25 @@ public BackupVO() { @Transient private List volumeIds; + public VMBackupVO() { + this.uuid = UUID.randomUUID().toString(); + this.created = new Date(); + volumeIds = new ArrayList<>(); + } + + public VMBackupVO(final String name, final String description, final long policyId, final Long vmId, + final Status status, final long accountId, final Long zoneId) { + this.uuid = UUID.randomUUID().toString(); + this.name = name; + this.description = description; + this.policyId = policyId; + this.vmId = vmId; + this.status = status; + this.accountId = accountId; + this.zoneId = zoneId; + this.created = new Date(); + } + @Override public long getId() { return id; @@ -133,11 +154,6 @@ public String getDescription() { return description; } - @Override - public Long getParentId() { - return parentId; - } - @Override public Long getVmId() { return vmId; @@ -148,9 +164,17 @@ public Status getStatus() { return status; } + public Long getSize() { + return size; + } + + public Long getProtectedSize() { + return protectedSize; + } + @Override - public Date getStartTime() { - return startTime; + public Date getCreated() { + return created; } public void setId(long id) { @@ -173,10 +197,6 @@ public void setDescription(String description) { this.description = description; } - public void setParentId(Long parentId) { - this.parentId = parentId; - } - public void setVmId(Long vmId) { this.vmId = vmId; } @@ -185,47 +205,41 @@ public void setStatus(Status status) { this.status = status; } - public void setStartTime(Date start) { - this.startTime = start; + public void setSize(Long size) { + this.size = size; } - protected void convertVolumeStringToList() { - volumeIds = new ArrayList<>(); - if (StringUtils.isNotBlank(volumes)) { - String[] strIds = StringUtils.substringBetween(volumes,"[", "]").split(","); - for (String strId: strIds) { - if (StringUtils.isNotBlank(strId)) { - volumeIds.add(Long.valueOf(strId)); - } - } - } + public void setProtectedSize(Long protectedSize) { + this.protectedSize = protectedSize; } - @Override - public List getVolumeIds() { - convertVolumeStringToList(); - return volumeIds; + public void setCreated(Date start) { + this.created = start; } - public void setVolumeIds(List volumes) { - if (CollectionUtils.isEmpty(volumes)) { - volumeIds = new ArrayList<>(); - } else { - volumeIds = new ArrayList<>(volumes); + @Override + public List getBackedUpVolumes() { + List info = new ArrayList<>(); + if (StringUtils.isNotBlank(this.volumes)) { + String[] volumes = StringUtils.substringBetween(this.volumes,"[", "]").split(","); + for (String vol : volumes) { + String[] volParts = vol.split(":"); + VMBackup.VolumeInfo volumeInfo = new VolumeInfo(volParts[0], volParts[1], + Volume.Type.valueOf(volParts[2]), Long.valueOf(volParts[3])); + info.add(volumeInfo); + } } - convertVolumeIdsToString(); + return info; } - private void convertVolumeIdsToString() { + public void setBackedUpVolumes(List volumes) { StringJoiner stringJoiner = new StringJoiner(",", "[", "]"); - if (CollectionUtils.isNotEmpty(volumeIds)) { - for (Long volId : volumeIds) { - stringJoiner.add(String.valueOf(volId)); - } - volumes = stringJoiner.toString(); - } else { - volumes = null; + for (VolumeVO volume : volumes) { + String volTag = volume.getUuid() + ":" + volume.getPath() + ":" + + volume.getVolumeType().toString() + ":" + volume.getSize(); + stringJoiner.add(volTag); } + this.volumes = stringJoiner.toString(); } public Date getRemoved() { @@ -243,4 +257,12 @@ protected String getVolumes() { protected void setVolumes(String volumes) { this.volumes = volumes; } + + public long getPolicyId() { + return policyId; + } + + public void setPolicyId(long policyId) { + this.policyId = policyId; + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDao.java deleted file mode 100644 index 61b0fdf90430..000000000000 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDao.java +++ /dev/null @@ -1,37 +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.dao; - -import java.util.List; - -import org.apache.cloudstack.api.response.BackupPolicyVMMapResponse; -import org.apache.cloudstack.backup.BackupPolicyVMMap; -import org.apache.cloudstack.backup.BackupPolicyVMMapVO; - -import com.cloud.utils.db.GenericDao; - -public interface BackupPolicyVMMapDao extends GenericDao { - - BackupPolicyVMMapVO findByVMId(long vmId); - List listByPolicyId(long policyId); - List listByPolicyIdAndVMId(long policyId, long vmId); - List listByZoneId(Long zoneId); - BackupPolicyVMMapResponse newBackupPolicyVMMappingResponse(BackupPolicyVMMap map); -} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDaoImpl.java deleted file mode 100644 index a4b0a37dba57..000000000000 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyVMMapDaoImpl.java +++ /dev/null @@ -1,128 +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.dao; - -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; - -import org.apache.cloudstack.api.response.BackupPolicyVMMapResponse; -import org.apache.cloudstack.backup.BackupPolicyVMMap; -import org.apache.cloudstack.backup.BackupPolicyVMMapVO; -import org.apache.cloudstack.backup.BackupPolicyVO; -import org.apache.commons.collections.CollectionUtils; -import org.springframework.stereotype.Component; - -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; -import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.vm.VMInstanceVO; -import com.cloud.vm.dao.VMInstanceDao; - -@Component -public class BackupPolicyVMMapDaoImpl extends GenericDaoBase implements BackupPolicyVMMapDao { - - @Inject - private VMInstanceDao vmInstanceDao; - - @Inject - private BackupPolicyDao backupPolicyDao; - - @Inject - private DataCenterDao dataCenterDao; - - private SearchBuilder mapSearch; - - public BackupPolicyVMMapDaoImpl() { - } - - @PostConstruct - protected void init() { - mapSearch = createSearchBuilder(); - mapSearch.and("vm_id", mapSearch.entity().getVmId(), SearchCriteria.Op.EQ); - mapSearch.and("policy_id", mapSearch.entity().getPolicyId(), SearchCriteria.Op.EQ); - mapSearch.and("zone_id", mapSearch.entity().getZoneId(), SearchCriteria.Op.EQ); - mapSearch.done(); - } - - @Override - public BackupPolicyVMMapVO findByVMId(long vmId) { - SearchCriteria sc = mapSearch.create(); - sc.setParameters("vm_id", vmId); - List maps = listBy(sc); - if (CollectionUtils.isNotEmpty(maps)) { - if (maps.size() > 1) { - throw new CloudRuntimeException("Error: Vm " + vmId + " is assigned to multiple policies"); - } - return maps.get(0); - } - return null; - } - - @Override - public List listByPolicyId(long policyId) { - SearchCriteria sc = mapSearch.create(); - sc.setParameters("policy_id", policyId); - return listBy(sc); - } - - @Override - public List listByPolicyIdAndVMId(long policyId, long vmId) { - SearchCriteria sc = mapSearch.create(); - sc.setParameters("policy_id", policyId); - sc.setParameters("vm_id", vmId); - return listBy(sc); - } - - @Override - public List listByZoneId(Long zoneId) { - SearchCriteria sc = mapSearch.create(); - if (zoneId != null) { - sc.setParameters("zone_id", zoneId); - } - return listBy(sc); - } - - @Override - public BackupPolicyVMMapResponse newBackupPolicyVMMappingResponse(BackupPolicyVMMap map) { - BackupPolicyVO policy = backupPolicyDao.findById(map.getPolicyId()); - if (policy == null) { - throw new CloudRuntimeException("Policy " + map.getPolicyId() + " does not exist"); - } - VMInstanceVO vm = vmInstanceDao.findById(map.getVmId()); - if (vm == null) { - throw new CloudRuntimeException("VM " + map.getVmId() + " does not exist"); - } - DataCenterVO zone = dataCenterDao.findById(map.getZoneId()); - if (zone == null) { - throw new CloudRuntimeException("Zone " + map.getZoneId() + " does not exist"); - } - BackupPolicyVMMapResponse response = new BackupPolicyVMMapResponse(); - response.setBackupPolicyId(policy.getUuid()); - response.setVmId(vm.getUuid()); - response.setZoneId(zone.getUuid()); - response.setObjectName("backuppolicyvmmap"); - return response; - } -} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/VeeamObjectType.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDao.java similarity index 55% rename from plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/VeeamObjectType.java rename to engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDao.java index 82aea34ebf9b..c6b8cefa8d17 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/VeeamObjectType.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDao.java @@ -15,21 +15,22 @@ // specific language governing permissions and limitations // under the License. -package org.apache.cloudstack.backup.veeam.api; +package org.apache.cloudstack.backup.dao; -public enum VeeamObjectType { - HierarchyRoot, - HierarchyRootReference, +import java.util.List; - Job, - JobReference, +import org.apache.cloudstack.api.response.VMBackupResponse; +import org.apache.cloudstack.backup.VMBackup; +import org.apache.cloudstack.backup.VMBackupVO; - Backup, - BackupReference, - BackupJobSessionReferenceList, - BackupServerReference, - BackupFileReferenceList, +import com.cloud.utils.db.GenericDao; - RestorePointReferenceList, - RepositoryReference, +public interface VMBackupDao extends GenericDao { + + List listByVmId(Long zoneId, Long vmId); + List syncVMBackups(Long zoneId, Long vmId, List externalBackups); + List listByZoneAndState(Long zoneId, VMBackup.Status state); + + VMBackupResponse newBackupResponse(VMBackup backup); + VMBackupVO getBackupVO(VMBackup 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/VMBackupDaoImpl.java similarity index 65% rename from engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java rename to engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java index f10d2f9278ba..37b006ea3897 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/VMBackupDaoImpl.java @@ -17,9 +17,19 @@ 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.VMBackupResponse; +import org.apache.cloudstack.backup.VMBackup; +import org.apache.cloudstack.backup.VMBackupVO; +import org.apache.commons.collections.CollectionUtils; + import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; -import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.VolumeDao; import com.cloud.user.AccountVO; import com.cloud.user.dao.AccountDao; @@ -28,19 +38,8 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.VMInstanceDao; -import org.apache.cloudstack.api.response.BackupResponse; -import org.apache.cloudstack.backup.BackupTO; -import org.apache.cloudstack.backup.BackupVO; -import org.apache.cloudstack.backup.Backup; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang.StringUtils; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import java.util.ArrayList; -import java.util.List; -public class BackupDaoImpl extends GenericDaoBase implements BackupDao { +public class VMBackupDaoImpl extends GenericDaoBase implements VMBackupDao { @Inject AccountDao accountDao; @@ -54,9 +53,9 @@ public class BackupDaoImpl extends GenericDaoBase implements Bac @Inject VolumeDao volumeDao; - private SearchBuilder backupSearch; + private SearchBuilder backupSearch; - public BackupDaoImpl() { + public VMBackupDaoImpl() { } @PostConstruct @@ -65,88 +64,88 @@ protected void init() { backupSearch.and("vm_id", backupSearch.entity().getVmId(), 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("status", backupSearch.entity().getStatus(), SearchCriteria.Op.EQ); backupSearch.done(); } @Override - public List listByVmId(Long zoneId, Long vmId) { - SearchCriteria sc = backupSearch.create(); + 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)); } - private Backup findByExternalId(Long zoneId, String externalId) { - SearchCriteria sc = backupSearch.create(); + private VMBackup 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(); + public VMBackupVO getBackupVO(VMBackup backup) { + VMBackupVO backupVO = new VMBackupVO(); backupVO.setZoneId(backup.getZoneId()); backupVO.setAccountId(backup.getAccountId()); backupVO.setExternalId(backup.getExternalId()); backupVO.setName(backup.getName()); backupVO.setDescription(backup.getDescription()); - if (backup instanceof BackupTO) { - String parentExternalId = ((BackupTO) backup).getParentExternalId(); - if (StringUtils.isNotBlank(parentExternalId)) { - Backup parent = findByExternalId(backup.getZoneId(), parentExternalId); - backupVO.setParentId(parent.getId()); - } - } backupVO.setVmId(backup.getVmId()); - backupVO.setVolumeIds(backup.getVolumeIds()); backupVO.setStatus(backup.getStatus()); - backupVO.setStartTime(backup.getStartTime()); + backupVO.setCreated(backup.getCreated()); return backupVO; } public void removeExistingVMBackups(Long zoneId, Long vmId) { - SearchCriteria sc = backupSearch.create(); + SearchCriteria sc = backupSearch.create(); sc.setParameters("vm_id", vmId); sc.setParameters("zone_id", zoneId); expunge(sc); } @Override - public List syncVMBackups(Long zoneId, Long vmId, List externalBackups) { - for (Backup backup : externalBackups) { - BackupVO backupVO = getBackupVO(backup); + public List syncVMBackups(Long zoneId, Long vmId, List externalBackups) { + for (VMBackup backup : externalBackups) { + VMBackupVO backupVO = getBackupVO(backup); persist(backupVO); } return listByVmId(zoneId, vmId); } @Override - public BackupResponse newBackupResponse(Backup backup) { + public List listByZoneAndState(Long zoneId, VMBackup.Status state) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters("zone_id", zoneId); + if (state != null) { + sc.setParameters("status", state); + } + return new ArrayList<>(listIncludingRemovedBy(sc)); + } + + @Override + public VMBackupResponse newBackupResponse(VMBackup backup) { AccountVO account = accountDao.findById(backup.getAccountId()); - BackupVO parent = findById(backup.getParentId()); VMInstanceVO vm = vmInstanceDao.findById(backup.getVmId()); DataCenterVO zone = dataCenterDao.findById(backup.getZoneId()); - BackupResponse backupResponse = new BackupResponse(); + VMBackupResponse backupResponse = new VMBackupResponse(); backupResponse.setZoneId(zone.getUuid()); backupResponse.setId(backup.getUuid()); backupResponse.setAccountId(account.getUuid()); backupResponse.setExternalId(backup.getExternalId()); backupResponse.setName(backup.getName()); backupResponse.setDescription(backup.getDescription()); - if (parent != null) { - backupResponse.setParentId(parent.getUuid()); - } backupResponse.setVmId(vm.getUuid()); - if (CollectionUtils.isNotEmpty(backup.getVolumeIds())) { + if (CollectionUtils.isNotEmpty(backup.getBackedUpVolumes())) { List volIds = new ArrayList<>(); - for (Long volId : backup.getVolumeIds()) { - VolumeVO volume = volumeDao.findById(volId); - volIds.add(volume.getUuid()); + for (VMBackup.VolumeInfo volInfo : backup.getBackedUpVolumes()) { + volIds.add(volInfo.toString()); } backupResponse.setVolumeIds(volIds); } backupResponse.setStatus(backup.getStatus()); - backupResponse.setStartDate(backup.getStartTime()); + backupResponse.setSize(backup.getSize()); + backupResponse.setProtectedSize(backup.getProtectedSize()); + backupResponse.setCreatedDate(backup.getCreated()); backupResponse.setObjectName("backup"); return backupResponse; } 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 011075ae7c1a..3acdb07ccce7 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 @@ -291,6 +291,7 @@ + @@ -357,6 +358,5 @@ - - + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql b/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql index d01af4b85e04..15ab236517b5 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql @@ -45,39 +45,46 @@ CREATE TABLE IF NOT EXISTS `cloud`.`backup_policy` ( `description` varchar(255) NOT NULL COMMENT 'backup policy description', `external_id` varchar(80) NOT NULL COMMENT 'backup policy 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_policy__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -CREATE TABLE IF NOT EXISTS `cloud`.`backup_policy_vm_map` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `zone_id` bigint(20) unsigned NOT NULL, - `policy_id` bigint(20) unsigned NOT NULL, - `vm_id` bigint(20) unsigned NOT NULL, - PRIMARY KEY (`id`), - CONSTRAINT `fk_backup_policy_vm_map__policy_id` FOREIGN KEY (`policy_id`) REFERENCES `backup_policy` (`id`) ON DELETE CASCADE, - CONSTRAINT `fk_backup_policy_vm_map__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance` (`id`) ON DELETE CASCADE, - CONSTRAINT `fk_backup_policy_vm_map__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - -CREATE TABLE IF NOT EXISTS `cloud`.`backup` ( +CREATE TABLE IF NOT EXISTS `cloud`.`vm_backup` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `uuid` varchar(40) NOT NULL, - `account_id` bigint(20) unsigned NOT NULL, - `zone_id` bigint(20) unsigned NOT NULL, - `external_id` varchar(80) NOT NULL COMMENT 'backup ID on provider side', `name` varchar(255) NOT NULL COMMENT 'backup name', `description` varchar(255) COMMENT 'backup description', - `parent_id` bigint(20) unsigned COMMENT 'backup parent id', + `external_id` varchar(80) COMMENT 'backup ID on provider side', + `policy_id` bigint(20) unsigned NOT NULL, `vm_id` bigint(20) unsigned NOT NULL, - `volumes` varchar(100), + `volumes` varchar(5100), `status` varchar(20) NOT NULL, - `start` datetime DEFAULT 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__parent_id` FOREIGN KEY (`parent_id`) REFERENCES `backup` (`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_vm_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_vmbackup` (`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/engine/schema/src/test/java/org/apache/cloudstack/backup/BackupVOTest.java b/engine/schema/src/test/java/org/apache/cloudstack/backup/BackupVOTest.java deleted file mode 100644 index 33aca9682e25..000000000000 --- a/engine/schema/src/test/java/org/apache/cloudstack/backup/BackupVOTest.java +++ /dev/null @@ -1,78 +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 -//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.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -@RunWith(JUnit4.class) -public class BackupVOTest { - - private BackupVO vo = new BackupVO(); - - private final List ids = Arrays.asList(1L, 2L, 3L, 4L); - - @Test - public void testVolumeIdsNotEmptyList() { - vo.setVolumeIds(ids); - Assert.assertEquals("[1,2,3,4]", vo.getVolumes()); - Assert.assertEquals(ids, vo.getVolumeIds()); - } - - @Test - public void testVolumeIdsEmptyList() { - vo.setVolumeIds(new ArrayList<>()); - Assert.assertEquals(null, vo.getVolumes()); - Assert.assertEquals(new ArrayList<>(), vo.getVolumeIds()); - } - - @Test - public void testVolumeIdsUnsetVolumeIds() { - Assert.assertEquals(null, vo.getVolumes()); - Assert.assertEquals(new ArrayList<>(), vo.getVolumeIds()); - } - - @Test - public void testDecodeVolumesStringNotEmptyList() { - vo.setVolumes("[1,2,3,4]"); - Assert.assertEquals(ids, vo.getVolumeIds()); - } - - @Test - public void testDecodeVolumesStringEmptyList() { - vo.setVolumes(null); - Assert.assertEquals(new ArrayList<>(), vo.getVolumeIds()); - } - - @Test - public void testSetVolumeIdsMultipleTimes() { - List list = Arrays.asList(1L, 2L); - vo.setVolumeIds(list); - Assert.assertEquals("[1,2]", vo.getVolumes()); - vo.setVolumeIds(new ArrayList<>()); - Assert.assertEquals(null, vo.getVolumes()); - vo.setVolumeIds(ids); - Assert.assertEquals("[1,2,3,4]", vo.getVolumes()); - } -} 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..e1f13559ce81 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.VM_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 97e22da111a3..838ce98dcf6d 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 @@ -56,6 +56,7 @@ public class QuotaTypes extends UsageTypes { quotaTypeList.put(VM_DISK_BYTES_WRITE, new QuotaTypes(VM_DISK_BYTES_WRITE, "VPN_USERS", "GB", "VM Disk usage(Bytes Write)")); quotaTypeList.put(VM_SNAPSHOT, new QuotaTypes(VM_SNAPSHOT, "VM_SNAPSHOT", "GB-Month", "VM Snapshot 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(VM_BACKUP, new QuotaTypes(VM_BACKUP, "VM_BACKUP", "GB-Month", "VM 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/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java index 0aacf9104dc1..cc49524dfee8 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,27 +17,26 @@ package org.apache.cloudstack.backup; import java.util.Arrays; -import java.util.Comparator; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import org.apache.cloudstack.backup.dao.BackupDao; +import javax.inject.Inject; + +import com.cloud.utils.Pair; +import org.apache.cloudstack.backup.dao.VMBackupDao; import org.apache.log4j.Logger; -import com.cloud.agent.api.to.VolumeTO; -import com.cloud.storage.Storage; -import com.cloud.storage.Volume; import com.cloud.utils.component.AdapterBase; import com.cloud.vm.VirtualMachine; -import javax.inject.Inject; - public class DummyBackupProvider extends AdapterBase implements BackupProvider { private static final Logger s_logger = Logger.getLogger(DummyBackupProvider.class); @Inject - private BackupDao backupDao; + private VMBackupDao backupDao; @Override public String getName() { @@ -52,79 +51,78 @@ public String getDescription() { @Override public List listBackupPolicies(Long zoneId) { s_logger.debug("Listing backup policies on Dummy B&R Plugin"); - BackupPolicy policy1 = new BackupPolicyTO("aaaa-aaaa", "Golden Policy", "Gold description"); - BackupPolicy policy2 = new BackupPolicyTO("bbbb-bbbb", "Silver Policy", "Silver description"); + BackupPolicy policy1 = new BackupPolicyVO("aaaa-aaaa", "Golden Policy", "Gold description"); + BackupPolicy policy2 = new BackupPolicyVO("bbbb-bbbb", "Silver Policy", "Silver description"); return Arrays.asList(policy1, policy2); } @Override public boolean isBackupPolicy(Long zoneId, String uuid) { - s_logger.debug("Checking if backup policy exists on the Dummy Backup Provider"); - return true; - } - - @Override - public boolean addVMToBackupPolicy(BackupPolicy policy, VirtualMachine vm) { - s_logger.debug("Assigning VM " + vm.getInstanceName() + " to backup policy " + policy.getName()); - return true; - } - - @Override - public boolean removeVMFromBackupPolicy(BackupPolicy policy, VirtualMachine vm) { - s_logger.debug("Removing VM " + vm.getInstanceName() + " from backup policy " + policy.getName()); + s_logger.debug("Checking if backup policy exists on the Dummy VMBackup Provider"); return true; } @Override - public Backup createVMBackup(BackupPolicy policy, VirtualMachine vm) { + public VMBackup createVMBackup(BackupPolicy policy, VirtualMachine vm, VMBackup backup) { s_logger.debug("Creating VM backup for VM " + vm.getInstanceName() + " from backup policy " + policy.getName()); - List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); - String backupNumber = String.valueOf(backups.size() + 1); - Backup lastBackup = null; - if (backups.size() > 0) { - backups.sort(Comparator.comparing(Backup::getStartTime)); - lastBackup = backups.get(backups.size() - 1); - } - BackupTO newBackup = new BackupTO(vm.getDataCenterId(), vm.getAccountId(), - "xxxx-xxxx-" + vm.getUuid() + "-" + backupNumber, "Backup-" + vm.getUuid() + backupNumber, - "VM-" + vm.getInstanceName() + "-backup-" + backupNumber, - lastBackup != null ? lastBackup.getExternalId() : null, vm.getId(), null, - Backup.Status.BackedUp, new Date()); - backups.add(newBackup); - - return newBackup; + List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + VMBackupVO dummyBackup = (VMBackupVO) backup; + dummyBackup.setStatus(VMBackup.Status.BackedUp); + dummyBackup.setCreated(new Date()); + backups.add(dummyBackup); + return dummyBackup; } @Override - public boolean restoreVMFromBackup(String vmUuid, String backupUuid) { - s_logger.debug("Restoring vm " + vmUuid + "from backup " + backupUuid + " on the Dummy Backup Provider"); + 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 VolumeTO restoreVolumeFromBackup(String volumeUuid, String backupUuid) { - s_logger.debug("Restoring volume " + volumeUuid + "from backup " + backupUuid + " on the Dummy Backup Provider"); - return new VolumeTO(0L, Volume.Type.DATADISK, Storage.StoragePoolType.NetworkFilesystem, "pool-aaaa", "volumeTest", - "/test", "volTest", 1024L, "", ""); + 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 VMBackup Provider"); + return null; } @Override - public List listVMBackups(Long zoneId, VirtualMachine vm) { - s_logger.debug("Listing VM " + vm.getInstanceName() + "backups on the Dummy Backup Provider"); + public List listVMBackups(Long zoneId, VirtualMachine vm) { + s_logger.debug("Listing VM " + vm.getInstanceName() + "backups on the Dummy VMBackup Provider"); return backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); } @Override - public boolean removeVMBackup(VirtualMachine vm, String backupId) { - s_logger.debug("Removing VM backup " + backupId + " for VM " + vm.getInstanceName() + " on the Dummy Backup Provider"); + public Map getBackupMetrics(Long zoneId, List backupList) { + final Map metrics = new HashMap<>(); + final VMBackup.Metric metric = new VMBackup.Metric(1000L, 100L); + for (VMBackup backup : backupList) { + metrics.put(backup, metric); + } + return metrics; + } + + @Override + public boolean removeVMBackup(VirtualMachine vm, VMBackup backup) { + s_logger.debug("Removing VM backup " + backup.getUuid() + " for VM " + vm.getInstanceName() + " on the Dummy Backup Provider"); - List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); - for (Backup backup : backups) { - if (backup.getExternalId().equals(backupId)) { + List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + for (VMBackup vmBackup : backups) { + if (vmBackup.getExternalId().equals(backup.getExternalId())) { return true; } } return false; } + + @Override + public boolean startBackup(VMBackup vmBackup) { + return true; + } + + @Override + public List listVMBackupRestorePoints(String backupUuid, VirtualMachine vm) { + return null; + } } diff --git a/plugins/backup/veeam/pom.xml b/plugins/backup/veeam/pom.xml index bb2fa1911b11..794d60ae3e72 100644 --- a/plugins/backup/veeam/pom.xml +++ b/plugins/backup/veeam/pom.xml @@ -43,6 +43,22 @@ commons-lang3 ${cs.commons-lang3.version} + + io.cloudsoft.windows + winrm4j-client + ${cs.winrm4j.version} + + + io.cloudsoft.windows + winrm4j-service + ${cs.winrm4j.version} + provided + + + io.cloudsoft.windows + winrm4j + ${cs.winrm4j.version} + com.github.tomakehurst wiremock-standalone 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 84e442aa4cc4..40b34f3a7902 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 @@ -20,17 +20,22 @@ 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 com.cloud.utils.Pair; 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.agent.api.to.VolumeTO; import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.vmware.VmwareDatacenter; import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMap; @@ -43,19 +48,20 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, Configurable { private static final Logger LOG = Logger.getLogger(VeeamBackupProvider.class); + public static final String BACKUP_IDENTIFIER = "-CSBKP-"; - private ConfigKey VeeamUrl = new ConfigKey<>("Advanced", String.class, + 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.username", + "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.password", + "backup.plugin.veeam.host.password", "P@ssword123", "The Veeam backup and recovery password.", true, ConfigKey.Scope.Zone); @@ -83,7 +89,13 @@ private VeeamClient getClient(final Long zoneId) { } public List listBackupPolicies(final Long zoneId) { - return getClient(zoneId).listBackupPolicies(); + List policies = new ArrayList<>(); + for (final BackupPolicy policy : getClient(zoneId).listJobs()) { + if (!policy.getName().contains(BACKUP_IDENTIFIER)) { + policies.add(policy); + } + } + return policies; } @Override @@ -115,47 +127,98 @@ private VmwareDatacenter findVmwareDatacenterForVM(final VirtualMachine vm) { return vmwareDatacenter; } - @Override public boolean addVMToBackupPolicy(final BackupPolicy policy, final VirtualMachine vm) { final VmwareDatacenter vmwareDatacenter = findVmwareDatacenterForVM(vm); - return getClient(vm.getDataCenterId()).addVMToVeeamJob(policy.getExternalId(), vm.getInstanceName(), vmwareDatacenter.getVcenterHost()); + 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 boolean removeVMFromBackupPolicy(final BackupPolicy policy, final VirtualMachine vm) { - final VmwareDatacenter vmwareDatacenter = findVmwareDatacenterForVM(vm); - return getClient(vm.getDataCenterId()).removeVMFromVeeamJob(policy.getExternalId(), vm.getInstanceName(), vmwareDatacenter.getVcenterHost()); + public VMBackup createVMBackup(final BackupPolicy policy, final VirtualMachine vm, final VMBackup 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 (BackupPolicy 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())) { + VMBackupVO vmBackup = ((VMBackupVO) backup); + vmBackup.setStatus(VMBackup.Status.Queued); + vmBackup.setExternalId(job.getExternalId()); + vmBackup.setCreated(new Date()); + if (!startBackup(vmBackup)) { + 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 policy) for policy id: " + policy.getExternalId()); + } + ((VMBackupVO) backup).setStatus(VMBackup.Status.Error); + return backup; } @Override - public Backup createVMBackup(BackupPolicy policy, VirtualMachine vm) { - //TODO: Return backup - getClient(vm.getDataCenterId()).startBackupJob(policy.getExternalId()); - return null; + public boolean removeVMBackup(final VirtualMachine vm, final VMBackup backup) { + final VeeamClient client = getClient(vm.getDataCenterId()); + final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm); + if (!client.removeVMFromVeeamJob(backup.getExternalId(), vm.getInstanceName(), vmwareDC.getVcenterHost())) { + LOG.warn("Failed to remove VM from Veeam Job id: " + backup.getExternalId()); + } + final String clonedJobName = getGuestBackupName(vm.getInstanceName(), backup.getUuid()); + return client.deleteJobAndBackup(clonedJobName) && client.listJob(clonedJobName) == null; } @Override - public boolean restoreVMFromBackup(String vmUuid, String backupUuid) { - //TODO - return false; + public boolean startBackup(final VMBackup vmBackup) { + final VeeamClient client = getClient(vmBackup.getZoneId()); + return client.startBackupJob(vmBackup.getExternalId()); } @Override - public VolumeTO restoreVolumeFromBackup(String volumeUuid, String backupUuid) { - //TODO - return null; + 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 listVMBackups(Long zoneId, VirtualMachine vm) { + public List listVMBackups(Long zoneId, VirtualMachine vm) { //return getClient(zoneId).listAllBackups(); return null; } @Override - public boolean removeVMBackup(VirtualMachine vm, String backupId) { - //TODO: Implement - return false; + public Map getBackupMetrics(final Long zoneId, final List backupList) { + final Map metrics = new HashMap<>(); + final Map backendMetrics = getClient(zoneId).getBackupMetrics(); + for (final VMBackup backup : backupList) { + if (!backendMetrics.containsKey(backup.getUuid())) { + continue; + } + metrics.put(backup, backendMetrics.get(backup.getUuid())); + } + return metrics; + } + + @Override + public List listVMBackupRestorePoints(String backupUuid, VirtualMachine vm) { + String backupName = getGuestBackupName(vm.getInstanceName(), backupUuid); + return getClient(vm.getDataCenterId()).listRestorePoints(backupName, vm.getInstanceName()); } @Override 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 285542a01baf..3ab95f6cbedf 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 @@ -20,9 +20,9 @@ import java.util.Date; import java.util.List; -import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.VMBackup; -public class VeeamBackup implements Backup { +public class VeeamBackup implements VMBackup { private String name; private String uid; @@ -58,27 +58,32 @@ public String getDescription() { } @Override - public Long getParentId() { + public Long getVmId() { return null; } @Override - public Long getVmId() { + public List getBackedUpVolumes() { return null; } @Override - public List getVolumeIds() { + public Status getStatus() { return null; } @Override - public Status getStatus() { + public Long getSize() { + return null; + } + + @Override + public Long getProtectedSize() { return null; } @Override - public Date getStartTime() { + public Date getCreated() { return null; } 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 16e402a23721..882312de1c4a 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 @@ -17,7 +17,7 @@ package org.apache.cloudstack.backup.veeam; -import static org.apache.cloudstack.backup.veeam.api.VeeamObjectType.HierarchyRootReference; +import static org.apache.cloudstack.backup.VeeamBackupProvider.BACKUP_IDENTIFIER; import java.io.IOException; import java.net.SocketTimeoutException; @@ -27,21 +27,32 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.UUID; import javax.net.ssl.SSLContext; import javax.net.ssl.X509TrustManager; +import com.cloud.utils.Pair; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.backup.BackupPolicy; +import org.apache.cloudstack.backup.VMBackup; +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; @@ -55,6 +66,7 @@ import org.apache.http.client.CookieStore; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; +import org.apache.http.client.config.AuthSchemes; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; @@ -78,13 +90,18 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; +import io.cloudsoft.winrm4j.winrm.WinRmTool; +import io.cloudsoft.winrm4j.winrm.WinRmToolResponse; + 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 final WinRmTool winRmTool; 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); @@ -130,6 +147,15 @@ public VeeamClient(final String url, final String username, final String passwor } catch (final IOException e) { throw new CloudRuntimeException("Failed to authenticate Veeam API service due to:" + e.getMessage()); } + + final WinRmTool.Builder builder = WinRmTool.Builder.builder(apiURI.getHost(), username, password); + builder.useHttps(true); + builder.disableCertificateChecks(true); + builder.setAuthenticationScheme(AuthSchemes.NTLM); + builder.port(WinRmTool.DEFAULT_WINRM_HTTPS_PORT); + winRmTool = builder.build(); + winRmTool.setOperationTimeout(timeout * 1000L); + winRmTool.setRetriesForConnectionFailures(1); } private void checkAuthFailure(final HttpResponse response) { @@ -206,7 +232,7 @@ private String findDCHierarchy(final String vmwareDcName) { 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)) { + if (ref.getName().equals(vmwareDcName) && ref.getType().equals("HierarchyRootReference")) { return ref.getUid(); } } @@ -246,6 +272,12 @@ private Task parseTaskResponse(HttpResponse response) throws IOException { 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); + } + private boolean checkTaskStatus(final HttpResponse response) throws IOException { final Task task = parseTaskResponse(response); for (int i = 0; i < 20; i++) { @@ -257,6 +289,27 @@ private boolean checkTaskStatus(final HttpResponse response) throws IOException 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 policy due to: " + polledTask.getResult().getMessage()); @@ -270,10 +323,38 @@ private boolean checkTaskStatus(final HttpResponse response) throws IOException 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 { @@ -293,7 +374,7 @@ public List listAllBackups() { return new ArrayList<>(); } - public List listBackupPolicies() { + public List listJobs() { LOG.debug("Trying to list backup policies that are Veeam jobs"); try { final HttpResponse response = get("/jobs"); @@ -312,6 +393,36 @@ public List listBackupPolicies() { 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 { @@ -324,6 +435,24 @@ public boolean startBackupJob(final String jobId) { 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 policy that is a veeam job: " + jobId); try { @@ -351,18 +480,153 @@ public boolean removeVMFromVeeamJob(final String jobId, final String vmwareInsta 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); } } - throw new CloudRuntimeException("VM was not found to be assigned to backup policy"); + LOG.warn(vmwareInstanceName + " VM was not found to be attached to veaam job (backup policy): " + jobId); + return false; } catch (final IOException e) { LOG.error("Failed to list Veeam jobs due to:", e); checkResponseTimeOut(e); } - throw new CloudRuntimeException("Failed to remove VM from backup policy, please check veeam tasks"); + 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 ///////////////////// + ///////////////////////////////////////////////////////////////// + + public boolean deleteJobAndBackup(final String jobName) { + final WinRmToolResponse response = winRmTool.executePs( + Collections.singletonList( + String.format( + "Add-PSSnapin VeeamPSSnapin\n" + + "Get-VBRBackup -Name \"%s\" | Remove-VBRBackup -FromDisk -Confirm:$false\n" + + "Get-VBRJob -Name \"%s\" | Remove-VBRJob -Confirm:$false\n" + + "Get-VBRBackupRepository | Sync-VBRBackupRepository", jobName, jobName) + )); + return response.getStatusCode() == 0 && !response.getStdErr().contains("Failed to delete"); + } + + public Map getBackupMetrics() { + final List psScript = Collections.singletonList( + "Add-PSSnapin VeeamPSSnapin\n" + + "$backups = Get-VBRBackup\n" + + "foreach ($backup in $backups) {\n" + + "$backup.JobName\n" + + "$storageGroups = $backup.GetStorageGroups()\n" + + "foreach ($group in $storageGroups)\n" + + "{\n" + + " $usedSize = 0\n" + + " $dataSize = 0\n" + + " $sizePerStorage = $group.GetStorages().Stats.BackupSize\n" + + " $dataPerStorage = $group.GetStorages().Stats.DataSize\n" + + " foreach ($size in $sizePerStorage)\n" + + " {\n" + + " $usedSize += $size\n" + + " }\n" + + " foreach ($size in $dataPerStorage)\n" + + " {\n" + + " $dataSize += $size\n" + + " }\n" + + " $usedSize\n" + + " $dataSize\n" + + "}\n" + + "echo \";\"" + + "}\n"); + final WinRmToolResponse response = winRmTool.executePs(psScript); + final Map sizes = new HashMap<>(); + for (final String block : response.getStdOut().split(";\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 VMBackup.Metric(Long.valueOf(parts[1]), Long.valueOf(parts[2]))); + } + } + return sizes; + } + + private VMBackup.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]; + } 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 VMBackup.RestorePoint(id, created, type); + } + + public List listRestorePoints(String backupName, String vmInternalName) { + final List psScript = Collections.singletonList( + String.format( + "Add-PSSnapin VeeamPSSnapin\n" + + "$backup = Get-VBRBackup -Name \"%s\"\n" + + "Get-VBRRestorePoint -Backup:$backup -Name \"%s\"", + backupName, vmInternalName) + ); + final WinRmToolResponse response = winRmTool.executePs(psScript); + if (response == null) { + throw new CloudRuntimeException("Failed to list restore points"); + } + final List restorePoints = new ArrayList<>(); + for (final String block : response.getStdOut().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 psScript = Collections.singletonList( + String.format( + "Add-PSSnapin VeeamPSSnapin\n" + + "$point = Get-VBRRestorePoint | where {$_.Id -eq \"%s\"}\n" + + "$server = Get-VBRServer -Name \"%s\"\n" + + "$ds = Find-VBRViDatastore -Server:$server -Name \"%s\"\n" + + "Start-VBRRestoreVM -RestorePoint:$point -Server:$server -Datastore:$ds -VMName \"%s\"", + restorePointId, hostIp, datastoreId, restoreLocation) + ); + final WinRmToolResponse response = winRmTool.executePs(psScript); + if (response == null) { + throw new CloudRuntimeException("Failed to restore VM to location " + restoreLocation); + } + return new Pair<>(response.getStatusCode() == 0, 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 index b8f368d986c3..6ecf08081d86 100644 --- 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 @@ -19,12 +19,10 @@ import java.util.List; -import org.apache.cloudstack.backup.veeam.api.VeeamObjectType; - public interface VeeamObject { String getUuid(); String getName(); String getHref(); - VeeamObjectType getType(); + 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/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 index f45ba4fe673a..b89d77fa550a 100644 --- 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 @@ -30,7 +30,7 @@ public class Link { private String href; @JacksonXmlProperty(localName = "Type", isAttribute = true) - private VeeamObjectType type; + private String type; @JacksonXmlProperty(localName = "Rel", isAttribute = true) private String rel; @@ -51,12 +51,12 @@ public void setHref(String href) { this.href = href; } - public VeeamObjectType getType() { + public String getType() { return type; } public void setType(String type) { - this.type = VeeamObjectType.valueOf(type); + this.type = type; } public String getRel() { 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 index c21c554aebe8..683fd3773f6c 100644 --- 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 @@ -35,7 +35,7 @@ public class Ref { private String href; @JacksonXmlProperty(localName = "Type", isAttribute = true) - private VeeamObjectType type; + private String type; @JacksonXmlProperty(localName = "Link") @JacksonXmlElementWrapper(localName = "Links") @@ -73,11 +73,11 @@ public void setHref(String href) { this.href = href; } - public VeeamObjectType getType() { + public String getType() { return type; } public void setType(String type) { - this.type = VeeamObjectType.valueOf(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/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java b/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java index 1eeb7a240b2c..aec9ff3a7a04 100644 --- 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 @@ -26,8 +26,11 @@ import static com.github.tomakehurst.wiremock.client.WireMock.verify; import java.util.List; +import java.util.Map; import org.apache.cloudstack.backup.BackupPolicy; +import org.apache.cloudstack.backup.VMBackup; +import org.apache.cloudstack.backup.veeam.api.Job; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -77,9 +80,26 @@ public void testVeeamJobs() { " \n" + " \n" + ""))); - List policies = client.listBackupPolicies(); + 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-12345"); + } + + 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"); + } + @Test + public void test() { + String a = "Id :asdasd"; + System.out.println(a.matches("Id(/s)*")); + } } \ 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 81dfc33bb883..82c99003db94 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 @@ -26,6 +26,51 @@ import javax.inject.Inject; +import com.cloud.exception.InvalidParameterValueException; +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.mo.DatacenterMO; +import com.cloud.hypervisor.vmware.mo.NetworkMO; +import com.cloud.hypervisor.vmware.mo.VirtualDiskManagerMO; +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.Networks; +import com.cloud.network.dao.PhysicalNetworkDao; +import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.VMTemplateStoragePoolVO; +import com.cloud.storage.VMTemplateStorageResourceAssoc; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VMTemplatePoolDao; +import com.cloud.utils.UuidUtils; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackWithException; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.vm.Nic; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.UserVmDao; +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; +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.backup.VMBackup; +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; @@ -38,7 +83,9 @@ 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.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; @@ -144,6 +191,22 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co PrimaryDataStoreDao _storagePoolDao; @Inject VolumeDataFactory _volFactory; + @Inject + 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; protected VMwareGuru() { super(); @@ -640,4 +703,663 @@ public Map getClusterSettings(long vmId) { details.put(VmwareReserveMemory.key(), VmwareReserveMemory.valueIn(clusterId).toString()); 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) { + if (!disksMapping.containsKey(disk)) { + return false; + } + VolumeVO volumeVO = disksMapping.get(disk); + return volumeVO != null && volumeVO.getVolumeType().equals(Volume.Type.ROOT); + } + + /** + * 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 pool ID for VM being imported + */ + private Long getImportingVMPoolId(List disks, Map disksMapping) { + for (VirtualDisk disk : disks) { + if (isRootDisk(disk, disksMapping)) { + return getPoolId(disk); + } + } + throw new CloudRuntimeException("No ROOT disk found"); + } + + /** + * 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) throws Exception { + List disks = template.getVirtualDisks(); + for (VirtualDisk disk : disks) { + if (isRootDisk(disk, disksMapping)) { + return disk.getCapacityInBytes(); + } + } + throw new CloudRuntimeException("Could not find ROOT disk for VM: " + vmInternalName); + } + + /** + * 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) throws Exception { + for (VirtualDisk disk : virtualDisks) { + if (isRootDisk(disk, disksMapping)) { + 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); + 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) { + 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); + 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, long domainId) { + List offerings = diskOfferingDao.listAllBySizeAndProvisioningType(size, provisioningType, domainId); + 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.setName(volumeName); + volumeVO.setPath(volumeName); + volumeVO.setPoolId(poolId); + _volumeDao.update(volumeVO.getId(), volumeVO); + return volumeVO; + } + + /** + * Get volumes for VM being imported + */ + private void importVMVolumes(VMInstanceVO vmInstanceVO, Long poolId, List virtualDisks, + Map disksMapping, VirtualMachineMO vmToImport) 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) { + VolumeVO volumeVO; + if (disksMapping.containsKey(disk)) { + volumeVO = updateVolume(disk, disksMapping, vmToImport, poolId); + } else { + volumeVO = createVolume(disk, vmToImport, domainId, zoneId, accountId, instanceId, poolId, templateId); + } + 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 VolumeVO createVolume(VirtualDisk disk, VirtualMachineMO vmToImport, long domainId, long zoneId, + long accountId, long instanceId, Long poolId, long templateId) throws Exception { + Long size = disk.getCapacityInBytes(); + 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, domainId); + Volume.Type type = Volume.Type.DATADISK; + Integer unitNumber = disk.getUnitNumber(); + return createVolumeRecord(type, volumeName, zoneId, domainId, accountId, diskOfferingId, + provisioningType, size, instanceId, poolId, templateId, unitNumber); + } + + /** + * 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(VMBackup backup, List virtualDisks) { + Map map = new HashMap<>(); + List backedUpVolumes = backup.getBackedUpVolumes(); + for (VMBackup.VolumeInfo backedUpVol : backedUpVolumes) { + for (VirtualDisk disk : virtualDisks) { + if (backedUpVol.getSize().equals(disk.getCapacityInBytes())) { + String volId = backedUpVol.getUuid(); + VolumeVO vol = _volumeDao.findByUuid(volId); + 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(VMBackup.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, VMBackup 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 poolId = getImportingVMPoolId(virtualDisks, disksMapping); + long templateId = getImportingVMTemplate(virtualDisks, dcMo, vmInternalName, guestOsId, accountId, disksMapping); + + VMInstanceVO vmInstanceVO = getVM(vmInternalName, templateId, guestOsId, + serviceOfferingId, zoneId, accountId, userId, domainId); + + importVMNics(nicDevices, dcMo, networksMapping, vmInstanceVO); + importVMVolumes(vmInstanceVO, poolId, virtualDisks, disksMapping, vmToImport); + return vmInstanceVO; + } + }); + } + + @Override + public boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location, VMBackup.VolumeInfo volumeInfo, + VirtualMachine vm, long poolId) 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); + + //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); + + //Attach volume to VM + vmMo.attachDisk(new String[] {destPath}, getDestStoreMor(vmMo)); + + //Destroy restored VM + vmRestored.destroy(); + + VirtualDisk attachedDisk = getAttachedDisk(vmMo, diskPath); + createVolume(attachedDisk, vmMo, vm.getDomainId(), vm.getDataCenterId(), vm.getAccountId(), vm.getId(), + poolId, vm.getTemplateId()); + + 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; + } } 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 f586f3938215..8c667e2c678e 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 @@ -182,7 +182,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 @@ -1335,7 +1335,7 @@ private void startTemplateCleanJobSchedule() { */ private Runnable getCleanupFullyClonedTemplatesTask() { return new CleanupFullyClonedTemplatesTask(primaryStorageDao, - templateDataStoreDao, + templateStoragePoolDao, templateDao, vmInstanceDao, cloneSettingDao, diff --git a/plugins/pom.xml b/plugins/pom.xml index 7e693af5bbef..83048ac600e3 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 d2913247f6ac..adc14735b857 100644 --- a/pom.xml +++ b/pom.xml @@ -159,7 +159,8 @@ 4.0.0 8.0.30 1.0.0-build221 - 6.5 + 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 d07e37666b9c..797f056b28e6 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -40,8 +40,6 @@ import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.AsyncJobResponse; import org.apache.cloudstack.api.response.BackupPolicyResponse; -import org.apache.cloudstack.api.response.BackupPolicyVMMapResponse; -import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.DomainRouterResponse; @@ -62,17 +60,16 @@ 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.VMBackupResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.backup.BackupPolicy; -import org.apache.cloudstack.backup.BackupPolicyVMMap; -import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.backup.VMBackup; +import org.apache.cloudstack.backup.dao.VMBackupDao; import org.apache.cloudstack.backup.dao.BackupPolicyDao; -import org.apache.cloudstack.backup.dao.BackupPolicyVMMapDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; -import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.jobs.AsyncJob; import org.apache.cloudstack.framework.jobs.AsyncJobManager; @@ -194,6 +191,7 @@ import com.cloud.network.dao.AccountGuestVlanMapVO; import com.cloud.network.dao.FirewallRulesCidrsDao; import com.cloud.network.dao.FirewallRulesDao; +import com.cloud.network.dao.FirewallRulesDcidrsDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.LoadBalancerDao; @@ -286,6 +284,7 @@ import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; import com.cloud.user.AccountDetailsDao; +import com.cloud.user.AccountManager; import com.cloud.user.AccountService; import com.cloud.user.AccountVO; import com.cloud.user.ResourceLimitService; @@ -321,8 +320,6 @@ import com.cloud.vm.dao.VMInstanceDao; import com.cloud.vm.snapshot.VMSnapshot; import com.cloud.vm.snapshot.dao.VMSnapshotDao; -import com.cloud.user.AccountManager; -import com.cloud.network.dao.FirewallRulesDcidrsDao; public class ApiDBUtils { private static ManagementServer s_ms; @@ -443,9 +440,8 @@ public class ApiDBUtils { static ResourceMetaDataService s_resourceDetailsService; static HostGpuGroupsDao s_hostGpuGroupsDao; static VGPUTypesDao s_vgpuTypesDao; - static BackupDao s_backupDao; + static VMBackupDao s_backupDao; static BackupPolicyDao s_backupPolicyDao; - static BackupPolicyVMMapDao s_backupPolicyVMMapDao; @Inject private ManagementServer ms; @@ -679,11 +675,9 @@ public class ApiDBUtils { @Inject private VGPUTypesDao vgpuTypesDao; @Inject - private BackupDao backupDao; + private VMBackupDao backupDao; @Inject private BackupPolicyDao backupPolicyDao; - @Inject - private BackupPolicyVMMapDao backupPolicyVMMapDao; @PostConstruct void init() { @@ -805,7 +799,6 @@ void init() { s_vgpuTypesDao = vgpuTypesDao; s_backupDao = backupDao; s_backupPolicyDao = backupPolicyDao; - s_backupPolicyVMMapDao = backupPolicyVMMapDao; } // /////////////////////////////////////////////////////////// @@ -2022,15 +2015,11 @@ public static List listResourceTagViewByResourceUUID(String r return s_tagJoinDao.listBy(resourceUUID, resourceType); } - public static BackupResponse newBackupResponse(Backup backup) { + public static VMBackupResponse newBackupResponse(VMBackup backup) { return s_backupDao.newBackupResponse(backup); } public static BackupPolicyResponse newBackupPolicyResponse(BackupPolicy policy) { return s_backupPolicyDao.newBackupPolicyResponse(policy); } - - public static BackupPolicyVMMapResponse newBackupPolicyVMMappingResponse(BackupPolicyVMMap map) { - return s_backupPolicyVMMapDao.newBackupPolicyVMMappingResponse(map); - } } diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 2231282aea6f..3d9db9e7f74d 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -16,8 +16,157 @@ // under the License. package com.cloud.api; -import com.cloud.utils.crypt.DBEncryptionUtil; -import com.cloud.tags.dao.ResourceTagDao; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.acl.ControlledEntity.ACLType; +import org.apache.cloudstack.affinity.AffinityGroup; +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.api.ApiConstants.HostDetails; +import org.apache.cloudstack.api.ApiConstants.VMDetails; +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.ResponseObject.ResponseView; +import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd; +import org.apache.cloudstack.api.response.AccountResponse; +import org.apache.cloudstack.api.response.ApplicationLoadBalancerInstanceResponse; +import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse; +import org.apache.cloudstack.api.response.ApplicationLoadBalancerRuleResponse; +import org.apache.cloudstack.api.response.AsyncJobResponse; +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.BackupPolicyResponse; +import org.apache.cloudstack.api.response.CapabilityResponse; +import org.apache.cloudstack.api.response.CapacityResponse; +import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.api.response.ConditionResponse; +import org.apache.cloudstack.api.response.ConfigurationResponse; +import org.apache.cloudstack.api.response.ControlledEntityResponse; +import org.apache.cloudstack.api.response.ControlledViewEntityResponse; +import org.apache.cloudstack.api.response.CounterResponse; +import org.apache.cloudstack.api.response.CreateCmdResponse; +import org.apache.cloudstack.api.response.CreateSSHKeyPairResponse; +import org.apache.cloudstack.api.response.DiskOfferingResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.DomainRouterResponse; +import org.apache.cloudstack.api.response.EventResponse; +import org.apache.cloudstack.api.response.ExtractResponse; +import org.apache.cloudstack.api.response.FirewallResponse; +import org.apache.cloudstack.api.response.FirewallRuleResponse; +import org.apache.cloudstack.api.response.GlobalLoadBalancerResponse; +import org.apache.cloudstack.api.response.GuestOSResponse; +import org.apache.cloudstack.api.response.GuestOsMappingResponse; +import org.apache.cloudstack.api.response.GuestVlanRangeResponse; +import org.apache.cloudstack.api.response.HostForMigrationResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.HypervisorCapabilitiesResponse; +import org.apache.cloudstack.api.response.IPAddressResponse; +import org.apache.cloudstack.api.response.ImageStoreResponse; +import org.apache.cloudstack.api.response.InstanceGroupResponse; +import org.apache.cloudstack.api.response.InternalLoadBalancerElementResponse; +import org.apache.cloudstack.api.response.IpForwardingRuleResponse; +import org.apache.cloudstack.api.response.IsolationMethodResponse; +import org.apache.cloudstack.api.response.LBHealthCheckPolicyResponse; +import org.apache.cloudstack.api.response.LBHealthCheckResponse; +import org.apache.cloudstack.api.response.LBStickinessPolicyResponse; +import org.apache.cloudstack.api.response.LBStickinessResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.LoadBalancerResponse; +import org.apache.cloudstack.api.response.NetworkACLItemResponse; +import org.apache.cloudstack.api.response.NetworkACLResponse; +import org.apache.cloudstack.api.response.NetworkOfferingResponse; +import org.apache.cloudstack.api.response.NetworkResponse; +import org.apache.cloudstack.api.response.NicExtraDhcpOptionResponse; +import org.apache.cloudstack.api.response.NicResponse; +import org.apache.cloudstack.api.response.NicSecondaryIpResponse; +import org.apache.cloudstack.api.response.OvsProviderResponse; +import org.apache.cloudstack.api.response.PhysicalNetworkResponse; +import org.apache.cloudstack.api.response.PodResponse; +import org.apache.cloudstack.api.response.PortableIpRangeResponse; +import org.apache.cloudstack.api.response.PortableIpResponse; +import org.apache.cloudstack.api.response.PrivateGatewayResponse; +import org.apache.cloudstack.api.response.ProjectAccountResponse; +import org.apache.cloudstack.api.response.ProjectInvitationResponse; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ProviderResponse; +import org.apache.cloudstack.api.response.RegionResponse; +import org.apache.cloudstack.api.response.RemoteAccessVpnResponse; +import org.apache.cloudstack.api.response.ResourceCountResponse; +import org.apache.cloudstack.api.response.ResourceLimitResponse; +import org.apache.cloudstack.api.response.ResourceTagResponse; +import org.apache.cloudstack.api.response.SSHKeyPairResponse; +import org.apache.cloudstack.api.response.SecurityGroupResponse; +import org.apache.cloudstack.api.response.SecurityGroupRuleResponse; +import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.ServiceResponse; +import org.apache.cloudstack.api.response.Site2SiteCustomerGatewayResponse; +import org.apache.cloudstack.api.response.Site2SiteVpnConnectionResponse; +import org.apache.cloudstack.api.response.Site2SiteVpnGatewayResponse; +import org.apache.cloudstack.api.response.SnapshotPolicyResponse; +import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.api.response.SnapshotScheduleResponse; +import org.apache.cloudstack.api.response.StaticRouteResponse; +import org.apache.cloudstack.api.response.StorageNetworkIpRangeResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; +import org.apache.cloudstack.api.response.SystemVmInstanceResponse; +import org.apache.cloudstack.api.response.SystemVmResponse; +import org.apache.cloudstack.api.response.TemplatePermissionsResponse; +import org.apache.cloudstack.api.response.TemplateResponse; +import org.apache.cloudstack.api.response.TrafficMonitorResponse; +import org.apache.cloudstack.api.response.TrafficTypeResponse; +import org.apache.cloudstack.api.response.UpgradeRouterTemplateResponse; +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.VMBackupResponse; +import org.apache.cloudstack.api.response.VMSnapshotResponse; +import org.apache.cloudstack.api.response.VirtualRouterProviderResponse; +import org.apache.cloudstack.api.response.VlanIpRangeResponse; +import org.apache.cloudstack.api.response.VolumeResponse; +import org.apache.cloudstack.api.response.VpcOfferingResponse; +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.BackupPolicy; +import org.apache.cloudstack.backup.BackupPolicyVO; +import org.apache.cloudstack.backup.VMBackup; +import org.apache.cloudstack.backup.VMBackupVO; +import org.apache.cloudstack.config.Configuration; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.framework.jobs.AsyncJob; +import org.apache.cloudstack.framework.jobs.AsyncJobManager; +import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; +import org.apache.cloudstack.region.PortableIp; +import org.apache.cloudstack.region.PortableIpRange; +import org.apache.cloudstack.region.Region; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.usage.Usage; +import org.apache.cloudstack.usage.UsageService; +import org.apache.cloudstack.usage.UsageTypes; +import org.apache.commons.collections.CollectionUtils; +import org.apache.log4j.Logger; + import com.cloud.agent.api.VgpuTypesInfo; import com.cloud.api.query.ViewResponseHelper; import com.cloud.api.query.vo.AccountJoinVO; @@ -153,6 +302,7 @@ import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.snapshot.SnapshotPolicy; import com.cloud.storage.snapshot.SnapshotSchedule; +import com.cloud.tags.dao.ResourceTagDao; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; import com.cloud.user.AccountManager; @@ -162,11 +312,12 @@ import com.cloud.uservm.UserVm; import com.cloud.utils.Pair; import com.cloud.utils.StringUtils; +import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.db.EntityManager; -import com.cloud.utils.net.Dhcp; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.Dhcp; import com.cloud.utils.net.Ip; import com.cloud.utils.net.NetUtils; import com.cloud.vm.ConsoleProxyVO; @@ -182,155 +333,6 @@ import com.cloud.vm.dao.NicExtraDhcpOptionDao; import com.cloud.vm.dao.NicSecondaryIpVO; import com.cloud.vm.snapshot.VMSnapshot; -import org.apache.cloudstack.acl.ControlledEntity; -import org.apache.cloudstack.acl.ControlledEntity.ACLType; -import org.apache.cloudstack.affinity.AffinityGroup; -import org.apache.cloudstack.affinity.AffinityGroupResponse; -import org.apache.cloudstack.api.ApiConstants.HostDetails; -import org.apache.cloudstack.api.ApiConstants.VMDetails; -import org.apache.cloudstack.api.ResponseGenerator; -import org.apache.cloudstack.api.ResponseObject.ResponseView; -import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd; -import org.apache.cloudstack.api.response.AccountResponse; -import org.apache.cloudstack.api.response.ApplicationLoadBalancerInstanceResponse; -import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse; -import org.apache.cloudstack.api.response.ApplicationLoadBalancerRuleResponse; -import org.apache.cloudstack.api.response.AsyncJobResponse; -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.BackupPolicyResponse; -import org.apache.cloudstack.api.response.BackupPolicyVMMapResponse; -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; -import org.apache.cloudstack.api.response.ConditionResponse; -import org.apache.cloudstack.api.response.ConfigurationResponse; -import org.apache.cloudstack.api.response.ControlledEntityResponse; -import org.apache.cloudstack.api.response.ControlledViewEntityResponse; -import org.apache.cloudstack.api.response.CounterResponse; -import org.apache.cloudstack.api.response.CreateCmdResponse; -import org.apache.cloudstack.api.response.CreateSSHKeyPairResponse; -import org.apache.cloudstack.api.response.DiskOfferingResponse; -import org.apache.cloudstack.api.response.DomainResponse; -import org.apache.cloudstack.api.response.DomainRouterResponse; -import org.apache.cloudstack.api.response.EventResponse; -import org.apache.cloudstack.api.response.ExtractResponse; -import org.apache.cloudstack.api.response.FirewallResponse; -import org.apache.cloudstack.api.response.FirewallRuleResponse; -import org.apache.cloudstack.api.response.GlobalLoadBalancerResponse; -import org.apache.cloudstack.api.response.GuestOSResponse; -import org.apache.cloudstack.api.response.GuestOsMappingResponse; -import org.apache.cloudstack.api.response.GuestVlanRangeResponse; -import org.apache.cloudstack.api.response.HostForMigrationResponse; -import org.apache.cloudstack.api.response.HostResponse; -import org.apache.cloudstack.api.response.HypervisorCapabilitiesResponse; -import org.apache.cloudstack.api.response.IPAddressResponse; -import org.apache.cloudstack.api.response.ImageStoreResponse; -import org.apache.cloudstack.api.response.InstanceGroupResponse; -import org.apache.cloudstack.api.response.InternalLoadBalancerElementResponse; -import org.apache.cloudstack.api.response.IpForwardingRuleResponse; -import org.apache.cloudstack.api.response.IsolationMethodResponse; -import org.apache.cloudstack.api.response.LBHealthCheckPolicyResponse; -import org.apache.cloudstack.api.response.LBHealthCheckResponse; -import org.apache.cloudstack.api.response.LBStickinessPolicyResponse; -import org.apache.cloudstack.api.response.LBStickinessResponse; -import org.apache.cloudstack.api.response.ListResponse; -import org.apache.cloudstack.api.response.LoadBalancerResponse; -import org.apache.cloudstack.api.response.NetworkACLItemResponse; -import org.apache.cloudstack.api.response.NetworkACLResponse; -import org.apache.cloudstack.api.response.NetworkOfferingResponse; -import org.apache.cloudstack.api.response.NetworkResponse; -import org.apache.cloudstack.api.response.NicExtraDhcpOptionResponse; -import org.apache.cloudstack.api.response.NicResponse; -import org.apache.cloudstack.api.response.NicSecondaryIpResponse; -import org.apache.cloudstack.api.response.OvsProviderResponse; -import org.apache.cloudstack.api.response.PhysicalNetworkResponse; -import org.apache.cloudstack.api.response.PodResponse; -import org.apache.cloudstack.api.response.PortableIpRangeResponse; -import org.apache.cloudstack.api.response.PortableIpResponse; -import org.apache.cloudstack.api.response.PrivateGatewayResponse; -import org.apache.cloudstack.api.response.ProjectAccountResponse; -import org.apache.cloudstack.api.response.ProjectInvitationResponse; -import org.apache.cloudstack.api.response.ProjectResponse; -import org.apache.cloudstack.api.response.ProviderResponse; -import org.apache.cloudstack.api.response.RegionResponse; -import org.apache.cloudstack.api.response.RemoteAccessVpnResponse; -import org.apache.cloudstack.api.response.ResourceCountResponse; -import org.apache.cloudstack.api.response.ResourceLimitResponse; -import org.apache.cloudstack.api.response.ResourceTagResponse; -import org.apache.cloudstack.api.response.SSHKeyPairResponse; -import org.apache.cloudstack.api.response.SecurityGroupResponse; -import org.apache.cloudstack.api.response.SecurityGroupRuleResponse; -import org.apache.cloudstack.api.response.ServiceOfferingResponse; -import org.apache.cloudstack.api.response.ServiceResponse; -import org.apache.cloudstack.api.response.Site2SiteCustomerGatewayResponse; -import org.apache.cloudstack.api.response.Site2SiteVpnConnectionResponse; -import org.apache.cloudstack.api.response.Site2SiteVpnGatewayResponse; -import org.apache.cloudstack.api.response.SnapshotPolicyResponse; -import org.apache.cloudstack.api.response.SnapshotResponse; -import org.apache.cloudstack.api.response.SnapshotScheduleResponse; -import org.apache.cloudstack.api.response.StaticRouteResponse; -import org.apache.cloudstack.api.response.StorageNetworkIpRangeResponse; -import org.apache.cloudstack.api.response.StoragePoolResponse; -import org.apache.cloudstack.api.response.SystemVmInstanceResponse; -import org.apache.cloudstack.api.response.SystemVmResponse; -import org.apache.cloudstack.api.response.TemplatePermissionsResponse; -import org.apache.cloudstack.api.response.TemplateResponse; -import org.apache.cloudstack.api.response.TrafficMonitorResponse; -import org.apache.cloudstack.api.response.TrafficTypeResponse; -import org.apache.cloudstack.api.response.UpgradeRouterTemplateResponse; -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.VMSnapshotResponse; -import org.apache.cloudstack.api.response.VirtualRouterProviderResponse; -import org.apache.cloudstack.api.response.VlanIpRangeResponse; -import org.apache.cloudstack.api.response.VolumeResponse; -import org.apache.cloudstack.api.response.VpcOfferingResponse; -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.BackupPolicy; -import org.apache.cloudstack.backup.BackupPolicyVMMap; -import org.apache.cloudstack.config.Configuration; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; -import org.apache.cloudstack.backup.Backup; -import org.apache.cloudstack.framework.jobs.AsyncJob; -import org.apache.cloudstack.framework.jobs.AsyncJobManager; -import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; -import org.apache.cloudstack.region.PortableIp; -import org.apache.cloudstack.region.PortableIpRange; -import org.apache.cloudstack.region.Region; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.apache.cloudstack.usage.Usage; -import org.apache.cloudstack.usage.UsageService; -import org.apache.cloudstack.usage.UsageTypes; -import org.apache.commons.collections.CollectionUtils; -import org.apache.log4j.Logger; - -import javax.inject.Inject; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TimeZone; -import java.util.stream.Collectors; public class ApiResponseHelper implements ResponseGenerator { @@ -3506,6 +3508,19 @@ public UsageRecordResponse createUsageResponse(Usage usageRecord, Map getClusterSettings(long vmId) { return null; } + @Override + public VirtualMachine importVirtualMachine(long zoneId, long domainId, long accountId, long userId, + String vmInternalName, VMBackup backup) throws Exception { + return null; + } + + @Override + public boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location, VMBackup.VolumeInfo volumeInfo, + VirtualMachine vm, long poolId) throws Exception { + return false; + } } 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 b57ab16d0100..3a8f5f53938f 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -25,39 +25,55 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.event.ActionEvent; -import com.cloud.event.EventTypes; +import com.cloud.storage.ScopeType; +import com.cloud.storage.Volume; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.utils.Pair; import org.apache.cloudstack.api.command.admin.backup.DeleteBackupPolicyCmd; import org.apache.cloudstack.api.command.admin.backup.ImportBackupPolicyCmd; import org.apache.cloudstack.api.command.admin.backup.ListBackupProvidersCmd; -import org.apache.cloudstack.api.command.user.backup.AddVMToBackupPolicyCmd; +import org.apache.cloudstack.api.command.admin.vm.ImportVMCmdByAdmin; import org.apache.cloudstack.api.command.user.backup.CreateVMBackupCmd; import org.apache.cloudstack.api.command.user.backup.DeleteVMBackupCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupPoliciesCmd; -import org.apache.cloudstack.api.command.user.backup.ListBackupPolicyVMMappingsCmd; +import org.apache.cloudstack.api.command.user.backup.ListVMBackupRestorePoints; import org.apache.cloudstack.api.command.user.backup.ListVMBackupsCmd; -import org.apache.cloudstack.api.command.user.backup.RemoveVMFromBackupPolicyCmd; -import org.apache.cloudstack.api.command.user.backup.RestoreVolumeFromBackupAndAttachToVMCmd; import org.apache.cloudstack.api.command.user.backup.RestoreVMFromBackupCmd; -import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.api.command.user.backup.RestoreVolumeFromBackupAndAttachToVMCmd; +import org.apache.cloudstack.api.command.user.backup.StartVMBackupCmd; import org.apache.cloudstack.backup.dao.BackupPolicyDao; -import org.apache.cloudstack.backup.dao.BackupPolicyVMMapDao; +import org.apache.cloudstack.backup.dao.VMBackupDao; 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.commons.lang.BooleanUtils; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; -import com.cloud.agent.api.to.VolumeTO; +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.storage.VolumeApiService; +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.VolumeVO; import com.cloud.storage.dao.VolumeDao; +import com.cloud.usage.dao.UsageVMBackupDao; import com.cloud.user.Account; import com.cloud.user.AccountService; 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; @@ -67,31 +83,36 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { @Inject private BackupPolicyDao backupPolicyDao; - @Inject private VMInstanceDao vmInstanceDao; - @Inject private AccountService accountService; - @Inject - private BackupPolicyVMMapDao backupPolicyVMMapDao; - + private VMBackupDao backupDao; @Inject - private BackupDao backupDao; - + private UsageVMBackupDao usageVMBackupDao; @Inject private VolumeDao volumeDao; - @Inject - private VolumeApiService volumeApiService; + 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_IMPORT_BACKUP_POLICY, eventDescription = "importing backup policy", async = true) - public BackupPolicy importBackupPolicy(Long zoneId, String policyExternalId, String policyName, String policyDescription) { + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_IMPORT_POLICY, eventDescription = "importing backup policy", async = true) + public BackupPolicy importBackupPolicy(final Long zoneId, final String policyExternalId, + final String policyName, final String policyDescription) { final BackupProvider provider = getBackupProvider(zoneId); if (!provider.isBackupPolicy(zoneId, policyExternalId)) { throw new CloudRuntimeException("Policy " + policyExternalId + " does not exist on provider " + provider.getName() + " on zone " + zoneId); @@ -106,86 +127,6 @@ public BackupPolicy importBackupPolicy(Long zoneId, String policyExternalId, Str return savedPolicy; } - @Override - @ActionEvent(eventType = EventTypes.EVENT_ADD_VM_TO_BACKUP_POLICY, eventDescription = "adding VM to backup policy", async = true) - public boolean addVMToBackupPolicy(Long policyId, Long virtualMachineId) { - VMInstanceVO vm = vmInstanceDao.findById(virtualMachineId); - if (vm == null) { - throw new CloudRuntimeException("VM " + virtualMachineId + " does not exist"); - } - // FIXME: check if VM is already assigned to a policy? - BackupPolicyVO policy = backupPolicyDao.findById(policyId); - if (policy == null) { - throw new CloudRuntimeException("Policy " + policy + " does not exist"); - } - BackupProvider backupProvider = getBackupProvider(vm.getDataCenterId()); - boolean result = backupProvider.addVMToBackupPolicy(policy, vm); - if (result) { - BackupPolicyVMMapVO map = backupPolicyVMMapDao.findByVMId(virtualMachineId); - if (map != null) { - backupPolicyVMMapDao.expunge(map.getId()); - } - map = new BackupPolicyVMMapVO(vm.getDataCenterId(), policy.getId(), virtualMachineId); - backupPolicyVMMapDao.persist(map); - LOG.debug("Successfully assigned VM " + virtualMachineId + " to backup policy " + policy.getName()); - } else { - LOG.debug("Could not assign VM " + virtualMachineId + " to backup policy " + policyId); - } - return result; - } - - @Override - @ActionEvent(eventType = EventTypes.EVENT_REMOVE_VM_FROM_BACKUP_POLICY, eventDescription = "removing VM from backup policy", async = true) - public boolean removeVMFromBackupPolicy(Long policyId, Long vmId) { - BackupPolicyVO policy = backupPolicyDao.findById(policyId); - if (policy == null) { - throw new CloudRuntimeException("Policy " + policyId + " does not exist"); - } - VMInstanceVO vm = vmInstanceDao.findById(vmId); - if (vm == null) { - throw new CloudRuntimeException("VM " + vmId + " does not exist"); - } - BackupProvider backupProvider = getBackupProvider(vm.getDataCenterId()); - boolean result = backupProvider.removeVMFromBackupPolicy(policy, vm); - if (result) { - List map = backupPolicyVMMapDao.listByPolicyIdAndVMId(policyId, vmId); - if (map.size() > 1) { - throw new CloudRuntimeException("More than one mapping between VM " + vmId + " and policy " + policyId); - } - backupPolicyVMMapDao.expunge(map.get(0).getId()); - LOG.debug("Successfully removed VM " + vmId + " from backup policy " + policy.getName()); - } else { - LOG.debug("Could not remove VM " + vmId + " from backup policy " + policyId); - } - return result; - } - - @Override - public List listBackupPolicyVMMappings(Long vmId, Long zoneId, Long policyId) { - if (vmId != null) { - return Collections.singletonList(backupPolicyVMMapDao.findByVMId(vmId)); - } - if (zoneId != null) { - return new ArrayList<>(backupPolicyVMMapDao.listByZoneId(zoneId)); - } - if (policyId != null) { - return new ArrayList<>(backupPolicyVMMapDao.listByPolicyId(policyId)); - } - return new ArrayList<>(backupPolicyVMMapDao.listAll()); - } - - @Override - //TODO: Add background job to sync VM backups from the provider - public List listVMBackups(Long vmId) { - VMInstanceVO vm = vmInstanceDao.findById(vmId); - if (vm == null) { - throw new CloudRuntimeException("VM " + vmId + " does not exist"); - } - Long zoneId = vm.getDataCenterId(); - BackupProvider backupProvider = getBackupProvider(zoneId); - return backupDao.listByVmId(zoneId, vmId); - } - /** * List external backup policies for the Backup and Recovery provider registered in the zone zoneId */ @@ -220,7 +161,7 @@ private List listInternalPolicyById(Long policyId) { } @Override - public List listBackupPolicies(Long zoneId, Boolean external, Long policyId) { + public List listBackupPolicies(final Long zoneId, final Boolean external, final Long policyId) { if (policyId != null) { return listInternalPolicyById(policyId); } else { @@ -229,55 +170,112 @@ public List listBackupPolicies(Long zoneId, Boolean external, Long } @Override - @ActionEvent(eventType = EventTypes.EVENT_CREATE_VM_BACKUP, eventDescription = "creating VM backup", async = true) - public Backup createBackup(Long vmId) { - VMInstanceVO vm = vmInstanceDao.findById(vmId); + public List listVMBackups(final Long vmId) { + final VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { - throw new CloudRuntimeException("VM does not exist"); + return new ArrayList<>(backupDao.listAll()); } - BackupPolicyVMMap vmMap = backupPolicyVMMapDao.findByVMId(vmId); - if (vmMap == null) { - throw new CloudRuntimeException("VM " + vmId + " is not assigned to any backup policy"); + return backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_CREATE, eventDescription = "creating VM backup", async = true) + public VMBackup createBackup(final String name, final String description, final Long vmId, final Long policyId) { + final VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM does not exist"); } - BackupPolicyVO policy = backupPolicyDao.findById(vmMap.getPolicyId()); + final BackupPolicyVO policy = backupPolicyDao.findById(policyId); if (policy == null) { throw new CloudRuntimeException("Policy does not exist"); } - BackupProvider backupProvider = getBackupProvider(vm.getDataCenterId()); + VMBackupVO backup = new VMBackupVO(name, description, policyId, vmId, VMBackup.Status.Allocated, vm.getAccountId(), vm.getDataCenterId()); + setBackupVolumes(backup, vm); + backup = backupDao.persist(backup); + if (backup == null) { + throw new CloudRuntimeException("Failed to save backup object in database"); + } + final BackupProvider backupProvider = getBackupProvider(vm.getDataCenterId()); + backup = (VMBackupVO) backupProvider.createVMBackup(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.getPolicyId(), backup.getVmId(), null, + VMBackup.class.getSimpleName(), backup.getUuid()); + } + return backup; + } + + private void setBackupVolumes(VMBackupVO backup, VMInstanceVO vm) { + List vmVolumes = volumeDao.findByInstance(vm.getId()); + backup.setBackedUpVolumes(vmVolumes); + } - Backup vmBackup = backupProvider.createVMBackup(policy, vm); + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_START, eventDescription = "starting VM backup", async = true) + public boolean startVMBackup(final Long vmBackupId) { + final VMBackup vmBackup = backupDao.findById(vmBackupId); if (vmBackup == null) { - return null; + throw new CloudRuntimeException("VM Backup id " + vmBackupId + " does not exist"); } - BackupVO backupVO = backupDao.getBackupVO(vmBackup); - return backupDao.persist(backupVO); + final BackupProvider backupProvider = getBackupProvider(vmBackup.getZoneId()); + return backupProvider.startBackup(vmBackup); } @Override - @ActionEvent(eventType = EventTypes.EVENT_DELETE_VM_BACKUP, eventDescription = "deleting VM backup", async = true) - public boolean deleteBackup(Long backupId) { - BackupVO backup = backupDao.findById(backupId); + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_DELETE, eventDescription = "deleting VM backup", async = true) + public boolean deleteBackup(final Long backupId) { + final VMBackupVO backup = backupDao.findByIdIncludingRemoved(backupId); if (backup == null) { throw new CloudRuntimeException("Backup " + backupId + " does not exist"); } - Long zoneId = backup.getZoneId(); - Long vmId = backup.getVmId(); - VMInstanceVO vm = vmInstanceDao.findById(vmId); + final Long vmId = backup.getVmId(); + final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); if (vm == null) { throw new CloudRuntimeException("VM " + vmId + " does not exist"); } - BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); - boolean result = backupProvider.removeVMBackup(vm, backup.getExternalId()); + + final BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + boolean result = backupProvider.removeVMBackup(vm, backup); if (result) { + backup.setStatus(VMBackup.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.getPolicyId(), backup.getVmId(), null, + VMBackup.class.getSimpleName(), backup.getUuid()); + return true; + } + } + // FIXME: fsm to deal with GC+deletion + backup.setStatus(VMBackup.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, VMBackup backup) { + //TODO: Remove it from the backup manager interface + HypervisorGuru guru = hypervisorGuruManager.getGuru(hypervisorType); + try { + guru.importVirtualMachine(zoneId, domainId, accountId, userId, vmInternalName, backup); + } catch (Exception e) { + e.printStackTrace(); + throw new CloudRuntimeException("Error during vm import: " + e.getMessage()); + } + return true; + } + @Override - @ActionEvent(eventType = EventTypes.EVENT_RESTORE_VM_FROM_BACKUP, eventDescription = "restoring VM from backup", async = true) - public boolean restoreVMFromBackup(Long backupId) { - BackupVO backup = backupDao.findById(backupId); + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true) + public boolean restoreVMFromBackup(final Long backupId, final String restorePointId) { + VMBackupVO backup = backupDao.findById(backupId); if (backup == null) { throw new CloudRuntimeException("Backup " + backupId + " does not exist"); } @@ -287,13 +285,30 @@ public boolean restoreVMFromBackup(Long backupId) { if (vm == null) { throw new CloudRuntimeException("VM " + vmId + " does not exist"); } - return backupProvider.restoreVMFromBackup(vm.getUuid(), backup.getUuid()); + if (!vm.getState().equals(VirtualMachine.State.Stopped)) { + throw new CloudRuntimeException("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 VMBackup.VolumeInfo getVolumeInfo(List backedUpVolumes, String volumeUuid) { + for (VMBackup.VolumeInfo volInfo : backedUpVolumes) { + if (volInfo.getUuid().equals(volumeUuid)) { + return volInfo; + } + } + return null; } @Override - @ActionEvent(eventType = EventTypes.EVENT_RESTORE_VM_FROM_BACKUP, eventDescription = "restoring VM from backup", async = true) - public boolean restoreBackupVolumeAndAttachToVM(Long volumeId, Long vmId, Long backupId) { - BackupVO backup = backupDao.findById(backupId); + @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 { + VMBackupVO backup = backupDao.findById(backupId); if (backup == null) { throw new CloudRuntimeException("Backup " + backupId + " does not exist"); } @@ -303,26 +318,72 @@ public boolean restoreBackupVolumeAndAttachToVM(Long volumeId, Long vmId, Long b if (vm == null) { throw new CloudRuntimeException("VM " + vmId + " does not exist"); } - VolumeVO volume = volumeDao.findByIdIncludingRemoved(volumeId); - if (volume == null) { - throw new CloudRuntimeException("Volume " + volumeId + " could not be found"); + + 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()); + 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)) { + throw new CloudRuntimeException("Error attaching volume " + backedUpVolumeUuid + " to VM " + vm.getUuid()); } - LOG.debug("Asking provider to restore volume " + volumeId + " from backup " + backupId); - VolumeTO restoredVolume = backupProvider.restoreVolumeFromBackup(backup.getUuid(), volume.getUuid()); - attachVolumeToVM(restoredVolume, vm); - return false; + 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.findByInstanceAndType(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 void attachVolumeToVM(VolumeTO restoredVolume, VMInstanceVO vm) { + private boolean attachVolumeToVM(Long zoneId, String restoredVolumeLocation, List backedUpVolumes, + String volumeUuid, VMInstanceVO vm, String datastoreUuid) throws Exception { + HypervisorGuru guru = hypervisorGuruManager.getGuru(vm.getHypervisorType()); + VMBackup.VolumeInfo volumeInfo = getVolumeInfo(backedUpVolumes, volumeUuid); + StoragePoolVO pool = primaryDataStoreDao.findByUuid(datastoreUuid); + LOG.debug("Attaching the restored volume to VM " + vm.getId()); - volumeDao.attachVolume(restoredVolume.getId(), vm.getId(), restoredVolume.getDeviceId()); + try { + return guru.attachRestoredVolumeToVirtualMachine(zoneId, restoredVolumeLocation, volumeInfo, vm, pool.getId()); + } catch (Exception e) { + throw new CloudRuntimeException("Error attach restored volume to VM " + vm.getUuid()); + } } @Override - public boolean deleteBackupPolicy(Long policyId) { + public boolean deleteBackupPolicy(final Long policyId) { BackupPolicyVO policy = backupPolicyDao.findById(policyId); if (policy == null) { throw new CloudRuntimeException("Could not find a backup policy with id: " + policyId); @@ -330,9 +391,24 @@ public boolean deleteBackupPolicy(Long policyId) { return backupPolicyDao.expunge(policy.getId()); } + @Override + public List listVMBackupRestorePoints(final Long backupId) { + VMBackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Could not find backup " + backupId); + } + VMInstanceVO vm = vmInstanceDao.findById(backup.getVmId()); + if (vm == null) { + throw new CloudRuntimeException("Could not find VM: " + backup.getVmId()); + } + BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + return backupProvider.listVMBackupRestorePoints(backup.getUuid(), vm); + } + @Override public boolean configure(String name, Map params) throws ConfigurationException { super.configure(name, params); + backgroundPollManager.submitTask(new BackupSyncTask(this)); return true; } @@ -360,23 +436,20 @@ public BackupProvider getBackupProvider(final Long zoneId) { @Override public List> getCommands() { final List> cmdList = new ArrayList>(); - - // Backup Policy APIs + // VM Backup Policy APIs cmdList.add(ListBackupProvidersCmd.class); cmdList.add(ListBackupPoliciesCmd.class); cmdList.add(ImportBackupPolicyCmd.class); cmdList.add(DeleteBackupPolicyCmd.class); - cmdList.add(AddVMToBackupPolicyCmd.class); - cmdList.add(RemoveVMFromBackupPolicyCmd.class); - cmdList.add(ListBackupPolicyVMMappingsCmd.class); - - // Backup and Restore APIs + // VM Backup APIs cmdList.add(ListVMBackupsCmd.class); cmdList.add(CreateVMBackupCmd.class); + cmdList.add(StartVMBackupCmd.class); cmdList.add(DeleteVMBackupCmd.class); cmdList.add(RestoreVMFromBackupCmd.class); cmdList.add(RestoreVolumeFromBackupAndAttachToVMCmd.class); - + cmdList.add(ImportVMCmdByAdmin.class); + cmdList.add(ListVMBackupRestorePoints.class); return cmdList; } @@ -387,7 +460,11 @@ public String getConfigComponentName() { @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[]{BackupFrameworkEnabled, BackupProviderPlugin}; + return new ConfigKey[]{ + BackupFrameworkEnabled, + BackupProviderPlugin, + BackupSyncPollingInterval + }; } public void setBackupProviders(final List backupProviders) { @@ -407,4 +484,63 @@ private void initializeBackupProviderMap() { } } } + + //////////////////////////////////////////////////// + /////////////// 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 VMBackup backup : backupDao.listByZoneAndState(dataCenter.getId(), VMBackup.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 VMBackup backup : metrics.keySet()) { + final VMBackup.Metric metric = metrics.get(backup); + final VMBackupVO backupVO = (VMBackupVO) backup; + backupVO.setSize(metric.getBackupSize()); + backupVO.setProtectedSize(metric.getDataSize()); + if (backupDao.update(backupVO.getId(), backupVO)) { + usageVMBackupDao.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/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/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java b/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java index d70910249b2f..1645589b7c43 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.UsageVMBackupDao; 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.VMBackupUsageParser; 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 UsageVMBackupDao usageVMBackupDao; + @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 = VMBackupUsageParser.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 (isVMBackupEvent(eventType)) { + createVMBackupEvent(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 isVMBackupEvent(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 @@ -1868,6 +1884,24 @@ private void createVmSnapshotOnPrimaryEvent(UsageEventVO event) { } } + private void createVMBackupEvent(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 UsageVMBackupVO vmBackupVO = new UsageVMBackupVO(zoneId, accountId, domainId, backupId, vmId, created); + usageVMBackupDao.persist(vmBackupVO); + } else if (EventTypes.EVENT_VM_BACKUP_DELETE.equals(event.getType())) { + usageVMBackupDao.removeUsage(accountId, zoneId, backupId); + } + + } + private class Heartbeat extends ManagedContextRunnable { @Override protected void runInContext() { diff --git a/usage/src/main/java/com/cloud/usage/parser/VMBackupUsageParser.java b/usage/src/main/java/com/cloud/usage/parser/VMBackupUsageParser.java new file mode 100644 index 000000000000..aa77f0624c46 --- /dev/null +++ b/usage/src/main/java/com/cloud/usage/parser/VMBackupUsageParser.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.VMBackup; +import org.apache.cloudstack.usage.UsageTypes; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.usage.UsageVMBackupVO; +import com.cloud.usage.UsageVO; +import com.cloud.usage.dao.UsageDao; +import com.cloud.usage.dao.UsageVMBackupDao; +import com.cloud.user.AccountVO; + +@Component +public class VMBackupUsageParser { + public static final Logger LOGGER = Logger.getLogger(VMBackupUsageParser.class); + + private static UsageDao s_usageDao; + private static UsageVMBackupDao s_usageVMBackupDao; + + @Inject + private UsageDao usageDao; + @Inject + private UsageVMBackupDao usageVMBackupDao; + + @PostConstruct + void init() { + s_usageDao = usageDao; + s_usageVMBackupDao = usageVMBackupDao; + } + + 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 usageVMBackups = s_usageVMBackupDao.getUsageRecords(account.getId(), startDate, endDate); + if (usageVMBackups == null || usageVMBackups.isEmpty()) { + LOGGER.debug("No VM Backup usage for this period"); + return true; + } + + final Map vmUsageMap = new HashMap<>(); + for (final UsageVMBackupVO usageVMBackup : usageVMBackups) { + final Long vmId = usageVMBackup.getVmId(); + final Long zoneId = usageVMBackup.getZoneId(); + if (vmUsageMap.get(vmId) == null) { + vmUsageMap.put(vmId, new VMBackupUsageParser.BackupInfo(new VMBackup.Metric(0L, 0L), zoneId, vmId)); + } + final VMBackup.Metric metric = vmUsageMap.get(vmId).getMetric(); + metric.setBackupSize(metric.getBackupSize() + usageVMBackup.getSize()); + metric.setDataSize(metric.getDataSize() + usageVMBackup.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("VMBackup 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.VM_BACKUP, rawUsage, vmId, null, null, null, vmId, + backupInfo.getMetric().getBackupSize(), backupInfo.getMetric().getDataSize(), startDate, endDate); + s_usageDao.persist(usageRecord); + } + + return true; + } + + static class BackupInfo { + VMBackup.Metric metric; + Long zoneId; + Long vmId; + + public BackupInfo(VMBackup.Metric metric, Long zoneId, Long vmId) { + this.metric = metric; + this.zoneId = zoneId; + this.vmId = vmId; + } + + public VMBackup.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"); + } } From 8833ddd07b4169a61e4ee243d203098fb81ec476 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 16 Aug 2018 17:35:41 +0530 Subject: [PATCH 03/21] phase2: fix build/dependency issues with winrm Fix restore volume powershell cmdlets, several other fixes/refactorings --- .../user/backup/DeleteVMBackupCmd.java | 1 + client/pom.xml | 27 +++ .../backup/DummyBackupProvider.java | 4 +- plugins/backup/veeam/pom.xml | 17 +- .../backup/VeeamBackupProvider.java | 10 +- .../cloudstack/backup/veeam/VeeamClient.java | 167 ++++++++++-------- .../backup/veeam/VeeamClientTest.java | 13 +- pom.xml | 4 +- .../hypervisor/vmware/util/VmwareClient.java | 2 +- 9 files changed, 144 insertions(+), 101 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java index 778353bbf7b3..6c144cc0e959 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java @@ -42,6 +42,7 @@ description = "Delete VM backup", responseObject = SuccessResponse.class, since = "4.12.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +// FIXME: make this async command public class DeleteVMBackupCmd extends BaseCmd { public static final String APINAME = "deleteVMBackup"; diff --git a/client/pom.xml b/client/pom.xml index 0613bdc888f1..ee50e801b194 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -764,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 + 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 cc49524dfee8..fc6279329b90 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 @@ -24,10 +24,10 @@ import javax.inject.Inject; -import com.cloud.utils.Pair; import org.apache.cloudstack.backup.dao.VMBackupDao; import org.apache.log4j.Logger; +import com.cloud.utils.Pair; import com.cloud.utils.component.AdapterBase; import com.cloud.vm.VirtualMachine; @@ -45,7 +45,7 @@ public String getName() { @Override public String getDescription() { - return "Dummy B&R Plugin"; + return "Dummy Backup Plugin"; } @Override diff --git a/plugins/backup/veeam/pom.xml b/plugins/backup/veeam/pom.xml index 794d60ae3e72..9a6515ddca28 100644 --- a/plugins/backup/veeam/pom.xml +++ b/plugins/backup/veeam/pom.xml @@ -28,6 +28,7 @@ + org.apache.cloudstack cloud-plugin-hypervisor-vmware @@ -43,22 +44,6 @@ commons-lang3 ${cs.commons-lang3.version} - - io.cloudsoft.windows - winrm4j-client - ${cs.winrm4j.version} - - - io.cloudsoft.windows - winrm4j-service - ${cs.winrm4j.version} - provided - - - io.cloudsoft.windows - winrm4j - ${cs.winrm4j.version} - com.github.tomakehurst wiremock-standalone 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 40b34f3a7902..2b035d0e433a 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 @@ -28,7 +28,6 @@ import javax.inject.Inject; -import com.cloud.utils.Pair; import org.apache.cloudstack.backup.veeam.VeeamClient; import org.apache.cloudstack.backup.veeam.api.Job; import org.apache.cloudstack.framework.config.ConfigKey; @@ -41,6 +40,7 @@ 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; @@ -176,7 +176,10 @@ public boolean removeVMBackup(final VirtualMachine vm, final VMBackup backup) { LOG.warn("Failed to remove VM from Veeam Job id: " + backup.getExternalId()); } final String clonedJobName = getGuestBackupName(vm.getInstanceName(), backup.getUuid()); - return client.deleteJobAndBackup(clonedJobName) && client.listJob(clonedJobName) == null; + if (!client.deleteJobAndBackup(clonedJobName)) { + LOG.warn("Failed to remove Veeam job and backup for job: " + clonedJobName); + } + return client.listJob(clonedJobName) == null; } @Override @@ -193,6 +196,7 @@ public boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String @Override public Pair restoreBackedUpVolume(long zoneId, String backupUuid, String restorePointId, String volumeUuid, String hostIp, String dataStoreUuid) { + return getClient(zoneId).restoreVMToDifferentLocation(restorePointId, hostIp, dataStoreUuid); } @@ -244,6 +248,6 @@ public String getName() { @Override public String getDescription() { - return "Veeam B&R Plugin"; + return "Veeam Backup Plugin"; } } 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 882312de1c4a..b22d0c26e9ee 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 @@ -27,16 +27,16 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.ArrayList; -import java.util.Collections; +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 com.cloud.utils.Pair; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.backup.BackupPolicy; @@ -66,7 +66,6 @@ import org.apache.http.client.CookieStore; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; -import org.apache.http.client.config.AuthSchemes; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; @@ -83,16 +82,15 @@ 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; -import io.cloudsoft.winrm4j.winrm.WinRmTool; -import io.cloudsoft.winrm4j.winrm.WinRmToolResponse; - public class VeeamClient { private static final Logger LOG = Logger.getLogger(VeeamClient.class); @@ -101,7 +99,11 @@ public class VeeamClient { private final HttpClient httpClient; private final HttpClientContext httpContext = HttpClientContext.create(); private final CookieStore httpCookieStore = new BasicCookieStore(); - private final WinRmTool winRmTool; + + 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); @@ -148,14 +150,13 @@ public VeeamClient(final String url, final String username, final String passwor throw new CloudRuntimeException("Failed to authenticate Veeam API service due to:" + e.getMessage()); } - final WinRmTool.Builder builder = WinRmTool.Builder.builder(apiURI.getHost(), username, password); - builder.useHttps(true); - builder.disableCertificateChecks(true); - builder.setAuthenticationScheme(AuthSchemes.NTLM); - builder.port(WinRmTool.DEFAULT_WINRM_HTTPS_PORT); - winRmTool = builder.build(); - winRmTool.setOperationTimeout(timeout * 1000L); - winRmTool.setRetriesForConnectionFailures(1); + 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) { @@ -278,9 +279,10 @@ private RestoreSession parseRestoreSessionResponse(HttpResponse response) throws 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 < 20; i++) { + for (int i = 0; i < 60; i++) { final HttpResponse taskResponse = get("/tasks/" + task.getTaskId()); final Task polledTask = parseTaskResponse(taskResponse); if (polledTask.getState().equals("Finished")) { @@ -515,47 +517,73 @@ public boolean restoreFullVM(final String vmwareInstanceName, final String resto //////////////// 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 deleteJobAndBackup(final String jobName) { - final WinRmToolResponse response = winRmTool.executePs( - Collections.singletonList( - String.format( - "Add-PSSnapin VeeamPSSnapin\n" + - "Get-VBRBackup -Name \"%s\" | Remove-VBRBackup -FromDisk -Confirm:$false\n" + - "Get-VBRJob -Name \"%s\" | Remove-VBRJob -Confirm:$false\n" + - "Get-VBRBackupRepository | Sync-VBRBackupRepository", jobName, jobName) - )); - return response.getStatusCode() == 0 && !response.getStdErr().contains("Failed to delete"); + Pair result = executePowerShellCommands(Arrays.asList( + String.format("$job = Get-VBRJob -Name \"%s\"", jobName), + "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 List psScript = Collections.singletonList( - "Add-PSSnapin VeeamPSSnapin\n" + - "$backups = Get-VBRBackup\n" + - "foreach ($backup in $backups) {\n" + - "$backup.JobName\n" + - "$storageGroups = $backup.GetStorageGroups()\n" + - "foreach ($group in $storageGroups)\n" + - "{\n" + - " $usedSize = 0\n" + - " $dataSize = 0\n" + - " $sizePerStorage = $group.GetStorages().Stats.BackupSize\n" + - " $dataPerStorage = $group.GetStorages().Stats.DataSize\n" + - " foreach ($size in $sizePerStorage)\n" + - " {\n" + - " $usedSize += $size\n" + - " }\n" + - " foreach ($size in $dataPerStorage)\n" + - " {\n" + - " $dataSize += $size\n" + - " }\n" + - " $usedSize\n" + - " $dataSize\n" + - "}\n" + - "echo \";\"" + - "}\n"); - final WinRmToolResponse response = winRmTool.executePs(psScript); + 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.getStdOut().split(";\r\n")) { + for (final String block : response.second().split(separator + "\r\n")) { final String[] parts = block.split("\r\n"); if (parts.length != 3) { continue; @@ -589,19 +617,16 @@ private VMBackup.RestorePoint getRestorePointFromBlock(String[] parts) { } public List listRestorePoints(String backupName, String vmInternalName) { - final List psScript = Collections.singletonList( - String.format( - "Add-PSSnapin VeeamPSSnapin\n" + - "$backup = Get-VBRBackup -Name \"%s\"\n" + - "Get-VBRRestorePoint -Backup:$backup -Name \"%s\"", - backupName, vmInternalName) + final List cmds = Arrays.asList( + String.format("$backup = Get-VBRBackup -Name \"%s\"", backupName), + String.format("Get-VBRRestorePoint -Backup:$backup -Name \"%s\"", vmInternalName) ); - final WinRmToolResponse response = winRmTool.executePs(psScript); - if (response == null) { + 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.getStdOut().split("\r\n\r\n")) { + for (final String block : response.second().split("\r\n\r\n")) { if (block.isEmpty()) { continue; } @@ -614,19 +639,17 @@ public List listRestorePoints(String backupName, String v public Pair restoreVMToDifferentLocation(String restorePointId, String hostIp, String dataStoreUuid) { final String restoreLocation = "CS-RSTR-" + UUID.randomUUID().toString(); final String datastoreId = dataStoreUuid.replace("-",""); - final List psScript = Collections.singletonList( - String.format( - "Add-PSSnapin VeeamPSSnapin\n" + - "$point = Get-VBRRestorePoint | where {$_.Id -eq \"%s\"}\n" + - "$server = Get-VBRServer -Name \"%s\"\n" + - "$ds = Find-VBRViDatastore -Server:$server -Name \"%s\"\n" + - "Start-VBRRestoreVM -RestorePoint:$point -Server:$server -Datastore:$ds -VMName \"%s\"", - restorePointId, hostIp, datastoreId, restoreLocation) + 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) ); - final WinRmToolResponse response = winRmTool.executePs(psScript); - if (response == null) { + Pair result = executePowerShellCommands(cmds); + if (result == null || !result.first()) { throw new CloudRuntimeException("Failed to restore VM to location " + restoreLocation); } - return new Pair<>(response.getStatusCode() == 0, restoreLocation); + return new Pair<>(result.first(), restoreLocation); } } 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 index aec9ff3a7a04..26b15d04095d 100644 --- 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 @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; +import com.cloud.utils.Pair; import org.apache.cloudstack.backup.BackupPolicy; import org.apache.cloudstack.backup.VMBackup; import org.apache.cloudstack.backup.veeam.api.Job; @@ -89,7 +90,8 @@ public void testVeeamJobs() { 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-12345"); + boolean result = client.cloneVeeamJob(j, "some-uuid-cloned"); + client.deleteJobAndBackup("some-uuid-cloned"); } public void testVeeamPS() throws Exception { @@ -97,9 +99,10 @@ public void testVeeamPS() throws Exception { Map sizes = client.getBackupMetrics(); Job j = client.listJob("ZONE1-GOLD_clone1"); } - @Test - public void test() { - String a = "Id :asdasd"; - System.out.println(a.matches("Id(/s)*")); + + 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/pom.xml b/pom.xml index adc14735b857..63edab43a631 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,7 +130,7 @@ 23.6-jre 4.5.4 4.4.8 - 2.9.5 + 2.9.6 1.9.2 0.16 3.22.0-GA 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 660cfa78727426b4f9e3723eb574066d2a800119 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Thu, 23 Aug 2018 00:39:36 -0300 Subject: [PATCH 04/21] Improvements and refactors --- .../apache/cloudstack/api/ApiConstants.java | 2 +- .../api/response/VMBackupResponse.java | 15 ++++--- .../apache/cloudstack/backup/VMBackup.java | 1 + .../apache/cloudstack/backup/VMBackupVO.java | 42 ++++--------------- .../backup/dao/VMBackupDaoImpl.java | 9 +--- .../META-INF/db/schema-41110to41200.sql | 2 +- .../backup/DummyBackupProvider.java | 7 +++- .../backup/VeeamBackupProvider.java | 3 +- .../cloudstack/backup/veeam/VeeamBackup.java | 5 +++ .../cloudstack/backup/veeam/VeeamClient.java | 2 +- .../cloudstack/backup/BackupManagerImpl.java | 11 ++++- 11 files changed, 43 insertions(+), 56 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 2ac84f9e96c2..ec4c006c3761 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -338,7 +338,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 VOLUME_IDS = "volumeids"; + public static final String VOLUMES = "volumes"; public static final String ZONE_ID = "zoneid"; public static final String ZONE_NAME = "zonename"; public static final String NETWORK_TYPE = "networktype"; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VMBackupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VMBackupResponse.java index 28b8ce49fd76..2b3a862eef0f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VMBackupResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VMBackupResponse.java @@ -18,7 +18,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; @@ -59,9 +58,9 @@ public class VMBackupResponse extends BaseResponse { @Param(description = "backup vm id") private String vmId; - @SerializedName(ApiConstants.VOLUME_IDS) - @Param(description = "backup volume ids") - private List volumeIds; + @SerializedName(ApiConstants.VOLUMES) + @Param(description = "backup volumes") + private String volumes; @SerializedName(ApiConstants.STATUS) @Param(description = "backup volume ids") @@ -159,12 +158,12 @@ public void setProtectedSize(Long protectedSize) { this.protectedSize = protectedSize; } - public List getVolumeIds() { - return volumeIds; + public String getVolumes() { + return volumes; } - public void setVolumeIds(List volumeIds) { - this.volumeIds = volumeIds; + public void setVolumes(String volumes) { + this.volumes = volumes; } public Date getCreatedDate() { diff --git a/api/src/main/java/org/apache/cloudstack/backup/VMBackup.java b/api/src/main/java/org/apache/cloudstack/backup/VMBackup.java index 16a69bc287d5..87c76a1ad9f5 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/VMBackup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/VMBackup.java @@ -135,6 +135,7 @@ public String toString() { String getDescription(); Long getVmId(); List getBackedUpVolumes(); + String getVolumes(); Status getStatus(); Long getSize(); Long getProtectedSize(); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/VMBackupVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/VMBackupVO.java index c42ff799ca56..76813026400f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/VMBackupVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/VMBackupVO.java @@ -17,10 +17,9 @@ package org.apache.cloudstack.backup; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.List; -import java.util.StringJoiner; import java.util.UUID; import javax.persistence.Column; @@ -31,11 +30,8 @@ import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; -import javax.persistence.Transient; -import com.cloud.storage.Volume; -import com.cloud.storage.VolumeVO; -import org.apache.commons.lang.StringUtils; +import com.google.gson.Gson; @Entity @Table(name = "vm_backup") @@ -63,7 +59,7 @@ public class VMBackupVO implements VMBackup { @Column(name = "vm_id") private Long vmId; - @Column(name = "volumes") + @Column(name = "volumes", length = 65535) private String volumes; @Column(name = "size") @@ -89,13 +85,9 @@ public class VMBackupVO implements VMBackup { @Temporal(value = TemporalType.TIMESTAMP) private Date removed; - @Transient - private List volumeIds; - public VMBackupVO() { this.uuid = UUID.randomUUID().toString(); this.created = new Date(); - volumeIds = new ArrayList<>(); } public VMBackupVO(final String name, final String description, final long policyId, final Long vmId, @@ -219,27 +211,11 @@ public void setCreated(Date start) { @Override public List getBackedUpVolumes() { - List info = new ArrayList<>(); - if (StringUtils.isNotBlank(this.volumes)) { - String[] volumes = StringUtils.substringBetween(this.volumes,"[", "]").split(","); - for (String vol : volumes) { - String[] volParts = vol.split(":"); - VMBackup.VolumeInfo volumeInfo = new VolumeInfo(volParts[0], volParts[1], - Volume.Type.valueOf(volParts[2]), Long.valueOf(volParts[3])); - info.add(volumeInfo); - } - } - return info; - } - - public void setBackedUpVolumes(List volumes) { - StringJoiner stringJoiner = new StringJoiner(",", "[", "]"); - for (VolumeVO volume : volumes) { - String volTag = volume.getUuid() + ":" + volume.getPath() + ":" + - volume.getVolumeType().toString() + ":" + volume.getSize(); - stringJoiner.add(volTag); - } - this.volumes = stringJoiner.toString(); + return Arrays.asList(new Gson().fromJson(this.volumes, VolumeInfo[].class)); + } + + public void setBackedUpVolumes(List volumes) { + this.volumes = new Gson().toJson(volumes); } public Date getRemoved() { @@ -250,7 +226,7 @@ public void setRemoved(Date removed) { this.removed = removed; } - protected String getVolumes() { + public String getVolumes() { return volumes; } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java index 37b006ea3897..0d9dee0bdc8e 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java @@ -26,7 +26,6 @@ import org.apache.cloudstack.api.response.VMBackupResponse; import org.apache.cloudstack.backup.VMBackup; import org.apache.cloudstack.backup.VMBackupVO; -import org.apache.commons.collections.CollectionUtils; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; @@ -135,13 +134,7 @@ public VMBackupResponse newBackupResponse(VMBackup backup) { backupResponse.setName(backup.getName()); backupResponse.setDescription(backup.getDescription()); backupResponse.setVmId(vm.getUuid()); - if (CollectionUtils.isNotEmpty(backup.getBackedUpVolumes())) { - List volIds = new ArrayList<>(); - for (VMBackup.VolumeInfo volInfo : backup.getBackedUpVolumes()) { - volIds.add(volInfo.toString()); - } - backupResponse.setVolumeIds(volIds); - } + backupResponse.setVolumes(backup.getVolumes()); backupResponse.setStatus(backup.getStatus()); backupResponse.setSize(backup.getSize()); backupResponse.setProtectedSize(backup.getProtectedSize()); diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql b/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql index 15ab236517b5..271c8fbe7c5c 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql @@ -60,7 +60,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`vm_backup` ( `external_id` varchar(80) COMMENT 'backup ID on provider side', `policy_id` bigint(20) unsigned NOT NULL, `vm_id` bigint(20) unsigned NOT NULL, - `volumes` varchar(5100), + `volumes` text, `status` varchar(20) NOT NULL, `size` bigint(20) DEFAULT 0, `protected_size` bigint(20) DEFAULT 0, 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 fc6279329b90..3da90b6519b9 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 @@ -84,7 +84,7 @@ public boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String 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 VMBackup Provider"); - return null; + return new Pair<>(true, null); } @Override @@ -118,11 +118,14 @@ public boolean removeVMBackup(VirtualMachine vm, VMBackup backup) { @Override public boolean startBackup(VMBackup vmBackup) { + s_logger.debug("Starting backup " + vmBackup.getUuid() + " on Dummy provider"); return true; } @Override public List listVMBackupRestorePoints(String backupUuid, VirtualMachine vm) { - return null; + return Arrays.asList( + new VMBackup.RestorePoint("aaaaaaaa", "22/08/2017", "Full"), + new VMBackup.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 2b035d0e433a..1ca6c5cb35da 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 @@ -151,10 +151,11 @@ public VMBackup createVMBackup(final BackupPolicy policy, final VirtualMachine v final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm); if (client.addVMToVeeamJob(job.getExternalId(), vm.getInstanceName(), vmwareDC.getVcenterHost())) { VMBackupVO vmBackup = ((VMBackupVO) backup); - vmBackup.setStatus(VMBackup.Status.Queued); + vmBackup.setStatus(VMBackup.Status.BackedUp); vmBackup.setExternalId(job.getExternalId()); vmBackup.setCreated(new Date()); if (!startBackup(vmBackup)) { + vmBackup.setStatus(VMBackup.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/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 3ab95f6cbedf..ffeb4668286d 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 @@ -67,6 +67,11 @@ public List getBackedUpVolumes() { return null; } + @Override + public String getVolumes() { + return null; + } + @Override public Status getStatus() { return null; 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 b22d0c26e9ee..35432766afe9 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 @@ -604,7 +604,7 @@ private VMBackup.RestorePoint getRestorePointFromBlock(String[] parts) { for (String part : parts) { if (part.matches("Id(\\s)+:(.)*")) { String[] split = part.split(":"); - id = split[1]; + id = split[1].trim(); } else if (part.matches("CreationTime(\\s)+:(.)*")) { String [] split = part.split(":", 2); created = split[1].trim(); 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 3a8f5f53938f..5073b0134f09 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -210,7 +210,16 @@ public VMBackup createBackup(final String name, final String description, final private void setBackupVolumes(VMBackupVO backup, VMInstanceVO vm) { List vmVolumes = volumeDao.findByInstance(vm.getId()); - backup.setBackedUpVolumes(vmVolumes); + List volInfo = createVolumeInfoFromVolumes(vmVolumes); + backup.setBackedUpVolumes(volInfo); + } + + private List createVolumeInfoFromVolumes(List vmVolumes) { + List list = new ArrayList<>(); + for (VolumeVO vol : vmVolumes) { + list.add(new VMBackup.VolumeInfo(vol.getUuid(), vol.getPath(), vol.getVolumeType(), vol.getSize())); + } + return list; } @Override From 240ff8220fc540da42e4deb57a0680286824c8f5 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 23 Aug 2018 16:35:13 +0530 Subject: [PATCH 05/21] make API args consistent with arg/types Signed-off-by: Rohit Yadav --- .../user/backup/ListVMBackupRestorePoints.java | 18 ++++++++++-------- .../user/backup/RestoreVMFromBackupCmd.java | 6 +++--- .../backup/veeam/VeeamClientTest.java | 2 +- .../cloudstack/backup/BackupManagerImpl.java | 10 +++++----- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupRestorePoints.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupRestorePoints.java index 44aa57f190e2..838166a8a115 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupRestorePoints.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupRestorePoints.java @@ -17,11 +17,10 @@ package org.apache.cloudstack.api.command.user.backup; -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 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; @@ -34,8 +33,11 @@ import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.backup.VMBackup; -import javax.inject.Inject; -import java.util.List; +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 = ListVMBackupRestorePoints.APINAME, description = "Lists VM backup restore points", @@ -51,7 +53,7 @@ public class ListVMBackupRestorePoints extends BaseBackupListCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.ID, + @Parameter(name = ApiConstants.VM_BACKUP_ID, type = CommandType.UUID, entityType = VMBackupResponse.class, required = true, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java index daad3fb1257f..b8d7545afab6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java @@ -19,7 +19,6 @@ import javax.inject.Inject; -import com.cloud.event.EventTypes; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -28,11 +27,12 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.VMBackupResponse; import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.VMBackupResponse; 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; @@ -54,7 +54,7 @@ public class RestoreVMFromBackupCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.ID, + @Parameter(name = ApiConstants.VM_BACKUP_ID, type = CommandType.UUID, entityType = VMBackupResponse.class, required = true, 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 index 26b15d04095d..1e877e1bbfb6 100644 --- 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 @@ -28,7 +28,6 @@ import java.util.List; import java.util.Map; -import com.cloud.utils.Pair; import org.apache.cloudstack.backup.BackupPolicy; import org.apache.cloudstack.backup.VMBackup; import org.apache.cloudstack.backup.veeam.api.Job; @@ -37,6 +36,7 @@ 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; 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 5073b0134f09..0226ac77b879 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -25,10 +25,6 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.storage.ScopeType; -import com.cloud.storage.Volume; -import com.cloud.storage.dao.DiskOfferingDao; -import com.cloud.utils.Pair; import org.apache.cloudstack.api.command.admin.backup.DeleteBackupPolicyCmd; import org.apache.cloudstack.api.command.admin.backup.ImportBackupPolicyCmd; import org.apache.cloudstack.api.command.admin.backup.ListBackupProvidersCmd; @@ -65,11 +61,15 @@ 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.UsageVMBackupDao; import com.cloud.user.Account; 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; @@ -455,10 +455,10 @@ public List> getCommands() { cmdList.add(CreateVMBackupCmd.class); cmdList.add(StartVMBackupCmd.class); cmdList.add(DeleteVMBackupCmd.class); + cmdList.add(ListVMBackupRestorePoints.class); cmdList.add(RestoreVMFromBackupCmd.class); cmdList.add(RestoreVolumeFromBackupAndAttachToVMCmd.class); cmdList.add(ImportVMCmdByAdmin.class); - cmdList.add(ListVMBackupRestorePoints.class); return cmdList; } From 2fe6c44eedb63a52301d62022856d2620336168f Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 24 Aug 2018 16:38:29 +0530 Subject: [PATCH 06/21] bugfix: allow listing of vmbackups of removed VMs Signed-off-by: Rohit Yadav --- .../org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java | 2 +- .../org/apache/cloudstack/backup/BackupManagerImpl.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java index 0d9dee0bdc8e..bd5b03a04196 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java @@ -123,7 +123,7 @@ public List listByZoneAndState(Long zoneId, VMBackup.Status state) { @Override public VMBackupResponse newBackupResponse(VMBackup backup) { AccountVO account = accountDao.findById(backup.getAccountId()); - VMInstanceVO vm = vmInstanceDao.findById(backup.getVmId()); + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); DataCenterVO zone = dataCenterDao.findById(backup.getZoneId()); VMBackupResponse backupResponse = new VMBackupResponse(); 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 0226ac77b879..552686d0cfcb 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -171,7 +171,7 @@ public List listBackupPolicies(final Long zoneId, final Boolean ex @Override public List listVMBackups(final Long vmId) { - final VMInstanceVO vm = vmInstanceDao.findById(vmId); + final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); if (vm == null) { return new ArrayList<>(backupDao.listAll()); } @@ -290,7 +290,7 @@ public boolean restoreVMFromBackup(final Long backupId, final String restorePoin } Long vmId = backup.getVmId(); BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); - VMInstanceVO vm = vmInstanceDao.findById(vmId); + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); if (vm == null) { throw new CloudRuntimeException("VM " + vmId + " does not exist"); } @@ -323,7 +323,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, } BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); - VMInstanceVO vm = vmInstanceDao.findById(vmId); + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); if (vm == null) { throw new CloudRuntimeException("VM " + vmId + " does not exist"); } From bceb29f2b02e5bc32047f2a7e66c8eda055a437e Mon Sep 17 00:00:00 2001 From: nvazquez Date: Sun, 26 Aug 2018 23:12:27 -0300 Subject: [PATCH 07/21] Bugfixes: import destroyed VM, check if VM is stopped only for existing VMs, and others --- .../com/cloud/hypervisor/HypervisorGuru.java | 2 +- .../com/cloud/hypervisor/guru/VMwareGuru.java | 61 +++++++++++-------- .../cloud/hypervisor/HypervisorGuruBase.java | 2 +- .../cloudstack/backup/BackupManagerImpl.java | 12 ++-- 4 files changed, 45 insertions(+), 32 deletions(-) diff --git a/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java b/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java index 095c41790fca..fc1c889d87b1 100644 --- a/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java +++ b/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java @@ -90,5 +90,5 @@ VirtualMachine importVirtualMachine(long zoneId, long domainId, long accountId, String vmInternalName, VMBackup backup) throws Exception; boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location, VMBackup.VolumeInfo volumeInfo, - VirtualMachine vm, long poolId) throws Exception; + VirtualMachine vm, long poolId, VMBackup backup) throws Exception; } 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 82c99003db94..682f024c1360 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 @@ -769,12 +769,22 @@ private Long getImportingVMServiceOffering(VirtualMachineConfigSummary configSum /** * Check if disk is ROOT disk */ - private boolean isRootDisk(VirtualDisk disk, Map disksMapping) { + private boolean isRootDisk(VirtualDisk disk, Map disksMapping, VMBackup backup) { if (!disksMapping.containsKey(disk)) { return false; } VolumeVO volumeVO = disksMapping.get(disk); - return volumeVO != null && volumeVO.getVolumeType().equals(Volume.Type.ROOT); + if (volumeVO == null) { + List backedUpVolumes = backup.getBackedUpVolumes(); + for (VMBackup.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"); } /** @@ -813,9 +823,9 @@ private Long getPoolId(VirtualDisk disk) { /** * Get pool ID for VM being imported */ - private Long getImportingVMPoolId(List disks, Map disksMapping) { + private Long getImportingVMPoolId(List disks, Map disksMapping, VMBackup backup) { for (VirtualDisk disk : disks) { - if (isRootDisk(disk, disksMapping)) { + if (isRootDisk(disk, disksMapping, backup)) { return getPoolId(disk); } } @@ -868,14 +878,12 @@ private Long getTemplatePoolId(VirtualMachineMO template) throws Exception { * Get template size */ private Long getTemplateSize(VirtualMachineMO template, String vmInternalName, - Map disksMapping) throws Exception { + Map disksMapping, VMBackup backup) throws Exception { List disks = template.getVirtualDisks(); - for (VirtualDisk disk : disks) { - if (isRootDisk(disk, disksMapping)) { - return disk.getCapacityInBytes(); - } + if (CollectionUtils.isEmpty(disks)) { + throw new CloudRuntimeException("Couldn't find VM template size"); } - throw new CloudRuntimeException("Could not find ROOT disk for VM: " + vmInternalName); + return disks.get(0).getCapacityInBytes(); } /** @@ -921,15 +929,15 @@ private void updateTemplateRef(long templateId, Long poolId, String templatePath * 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) throws Exception { + Long guestOsId, long accountId, Map disksMapping, VMBackup backup) throws Exception { for (VirtualDisk disk : virtualDisks) { - if (isRootDisk(disk, disksMapping)) { + 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); + Long templateSize = getTemplateSize(template, vmInternalName, disksMapping, backup); long templateId = getTemplateId(templatePath, vmInternalName, guestOsId, accountId); updateTemplateRef(templateId, poolId, templatePath, templateSize); return templateId; @@ -1015,7 +1023,6 @@ private long getDiskOfferingId(long size, Storage.ProvisioningType provisioningT protected VolumeVO updateVolume(VirtualDisk disk, Map disksMapping, VirtualMachineMO vmToImport, Long poolId) throws Exception { VolumeVO volumeVO = disksMapping.get(disk); String volumeName = getVolumeName(disk, vmToImport); - volumeVO.setName(volumeName); volumeVO.setPath(volumeName); volumeVO.setPoolId(poolId); _volumeDao.update(volumeVO.getId(), volumeVO); @@ -1026,7 +1033,7 @@ protected VolumeVO updateVolume(VirtualDisk disk, Map dis * Get volumes for VM being imported */ private void importVMVolumes(VMInstanceVO vmInstanceVO, Long poolId, List virtualDisks, - Map disksMapping, VirtualMachineMO vmToImport) throws Exception { + Map disksMapping, VirtualMachineMO vmToImport, VMBackup backup) throws Exception { long zoneId = vmInstanceVO.getDataCenterId(); long accountId = vmInstanceVO.getAccountId(); long domainId = vmInstanceVO.getDomainId(); @@ -1036,10 +1043,10 @@ private void importVMVolumes(VMInstanceVO vmInstanceVO, Long poolId, List vols = new ArrayList<>(); for (VirtualDisk disk : virtualDisks) { VolumeVO volumeVO; - if (disksMapping.containsKey(disk)) { + 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); + volumeVO = createVolume(disk, vmToImport, domainId, zoneId, accountId, instanceId, poolId, templateId, backup); } vols.add(volumeVO); } @@ -1058,15 +1065,21 @@ private void removeUnusedVolumes(List vols, VMInstanceVO vmInstanceVO) } private VolumeVO createVolume(VirtualDisk disk, VirtualMachineMO vmToImport, long domainId, long zoneId, - long accountId, long instanceId, Long poolId, long templateId) throws Exception { + long accountId, long instanceId, Long poolId, long templateId, VMBackup backup) throws Exception { Long size = disk.getCapacityInBytes(); + List backedUpVolumes = backup.getBackedUpVolumes(); + Volume.Type type = Volume.Type.DATADISK; + for (VMBackup.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, domainId); - Volume.Type type = Volume.Type.DATADISK; Integer unitNumber = disk.getUnitNumber(); return createVolumeRecord(type, volumeName, zoneId, domainId, accountId, diskOfferingId, provisioningType, size, instanceId, poolId, templateId, unitNumber); @@ -1308,14 +1321,14 @@ public VMInstanceVO doInTransaction(TransactionStatus status) throws Exception { long guestOsId = getImportingVMGuestOs(configSummary); long serviceOfferingId = getImportingVMServiceOffering(configSummary, runtimeInfo); - long poolId = getImportingVMPoolId(virtualDisks, disksMapping); - long templateId = getImportingVMTemplate(virtualDisks, dcMo, vmInternalName, guestOsId, accountId, disksMapping); + long poolId = getImportingVMPoolId(virtualDisks, disksMapping, backup); + 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, poolId, virtualDisks, disksMapping, vmToImport); + importVMVolumes(vmInstanceVO, poolId, virtualDisks, disksMapping, vmToImport, backup); return vmInstanceVO; } }); @@ -1323,7 +1336,7 @@ public VMInstanceVO doInTransaction(TransactionStatus status) throws Exception { @Override public boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location, VMBackup.VolumeInfo volumeInfo, - VirtualMachine vm, long poolId) throws Exception { + VirtualMachine vm, long poolId, VMBackup backup) throws Exception { DatacenterMO dcMo = getDatacenterMO(zoneId); VirtualMachineMO vmRestored = findVM(dcMo, location); VirtualMachineMO vmMo = findVM(dcMo, vm.getInstanceName()); @@ -1349,7 +1362,7 @@ public boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location VirtualDisk attachedDisk = getAttachedDisk(vmMo, diskPath); createVolume(attachedDisk, vmMo, vm.getDomainId(), vm.getDataCenterId(), vm.getAccountId(), vm.getId(), - poolId, vm.getTemplateId()); + poolId, vm.getTemplateId(), backup); return true; } diff --git a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java index 7177a82209f4..db0c07491eb9 100644 --- a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java +++ b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java @@ -233,7 +233,7 @@ public VirtualMachine importVirtualMachine(long zoneId, long domainId, long acco @Override public boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location, VMBackup.VolumeInfo volumeInfo, - VirtualMachine vm, long poolId) throws Exception { + VirtualMachine vm, long poolId, VMBackup backup) throws Exception { return false; } } 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 552686d0cfcb..1b4d5401e03d 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -292,10 +292,10 @@ public boolean restoreVMFromBackup(final Long backupId, final String restorePoin BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); if (vm == null) { - throw new CloudRuntimeException("VM " + vmId + " does not exist"); + throw new CloudRuntimeException("VM " + vmId + " couldn't be found on existing or removed VMs"); } - if (!vm.getState().equals(VirtualMachine.State.Stopped)) { - throw new CloudRuntimeException("VM should be stopped before being restored from backup"); + if (vm.getRemoved() == null && !vm.getState().equals(VirtualMachine.State.Stopped)) { + 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()); @@ -340,7 +340,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, throw new CloudRuntimeException("Error restoring volume " + backedUpVolumeUuid); } if (!attachVolumeToVM(backup.getZoneId(), result.second(), backup.getBackedUpVolumes(), - backedUpVolumeUuid, vm, datastoreUuid)) { + backedUpVolumeUuid, vm, datastoreUuid, backup)) { throw new CloudRuntimeException("Error attaching volume " + backedUpVolumeUuid + " to VM " + vm.getUuid()); } return true; @@ -378,14 +378,14 @@ private String getHostIp(StoragePoolVO storagePoolVO) { * Attach volume to VM */ private boolean attachVolumeToVM(Long zoneId, String restoredVolumeLocation, List backedUpVolumes, - String volumeUuid, VMInstanceVO vm, String datastoreUuid) throws Exception { + String volumeUuid, VMInstanceVO vm, String datastoreUuid, VMBackup backup) throws Exception { HypervisorGuru guru = hypervisorGuruManager.getGuru(vm.getHypervisorType()); VMBackup.VolumeInfo volumeInfo = getVolumeInfo(backedUpVolumes, volumeUuid); StoragePoolVO pool = primaryDataStoreDao.findByUuid(datastoreUuid); LOG.debug("Attaching the restored volume to VM " + vm.getId()); try { - return guru.attachRestoredVolumeToVirtualMachine(zoneId, restoredVolumeLocation, volumeInfo, vm, pool.getId()); + return guru.attachRestoredVolumeToVirtualMachine(zoneId, restoredVolumeLocation, volumeInfo, vm, pool.getId(), backup); } catch (Exception e) { throw new CloudRuntimeException("Error attach restored volume to VM " + vm.getUuid()); } From 0bf0fbf0c1eca6547c9b264f804588536a6f790a Mon Sep 17 00:00:00 2001 From: nvazquez Date: Mon, 27 Aug 2018 22:06:19 -0300 Subject: [PATCH 08/21] Other round of bugfixes --- .../com/cloud/hypervisor/guru/VMwareGuru.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 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 682f024c1360..024b715b9872 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 @@ -34,6 +34,7 @@ import com.cloud.hypervisor.vmware.mo.DatacenterMO; import com.cloud.hypervisor.vmware.mo.NetworkMO; import com.cloud.hypervisor.vmware.mo.VirtualDiskManagerMO; +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; @@ -58,6 +59,7 @@ import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.UserVmDao; +import com.google.gson.Gson; import com.vmware.vim25.ManagedObjectReference; import com.vmware.vim25.VirtualDevice; import com.vmware.vim25.VirtualDeviceBackingInfo; @@ -83,6 +85,7 @@ 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; @@ -977,7 +980,7 @@ private VMInstanceVO getVM(String vmInternalName, long templateId, long guestOsI */ 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) { + 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); @@ -986,6 +989,7 @@ private VolumeVO createVolumeRecord(Volume.Type type, String volumeName, long zo volumeVO.setInstanceId(instanceId); volumeVO.setPoolId(poolId); volumeVO.setTemplateId(templateId); + volumeVO.setChainInfo(new Gson().toJson(diskInfo)); if (unitNumber != null) { volumeVO.setDeviceId(unitNumber.longValue()); } @@ -1025,6 +1029,8 @@ protected VolumeVO updateVolume(VirtualDisk disk, Map dis 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; } @@ -1064,6 +1070,12 @@ private void removeUnusedVolumes(List vols, VMInstanceVO vmInstanceVO) } } + 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, VMBackup backup) throws Exception { Long size = disk.getCapacityInBytes(); @@ -1081,8 +1093,9 @@ private VolumeVO createVolume(VirtualDisk disk, VirtualMachineMO vmToImport, lon Storage.ProvisioningType provisioningType = getProvisioningType(info); long diskOfferingId = getDiskOfferingId(size, provisioningType, domainId); Integer unitNumber = disk.getUnitNumber(); + VirtualMachineDiskInfo diskInfo = getDiskInfo(vmToImport, poolId, volumeName); return createVolumeRecord(type, volumeName, zoneId, domainId, accountId, diskOfferingId, - provisioningType, size, instanceId, poolId, templateId, unitNumber); + provisioningType, size, instanceId, poolId, templateId, unitNumber, diskInfo); } /** @@ -1232,7 +1245,7 @@ private Map getDisksMapping(VMBackup backup, List backedUpVolumes = backup.getBackedUpVolumes(); for (VMBackup.VolumeInfo backedUpVol : backedUpVolumes) { for (VirtualDisk disk : virtualDisks) { - if (backedUpVol.getSize().equals(disk.getCapacityInBytes())) { + if (!map.containsKey(disk) && backedUpVol.getSize().equals(disk.getCapacityInBytes())) { String volId = backedUpVol.getUuid(); VolumeVO vol = _volumeDao.findByUuid(volId); map.put(disk, vol); From 4c1afcf5a52b99820d879d2f210546d8fd015539 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 27 Aug 2018 14:15:55 +0530 Subject: [PATCH 09/21] fix NPE in case usage metrics updation got a removed vmbackup Signed-off-by: Rohit Yadav --- .../main/java/com/cloud/usage/dao/UsageVMBackupDaoImpl.java | 3 +++ .../java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMBackupDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMBackupDaoImpl.java index a1ab1c289541..44f90c0840e1 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMBackupDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMBackupDaoImpl.java @@ -55,6 +55,9 @@ public Boolean doInTransaction(final TransactionStatus status) { qb.and(qb.entity().getZoneId(), SearchCriteria.Op.EQ, backup.getZoneId()); qb.and(qb.entity().getBackupId(), SearchCriteria.Op.EQ, backup.getId()); final UsageVMBackupVO entry = findOneBy(qb.create()); + if (entry == null) { + return false; + } entry.setSize(backup.getSize()); entry.setProtectedSize(backup.getProtectedSize()); return update(entry.getId(), entry); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java index bd5b03a04196..e8992a868250 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java @@ -116,8 +116,9 @@ public List listByZoneAndState(Long zoneId, VMBackup.Status state) { sc.setParameters("zone_id", zoneId); if (state != null) { sc.setParameters("status", state); + return new ArrayList<>(listIncludingRemovedBy(sc)); } - return new ArrayList<>(listIncludingRemovedBy(sc)); + return new ArrayList<>(listBy(sc)); } @Override From 820b445993bbde622b70ec1e91fedfec265746ff Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Tue, 28 Aug 2018 14:45:01 +0530 Subject: [PATCH 10/21] implemented access/privilege checks based on the access on the VM if user is allowed to access a VM, it should be only allowed to do all sorts of VM backup/recovery APIs on that VM. Signed-off-by: Rohit Yadav --- ...BackupCmd.java => RestoreVMBackupCmd.java} | 10 +++--- .../cloudstack/backup/BackupManager.java | 4 +-- .../cloudstack/backup/dao/VMBackupDao.java | 1 + .../backup/dao/VMBackupDaoImpl.java | 9 +++++ .../cloudstack/backup/BackupManagerImpl.java | 36 +++++++++++++------ 5 files changed, 43 insertions(+), 17 deletions(-) rename api/src/main/java/org/apache/cloudstack/api/command/user/backup/{RestoreVMFromBackupCmd.java => RestoreVMBackupCmd.java} (93%) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMBackupCmd.java similarity index 93% rename from api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java rename to api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMBackupCmd.java index b8d7545afab6..b08b845a8dfe 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMBackupCmd.java @@ -40,12 +40,12 @@ import com.cloud.exception.ResourceUnavailableException; import com.cloud.utils.exception.CloudRuntimeException; -@APICommand(name = RestoreVMFromBackupCmd.APINAME, - description = "Restore VM from backup", +@APICommand(name = RestoreVMBackupCmd.APINAME, + description = "Restore a VM from a VM backup", responseObject = SuccessResponse.class, since = "4.12.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class RestoreVMFromBackupCmd extends BaseAsyncCmd { - public static final String APINAME = "restoreVMFromBackup"; +public class RestoreVMBackupCmd extends BaseAsyncCmd { + public static final String APINAME = "restoreVMBackup"; @Inject private BackupManager backupManager; @@ -86,7 +86,7 @@ public String getRestorePointId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.restoreVMFromBackup(backupId, restorePointId); + boolean result = backupManager.restoreVMBackup(backupId, restorePointId); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); 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 a15979646450..4c131a730a8a 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -19,10 +19,10 @@ import java.util.List; -import com.cloud.hypervisor.Hypervisor; 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; @@ -94,7 +94,7 @@ BackupPolicy importBackupPolicy(final Long zoneId, final String policyExternalId /** * Restore a full VM from backup */ - boolean restoreVMFromBackup(final Long backupId, final String restorePointId); + boolean restoreVMBackup(final Long backupId, final String restorePointId); /** * Restore a backed up volume and attach it to a VM diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDao.java index c6b8cefa8d17..054223237bfc 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDao.java @@ -27,6 +27,7 @@ public interface VMBackupDao extends GenericDao { + List listByAccountId(Long accountId); List listByVmId(Long zoneId, Long vmId); List syncVMBackups(Long zoneId, Long vmId, List externalBackups); List listByZoneAndState(Long zoneId, VMBackup.Status state); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java index e8992a868250..b29d8334671e 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java @@ -61,11 +61,20 @@ public VMBackupDaoImpl() { 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("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 List listByVmId(Long zoneId, Long vmId) { SearchCriteria sc = backupSearch.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 1b4d5401e03d..13bdcc711c5d 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -34,7 +34,7 @@ import org.apache.cloudstack.api.command.user.backup.ListBackupPoliciesCmd; import org.apache.cloudstack.api.command.user.backup.ListVMBackupRestorePoints; import org.apache.cloudstack.api.command.user.backup.ListVMBackupsCmd; -import org.apache.cloudstack.api.command.user.backup.RestoreVMFromBackupCmd; +import org.apache.cloudstack.api.command.user.backup.RestoreVMBackupCmd; import org.apache.cloudstack.api.command.user.backup.RestoreVolumeFromBackupAndAttachToVMCmd; import org.apache.cloudstack.api.command.user.backup.StartVMBackupCmd; import org.apache.cloudstack.backup.dao.BackupPolicyDao; @@ -68,6 +68,7 @@ import com.cloud.storage.dao.VolumeDao; import com.cloud.usage.dao.UsageVMBackupDao; 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; @@ -88,6 +89,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { @Inject private AccountService accountService; @Inject + private AccountManager accountManager; + @Inject private VMBackupDao backupDao; @Inject private UsageVMBackupDao usageVMBackupDao; @@ -171,10 +174,16 @@ public List listBackupPolicies(final Long zoneId, final Boolean ex @Override public List listVMBackups(final Long vmId) { + final Account callerAccount = CallContext.current().getCallingAccount(); final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); if (vm == null) { - return new ArrayList<>(backupDao.listAll()); + 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()); } @@ -185,16 +194,19 @@ public VMBackup createBackup(final String name, final String description, final if (vm == null) { throw new CloudRuntimeException("VM does not exist"); } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); final BackupPolicyVO policy = backupPolicyDao.findById(policyId); if (policy == null) { throw new CloudRuntimeException("Policy does not exist"); } + VMBackupVO backup = new VMBackupVO(name, description, policyId, vmId, VMBackup.Status.Allocated, vm.getAccountId(), vm.getDataCenterId()); setBackupVolumes(backup, vm); backup = backupDao.persist(backup); if (backup == null) { throw new CloudRuntimeException("Failed to save backup object in database"); } + final BackupProvider backupProvider = getBackupProvider(vm.getDataCenterId()); backup = (VMBackupVO) backupProvider.createVMBackup(policy, vm, backup); if (backup == null) { @@ -229,6 +241,10 @@ public boolean startVMBackup(final Long vmBackupId) { if (vmBackup == null) { throw new CloudRuntimeException("VM Backup id " + vmBackupId + " does not exist"); } + final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmBackup.getVmId()); + if (vm != null) { + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + } final BackupProvider backupProvider = getBackupProvider(vmBackup.getZoneId()); return backupProvider.startBackup(vmBackup); } @@ -245,7 +261,7 @@ public boolean deleteBackup(final Long backupId) { 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.removeVMBackup(vm, backup); if (result) { @@ -283,7 +299,7 @@ public boolean importVM(long zoneId, long domainId, long accountId, long userId, @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true) - public boolean restoreVMFromBackup(final Long backupId, final String restorePointId) { + public boolean restoreVMBackup(final Long backupId, final String restorePointId) { VMBackupVO backup = backupDao.findById(backupId); if (backup == null) { throw new CloudRuntimeException("Backup " + backupId + " does not exist"); @@ -294,6 +310,7 @@ public boolean restoreVMFromBackup(final Long backupId, final String restorePoin 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)) { throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup"); } @@ -321,12 +338,11 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, if (backup == null) { throw new CloudRuntimeException("Backup " + backupId + " does not exist"); } - BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); - VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); if (vm == null) { throw new CloudRuntimeException("VM " + vmId + " does not exist"); } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); Pair restoreInfo = getRestoreVolumeHostAndDatastore(vm); String hostIp = restoreInfo.first(); @@ -334,6 +350,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(), restorePointId, backedUpVolumeUuid, hostIp, datastoreUuid); if (!result.first()) { @@ -406,10 +423,11 @@ public List listVMBackupRestorePoints(final Long backupId if (backup == null) { throw new CloudRuntimeException("Could not find backup " + backupId); } - VMInstanceVO vm = vmInstanceDao.findById(backup.getVmId()); + 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.listVMBackupRestorePoints(backup.getUuid(), vm); } @@ -445,18 +463,16 @@ public BackupProvider getBackupProvider(final Long zoneId) { @Override public List> getCommands() { final List> cmdList = new ArrayList>(); - // VM Backup Policy APIs cmdList.add(ListBackupProvidersCmd.class); cmdList.add(ListBackupPoliciesCmd.class); cmdList.add(ImportBackupPolicyCmd.class); cmdList.add(DeleteBackupPolicyCmd.class); - // VM Backup APIs cmdList.add(ListVMBackupsCmd.class); cmdList.add(CreateVMBackupCmd.class); cmdList.add(StartVMBackupCmd.class); cmdList.add(DeleteVMBackupCmd.class); cmdList.add(ListVMBackupRestorePoints.class); - cmdList.add(RestoreVMFromBackupCmd.class); + cmdList.add(RestoreVMBackupCmd.class); cmdList.add(RestoreVolumeFromBackupAndAttachToVMCmd.class); cmdList.add(ImportVMCmdByAdmin.class); return cmdList; From 5c2a4609948bc6fa7c699427f24270a0e89e19cc Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Tue, 28 Aug 2018 15:00:16 +0530 Subject: [PATCH 11/21] don't allow reimport of imported policy and don't allow non-root users to list external policies Signed-off-by: Rohit Yadav --- .../cloudstack/backup/dao/BackupPolicyDao.java | 6 +++--- .../cloudstack/backup/dao/BackupPolicyDaoImpl.java | 8 ++++++++ .../apache/cloudstack/backup/BackupManagerImpl.java | 13 +++++++++---- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDao.java index 42100d28fb85..60dd82f62fa2 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDao.java @@ -17,16 +17,16 @@ package org.apache.cloudstack.backup.dao; +import java.util.List; + import org.apache.cloudstack.api.response.BackupPolicyResponse; import org.apache.cloudstack.backup.BackupPolicy; import org.apache.cloudstack.backup.BackupPolicyVO; import com.cloud.utils.db.GenericDao; -import java.util.List; - public interface BackupPolicyDao extends GenericDao { - BackupPolicyResponse newBackupPolicyResponse(BackupPolicy policy); List listByZone(Long zoneId); + BackupPolicy listByExternalId(String externalId); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDaoImpl.java index 972672ea4766..4938504ae1c3 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDaoImpl.java @@ -49,6 +49,7 @@ public BackupPolicyDaoImpl() { 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(); } @@ -76,4 +77,11 @@ public List listByZone(Long zoneId) { } return new ArrayList<>(listBy(sc)); } + + @Override + public BackupPolicy listByExternalId(String externalId) { + SearchCriteria sc = backupPoliciesSearch.create(); + sc.setParameters("external_id", externalId); + 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 13bdcc711c5d..3f8808a830b2 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -116,11 +116,14 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_IMPORT_POLICY, eventDescription = "importing backup policy", async = true) public BackupPolicy importBackupPolicy(final Long zoneId, final String policyExternalId, final String policyName, final String policyDescription) { + final BackupPolicy existingPolicy = backupPolicyDao.listByExternalId(policyExternalId); + if (existingPolicy != null) { + throw new CloudRuntimeException("A backup policy with external ID " + policyExternalId + " already exists"); + } final BackupProvider provider = getBackupProvider(zoneId); if (!provider.isBackupPolicy(zoneId, policyExternalId)) { throw new CloudRuntimeException("Policy " + policyExternalId + " does not exist on provider " + provider.getName() + " on zone " + zoneId); } - final BackupPolicyVO policy = new BackupPolicyVO(zoneId, policyExternalId, policyName, policyDescription); final BackupPolicyVO savedPolicy = backupPolicyDao.persist(policy); if (savedPolicy == null) { @@ -147,7 +150,6 @@ private List listExternalPolicies(Long zoneId) { * List imported backup policies in the zone zoneId */ private List listInternalPolicies(Long zoneId) { - LOG.debug("Listing imported backup policies on zone " + zoneId); return backupPolicyDao.listByZone(zoneId); } @@ -159,7 +161,6 @@ private List listInternalPolicyById(Long policyId) { if (policy == null) { throw new CloudRuntimeException("Policy " + policyId + " does not exist"); } - LOG.debug("Listing imported backup policy with id: " + policyId); return Collections.singletonList(policy); } @@ -168,7 +169,11 @@ public List listBackupPolicies(final Long zoneId, final Boolean ex if (policyId != null) { return listInternalPolicyById(policyId); } else { - return BooleanUtils.isTrue(external) ? listExternalPolicies(zoneId) : listInternalPolicies(zoneId); + if (BooleanUtils.isTrue(external) && accountService.isRootAdmin(CallContext.current().getCallingAccountId())) { + return listExternalPolicies(zoneId); + } else { + return listInternalPolicies(zoneId); + } } } From f33ab5549f642c1638207675138bea0e86ecb707 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Tue, 28 Aug 2018 23:03:34 -0300 Subject: [PATCH 12/21] Fix CE298 --- .../src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java | 5 ++++- 1 file changed, 4 insertions(+), 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 024b715b9872..c9d1f278657e 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 @@ -1243,11 +1243,14 @@ private void removeUnusedNics(List nics, VMInstanceVO vmInstanceVO) { private Map getDisksMapping(VMBackup backup, List virtualDisks) { Map map = new HashMap<>(); List backedUpVolumes = backup.getBackedUpVolumes(); + Map usedVols = new HashMap<>(); for (VMBackup.VolumeInfo backedUpVol : backedUpVolumes) { for (VirtualDisk disk : virtualDisks) { - if (!map.containsKey(disk) && backedUpVol.getSize().equals(disk.getCapacityInBytes())) { + 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); } } From 506e8ecf5498f716b9d9e6fe154f87d6c552f686 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Wed, 29 Aug 2018 14:18:42 +0530 Subject: [PATCH 13/21] CE-302: restoring a root disk to another VM from a backup shouldnot add as a root volume but data disk only Signed-off-by: Rohit Yadav --- .../apache/cloudstack/backup/VMBackup.java | 9 +- .../com/cloud/hypervisor/guru/VMwareGuru.java | 104 +++++++++--------- .../cloudstack/backup/BackupManagerImpl.java | 6 +- 3 files changed, 65 insertions(+), 54 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/backup/VMBackup.java b/api/src/main/java/org/apache/cloudstack/backup/VMBackup.java index 87c76a1ad9f5..02fcd1e3ede7 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/VMBackup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/VMBackup.java @@ -20,11 +20,12 @@ import java.util.Date; import java.util.List; -import com.cloud.storage.Volume; -import com.cloud.utils.StringUtils; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; +import com.cloud.storage.Volume; +import com.cloud.utils.StringUtils; + public interface VMBackup extends InternalIdentity, Identity { enum Status { @@ -114,6 +115,10 @@ public Volume.Type getType() { return type; } + public void setType(Volume.Type type) { + this.type = type; + } + public String getPath() { return path; } 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 c9d1f278657e..4621476e973b 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 @@ -26,50 +26,6 @@ import javax.inject.Inject; -import com.cloud.exception.InvalidParameterValueException; -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.mo.DatacenterMO; -import com.cloud.hypervisor.vmware.mo.NetworkMO; -import com.cloud.hypervisor.vmware.mo.VirtualDiskManagerMO; -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.Networks; -import com.cloud.network.dao.PhysicalNetworkDao; -import com.cloud.network.dao.PhysicalNetworkVO; -import com.cloud.service.ServiceOfferingVO; -import com.cloud.service.dao.ServiceOfferingDao; -import com.cloud.storage.DiskOfferingVO; -import com.cloud.storage.VMTemplateStoragePoolVO; -import com.cloud.storage.VMTemplateStorageResourceAssoc; -import com.cloud.storage.VMTemplateVO; -import com.cloud.storage.dao.DiskOfferingDao; -import com.cloud.storage.dao.VMTemplateDao; -import com.cloud.storage.dao.VMTemplatePoolDao; -import com.cloud.utils.UuidUtils; -import com.cloud.utils.db.Transaction; -import com.cloud.utils.db.TransactionCallbackWithException; -import com.cloud.utils.db.TransactionStatus; -import com.cloud.vm.Nic; -import com.cloud.vm.UserVmVO; -import com.cloud.vm.VMInstanceVO; -import com.cloud.vm.dao.UserVmDao; -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; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.backup.VMBackup; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; @@ -79,8 +35,8 @@ 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; @@ -109,6 +65,7 @@ import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.cluster.ClusterManager; 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; @@ -116,46 +73,89 @@ 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.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.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); @@ -1052,7 +1052,7 @@ private void importVMVolumes(VMInstanceVO vmInstanceVO, Long poolId, List backedUpVolumes = backup.getBackedUpVolumes(); Volume.Type type = Volume.Type.DATADISK; - for (VMBackup.VolumeInfo volumeInfo : backedUpVolumes) { - if (volumeInfo.getSize().equals(disk.getCapacityInBytes())) { - type = volumeInfo.getType(); + if (isImport) { + for (VMBackup.VolumeInfo volumeInfo : backedUpVolumes) { + if (volumeInfo.getSize().equals(disk.getCapacityInBytes())) { + type = volumeInfo.getType(); + } } } VirtualDeviceBackingInfo backing = disk.getBacking(); @@ -1378,7 +1380,7 @@ public boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location VirtualDisk attachedDisk = getAttachedDisk(vmMo, diskPath); createVolume(attachedDisk, vmMo, vm.getDomainId(), vm.getDataCenterId(), vm.getAccountId(), vm.getId(), - poolId, vm.getTemplateId(), backup); + poolId, vm.getTemplateId(), backup, false); 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 3f8808a830b2..463676ced16d 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -403,9 +403,13 @@ private boolean attachVolumeToVM(Long zoneId, String restoredVolumeLocation, Lis String volumeUuid, VMInstanceVO vm, String datastoreUuid, VMBackup backup) throws Exception { HypervisorGuru guru = hypervisorGuruManager.getGuru(vm.getHypervisorType()); VMBackup.VolumeInfo volumeInfo = getVolumeInfo(backedUpVolumes, volumeUuid); - StoragePoolVO pool = primaryDataStoreDao.findByUuid(datastoreUuid); + 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) { From 49f0f2949d707b429a542e2cce43235a6d02c23d Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Wed, 29 Aug 2018 14:27:02 +0530 Subject: [PATCH 14/21] CE300 etc: do access check for vmbackup's VM and allow restoring of a destroyed VM that is not expunged yet. Signed-off-by: Rohit Yadav --- .../org/apache/cloudstack/backup/BackupManagerImpl.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 463676ced16d..fa5ef36a76e5 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -316,7 +316,8 @@ public boolean restoreVMBackup(final Long backupId, final String restorePointId) 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)) { + 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)) { @@ -347,6 +348,11 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, 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); From 8723698769a18c23f5f4555a697d0609e82e5b48 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Wed, 29 Aug 2018 15:15:55 +0530 Subject: [PATCH 15/21] CE-304: usage type/id should be of the VM Signed-off-by: Rohit Yadav --- .../java/com/cloud/api/ApiResponseHelper.java | 20 ++------- .../com/cloud/usage/UsageServiceImpl.java | 43 ++++++++++--------- 2 files changed, 27 insertions(+), 36 deletions(-) diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 3d9db9e7f74d..d422db1aabf6 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -141,9 +141,7 @@ import org.apache.cloudstack.api.response.VpnUsersResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.backup.BackupPolicy; -import org.apache.cloudstack.backup.BackupPolicyVO; import org.apache.cloudstack.backup.VMBackup; -import org.apache.cloudstack.backup.VMBackupVO; import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; @@ -3496,7 +3494,7 @@ public UsageRecordResponse createUsageResponse(Usage usageRecord, Map, Integer> getUsageRecords(GetUsageRecordsCmd c case UsageTypes.RUNNING_VM: case UsageTypes.ALLOCATED_VM: case UsageTypes.VM_SNAPSHOT: + case UsageTypes.VM_BACKUP: VMInstanceVO vm = _vmDao.findByUuidIncludingRemoved(usageId); if (vm != null) { usageDbId = vm.getId(); From 8a70f23409769ddd24c5713962bfa9b1741fc9f7 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Wed, 29 Aug 2018 15:25:19 +0530 Subject: [PATCH 16/21] better job deletion logic Signed-off-by: Rohit Yadav --- .../apache/cloudstack/backup/VeeamBackupProvider.java | 9 +++++++-- .../org/apache/cloudstack/backup/veeam/VeeamClient.java | 2 +- 2 files changed, 8 insertions(+), 3 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 1ca6c5cb35da..e5dbbcc56c8d 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 @@ -173,9 +173,14 @@ public VMBackup createVMBackup(final BackupPolicy policy, final VirtualMachine v public boolean removeVMBackup(final VirtualMachine vm, final VMBackup backup) { final VeeamClient client = getClient(vm.getDataCenterId()); final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm); - if (!client.removeVMFromVeeamJob(backup.getExternalId(), vm.getInstanceName(), vmwareDC.getVcenterHost())) { - LOG.warn("Failed to remove VM from Veeam Job id: " + backup.getExternalId()); + 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); 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 35432766afe9..3fdcca3d7153 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 @@ -548,7 +548,7 @@ protected Pair executePowerShellCommands(List cmds) { public boolean deleteJobAndBackup(final String jobName) { Pair result = executePowerShellCommands(Arrays.asList( String.format("$job = Get-VBRJob -Name \"%s\"", jobName), - "Remove-VBRJob -Job $job -Confirm:$false", + "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", From 3361ea02dbc7b72a125bc395f16d32a262ca4b3b Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Wed, 29 Aug 2018 16:05:33 +0530 Subject: [PATCH 17/21] CE-305: don't allow backup policy deletion if vmbackups use it Signed-off-by: Rohit Yadav --- .../org/apache/cloudstack/backup/dao/VMBackupDao.java | 1 + .../org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java | 8 ++++++++ .../org/apache/cloudstack/backup/BackupManagerImpl.java | 3 +++ 3 files changed, 12 insertions(+) diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDao.java index 054223237bfc..e2af1c36d31a 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDao.java @@ -29,6 +29,7 @@ public interface VMBackupDao extends GenericDao { List listByAccountId(Long accountId); List listByVmId(Long zoneId, Long vmId); + List listByPolicyId(Long policyId); List syncVMBackups(Long zoneId, Long vmId, List externalBackups); List listByZoneAndState(Long zoneId, VMBackup.Status state); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java index b29d8334671e..81422f4b43cc 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.java @@ -64,6 +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().getPolicyId(), SearchCriteria.Op.EQ); backupSearch.and("status", backupSearch.entity().getStatus(), SearchCriteria.Op.EQ); backupSearch.done(); } @@ -83,6 +84,13 @@ public List listByVmId(Long zoneId, Long vmId) { 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 VMBackup findByExternalId(Long zoneId, String externalId) { SearchCriteria sc = backupSearch.create(); sc.setParameters("external_id", externalId); 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 fa5ef36a76e5..2183a45427bb 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -425,6 +425,9 @@ private boolean attachVolumeToVM(Long zoneId, String restoredVolumeLocation, Lis @Override public boolean deleteBackupPolicy(final Long policyId) { + if (!backupDao.listByPolicyId(policyId).isEmpty()) { + throw new CloudRuntimeException("Cannot allow deletion of backup policy due to use in existing VM backups, please delete the VM backups first"); + } BackupPolicyVO policy = backupPolicyDao.findById(policyId); if (policy == null) { throw new CloudRuntimeException("Could not find a backup policy with id: " + policyId); From 3abc3b8b1cfef4ec2ee5398d13860dbf8ac84810 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Wed, 29 Aug 2018 11:46:09 -0300 Subject: [PATCH 18/21] Fix CE-303 --- .../java/org/apache/cloudstack/backup/BackupManagerImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2183a45427bb..deb646136668 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -316,8 +316,8 @@ public boolean restoreVMBackup(final Long backupId, final String restorePointId) 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))) { + 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)) { From c76c2908e399b7a90e05f11e8d3660e864b7d186 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Wed, 29 Aug 2018 13:04:16 -0300 Subject: [PATCH 19/21] Fix CE-298 --- .../com/cloud/hypervisor/guru/VMwareGuru.java | 18 +++--------------- 1 file changed, 3 insertions(+), 15 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 4621476e973b..d0db652d451d 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 @@ -823,18 +823,6 @@ private Long getPoolId(VirtualDisk disk) { return getPoolIdFromDatastoreUuid(datastoreUuid); } - /** - * Get pool ID for VM being imported - */ - private Long getImportingVMPoolId(List disks, Map disksMapping, VMBackup backup) { - for (VirtualDisk disk : disks) { - if (isRootDisk(disk, disksMapping, backup)) { - return getPoolId(disk); - } - } - throw new CloudRuntimeException("No ROOT disk found"); - } - /** * Get volume name from filename */ @@ -1038,7 +1026,7 @@ protected VolumeVO updateVolume(VirtualDisk disk, Map dis /** * Get volumes for VM being imported */ - private void importVMVolumes(VMInstanceVO vmInstanceVO, Long poolId, List virtualDisks, + private void importVMVolumes(VMInstanceVO vmInstanceVO, List virtualDisks, Map disksMapping, VirtualMachineMO vmToImport, VMBackup backup) throws Exception { long zoneId = vmInstanceVO.getDataCenterId(); long accountId = vmInstanceVO.getAccountId(); @@ -1048,6 +1036,7 @@ private void importVMVolumes(VMInstanceVO vmInstanceVO, Long poolId, 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); @@ -1339,14 +1328,13 @@ public VMInstanceVO doInTransaction(TransactionStatus status) throws Exception { long guestOsId = getImportingVMGuestOs(configSummary); long serviceOfferingId = getImportingVMServiceOffering(configSummary, runtimeInfo); - long poolId = getImportingVMPoolId(virtualDisks, disksMapping, backup); 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, poolId, virtualDisks, disksMapping, vmToImport, backup); + importVMVolumes(vmInstanceVO, virtualDisks, disksMapping, vmToImport, backup); return vmInstanceVO; } }); From 7da1799166ece5367ab4e3cfb141b37e04054aaf Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 30 Aug 2018 16:24:07 +0530 Subject: [PATCH 20/21] detect volume including removed, for target VM don't search removed VM Signed-off-by: Rohit Yadav --- .../src/main/java/com/cloud/storage/dao/VolumeDao.java | 2 ++ .../main/java/com/cloud/storage/dao/VolumeDaoImpl.java | 8 ++++++++ .../org/apache/cloudstack/backup/BackupManagerImpl.java | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) 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 51d46a06963f..ffaaefcfe137 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 findByInstanceIdDestroyed(long vmId); List findByPod(long podId); 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 663a5f5cfc87..b90b6086a524 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 @@ -173,6 +173,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/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index deb646136668..d23a3c6cf3d5 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -344,7 +344,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, if (backup == null) { throw new CloudRuntimeException("Backup " + backupId + " does not exist"); } - VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { throw new CloudRuntimeException("VM " + vmId + " does not exist"); } @@ -378,7 +378,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, * 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.findByInstanceAndType(vm.getId(), Volume.Type.ROOT); + List rootVmVolume = volumeDao.findIncludingRemovedByInstanceAndType(vm.getId(), Volume.Type.ROOT); Long poolId = rootVmVolume.get(0).getPoolId(); StoragePoolVO storagePoolVO = primaryDataStoreDao.findById(poolId); String datastoreUuid = storagePoolVO.getUuid(); From c5142ca476a0c25ad33ea9821c7f80abd09b1513 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Fri, 31 Aug 2018 08:42:49 -0300 Subject: [PATCH 21/21] Add error cause on exception --- .../java/org/apache/cloudstack/backup/BackupManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d23a3c6cf3d5..b7bf5762235f 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -419,7 +419,7 @@ private boolean attachVolumeToVM(Long zoneId, String restoredVolumeLocation, Lis 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()); + throw new CloudRuntimeException("Error attach restored volume to VM " + vm.getUuid() + " due to: " + e.getMessage()); } }