diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 907b93eca103..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"; diff --git a/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java b/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java index 45e19ee2674b..fc1c889d87b1 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, VMBackup backup) 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 f03ddc7465c3..ec4c006c3761 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"; @@ -253,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"; @@ -336,6 +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 VOLUMES = "volumes"; public static final String ZONE_ID = "zoneid"; public static final String ZONE_NAME = "zonename"; public static final String NETWORK_TYPE = "networktype"; @@ -613,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 new file mode 100644 index 000000000000..d36dea37f7ee --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java @@ -0,0 +1,86 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.api.response.BackupPolicyResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.VMBackupResponse; +import org.apache.cloudstack.api.response.VMBackupRestorePointResponse; +import org.apache.cloudstack.backup.BackupPolicy; +import org.apache.cloudstack.backup.VMBackup; +import org.apache.cloudstack.context.CallContext; + +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 (VMBackup backup : backups) { + if (backup == null) { + continue; + } + VMBackupResponse backupResponse = _responseGenerator.createBackupResponse(backup); + responses.add(backupResponse); + } + response.setResponses(responses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + 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; + } + 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()); + 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..64b9ed598a1f 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,7 @@ import org.apache.cloudstack.api.response.AutoScalePolicyResponse; import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse; import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse; +import org.apache.cloudstack.api.response.BackupPolicyResponse; import org.apache.cloudstack.api.response.CapacityResponse; import org.apache.cloudstack.api.response.ClusterResponse; import org.apache.cloudstack.api.response.ConditionResponse; @@ -108,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; @@ -116,6 +118,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.VMBackup; import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; import org.apache.cloudstack.region.PortableIp; @@ -462,4 +466,8 @@ List createTemplateResponses(ResponseView view, VirtualMachine ListResponse createUpgradeRouterTemplateResponse(List jobIds); SSHKeyPairResponse createSSHKeyPairResponse(SSHKeyPair sshkeyPair, boolean privatekey); + + VMBackupResponse createBackupResponse(VMBackup backup); + + BackupPolicyResponse createBackupPolicyResponse(BackupPolicy policy); } 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..a9cb41f61466 --- /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_VM_BACKUP_IMPORT_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/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 new file mode 100644 index 000000000000..494943eb9c9f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateVMBackupCmd.java @@ -0,0 +1,144 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.backup; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupPolicyResponse; +import org.apache.cloudstack.api.response.VMBackupResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +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; +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 = 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"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// 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, + required = true, + description = "id of the VM") + 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 String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Long getVmId() { + return vmId; + } + + public Long getPolicyId() { + return policyId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + VMBackup backup = backupManager.createBackup(name, description, vmId, policyId); + if (backup != null) { + VMBackupResponse response = _responseGenerator.createBackupResponse(backup); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new CloudRuntimeException("Error while creating backup of VM"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_CREATE; + } + + @Override + public String getEventDescription() { + return "Creating backup for VM " + vmId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java new file mode 100644 index 000000000000..6c144cc0e959 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java @@ -0,0 +1,101 @@ +// 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.VMBackupResponse; +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}) +// FIXME: make this async command +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 = VMBackupResponse.class, + required = true, + description = "id of the VM backup") + private Long backupId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return backupId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + boolean result = backupManager.deleteBackup(backupId); + // FIXME: the response type? + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new CloudRuntimeException("Error while deleting backup of VM"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} 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/ListVMBackupRestorePoints.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupRestorePoints.java new file mode 100644 index 000000000000..838166a8a115 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupRestorePoints.java @@ -0,0 +1,89 @@ +// 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.VMBackupResponse; +import org.apache.cloudstack.backup.BackupManager; +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; + +@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 ListVMBackupRestorePoints extends BaseBackupListCmd { + public static final String APINAME = "listVMBackupRestorePoints"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.VM_BACKUP_ID, + type = CommandType.UUID, + entityType = VMBackupResponse.class, + required = true, + description = "id of the VM backup") + private Long backupId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getBackupId() { + return backupId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + List restorePoints = backupManager.listVMBackupRestorePoints(backupId); + setupResponseRestorePointsList(restorePoints); + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + 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 new file mode 100644 index 000000000000..d93e1bdb37e8 --- /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.VMBackupResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.VMBackup; +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 = 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"; + + @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/RestoreVMBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMBackupCmd.java new file mode 100644 index 000000000000..b08b845a8dfe --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMBackupCmd.java @@ -0,0 +1,121 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.backup; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.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; +import com.cloud.utils.exception.CloudRuntimeException; + +@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 RestoreVMBackupCmd extends BaseAsyncCmd { + public static final String APINAME = "restoreVMBackup"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.VM_BACKUP_ID, + type = CommandType.UUID, + 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 /////////////////////// + ///////////////////////////////////////////////////// + + public Long getBackupId() { + return backupId; + } + + public String getRestorePointId() { + return restorePointId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + boolean result = backupManager.restoreVMBackup(backupId, restorePointId); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new CloudRuntimeException("Error while restoring VM from backup"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_RESTORE; + } + + @Override + public String getEventDescription() { + return "Restoring VM from restore point: " + restorePointId + " on backup: " + backupId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java new file mode 100644 index 000000000000..7058a9015eb6 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java @@ -0,0 +1,143 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.backup; + +import javax.inject.Inject; + +import 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.VMBackupResponse; +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; +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 ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.VM_BACKUP_ID, + type = CommandType.UUID, + 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 = "id of the backup restore point") + private String restorePointId; + + @Parameter(name = ApiConstants.VOLUME_ID, + type = CommandType.STRING, + required = true, + description = "id of the volume backed up") + private String volumeUuid; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + required = true, + description = "id of the VM where to attach the restored volume") + private Long vmId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getVolumeUuid() { + return volumeUuid; + } + + public Long getVmId() { + return vmId; + } + + public Long getBackupId() { + return backupId; + } + + public String getRestorePointId() { + return restorePointId; + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + boolean result = backupManager.restoreBackupVolumeAndAttachToVM(volumeUuid, vmId, backupId, restorePointId); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new CloudRuntimeException("Error restoring volume and attaching to VM"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_RESTORE_VOLUME_TO_VM; + } + + @Override + public String getEventDescription() { + return "Restoring volume "+ volumeUuid + " from backup " + backupId + " and attaching it to VM " + vmId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/StartVMBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/StartVMBackupCmd.java new file mode 100644 index 000000000000..1518a5f15b9a --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/StartVMBackupCmd.java @@ -0,0 +1,108 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.backup; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +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; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +@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 StartVMBackupCmd extends BaseAsyncCmd { + public static final String APINAME = "startVMBackup"; + + @Inject + private BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = VMBackupResponse.class, + required = true, + description = "id of the VM backup for which ad-hoc backup needs to be started") + 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.startVMBackup(getBackupId()); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to start VM backup"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return StartVMBackupCmd.APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_START; + } + + @Override + public String getEventDescription() { + return "Starting VM backup for backup id " + backupId; + } +} 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/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/VMBackupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VMBackupResponse.java new file mode 100644 index 000000000000..2b3a862eef0f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/VMBackupResponse.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.api.response; + +import java.util.Date; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.backup.VMBackup; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = VMBackup.class) +public class VMBackupResponse 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.VIRTUAL_MACHINE_ID) + @Param(description = "backup vm id") + private String vmId; + + @SerializedName(ApiConstants.VOLUMES) + @Param(description = "backup volumes") + private String volumes; + + @SerializedName(ApiConstants.STATUS) + @Param(description = "backup volume ids") + 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.CREATED) + @Param(description = "backup creation date") + private Date createdDate; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + public String 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 getVmId() { + return vmId; + } + + public void setVmId(String vmId) { + this.vmId = vmId; + } + + public VMBackup.Status getStatus() { + return 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 String getVolumes() { + return volumes; + } + + public void setVolumes(String volumes) { + this.volumes = volumes; + } + + public Date getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Date createdDate) { + this.createdDate = createdDate; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VMBackupRestorePointResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VMBackupRestorePointResponse.java new file mode 100644 index 000000000000..c9d07e74faea --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/VMBackupRestorePointResponse.java @@ -0,0 +1,65 @@ +// 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.VMBackup; + +@EntityReference(value = VMBackup.RestorePoint.class) +public class VMBackupRestorePointResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "external id of the restore point") + private String id; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "created time") + private String created; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "restore point type") + private String type; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getCreated() { + return created; + } + + public void setCreated(String created) { + this.created = created; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java new file mode 100644 index 000000000000..4c131a730a8a --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -0,0 +1,108 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup; + +import java.util.List; + +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; + +import com.cloud.hypervisor.Hypervisor; +import com.cloud.utils.component.Manager; +import com.cloud.utils.component.PluggableService; + +/** + * Backup and Recover Manager Interface + */ +public interface BackupManager extends BackupService, Configurable, PluggableService, Manager { + + ConfigKey BackupFrameworkEnabled = new ConfigKey<>("Advanced", Boolean.class, + "backup.framework.enabled", + "false", + "Is backup and recovery framework enabled.", true, ConfigKey.Scope.Zone); + + ConfigKey BackupProviderPlugin = new ConfigKey<>("Advanced", String.class, + "backup.framework.provider.plugin", + "dummy", + "The backup and recovery provider plugin.", true, ConfigKey.Scope.Zone); + + ConfigKey BackupSyncPollingInterval = new ConfigKey<>("Advanced", Long.class, + "backup.framework.sync.interval", + "300", + "The backup and recovery background sync task polling interval in seconds.", true); + /** + * Add a new Backup and Recovery policy to CloudStack by mapping an existing external backup 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(final Long zoneId, final String policyExternalId, + final String policyName, final String policyDescription); + + /** + * 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(final Long zoneId, final Boolean external, final Long policyId); + + /** + * Deletes a backup policy + */ + boolean deleteBackupPolicy(final Long policyId); + + /** + * List existing backups for a VM + */ + List listVMBackups(final Long vmId); + + /** + * Creates backup of a VM + * @param vmId Virtual Machine ID + * @return returns operation success + */ + 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(final Long backupId); + + /** + * Restore a full VM from backup + */ + boolean restoreVMBackup(final Long backupId, final String restorePointId); + + /** + * Restore a backed up volume and attach it to a VM + */ + boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long vmId, final Long backupId, final String restorePointId) throws Exception; + + boolean importVM(long zoneId, long domainId, long accountId, long userId, + String vmInternalName, Hypervisor.HypervisorType hypervisorType, VMBackup backup); + + List listVMBackupRestorePoints(final Long backupId); +} 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/BackupProvider.java b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java new file mode 100644 index 000000000000..69e7cf90aa70 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -0,0 +1,93 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.backup; + +import java.util.List; +import java.util.Map; + +import com.cloud.utils.Pair; +import com.cloud.vm.VirtualMachine; + +public interface BackupProvider { + + /** + * Returns the unique name of the provider + * @return returns provider name + */ + String getName(); + + /** + * Returns description about the backup and recovery provider plugin + * @return returns description + */ + String getDescription(); + + /** + * Returns the list of existing backup policies on the provider + * @return backup policies list + */ + List listBackupPolicies(Long zoneId); + + /** + * True if policy with id uuid exists on the backup provider + */ + boolean isBackupPolicy(Long zoneId, String uuid); + + /** + * Creates backup of a VM assigned to a policy + * @param policy + * @param vm + * @return true if backup successfully starts + */ + VMBackup createVMBackup(BackupPolicy policy, VirtualMachine vm, VMBackup backup); + + /** + * Removes a VM backup + * @param vm + * @param backup + * @return + */ + boolean removeVMBackup(VirtualMachine vm, VMBackup backup); + + /** + * Starts and creates an adhoc backup process + * for a previously registered VM backup + * @param vmBackup + * @return + */ + boolean startBackup(VMBackup vmBackup); + + /** + * Restore VM from backup + */ + boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String restorePointId); + + /** + * Restore a volume from a backup + */ + Pair restoreBackedUpVolume(long zoneId, String backupUuid, String restorePointId, String volumeUuid, + String hostIp, String dataStoreUuid); + + /** + * List VM Backups + */ + List listVMBackups(Long zoneId, VirtualMachine vm); + + Map getBackupMetrics(Long zoneId, List backupList); + + List listVMBackupRestorePoints(String backupUuid, VirtualMachine vm); +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupService.java b/api/src/main/java/org/apache/cloudstack/backup/BackupService.java new file mode 100644 index 000000000000..d4beb629fe0f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupService.java @@ -0,0 +1,37 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.backup; + +import java.util.List; + +/** + * Backup and Recovery Services + */ +public interface BackupService { + /** + * Lists backup and recovery provider plugins + * @return list of providers + */ + List listBackupProviders(); + + /** + * Find backup provider by zone ID + * @param zoneId zone id + * @return backup provider + */ + BackupProvider getBackupProvider(final Long zoneId); +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/VMBackup.java b/api/src/main/java/org/apache/cloudstack/backup/VMBackup.java new file mode 100644 index 000000000000..02fcd1e3ede7 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/VMBackup.java @@ -0,0 +1,149 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import java.util.Date; +import java.util.List; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import com.cloud.storage.Volume; +import com.cloud.utils.StringUtils; + +public interface 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 void setType(Volume.Type type) { + this.type = type; + } + + public String getPath() { + return path; + } + + public Long getSize() { + return size; + } + + @Override + public String toString() { + return StringUtils.join(":", uuid, path, type, size); + } + } + + Long getZoneId(); + Long getAccountId(); + String getExternalId(); + String getName(); + String getDescription(); + Long getVmId(); + List getBackedUpVolumes(); + String getVolumes(); + 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 baf49add1b4d..ee50e801b194 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -479,6 +479,11 @@ cloud-plugin-integrations-prometheus-exporter ${project.version} + + org.apache.cloudstack + cloud-plugin-backup-dummy + ${project.version} + @@ -759,6 +764,33 @@ META-INF/spring.schemas + + META-INF/services/com.sun.tools.xjc.Plugin + + + META-INF/cxf/cxf.extension + + + META-INF/extensions.xml + + + META-INF/cxf/extensions.xml + + + META-INF/cxf/bus-extensions.txt + + + META-INF/cxf/bus-extensions.xml + + + META-INF/wsdl.plugin.xml + + + META-INF/tools.service.validator.xml + + + META-INF/cxf/java2wsbeans.xml + @@ -1147,25 +1179,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 @@ -1174,7 +1201,7 @@ org.apache.cloudstack - cloud-plugin-api-vmware-sioc + cloud-plugin-database-mysqlha ${project.version} diff --git a/core/src/main/resources/META-INF/cloudstack/backup/module.properties b/core/src/main/resources/META-INF/cloudstack/backup/module.properties new file mode 100644 index 000000000000..b85b65ceeead --- /dev/null +++ b/core/src/main/resources/META-INF/cloudstack/backup/module.properties @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +name=backup +parent=backend diff --git a/core/src/main/resources/META-INF/cloudstack/backup/spring-core-lifecycle-backup-context-inheritable.xml b/core/src/main/resources/META-INF/cloudstack/backup/spring-core-lifecycle-backup-context-inheritable.xml new file mode 100644 index 000000000000..175d45e26752 --- /dev/null +++ b/core/src/main/resources/META-INF/cloudstack/backup/spring-core-lifecycle-backup-context-inheritable.xml @@ -0,0 +1,32 @@ + + + + + + + + + diff --git a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml index 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/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/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/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/com/cloud/usage/dao/UsageVMBackupDao.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMBackupDao.java new file mode 100644 index 000000000000..5b01e57ae72e --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMBackupDao.java @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.usage.dao; + +import java.util.Date; +import java.util.List; + +import org.apache.cloudstack.backup.VMBackup; + +import com.cloud.usage.UsageVMBackupVO; +import com.cloud.utils.db.GenericDao; + +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..44f90c0840e1 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMBackupDaoImpl.java @@ -0,0 +1,140 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.usage.dao; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +import org.apache.cloudstack.backup.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()); + if (entry == null) { + return false; + } + entry.setSize(backup.getSize()); + entry.setProtectedSize(backup.getProtectedSize()); + return update(entry.getId(), entry); + } + }); + if (!result) { + LOGGER.warn("Failed to update VM Backup metrics for backup id: " + backup.getId()); + } + } + + @Override + public void removeUsage(Long accountId, Long zoneId, Long backupId) { + boolean result = Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public Boolean doInTransaction(final TransactionStatus status) { + final QueryBuilder qb = QueryBuilder.create(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/BackupPolicyVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVO.java new file mode 100644 index 000000000000..245f1693cbc0 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupPolicyVO.java @@ -0,0 +1,112 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup; + +import java.util.Date; +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +@Entity +@Table(name = "backup_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; + + @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(); + } + + 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; + 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; + } + + 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/VMBackupVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/VMBackupVO.java new file mode 100644 index 000000000000..76813026400f --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/VMBackupVO.java @@ -0,0 +1,244 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import com.google.gson.Gson; + +@Entity +@Table(name = "vm_backup") +public class VMBackupVO implements VMBackup { + @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 = "policy_id") + private long policyId; + + @Column(name = "vm_id") + private Long vmId; + + @Column(name = "volumes", length = 65535) + private String volumes; + + @Column(name = "size") + private Long size; + + @Column(name = "protected_size") + private Long protectedSize; + + @Column(name = "status") + private Status status; + + @Column(name = "account_id") + private long accountId; + + @Column(name = "zone_id") + private Long zoneId; + + @Column(name = "created") + @Temporal(value = TemporalType.TIMESTAMP) + private Date created; + + @Column(name = "removed") + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + + public VMBackupVO() { + this.uuid = UUID.randomUUID().toString(); + this.created = new Date(); + } + + 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; + } + + @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 getVmId() { + return vmId; + } + + @Override + public Status getStatus() { + return status; + } + + public Long getSize() { + return size; + } + + public Long getProtectedSize() { + return protectedSize; + } + + @Override + public Date getCreated() { + return created; + } + + public void setId(long id) { + this.id = id; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + public void setName(String name) { + this.name = name; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setVmId(Long vmId) { + this.vmId = vmId; + } + + public void setStatus(Status status) { + this.status = status; + } + + public void setSize(Long size) { + this.size = size; + } + + public void setProtectedSize(Long protectedSize) { + this.protectedSize = protectedSize; + } + + public void setCreated(Date start) { + this.created = start; + } + + @Override + public List getBackedUpVolumes() { + return Arrays.asList(new Gson().fromJson(this.volumes, VolumeInfo[].class)); + } + + public void setBackedUpVolumes(List volumes) { + this.volumes = new Gson().toJson(volumes); + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + public String getVolumes() { + return volumes; + } + + protected void setVolumes(String volumes) { + this.volumes = volumes; + } + + 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/BackupPolicyDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDao.java new file mode 100644 index 000000000000..60dd82f62fa2 --- /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 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; + +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 new file mode 100644 index 000000000000..4938504ae1c3 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupPolicyDaoImpl.java @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.dao; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +import org.apache.cloudstack.api.response.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.and("external_id", backupPoliciesSearch.entity().getExternalId(), 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)); + } + + @Override + public BackupPolicy listByExternalId(String externalId) { + SearchCriteria sc = backupPoliciesSearch.create(); + sc.setParameters("external_id", externalId); + return findOneBy(sc); + } +} 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 new file mode 100644 index 000000000000..e2af1c36d31a --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDao.java @@ -0,0 +1,38 @@ +// 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.VMBackupResponse; +import org.apache.cloudstack.backup.VMBackup; +import org.apache.cloudstack.backup.VMBackupVO; + +import com.cloud.utils.db.GenericDao; + +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); + + VMBackupResponse newBackupResponse(VMBackup backup); + VMBackupVO getBackupVO(VMBackup backup); +} 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 new file mode 100644 index 000000000000..81422f4b43cc --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/VMBackupDaoImpl.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.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 com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.VMInstanceDao; + +public class VMBackupDaoImpl extends GenericDaoBase implements VMBackupDao { + + @Inject + AccountDao accountDao; + + @Inject + DataCenterDao dataCenterDao; + + @Inject + VMInstanceDao vmInstanceDao; + + @Inject + VolumeDao volumeDao; + + private SearchBuilder backupSearch; + + public VMBackupDaoImpl() { + } + + @PostConstruct + protected void init() { + backupSearch = createSearchBuilder(); + backupSearch.and("vm_id", backupSearch.entity().getVmId(), SearchCriteria.Op.EQ); + backupSearch.and("account_id", backupSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + backupSearch.and("zone_id", backupSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + backupSearch.and("external_id", backupSearch.entity().getExternalId(), SearchCriteria.Op.EQ); + backupSearch.and("policy_id", backupSearch.entity().getPolicyId(), 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(); + sc.setParameters("vm_id", vmId); + sc.setParameters("zone_id", zoneId); + return new ArrayList<>(listBy(sc)); + } + + @Override + public List listByPolicyId(Long policyId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters("policy_id", policyId); + return new ArrayList<>(listBy(sc)); + } + + private VMBackup findByExternalId(Long zoneId, String externalId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters("external_id", externalId); + sc.setParameters("zone_id", zoneId); + return findOneBy(sc); + } + + 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()); + backupVO.setVmId(backup.getVmId()); + backupVO.setStatus(backup.getStatus()); + backupVO.setCreated(backup.getCreated()); + 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 (VMBackup backup : externalBackups) { + VMBackupVO backupVO = getBackupVO(backup); + persist(backupVO); + } + return listByVmId(zoneId, vmId); + } + + @Override + 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)); + } + return new ArrayList<>(listBy(sc)); + } + + @Override + public VMBackupResponse newBackupResponse(VMBackup backup) { + AccountVO account = accountDao.findById(backup.getAccountId()); + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); + DataCenterVO zone = dataCenterDao.findById(backup.getZoneId()); + + 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()); + backupResponse.setVmId(vm.getUuid()); + backupResponse.setVolumes(backup.getVolumes()); + backupResponse.setStatus(backup.getStatus()); + backupResponse.setSize(backup.getSize()); + backupResponse.setProtectedSize(backup.getProtectedSize()); + backupResponse.setCreatedDate(backup.getCreated()); + backupResponse.setObjectName("backup"); + return backupResponse; + } +} diff --git a/engine/schema/src/main/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..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 @@ + @@ -356,4 +357,6 @@ + + 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..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 @@ -34,4 +34,57 @@ 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', + `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`.`vm_backup` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `uuid` varchar(40) NOT NULL, + `name` varchar(255) NOT NULL COMMENT 'backup name', + `description` varchar(255) COMMENT 'backup description', + `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` text, + `status` varchar(20) NOT NULL, + `size` bigint(20) DEFAULT 0, + `protected_size` bigint(20) DEFAULT 0, + `account_id` bigint(20) unsigned NOT NULL, + `zone_id` bigint(20) unsigned NOT NULL, + `created` datetime DEFAULT NULL, + `removed` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_backup__account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_backup__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_backup__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud_usage`.`usage_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/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/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..3da90b6519b9 --- /dev/null +++ b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java @@ -0,0 +1,131 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import org.apache.cloudstack.backup.dao.VMBackupDao; +import org.apache.log4j.Logger; + +import com.cloud.utils.Pair; +import com.cloud.utils.component.AdapterBase; +import com.cloud.vm.VirtualMachine; + +public class DummyBackupProvider extends AdapterBase implements BackupProvider { + + private static final Logger s_logger = Logger.getLogger(DummyBackupProvider.class); + + @Inject + private VMBackupDao backupDao; + + @Override + public String getName() { + return "dummy"; + } + + @Override + public String getDescription() { + return "Dummy Backup Plugin"; + } + + @Override + public List listBackupPolicies(Long zoneId) { + s_logger.debug("Listing backup policies on Dummy B&R Plugin"); + 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 VMBackup Provider"); + return true; + } + + @Override + 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()); + VMBackupVO dummyBackup = (VMBackupVO) backup; + dummyBackup.setStatus(VMBackup.Status.BackedUp); + dummyBackup.setCreated(new Date()); + backups.add(dummyBackup); + return dummyBackup; + } + + @Override + public boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String restorePointId) { + s_logger.debug("Restoring vm " + vm.getUuid() + "from backup " + backupUuid + " on the Dummy Backup Provider"); + return true; + } + + @Override + public Pair restoreBackedUpVolume(long zoneId, String backupUuid, String restorePointId, String volumeUuid, + String hostIp, String dataStoreUuid) { + s_logger.debug("Restoring volume " + volumeUuid + "from backup " + backupUuid + " on the Dummy VMBackup Provider"); + return new Pair<>(true, null); + } + + @Override + 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 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 (VMBackup vmBackup : backups) { + if (vmBackup.getExternalId().equals(backup.getExternalId())) { + return true; + } + } + return false; + } + + @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 Arrays.asList( + new VMBackup.RestorePoint("aaaaaaaa", "22/08/2017", "Full"), + new VMBackup.RestorePoint("bbbbbbbb", "23/08/2017", "Incremental")); + } +} diff --git a/plugins/backup/dummy/src/main/resources/META-INF/cloudstack/dummy-backup/module.properties b/plugins/backup/dummy/src/main/resources/META-INF/cloudstack/dummy-backup/module.properties new file mode 100644 index 000000000000..5969fb290545 --- /dev/null +++ b/plugins/backup/dummy/src/main/resources/META-INF/cloudstack/dummy-backup/module.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +name=dummy-backup +parent=backup diff --git a/plugins/backup/dummy/src/main/resources/META-INF/cloudstack/dummy-backup/spring-backup-dummy-context.xml b/plugins/backup/dummy/src/main/resources/META-INF/cloudstack/dummy-backup/spring-backup-dummy-context.xml new file mode 100644 index 000000000000..e154f9fe945e --- /dev/null +++ b/plugins/backup/dummy/src/main/resources/META-INF/cloudstack/dummy-backup/spring-backup-dummy-context.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/plugins/backup/veeam/pom.xml b/plugins/backup/veeam/pom.xml new file mode 100644 index 000000000000..9a6515ddca28 --- /dev/null +++ b/plugins/backup/veeam/pom.xml @@ -0,0 +1,55 @@ + + + 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..e5dbbcc56c8d --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java @@ -0,0 +1,259 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup; + +import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import org.apache.cloudstack.backup.veeam.VeeamClient; +import org.apache.cloudstack.backup.veeam.api.Job; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.commons.collections.CollectionUtils; +import org.apache.log4j.Logger; + +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.vmware.VmwareDatacenter; +import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMap; +import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao; +import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao; +import com.cloud.utils.Pair; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachine; + +public class VeeamBackupProvider extends AdapterBase implements BackupProvider, Configurable { + + private static final Logger LOG = Logger.getLogger(VeeamBackupProvider.class); + public static final String BACKUP_IDENTIFIER = "-CSBKP-"; + + public ConfigKey VeeamUrl = new ConfigKey<>("Advanced", String.class, + "backup.plugin.veeam.url", + "http://localhost:9399/api/", + "The Veeam backup and recovery URL.", true, ConfigKey.Scope.Zone); + + private ConfigKey VeeamUsername = new ConfigKey<>("Advanced", String.class, + "backup.plugin.veeam.host.username", + "administrator", + "The Veeam backup and recovery username.", true, ConfigKey.Scope.Zone); + + private ConfigKey VeeamPassword = new ConfigKey<>("Advanced", String.class, + "backup.plugin.veeam.host.password", + "P@ssword123", + "The Veeam backup and recovery password.", true, ConfigKey.Scope.Zone); + + private ConfigKey VeeamValidateSSLSecurity = new ConfigKey<>("Advanced", Boolean.class, "backup.plugin.veeam.validate.ssl", "true", + "When set to true, this will validate the SSL certificate when connecting to https/ssl enabled Veeam API service.", true, ConfigKey.Scope.Zone); + + private ConfigKey VeeamApiRequestTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.request.timeout", "300", + "The Veeam B&R API request timeout in seconds.", true, ConfigKey.Scope.Zone); + + @Inject + private VmwareDatacenterZoneMapDao vmwareDatacenterZoneMapDao; + @Inject + private VmwareDatacenterDao vmwareDatacenterDao; + + private VeeamClient getClient(final Long zoneId) { + try { + return new VeeamClient(VeeamUrl.valueIn(zoneId), VeeamUsername.valueIn(zoneId), VeeamPassword.valueIn(zoneId), + VeeamValidateSSLSecurity.valueIn(zoneId), VeeamApiRequestTimeout.valueIn(zoneId)); + } catch (URISyntaxException e) { + throw new CloudRuntimeException("Failed to parse Veeam API URL: " + e.getMessage()); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + LOG.error("Failed to build Veeam API client due to: ", e); + } + throw new CloudRuntimeException("Failed to build Veeam API client"); + } + + public List listBackupPolicies(final Long zoneId) { + List policies = new ArrayList<>(); + for (final BackupPolicy policy : getClient(zoneId).listJobs()) { + if (!policy.getName().contains(BACKUP_IDENTIFIER)) { + policies.add(policy); + } + } + return policies; + } + + @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; + } + + public boolean addVMToBackupPolicy(final BackupPolicy policy, final VirtualMachine vm) { + final VmwareDatacenter vmwareDatacenter = findVmwareDatacenterForVM(vm); + return getClient(vm.getDataCenterId()).removeVMFromVeeamJob(policy.getExternalId(), vm.getInstanceName(), vmwareDatacenter.getVcenterHost()); + } + + private String getGuestBackupName(final String instanceName, final String uuid) { + return String.format("%s%s%s", instanceName, BACKUP_IDENTIFIER, uuid); + } + + @Override + public 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.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; + } + } + } + } 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 boolean removeVMBackup(final VirtualMachine vm, final VMBackup backup) { + final VeeamClient client = getClient(vm.getDataCenterId()); + final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm); + try { + if (!client.removeVMFromVeeamJob(backup.getExternalId(), vm.getInstanceName(), vmwareDC.getVcenterHost())) { + LOG.warn("Failed to remove VM from Veeam Job id: " + backup.getExternalId()); + } + } catch (CloudRuntimeException e) { + LOG.debug("VM was removed from the job so could not remove again, trying to delete the veeam job now.", e); + } + + final String clonedJobName = getGuestBackupName(vm.getInstanceName(), backup.getUuid()); + if (!client.deleteJobAndBackup(clonedJobName)) { + LOG.warn("Failed to remove Veeam job and backup for job: " + clonedJobName); + } + return client.listJob(clonedJobName) == null; + } + + @Override + public boolean startBackup(final VMBackup vmBackup) { + final VeeamClient client = getClient(vmBackup.getZoneId()); + return client.startBackupJob(vmBackup.getExternalId()); + } + + @Override + public boolean restoreVMFromBackup(VirtualMachine vm, String backupUuid, String restorePointId) { + return getClient(vm.getDataCenterId()).restoreFullVM(vm.getInstanceName(), restorePointId); + } + + @Override + public Pair restoreBackedUpVolume(long zoneId, String backupUuid, String restorePointId, + String volumeUuid, String hostIp, String dataStoreUuid) { + + return getClient(zoneId).restoreVMToDifferentLocation(restorePointId, hostIp, dataStoreUuid); + } + + @Override + public List listVMBackups(Long zoneId, VirtualMachine vm) { + //return getClient(zoneId).listAllBackups(); + return null; + } + + @Override + public Map getBackupMetrics(final Long zoneId, final List backupList) { + final Map metrics = new HashMap<>(); + final Map backendMetrics = getClient(zoneId).getBackupMetrics(); + for (final 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 + public String getConfigComponentName() { + return BackupService.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[]{ + VeeamUrl, + VeeamUsername, + VeeamPassword, + VeeamValidateSSLSecurity, + VeeamApiRequestTimeout + }; + } + + @Override + public String getName() { + return "veeam"; + } + + @Override + public String getDescription() { + return "Veeam Backup Plugin"; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java new file mode 100644 index 000000000000..ffeb4668286d --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java @@ -0,0 +1,109 @@ +// 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.VMBackup; + +public class VeeamBackup implements VMBackup { + + 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 getVmId() { + return null; + } + + @Override + public List getBackedUpVolumes() { + return null; + } + + @Override + public String getVolumes() { + return null; + } + + @Override + public Status getStatus() { + return null; + } + + @Override + public Long getSize() { + return null; + } + + @Override + public Long getProtectedSize() { + return null; + } + + @Override + public Date getCreated() { + return null; + } + + @Override + public Date getRemoved() { + return null; + } + + @Override + public String getUuid() { + return uid; + } + + @Override + public long getId() { + return -1; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/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..3fdcca3d7153 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java @@ -0,0 +1,655 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam; + +import static org.apache.cloudstack.backup.VeeamBackupProvider.BACKUP_IDENTIFIER; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import java.util.UUID; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509TrustManager; + +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.backup.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; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.AuthCache; +import org.apache.http.client.CookieStore; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.client.BasicAuthCache; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.log4j.Logger; + +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.nio.TrustAllManager; +import com.cloud.utils.ssh.SshHelper; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; + +public class VeeamClient { + private static final Logger LOG = Logger.getLogger(VeeamClient.class); + + private final URI apiURI; + + private final HttpClient httpClient; + private final HttpClientContext httpContext = HttpClientContext.create(); + private final CookieStore httpCookieStore = new BasicCookieStore(); + + private String veeamServerIp; + private String veeamServerUsername; + private String veeamServerPassword; + private final int veeamServerPort = 22; + + public VeeamClient(final String url, final String username, final String password, final boolean validateCertificate, final int timeout) throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException { + this.apiURI = new URI(url); + + final CredentialsProvider provider = new BasicCredentialsProvider(); + provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password)); + final HttpHost adminHost = new HttpHost(this.apiURI.getHost(), this.apiURI.getPort(), this.apiURI.getScheme()); + final AuthCache authCache = new BasicAuthCache(); + authCache.put(adminHost, new BasicScheme()); + + this.httpContext.setCredentialsProvider(provider); + this.httpContext.setAuthCache(authCache); + + final RequestConfig config = RequestConfig.custom() + .setConnectTimeout(timeout * 1000) + .setConnectionRequestTimeout(timeout * 1000) + .setSocketTimeout(timeout * 1000) + .build(); + + if (!validateCertificate) { + final SSLContext sslcontext = SSLUtils.getSSLContext(); + sslcontext.init(null, new X509TrustManager[]{new TrustAllManager()}, new SecureRandom()); + final SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE); + this.httpClient = HttpClientBuilder.create() + .setDefaultCredentialsProvider(provider) + .setDefaultCookieStore(httpCookieStore) + .setDefaultRequestConfig(config) + .setSSLSocketFactory(factory) + .build(); + } else { + this.httpClient = HttpClientBuilder.create() + .setDefaultCredentialsProvider(provider) + .setDefaultCookieStore(httpCookieStore) + .setDefaultRequestConfig(config) + .build(); + } + + try { + final HttpResponse response = post("/sessionMngr/", null); + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) { + throw new CloudRuntimeException("Failed to create and authenticate Veeam API client, please check the settings."); + } + } catch (final IOException e) { + throw new CloudRuntimeException("Failed to authenticate Veeam API service due to:" + e.getMessage()); + } + + setVeeamSshCredentials(this.apiURI.getHost(), username, password); + } + + protected void setVeeamSshCredentials(String hostIp, String username, String password) { + this.veeamServerIp = hostIp; + this.veeamServerUsername = username; + this.veeamServerPassword = password; + } + + private void checkAuthFailure(final HttpResponse response) { + if (response != null && response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + final Credentials credentials = httpContext.getCredentialsProvider().getCredentials(AuthScope.ANY); + LOG.error("Veeam API authentication failed, please check Veeam configuration. Admin auth principal=" + credentials.getUserPrincipal() + ", password=" + credentials.getPassword() + ", API url=" + apiURI.toString()); + throw new ServerApiException(ApiErrorCode.UNAUTHORIZED, "Veeam B&R API call unauthorized, please ask your administrator to fix integration issues."); + } + } + + private void checkResponseOK(final HttpResponse response) { + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT) { + LOG.debug("Requested Veeam resource does not exist"); + return; + } + if (!(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK || + response.getStatusLine().getStatusCode() == HttpStatus.SC_ACCEPTED) && + response.getStatusLine().getStatusCode() != HttpStatus.SC_NO_CONTENT) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to get valid response from Veeam B&R API call, please ask your administrator to diagnose and fix issues."); + } + } + + private void checkResponseTimeOut(final Exception e) { + if (e instanceof ConnectTimeoutException || e instanceof SocketTimeoutException) { + throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, "Veeam API operation timed out, please try again."); + } + } + + private HttpResponse get(final String path) throws IOException { + final HttpGet request = new HttpGet(apiURI.toString() + path); + final HttpResponse response = httpClient.execute(request, httpContext); + checkAuthFailure(response); + return response; + } + + private HttpResponse post(final String path, final Object obj) throws IOException { + String xml = null; + if (obj != null) { + XmlMapper xmlMapper = new XmlMapper(); + xml = xmlMapper.writer() + .with(ToXmlGenerator.Feature.WRITE_XML_DECLARATION) + .writeValueAsString(obj); + // Remove invalid/empty xmlns + xml = xml.replace(" xmlns=\"\"", ""); + } + + final HttpPost request = new HttpPost(apiURI.toString() + path); + request.setHeader("Content-type", "application/xml"); + if (StringUtils.isNotBlank(xml)) { + request.setEntity(new StringEntity(xml)); + } + + final HttpResponse response = httpClient.execute(request, httpContext); + checkAuthFailure(response); + return response; + } + + private HttpResponse delete(final String path) throws IOException { + final HttpResponse response = httpClient.execute(new HttpDelete(apiURI.toString() + path), httpContext); + checkAuthFailure(response); + return response; + } + + /////////////////////////////////////////////////////////////////// + //////////////// Private Veeam Helper Methods ///////////////////// + /////////////////////////////////////////////////////////////////// + + private String findDCHierarchy(final String vmwareDcName) { + LOG.debug("Trying to find hierarchy ID for vmware datacenter: " + vmwareDcName); + + try { + final HttpResponse response = get("/hierarchyRoots"); + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + final EntityReferences references = objectMapper.readValue(response.getEntity().getContent(), EntityReferences.class); + for (final Ref ref : references.getRefs()) { + if (ref.getName().equals(vmwareDcName) && ref.getType().equals("HierarchyRootReference")) { + return ref.getUid(); + } + } + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + throw new CloudRuntimeException("Failed to find hierarchy reference for VMware datacenter " + vmwareDcName + " in Veeam, please ask administrator to check Veeam B&R manager configuration"); + } + + private String lookupVM(final String hierarchyId, final String vmName) { + LOG.debug("Trying to lookup VM from veeam hierarchy:" + hierarchyId + " for vm name:" + vmName); + + try { + final HttpResponse response = get(String.format("/lookup?host=%s&type=Vm&name=%s", hierarchyId, vmName)); + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + final HierarchyItems items = objectMapper.readValue(response.getEntity().getContent(), HierarchyItems.class); + if (items == null || items.getItems() == null || items.getItems().isEmpty()) { + throw new CloudRuntimeException("Could not find VM " + vmName + " in Veeam, please ask administrator to check Veeam B&R manager"); + } + for (final HierarchyItem item : items.getItems()) { + if (item.getObjectName().equals(vmName) && item.getObjectType().equals("Vm")) { + return item.getObjectRef(); + } + } + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + throw new CloudRuntimeException("Failed to lookup VM " + vmName + " in Veeam, please ask administrator to check Veeam B&R manager configuration"); + } + + private Task parseTaskResponse(HttpResponse response) throws IOException { + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + return objectMapper.readValue(response.getEntity().getContent(), Task.class); + } + + private RestoreSession parseRestoreSessionResponse(HttpResponse response) throws IOException { + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + return objectMapper.readValue(response.getEntity().getContent(), RestoreSession.class); + } + + // FIXME: configure task timeout/limits + private boolean checkTaskStatus(final HttpResponse response) throws IOException { + final Task task = parseTaskResponse(response); + for (int i = 0; i < 60; i++) { + final HttpResponse taskResponse = get("/tasks/" + task.getTaskId()); + final Task polledTask = parseTaskResponse(taskResponse); + if (polledTask.getState().equals("Finished")) { + final HttpResponse taskDeleteResponse = delete("/tasks/" + task.getTaskId()); + if (taskDeleteResponse.getStatusLine().getStatusCode() != HttpStatus.SC_NO_CONTENT) { + LOG.warn("Operation failed for veeam task id=" + task.getTaskId()); + } + if (polledTask.getResult().getSuccess().equals("true")) { + Pair pair = getRelatedLinkPair(polledTask.getLink()); + if (pair != null) { + String url = pair.first(); + String type = pair.second(); + String path = url.replace(apiURI.toString(), ""); + if (type.equals("RestoreSession")) { + for (int j = 0; j < 60; j++) { + HttpResponse relatedResponse = get(path); + RestoreSession session = parseRestoreSessionResponse(relatedResponse); + if (session.getResult().equals("Success")) { + return true; + } + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + throw new CloudRuntimeException("Related job type: " + type + " was not successful"); + } + } + return true; + } + throw new CloudRuntimeException("Failed to assign VM to backup 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; + } + + private Pair getRelatedLinkPair(List links) { + for (Link link : links) { + if (link.getRel().equals("Related")) { + return new Pair<>(link.getHref(), link.getType()); + } + } + return null; + } + + //////////////////////////////////////////////////////// + //////////////// Public Veeam APIs ///////////////////// + //////////////////////////////////////////////////////// + + public Ref listBackupRepository(final String backupServerId) { + LOG.debug("Trying to list backup repository for backup server id: " + backupServerId); + try { + final HttpResponse response = get(String.format("/backupServers/%s/repositories", backupServerId)); + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + final EntityReferences references = objectMapper.readValue(response.getEntity().getContent(), EntityReferences.class); + for (final Ref ref : references.getRefs()) { + if (ref.getType().equals("RepositoryReference")) { + return ref; + } + } + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + return null; + } + + public List listAllBackups() { + LOG.debug("Trying to list Veeam backups"); + try { + final HttpResponse response = get("/backups"); + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + final EntityReferences entityReferences = objectMapper.readValue(response.getEntity().getContent(), EntityReferences.class); + final List backups = new ArrayList<>(); + for (final Ref ref : entityReferences.getRefs()) { + backups.add(new VeeamBackup(ref.getName(), ref.getUid())); + } + return backups; + } catch (final IOException e) { + LOG.error("Failed to list Veeam backups due to:", e); + checkResponseTimeOut(e); + } + return new ArrayList<>(); + } + + public List listJobs() { + LOG.debug("Trying to list backup policies that are Veeam jobs"); + try { + final HttpResponse response = get("/jobs"); + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + final EntityReferences entityReferences = objectMapper.readValue(response.getEntity().getContent(), EntityReferences.class); + final List policies = new ArrayList<>(); + for (final Ref ref : entityReferences.getRefs()) { + policies.add(new 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 Job listJob(final String jobId) { + LOG.debug("Trying to list veeam job id: " + jobId); + try { + final HttpResponse response = get(String.format("/jobs/%s?format=Entity", + jobId.replace("urn:veeam:Job:", ""))); + checkResponseOK(response); + final ObjectMapper objectMapper = new XmlMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return objectMapper.readValue(response.getEntity().getContent(), Job.class); + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } catch (final ServerApiException e) { + LOG.error(e); + } + return null; + } + + public boolean toggleJobSchedule(final String jobId) { + LOG.debug("Trying to toggle schedule for Veeam job: " + jobId); + try { + final HttpResponse response = post(String.format("/jobs/%s?action=toggleScheduleEnabled", jobId), null); + return checkTaskStatus(response); + } catch (final IOException e) { + LOG.error("Failed to toggle Veeam job schedule due to:", e); + checkResponseTimeOut(e); + } + return false; + } + + public boolean startBackupJob(final String jobId) { + LOG.debug("Trying to start ad-hoc backup for Veeam job: " + jobId); + try { + final HttpResponse response = post(String.format("/jobs/%s?action=start", jobId), null); + return checkTaskStatus(response); + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + return false; + } + + public boolean cloneVeeamJob(final Job parentJob, final String clonedJobName) { + LOG.debug("Trying to clone veeam job: " + parentJob.getUid() + " with backup uuid: " + clonedJobName); + try { + final Ref repositoryRef = listBackupRepository(parentJob.getBackupServerId()); + final BackupJobCloneInfo cloneInfo = new BackupJobCloneInfo(); + cloneInfo.setJobName(clonedJobName); + cloneInfo.setFolderName(clonedJobName); + cloneInfo.setRepositoryUid(repositoryRef.getUid()); + final JobCloneSpec cloneSpec = new JobCloneSpec(cloneInfo); + final HttpResponse response = post(String.format("/jobs/%s?action=clone", parentJob.getId()), cloneSpec); + return checkTaskStatus(response); + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + return false; + } + + public boolean addVMToVeeamJob(final String jobId, final String vmwareInstanceName, final String vmwareDcName) { + LOG.debug("Trying to add VM to backup 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); + if (jobObjects == null || jobObjects.getObjects() == null) { + LOG.warn("No objects found in the Veeam job " + jobId); + return false; + } + for (final ObjectInJob jobObject : jobObjects.getObjects()) { + if (jobObject.getName().equals(vmwareInstanceName) && jobObject.getHierarchyObjRef().equals(veeamVmRefId)) { + final HttpResponse deleteResponse = delete(String.format("/jobs/%s/includes/%s", jobId, jobObject.getObjectInJobId())); + return checkTaskStatus(deleteResponse); + } + } + LOG.warn(vmwareInstanceName + " VM was not found to be attached to veaam job (backup policy): " + jobId); + return false; + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + return false; + } + + public boolean restoreFullVM(final String vmwareInstanceName, final String restorePointId) { + LOG.debug("Trying to restore full VM: " + vmwareInstanceName + " from backup"); + try { + final HttpResponse response = post(String.format("/vmRestorePoints/%s?action=restore", restorePointId), null); + return checkTaskStatus(response); + } catch (final IOException e) { + LOG.error("Failed to restore full VM due to: ", e); + checkResponseTimeOut(e); + } + throw new CloudRuntimeException("Failed to restore full VM from backup"); + } + + ///////////////////////////////////////////////////////////////// + //////////////// Public Veeam PS based APIs ///////////////////// + ///////////////////////////////////////////////////////////////// + + /** + * Generate a single command to be passed through SSH + */ + protected String transformPowerShellCommandList(List cmds) { + StringJoiner joiner = new StringJoiner(";"); + joiner.add("PowerShell Add-PSSnapin VeeamPSSnapin"); + for (String cmd : cmds) { + joiner.add(cmd); + } + return joiner.toString(); + } + + /** + * Execute a list of commands in a single call on PowerShell through SSH + */ + protected Pair executePowerShellCommands(List cmds) { + try { + // FIXME: make ssh timeouts configurable + Pair pairResult = SshHelper.sshExecute(veeamServerIp, veeamServerPort, + veeamServerUsername, null, veeamServerPassword, + transformPowerShellCommandList(cmds), + 120000, 120000, 900000); + return pairResult; + } catch (Exception e) { + throw new CloudRuntimeException("Error while executing PowerShell commands due to: " + e.getMessage()); + } + } + + public boolean deleteJobAndBackup(final String jobName) { + Pair result = executePowerShellCommands(Arrays.asList( + String.format("$job = Get-VBRJob -Name \"%s\"", jobName), + "if ($job) { Remove-VBRJob -Job $job -Confirm:$false }", + String.format("$backup = Get-VBRBackup -Name \"%s\"", jobName), + "if ($backup) { Remove-VBRBackup -Backup $backup -FromDisk -Confirm:$false }", + "$repo = Get-VBRBackupRepository", + "Sync-VBRBackupRepository -Repository $repo" + )); + return result.first() && !result.second().contains("Failed to delete"); + } + + public Map getBackupMetrics() { + final String separator = "====="; + final List cmds = Arrays.asList( + "$backups = Get-VBRBackup", + "foreach ($backup in $backups) {" + + "$backup.JobName;" + + "$storageGroups = $backup.GetStorageGroups();" + + "foreach ($group in $storageGroups) {" + + "$usedSize = 0;" + + "$dataSize = 0;" + + "$sizePerStorage = $group.GetStorages().Stats.BackupSize;" + + "$dataPerStorage = $group.GetStorages().Stats.DataSize;" + + "foreach ($size in $sizePerStorage) {" + + "$usedSize += $size;" + + "}" + + "foreach ($size in $dataPerStorage) {" + + "$dataSize += $size;" + + "}" + + "$usedSize;" + + "$dataSize;" + + "}" + + "echo \"" + separator + "\"" + + "}" + ); + Pair response = executePowerShellCommands(cmds); + final Map sizes = new HashMap<>(); + for (final String block : response.second().split(separator + "\r\n")) { + final String[] parts = block.split("\r\n"); + if (parts.length != 3) { + continue; + } + final String backupName = parts[0]; + if (backupName != null && backupName.contains(BACKUP_IDENTIFIER)) { + final String[] names = backupName.split(BACKUP_IDENTIFIER); + sizes.put(names[names.length - 1], new 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].trim(); + } else if (part.matches("CreationTime(\\s)+:(.)*")) { + String [] split = part.split(":", 2); + created = split[1].trim(); + } else if (part.matches("Type(\\s)+:(.)*")) { + String [] split = part.split(":"); + type = split[1].trim(); + } + } + return new VMBackup.RestorePoint(id, created, type); + } + + public List listRestorePoints(String backupName, String vmInternalName) { + final List cmds = Arrays.asList( + String.format("$backup = Get-VBRBackup -Name \"%s\"", backupName), + String.format("Get-VBRRestorePoint -Backup:$backup -Name \"%s\"", vmInternalName) + ); + Pair response = executePowerShellCommands(cmds); + if (response == null || !response.first()) { + throw new CloudRuntimeException("Failed to list restore points"); + } + final List restorePoints = new ArrayList<>(); + for (final String block : response.second().split("\r\n\r\n")) { + if (block.isEmpty()) { + continue; + } + final String[] parts = block.split("\r\n"); + restorePoints.add(getRestorePointFromBlock(parts)); + } + return restorePoints; + } + + public Pair restoreVMToDifferentLocation(String restorePointId, String hostIp, String dataStoreUuid) { + final String restoreLocation = "CS-RSTR-" + UUID.randomUUID().toString(); + final String datastoreId = dataStoreUuid.replace("-",""); + final List cmds = Arrays.asList( + "$points = Get-VBRRestorePoint", + String.format("foreach($point in $points) { if ($point.Id -eq '%s') { break; } }",restorePointId), + String.format("$server = Get-VBRServer -Name \"%s\"", hostIp), + String.format("$ds = Find-VBRViDatastore -Server:$server -Name \"%s\"", datastoreId), + String.format("Start-VBRRestoreVM -RestorePoint:$point -Server:$server -Datastore:$ds -VMName \"%s\"", restoreLocation) + ); + Pair result = executePowerShellCommands(cmds); + if (result == null || !result.first()) { + throw new CloudRuntimeException("Failed to restore VM to location " + restoreLocation); + } + return new Pair<>(result.first(), restoreLocation); + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObject.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObject.java new file mode 100644 index 000000000000..6ecf08081d86 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObject.java @@ -0,0 +1,28 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam; + +import java.util.List; + +public interface VeeamObject { + String getUuid(); + String getName(); + String getHref(); + String getType(); + List getLinks(); +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/BackupJobCloneInfo.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/BackupJobCloneInfo.java new file mode 100644 index 000000000000..fdb6081c6fec --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/BackupJobCloneInfo.java @@ -0,0 +1,58 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "BackupJobCloneInfo") +public class BackupJobCloneInfo { + + @JacksonXmlProperty(localName = "JobName") + private String jobName; + + @JacksonXmlProperty(localName = "FolderName") + private String folderName; + + @JacksonXmlProperty(localName = "RepositoryUid") + private String repositoryUid; + + public String getJobName() { + return jobName; + } + + public void setJobName(String jobName) { + this.jobName = jobName; + } + + public String getFolderName() { + return folderName; + } + + public void setFolderName(String folderName) { + this.folderName = folderName; + } + + public String getRepositoryUid() { + return repositoryUid; + } + + public void setRepositoryUid(String repositoryUid) { + this.repositoryUid = repositoryUid; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/CreateObjectInJobSpec.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/CreateObjectInJobSpec.java new file mode 100644 index 000000000000..16de447d3c06 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/CreateObjectInJobSpec.java @@ -0,0 +1,46 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "CreateObjectInJobSpec", namespace = "http://www.veeam.com/ent/v1.0") +public class CreateObjectInJobSpec { + @JacksonXmlProperty(localName = "HierarchyObjRef") + String objRef; + + @JacksonXmlProperty(localName = "HierarchyObjName") + String objName; + + public String getObjRef() { + return objRef; + } + + public void setObjRef(String objRef) { + this.objRef = objRef; + } + + public String getObjName() { + return objName; + } + + public void setObjName(String objName) { + this.objName = objName; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/EntityReferences.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/EntityReferences.java new file mode 100644 index 000000000000..928e0dae4a56 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/EntityReferences.java @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam.api; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "EntityReferences") +public class EntityReferences { + @JacksonXmlProperty(localName = "Ref") + @JacksonXmlElementWrapper(localName = "Ref", useWrapping = false) + private List refs; + + public List getRefs() { + return refs; + } + + public void setRefs(List refs) { + this.refs = refs; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/HierarchyItem.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/HierarchyItem.java new file mode 100644 index 000000000000..46f0e5e420cc --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/HierarchyItem.java @@ -0,0 +1,68 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "HierarchyItem") +public class HierarchyItem { + @JacksonXmlProperty(localName = "Type", isAttribute = true) + private String type; + + @JacksonXmlProperty(localName = "ObjectRef") + private String objectRef; + + @JacksonXmlProperty(localName = "ObjectType") + private String objectType; + + @JacksonXmlProperty(localName = "ObjectName") + private String objectName; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getObjectRef() { + return objectRef; + } + + public void setObjectRef(String objectRef) { + this.objectRef = objectRef; + } + + public String getObjectType() { + return objectType; + } + + public void setObjectType(String objectType) { + this.objectType = objectType; + } + + public String getObjectName() { + return objectName; + } + + public void setObjectName(String objectName) { + this.objectName = objectName; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/HierarchyItems.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/HierarchyItems.java new file mode 100644 index 000000000000..a87c6b0f9839 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/HierarchyItems.java @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam.api; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "HierarchyItems") +public class HierarchyItems { + @JacksonXmlProperty(localName = "HierarchyItem") + @JacksonXmlElementWrapper(localName = "HierarchyItem", useWrapping = false) + private List items; + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Job.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Job.java new file mode 100644 index 000000000000..e42ecf0d4ccc --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Job.java @@ -0,0 +1,163 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam.api; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "Job") +public class Job { + + @JacksonXmlProperty(localName = "Name", isAttribute = true) + private String name; + + @JacksonXmlProperty(localName = "Href", isAttribute = true) + private String href; + + @JacksonXmlProperty(localName = "Type", isAttribute = true) + private String type; + + @JacksonXmlProperty(localName = "UID", isAttribute = true) + private String uid; + + @JacksonXmlProperty(localName = "Link") + @JacksonXmlElementWrapper(localName = "Links") + private List link; + + @JacksonXmlProperty(localName = "Platform") + private String platform; + + @JacksonXmlProperty(localName = "Description") + private String description; + + @JacksonXmlProperty(localName = "NextRun") + private String nextRun; + + @JacksonXmlProperty(localName = "JobType") + private String jobType; + + @JacksonXmlProperty(localName = "ScheduleEnabled") + private Boolean scheduleEnabled; + + @JacksonXmlProperty(localName = "ScheduleConfigured") + private Boolean scheduleConfigured; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getUid() { + return uid; + } + + public String getId() { + return uid.replace("urn:veeam:Job:", ""); + } + + public void setUid(String uid) { + this.uid = uid; + } + + public List getLink() { + return link; + } + + public void setLink(List link) { + this.link = link; + } + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getNextRun() { + return nextRun; + } + + public void setNextRun(String nextRun) { + this.nextRun = nextRun; + } + + public String getJobType() { + return jobType; + } + + public void setJobType(String jobType) { + this.jobType = jobType; + } + + public String getBackupServerId() { + for (final Link l : link) { + if (l.getType().equals("BackupServerReference")) { + return "" + l.getHref().split("backupServers/")[1]; + } + } + return null; + } + + public Boolean getScheduleEnabled() { + return scheduleEnabled; + } + + public void setScheduleEnabled(String scheduleEnabled) { + this.scheduleEnabled = Boolean.valueOf(scheduleEnabled); + } + + public Boolean getScheduleConfigured() { + return scheduleConfigured; + } + + public void setScheduleConfigured(String scheduleConfigured) { + this.scheduleConfigured = Boolean.valueOf(scheduleConfigured); + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/JobCloneSpec.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/JobCloneSpec.java new file mode 100644 index 000000000000..fd1bdfbdedfd --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/JobCloneSpec.java @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "JobCloneSpec", namespace = "http://www.veeam.com/ent/v1.0") +public class JobCloneSpec { + @JacksonXmlProperty(localName = "BackupJobCloneInfo") + @JacksonXmlElementWrapper(localName = "BackupJobCloneInfo", useWrapping = false) + BackupJobCloneInfo jobCloneInfo; + + public JobCloneSpec(final BackupJobCloneInfo jobCloneInfo) { + this.jobCloneInfo = jobCloneInfo; + } + + public BackupJobCloneInfo getJobCloneInfo() { + return jobCloneInfo; + } + + public void setJobCloneInfo(BackupJobCloneInfo jobCloneInfo) { + this.jobCloneInfo = jobCloneInfo; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Link.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Link.java new file mode 100644 index 000000000000..b89d77fa550a --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Link.java @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "Link") +public class Link { + + @JacksonXmlProperty(localName = "Name", isAttribute = true) + private String name; + + @JacksonXmlProperty(localName = "Href", isAttribute = true) + private String href; + + @JacksonXmlProperty(localName = "Type", isAttribute = true) + private String type; + + @JacksonXmlProperty(localName = "Rel", isAttribute = true) + private String rel; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getRel() { + return rel; + } + + public void setRel(String rel) { + this.rel = rel; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/ObjectInJob.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/ObjectInJob.java new file mode 100644 index 000000000000..987176e59f89 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/ObjectInJob.java @@ -0,0 +1,94 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam.api; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "ObjectInJob") +public class ObjectInJob { + @JacksonXmlProperty(localName = "Href", isAttribute = true) + private String href; + + @JacksonXmlProperty(localName = "Type", isAttribute = true) + private String type; + + @JacksonXmlProperty(localName = "Link") + @JacksonXmlElementWrapper(localName = "Links") + private List link; + + @JacksonXmlProperty(localName = "ObjectInJobId", isAttribute = true) + private String objectInJobId; + + @JacksonXmlProperty(localName = "HierarchyObjRef", isAttribute = true) + private String hierarchyObjRef; + + @JacksonXmlProperty(localName = "Name", isAttribute = true) + private String name; + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List getLink() { + return link; + } + + public void setLink(List link) { + this.link = link; + } + + public String getObjectInJobId() { + return objectInJobId; + } + + public void setObjectInJobId(String objectInJobId) { + this.objectInJobId = objectInJobId; + } + + public String getHierarchyObjRef() { + return hierarchyObjRef; + } + + public void setHierarchyObjRef(String hierarchyObjRef) { + this.hierarchyObjRef = hierarchyObjRef; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/ObjectsInJob.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/ObjectsInJob.java new file mode 100644 index 000000000000..982dae592dca --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/ObjectsInJob.java @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam.api; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "ObjectsInJob") +public class ObjectsInJob { + @JacksonXmlProperty(localName = "ObjectInJob") + @JacksonXmlElementWrapper(localName = "ObjectInJob", useWrapping = false) + private List objects; + + public List getObjects() { + return objects; + } + + public void setObjects(List objects) { + this.objects = objects; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Ref.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Ref.java new file mode 100644 index 000000000000..683fd3773f6c --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Ref.java @@ -0,0 +1,83 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam.api; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "Ref") +public class Ref { + @JacksonXmlProperty(localName = "UID", isAttribute = true) + private String uid; + + @JacksonXmlProperty(localName = "Name", isAttribute = true) + private String name; + + @JacksonXmlProperty(localName = "Href", isAttribute = true) + private String href; + + @JacksonXmlProperty(localName = "Type", isAttribute = true) + private String type; + + @JacksonXmlProperty(localName = "Link") + @JacksonXmlElementWrapper(localName = "Links") + private List link; + + public List getLink() { + return link; + } + + public void setLink(List link) { + this.link = link; + } + + public String getUid() { + return uid; + } + + public void setUid(String uid) { + this.uid = uid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/RestoreSession.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/RestoreSession.java new file mode 100644 index 000000000000..0675e49fc276 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/RestoreSession.java @@ -0,0 +1,120 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +import java.util.List; + +@JacksonXmlRootElement(localName = "RestoreSession") +public class RestoreSession { + + @JacksonXmlProperty(localName = "Type", isAttribute = true) + private String type; + + @JacksonXmlProperty(localName = "Href", isAttribute = true) + private String href; + + @JacksonXmlProperty(localName = "Name", isAttribute = true) + private String name; + + @JacksonXmlProperty(localName = "UID", isAttribute = true) + private String uid; + + @JacksonXmlProperty(localName = "VmDisplayName", isAttribute = true) + private String vmDisplayName; + + @JacksonXmlProperty(localName = "Link") + @JacksonXmlElementWrapper(localName = "Links") + private List link; + + @JacksonXmlProperty(localName = "JobType") + private String jobType; + + @JacksonXmlProperty(localName = "CreationTimeUTC") + private String creationTimeUTC; + + @JacksonXmlProperty(localName = "EndTimeUTC") + private String endTimeUTC; + + @JacksonXmlProperty(localName = "State") + private String state; + + @JacksonXmlProperty(localName = "Result") + private String result; + + @JacksonXmlProperty(localName = "Progress") + private String progress; + + @JacksonXmlProperty(localName = "RestoredObjRef") + private String restoredObjRef; + + public List getLink() { + return link; + } + + public String getJobType() { + return jobType; + } + + public String getState() { + return state; + } + + public String getResult() { + return result; + } + + public String getType() { + return type; + } + + public String getHref() { + return href; + } + + public String getVmDisplayName() { + return vmDisplayName; + } + + public String getCreationTimeUTC() { + return creationTimeUTC; + } + + public String getEndTimeUTC() { + return endTimeUTC; + } + + public String getProgress() { + return progress; + } + + public String getName() { + return name; + } + + public String getUid() { + return uid; + } + + public String getRestoredObjRef() { + return restoredObjRef; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Result.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Result.java new file mode 100644 index 000000000000..26fc8634184f --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Result.java @@ -0,0 +1,47 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "Result") +public class Result { + + @JacksonXmlProperty(localName = "Success", isAttribute = true) + private String success; + + @JacksonXmlProperty(localName = "Message") + private String message; + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Task.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Task.java new file mode 100644 index 000000000000..e8d14e57d488 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/Task.java @@ -0,0 +1,106 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam.api; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "CreateObjectInJobSpec") +public class Task { + @JacksonXmlProperty(localName = "Type", isAttribute = true) + private String type; + + @JacksonXmlProperty(localName = "Href", isAttribute = true) + private String href; + + @JacksonXmlProperty(localName = "Link") + @JacksonXmlElementWrapper(localName = "Links") + private List link; + + @JacksonXmlProperty(localName = "TaskId") + private String taskId; + + @JacksonXmlProperty(localName = "State") + private String state; + + @JacksonXmlProperty(localName = "Operation") + private String operation; + + @JacksonXmlProperty(localName = "Result") + @JacksonXmlElementWrapper(localName = "Result", useWrapping = false) + private Result result; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public List getLink() { + return link; + } + + public void setLink(List link) { + this.link = link; + } + + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + + public Result getResult() { + return result; + } + + public void setResult(Result result) { + this.result = result; + } +} diff --git a/plugins/backup/veeam/src/main/resources/META-INF/cloudstack/veeam/module.properties b/plugins/backup/veeam/src/main/resources/META-INF/cloudstack/veeam/module.properties new file mode 100644 index 000000000000..ee40b21a323d --- /dev/null +++ b/plugins/backup/veeam/src/main/resources/META-INF/cloudstack/veeam/module.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +name=veeam +parent=backup diff --git a/plugins/backup/veeam/src/main/resources/META-INF/cloudstack/veeam/spring-backup-veeam-context.xml b/plugins/backup/veeam/src/main/resources/META-INF/cloudstack/veeam/spring-backup-veeam-context.xml new file mode 100644 index 000000000000..f2403cf37a4b --- /dev/null +++ b/plugins/backup/veeam/src/main/resources/META-INF/cloudstack/veeam/spring-backup-veeam-context.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java b/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java new file mode 100644 index 000000000000..1e877e1bbfb6 --- /dev/null +++ b/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java @@ -0,0 +1,108 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.veeam; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; + +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.backup.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; +import org.junit.Test; + +import com.cloud.utils.Pair; +import com.github.tomakehurst.wiremock.client.BasicCredentials; +import com.github.tomakehurst.wiremock.junit.WireMockRule; + +public class VeeamClientTest { + + private String adminUsername = "administrator"; + private String adminPassword = "P@ssword123"; + private VeeamClient client; + + @Rule + public WireMockRule wireMockRule = new WireMockRule(9399); + + @Before + public void setUp() throws Exception { + wireMockRule.stubFor(post(urlMatching(".*/sessionMngr/")) + .willReturn(aResponse() + .withStatus(201) + .withHeader("X-RestSvcSessionId", "some-session-auth-id") + .withBody(""))); + client = new VeeamClient("http://localhost:9399/api/", adminUsername, adminPassword, true, 60); + } + + @Test + public void testBasicAuth() { + verify(postRequestedFor(urlMatching(".*/sessionMngr/")) + .withBasicAuth(new BasicCredentials(adminUsername, adminPassword))); + } + + @Test + public void testVeeamJobs() { + wireMockRule.stubFor(get(urlMatching(".*/jobs")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/xml") + .withStatus(200) + .withBody("\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""))); + List policies = client.listJobs(); + verify(getRequestedFor(urlMatching(".*/jobs"))); + Assert.assertEquals(policies.size(), 1); + Assert.assertEquals(policies.get(0).getName(), "ZONE1-GOLD"); + } + + public void testJob() throws Exception { + VeeamClient client = new VeeamClient("http://10.2.2.89:9399/api/", adminUsername, adminPassword, true, 30); + Job j = client.listJob("8acac50d-3711-4c99-bf7b-76fe9c7e39c3"); + boolean result = client.cloneVeeamJob(j, "some-uuid-cloned"); + client.deleteJobAndBackup("some-uuid-cloned"); + } + + public void testVeeamPS() throws Exception { + VeeamClient client = new VeeamClient("http://10.2.2.89:9399/api/", adminUsername, adminPassword, true, 30); + Map sizes = client.getBackupMetrics(); + Job j = client.listJob("ZONE1-GOLD_clone1"); + } + + public void testRestoreVolume() { + client.setVeeamSshCredentials("10.2.2.89", "administrator", "P@ssword123"); + Pair booleanStringPair = client.restoreVMToDifferentLocation("362fcba7-283b-4416-9763-55ec59bd1285", "10.2.2.9", "24abcb8f-4211-374f-a2e1-e5c0b7e88a2d"); + booleanStringPair.first(); + } +} \ No newline at end of file diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java index 81dfc33bb883..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 @@ -26,19 +26,25 @@ import javax.inject.Inject; +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; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.storage.command.CopyCommand; -import org.apache.cloudstack.storage.command.DownloadCommand; import org.apache.cloudstack.storage.command.DeleteCommand; +import org.apache.cloudstack.storage.command.DownloadCommand; import org.apache.cloudstack.storage.command.StorageSubSystemCommand; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import com.cloud.agent.api.BackupSnapshotCommand; @@ -59,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; @@ -66,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); @@ -144,6 +194,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 +706,679 @@ 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, VMBackup backup) { + if (!disksMapping.containsKey(disk)) { + return false; + } + VolumeVO volumeVO = disksMapping.get(disk); + 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"); + } + + /** + * Check backing info + */ + private void checkBackingInfo(VirtualDeviceBackingInfo backingInfo) { + if (! (backingInfo instanceof VirtualDiskFlatVer2BackingInfo)) { + throw new CloudRuntimeException("Unsopported backing, expected " + VirtualDiskFlatVer2BackingInfo.class.getSimpleName()); + } + } + + /** + * Get pool ID from datastore UUID + */ + private Long getPoolIdFromDatastoreUuid(String datastoreUuid) { + String poolUuid = UuidUtils.normalize(datastoreUuid); + StoragePoolVO pool = _storagePoolDao.findByUuid(poolUuid); + if (pool == null) { + throw new CloudRuntimeException("Couldn't find storage pool " + poolUuid); + } + return pool.getId(); + } + + /** + * Get pool ID for disk + */ + private Long getPoolId(VirtualDisk disk) { + VirtualDeviceBackingInfo backing = disk.getBacking(); + checkBackingInfo(backing); + VirtualDiskFlatVer2BackingInfo info = (VirtualDiskFlatVer2BackingInfo) backing; + String[] fileNameParts = info.getFileName().split(" "); + String datastoreUuid = StringUtils.substringBetween(fileNameParts[0], "[", "]"); + return getPoolIdFromDatastoreUuid(datastoreUuid); + } + + /** + * Get volume name from filename + */ + private String getVolumeNameFromFileName(String fileName) { + String[] fileNameParts = fileName.split(" "); + String volumePath = fileNameParts[1]; + return volumePath.split("/")[1].replaceFirst(".vmdk", ""); + } + + /** + * Get root disk template path + */ + private String getRootDiskTemplatePath(VirtualDisk rootDisk) { + VirtualDeviceBackingInfo backing = rootDisk.getBacking(); + checkBackingInfo(backing); + VirtualDiskFlatVer2BackingInfo info = (VirtualDiskFlatVer2BackingInfo) backing; + VirtualDiskFlatVer2BackingInfo parent = info.getParent(); + return (parent != null) ? getVolumeNameFromFileName(parent.getFileName()) : getVolumeNameFromFileName(info.getFileName()); + } + + /** + * Get template MO + */ + private VirtualMachineMO getTemplate(DatacenterMO dcMo, String templatePath) throws Exception { + VirtualMachineMO template = dcMo.findVm(templatePath); + if (!template.isTemplate()) { + throw new CloudRuntimeException(templatePath + " is not a template"); + } + return template; + } + + /** + * Get template pool ID + */ + private Long getTemplatePoolId(VirtualMachineMO template) throws Exception { + VirtualMachineConfigSummary configSummary = template.getConfigSummary(); + String vmPathName = configSummary.getVmPathName(); + String[] pathParts = vmPathName.split(" "); + String dataStoreUuid = pathParts[0].replace("[", "").replace("]", ""); + return getPoolIdFromDatastoreUuid(dataStoreUuid); + } + + /** + * Get template size + */ + private Long getTemplateSize(VirtualMachineMO template, String vmInternalName, + Map disksMapping, VMBackup backup) throws Exception { + List disks = template.getVirtualDisks(); + if (CollectionUtils.isEmpty(disks)) { + throw new CloudRuntimeException("Couldn't find VM template size"); + } + return disks.get(0).getCapacityInBytes(); + } + + /** + * Create a VM Template record on DB + */ + private VMTemplateVO createVMTemplateRecord(String vmInternalName, long guestOsId, long accountId) { + Long nextTemplateId = vmTemplateDao.getNextInSequence(Long.class, "id"); + VMTemplateVO templateVO = new VMTemplateVO(nextTemplateId, "Imported-from-" + vmInternalName, + Storage.ImageFormat.OVA,false, false, false, Storage.TemplateType.USER, + null, false, 64, accountId, null, "Template imported from VM " + vmInternalName, + false, guestOsId, false, HypervisorType.VMware, null, null, + false, false, false); + return vmTemplateDao.persist(templateVO); + } + + /** + * Retrieve the template ID matching the template on templatePath. There are 2 cases: + * - There are no references on DB for primary storage -> create a template DB record and return its ID + * - There are references on DB for primary storage -> return template ID for any of those references + */ + private long getTemplateId(String templatePath, String vmInternalName, Long guestOsId, long accountId) { + List poolRefs = templateStoragePoolDao.listByTemplatePath(templatePath); + return CollectionUtils.isNotEmpty(poolRefs) ? + poolRefs.get(0).getTemplateId() : + createVMTemplateRecord(vmInternalName, guestOsId, accountId).getId(); + } + + /** + * Update template reference on primary storage, if needed + */ + private void updateTemplateRef(long templateId, Long poolId, String templatePath, Long templateSize) { + VMTemplateStoragePoolVO templateRef = templateStoragePoolDao.findByPoolPath(poolId, templatePath); + if (templateRef == null) { + templateRef = new VMTemplateStoragePoolVO(poolId, templateId, null, 100, + VMTemplateStorageResourceAssoc.Status.DOWNLOADED, templatePath, null, + null, templatePath, templateSize); + templateRef.setState(ObjectInDataStoreStateMachine.State.Ready); + templateStoragePoolDao.persist(templateRef); + } + } + + /** + * Get template ID for VM being imported. If it is not found, it is created + */ + private Long getImportingVMTemplate(List virtualDisks, DatacenterMO dcMo, String vmInternalName, + Long guestOsId, long accountId, Map disksMapping, VMBackup backup) throws Exception { + for (VirtualDisk disk : virtualDisks) { + if (isRootDisk(disk, disksMapping, backup)) { + VolumeVO volumeVO = disksMapping.get(disk); + if (volumeVO == null) { + String templatePath = getRootDiskTemplatePath(disk); + VirtualMachineMO template = getTemplate(dcMo, templatePath); + Long poolId = getTemplatePoolId(template); + Long templateSize = getTemplateSize(template, vmInternalName, disksMapping, backup); + long templateId = getTemplateId(templatePath, vmInternalName, guestOsId, accountId); + updateTemplateRef(templateId, poolId, templatePath, templateSize); + return templateId; + } else { + return volumeVO.getTemplateId(); + } + } + } + throw new CloudRuntimeException("Could not find ROOT disk for VM " + vmInternalName); + } + + /** + * If VM does not exist: create and persist VM + * If VM exists: update VM + */ + private VMInstanceVO getVM(String vmInternalName, long templateId, long guestOsId, + long serviceOfferingId, long zoneId, long accountId, long userId, + long domainId) { + VMInstanceVO existingVM = _vmDao.findVMByInstanceName(vmInternalName); + if (existingVM != null) { + existingVM.setTemplateId(templateId); + existingVM.setGuestOSId(guestOsId); + existingVM.setServiceOfferingId(serviceOfferingId); + return _vmDao.persist(existingVM); + } else { + long id = userVmDao.getNextInSequence(Long.class, "id"); + UserVmVO vmInstanceVO = new UserVmVO(id, vmInternalName, vmInternalName, templateId, HypervisorType.VMware, + guestOsId, false, false, domainId, accountId, userId, serviceOfferingId, + null, vmInternalName, null); + vmInstanceVO.setDataCenterId(zoneId); + return userVmDao.persist(vmInstanceVO); + } + } + + /** + * Create and persist volume + */ + private VolumeVO createVolumeRecord(Volume.Type type, String volumeName, long zoneId, long domainId, + long accountId, long diskOfferingId, Storage.ProvisioningType provisioningType, + Long size, long instanceId, Long poolId, long templateId, Integer unitNumber, VirtualMachineDiskInfo diskInfo) { + VolumeVO volumeVO = new VolumeVO(type, volumeName, zoneId, domainId, accountId, diskOfferingId, + provisioningType, size, null, null, null); + volumeVO.setFormat(Storage.ImageFormat.OVA); + volumeVO.setPath(volumeName); + volumeVO.setState(Volume.State.Ready); + volumeVO.setInstanceId(instanceId); + volumeVO.setPoolId(poolId); + volumeVO.setTemplateId(templateId); + volumeVO.setChainInfo(new Gson().toJson(diskInfo)); + if (unitNumber != null) { + volumeVO.setDeviceId(unitNumber.longValue()); + } + return _volumeDao.persist(volumeVO); + } + + /** + * Get volume name from VM disk + */ + private String getVolumeName(VirtualDisk disk, VirtualMachineMO vmToImport) throws Exception { + return vmToImport.getVmdkFileBaseName(disk); + } + + /** + * Get provisioning type for VM disk info + */ + private Storage.ProvisioningType getProvisioningType(VirtualDiskFlatVer2BackingInfo backing) { + Boolean thinProvisioned = backing.isThinProvisioned(); + if (BooleanUtils.isTrue(thinProvisioned)) { + return Storage.ProvisioningType.THIN; + } + return Storage.ProvisioningType.SPARSE; + } + + /** + * Get disk offering ID for volume being imported. If it is not found it is mapped to "Custom" ID + */ + private long getDiskOfferingId(long size, Storage.ProvisioningType provisioningType, 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.setPath(volumeName); + volumeVO.setPoolId(poolId); + VirtualMachineDiskInfo diskInfo = getDiskInfo(vmToImport, poolId, volumeName); + volumeVO.setChainInfo(new Gson().toJson(diskInfo)); + _volumeDao.update(volumeVO.getId(), volumeVO); + return volumeVO; + } + + /** + * Get volumes for VM being imported + */ + private void importVMVolumes(VMInstanceVO vmInstanceVO, List virtualDisks, + Map disksMapping, VirtualMachineMO vmToImport, VMBackup backup) throws Exception { + long zoneId = vmInstanceVO.getDataCenterId(); + long accountId = vmInstanceVO.getAccountId(); + long domainId = vmInstanceVO.getDomainId(); + long templateId = vmInstanceVO.getTemplateId(); + long instanceId = vmInstanceVO.getId(); + + List vols = new ArrayList<>(); + for (VirtualDisk disk : virtualDisks) { + Long poolId = getPoolId(disk); + VolumeVO volumeVO; + if (disksMapping.containsKey(disk) && disksMapping.get(disk) != null) { + volumeVO = updateVolume(disk, disksMapping, vmToImport, poolId); + } else { + volumeVO = createVolume(disk, vmToImport, domainId, zoneId, accountId, instanceId, poolId, templateId, backup, true); + } + vols.add(volumeVO); + } + removeUnusedVolumes(vols, vmInstanceVO); + } + + private void removeUnusedVolumes(List vols, VMInstanceVO vmInstanceVO) { + List existingVolumes = _volumeDao.findByInstance(vmInstanceVO.getId()); + if (existingVolumes.size() != vols.size()) { + for (VolumeVO existingVol : existingVolumes) { + if (!vols.contains(existingVol)) { + _volumeDao.remove(existingVol.getId()); + } + } + } + } + + private VirtualMachineDiskInfo getDiskInfo(VirtualMachineMO vmMo, Long poolId, String volumeName) throws Exception { + VirtualMachineDiskInfoBuilder diskInfoBuilder = vmMo.getDiskInfoBuilder(); + String poolName = _storagePoolDao.findById(poolId).getUuid().replace("-", ""); + return diskInfoBuilder.getDiskInfoByBackingFileBaseName(volumeName, poolName); + } + + private VolumeVO createVolume(VirtualDisk disk, VirtualMachineMO vmToImport, long domainId, long zoneId, + long accountId, long instanceId, Long poolId, long templateId, VMBackup backup, boolean isImport) throws Exception { + Long size = disk.getCapacityInBytes(); + List backedUpVolumes = backup.getBackedUpVolumes(); + Volume.Type type = Volume.Type.DATADISK; + if (isImport) { + 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); + Integer unitNumber = disk.getUnitNumber(); + VirtualMachineDiskInfo diskInfo = getDiskInfo(vmToImport, poolId, volumeName); + return createVolumeRecord(type, volumeName, zoneId, domainId, accountId, diskOfferingId, + provisioningType, size, instanceId, poolId, templateId, unitNumber, diskInfo); + } + + /** + * Get physical network ID from zoneId and Vmware label + */ + private long getPhysicalNetworkId(Long zoneId, String tag) { + List physicalNetworks = physicalNetworkDao.listByZone(zoneId); + for (PhysicalNetworkVO physicalNetwork : physicalNetworks) { + PhysicalNetworkTrafficTypeVO vo = _physicalNetworkTrafficTypeDao.findBy(physicalNetwork.getId(), TrafficType.Guest); + if (vo == null) { + continue; + } + String vmwareNetworkLabel = vo.getVmwareNetworkLabel(); + if (!vmwareNetworkLabel.startsWith(tag)) { + throw new CloudRuntimeException("Vmware network label does not start with: " + tag); + } + return physicalNetwork.getId(); + } + throw new CloudRuntimeException("Could not find guest physical network matching tag: " + tag + " on zone " + zoneId); + } + + /** + * Create and persist network + */ + private NetworkVO createNetworkRecord(Long zoneId, String tag, String vlan, long accountId, long domainId) { + Long physicalNetworkId = getPhysicalNetworkId(zoneId, tag); + final long id = _networkDao.getNextInSequence(Long.class, "id"); + NetworkVO networkVO = new NetworkVO(id, TrafficType.Guest, Networks.Mode.Dhcp, BroadcastDomainType.Vlan, 9L, + domainId, accountId, id, "Imported-network-" + id, "Imported-network-" + id, null, Network.GuestType.Isolated, + zoneId, physicalNetworkId, ControlledEntity.ACLType.Account, false, null, false); + networkVO.setBroadcastUri(BroadcastDomainType.Vlan.toUri(vlan)); + networkVO.setGuruName("ExternalGuestNetworkGuru"); + networkVO.setState(Network.State.Implemented); + return _networkDao.persist(networkVO); + } + + /** + * Get network from VM network name + */ + private NetworkVO getGuestNetworkFromNetworkMorName(String name, long accountId, Long zoneId, long domainId) { + String prefix = "cloud.guest."; + String nameWithoutPrefix = name.replace(prefix, ""); + String[] parts = nameWithoutPrefix.split("\\."); + String vlan = parts[0]; + String tag = parts[parts.length - 1]; + String[] tagSplit = tag.split("-"); + tag = tagSplit[tagSplit.length - 1]; + NetworkVO networkVO = _networkDao.findByVlan(vlan); + if (networkVO == null) { + networkVO = createNetworkRecord(zoneId, tag, vlan, accountId, domainId); + } + return networkVO; + } + + /** + * Get map between VM networks and its IDs on CloudStack + */ + private Map getNetworksMapping(String[] vmNetworkNames, long accountId, long zoneId, long domainId) { + Map mapping = new HashMap<>(); + for (String networkName : vmNetworkNames) { + NetworkVO networkVO = getGuestNetworkFromNetworkMorName(networkName, accountId, zoneId, domainId); + mapping.put(networkName, networkVO); + } + return mapping; + } + + /** + * Get network MO from VM NIC + */ + private NetworkMO getNetworkMO(VirtualE1000 nic, VmwareContext context) { + VirtualDeviceConnectInfo connectable = nic.getConnectable(); + VirtualEthernetCardNetworkBackingInfo info = (VirtualEthernetCardNetworkBackingInfo) nic.getBacking(); + ManagedObjectReference networkMor = info.getNetwork(); + if (networkMor == null) { + throw new CloudRuntimeException("Could not find network for NIC on: " + nic.getMacAddress()); + } + return new NetworkMO(context, networkMor); + } + + /** + * Create and persist NIC + */ + private NicVO createNicRecord(String macAddress, Long networkId, VMInstanceVO vmInstanceVO) { + NicVO nicVO = new NicVO("ExternalGuestNetworkGuru", vmInstanceVO.getId(), networkId, vmInstanceVO.getType()); + nicVO.setMacAddress(macAddress); + nicVO.setAddressFormat(Networks.AddressFormat.Ip4); + nicVO.setReservationStrategy(Nic.ReservationStrategy.Start); + nicVO.setState(Nic.State.Reserved); + nicVO.setDefaultNic(true); + return _nicDao.persist(nicVO); + } + + /** + * Remove volumes and nics if the VM already existed on CloudStack. No action performed if not existing + */ + private Pair, List> cleanupExistingVMVolumesAndNics(String vmInternalName) { + VMInstanceVO existingVm = _vmDao.findVMByInstanceName(vmInternalName); + if (existingVm != null) { + List rootDisks = _volumeDao.findReadyRootVolumesByInstance(existingVm.getId()); + List datadisks = _volumeDao.findByInstanceAndType(existingVm.getId(), Volume.Type.DATADISK); + _volumeDao.deleteVolumesByInstance(existingVm.getId()); + _nicDao.removeNicsForInstance(existingVm.getId()); + return new Pair<>(rootDisks, datadisks); + } + return new Pair<>(null, null); + } + + private Pair getNicMacAddressAndNetworkName(VirtualDevice nicDevice, VmwareContext context) throws Exception { + VirtualE1000 nic = (VirtualE1000) nicDevice; + String macAddress = nic.getMacAddress(); + NetworkMO networkMO = getNetworkMO(nic, context); + String networkName = networkMO.getName(); + return new Pair<>(macAddress, networkName); + } + + private void importVMNics(VirtualDevice[] nicDevices, DatacenterMO dcMo, Map networksMapping, + VMInstanceVO vmInstanceVO) throws Exception { + VmwareContext context = dcMo.getContext(); + List nics = new ArrayList<>(); + for (VirtualDevice nicDevice : nicDevices) { + Pair pair = getNicMacAddressAndNetworkName(nicDevice, context); + String macAddress = pair.first(); + String networkName = pair.second(); + NetworkVO networkVO = networksMapping.get(networkName); + NicVO nicVO = _nicDao.findByNetworkIdAndMacAddress(networkVO.getId(), macAddress); + if (nicVO == null) { + nicVO = createNicRecord(macAddress, networkVO.getId(), vmInstanceVO); + } + nics.add(nicVO); + } + removeUnusedNics(nics, vmInstanceVO); + } + + private void removeUnusedNics(List nics, VMInstanceVO vmInstanceVO) { + List existingVMNics = _nicDao.listByVmId(vmInstanceVO.getId()); + if (nics.size() != existingVMNics.size()) { + for (NicVO existingNic : existingVMNics) { + if (!nics.contains(existingNic)) { + _nicDao.remove(existingNic.getId()); + } + } + } + } + + private Map getDisksMapping(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()) + && !usedVols.containsKey(backedUpVol.getUuid())) { + String volId = backedUpVol.getUuid(); + VolumeVO vol = _volumeDao.findByUuid(volId); + usedVols.put(backedUpVol.getUuid(), true); + map.put(disk, vol); + } + } + } + return map; + } + + /** + * Find VM on datacenter + */ + private VirtualMachineMO findVM(DatacenterMO dcMo, String path) throws Exception { + VirtualMachineMO vm = dcMo.findVm(path); + if (vm == null) { + throw new CloudRuntimeException("Error finding VM: " + path); + } + return vm; + } + + /** + * Find restored volume based on volume info + */ + private VirtualDisk findRestoredVolume(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 templateId = getImportingVMTemplate(virtualDisks, dcMo, vmInternalName, guestOsId, accountId, disksMapping, backup); + + VMInstanceVO vmInstanceVO = getVM(vmInternalName, templateId, guestOsId, + serviceOfferingId, zoneId, accountId, userId, domainId); + + importVMNics(nicDevices, dcMo, networksMapping, vmInstanceVO); + importVMVolumes(vmInstanceVO, virtualDisks, disksMapping, vmToImport, backup); + return vmInstanceVO; + } + }); + } + + @Override + public boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location, VMBackup.VolumeInfo volumeInfo, + VirtualMachine vm, long poolId, VMBackup backup) throws Exception { + DatacenterMO dcMo = getDatacenterMO(zoneId); + VirtualMachineMO vmRestored = findVM(dcMo, location); + VirtualMachineMO vmMo = findVM(dcMo, vm.getInstanceName()); + VirtualDisk restoredDisk = findRestoredVolume(volumeInfo, vmRestored); + String diskPath = vmRestored.getVmdkFileBaseName(restoredDisk); + + //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(), backup, false); + + return true; + } + + private VirtualDisk getAttachedDisk(VirtualMachineMO vmMo, String diskPath) throws Exception { + for (VirtualDisk disk : vmMo.getVirtualDisks()) { + if (vmMo.getVmdkFileBaseName(disk).equals(diskPath)) { + return disk; + } + } + return null; + } } 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 7f8d3e0d7477..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.2 + 2.9.6 1.9.2 0.16 3.22.0-GA @@ -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 22ed2437d4de..797f056b28e6 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -39,6 +39,7 @@ import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.AsyncJobResponse; +import org.apache.cloudstack.api.response.BackupPolicyResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.DomainRouterResponse; @@ -59,8 +60,13 @@ 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.VMBackup; +import org.apache.cloudstack.backup.dao.VMBackupDao; +import org.apache.cloudstack.backup.dao.BackupPolicyDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; @@ -185,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; @@ -277,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; @@ -312,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; @@ -434,6 +440,8 @@ public class ApiDBUtils { static ResourceMetaDataService s_resourceDetailsService; static HostGpuGroupsDao s_hostGpuGroupsDao; static VGPUTypesDao s_vgpuTypesDao; + static VMBackupDao s_backupDao; + static BackupPolicyDao s_backupPolicyDao; @Inject private ManagementServer ms; @@ -666,6 +674,10 @@ public class ApiDBUtils { private HostGpuGroupsDao hostGpuGroupsDao; @Inject private VGPUTypesDao vgpuTypesDao; + @Inject + private VMBackupDao backupDao; + @Inject + private BackupPolicyDao backupPolicyDao; @PostConstruct void init() { @@ -785,6 +797,8 @@ void init() { s_resourceDetailsService = resourceDetailsService; s_hostGpuGroupsDao = hostGpuGroupsDao; s_vgpuTypesDao = vgpuTypesDao; + s_backupDao = backupDao; + s_backupPolicyDao = backupPolicyDao; } // /////////////////////////////////////////////////////////// @@ -2000,4 +2014,12 @@ public static boolean isAdmin(Account account) { public static List listResourceTagViewByResourceUUID(String resourceUUID, ResourceObjectType resourceType) { return s_tagJoinDao.listBy(resourceUUID, resourceType); } + + public static VMBackupResponse newBackupResponse(VMBackup backup) { + return s_backupDao.newBackupResponse(backup); + } + + public static BackupPolicyResponse newBackupPolicyResponse(BackupPolicy policy) { + return s_backupPolicyDao.newBackupPolicyResponse(policy); + } } diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index ba3d5c3e143f..d422db1aabf6 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -16,8 +16,155 @@ // 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.VMBackup; +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 +300,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 +310,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,149 +331,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.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.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 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 { @@ -3488,7 +3494,7 @@ 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, VMBackup backup) throws Exception { + return false; + } } diff --git a/server/src/main/java/com/cloud/usage/UsageServiceImpl.java b/server/src/main/java/com/cloud/usage/UsageServiceImpl.java index efd8ef4b8ad5..25a34540ab08 100644 --- a/server/src/main/java/com/cloud/usage/UsageServiceImpl.java +++ b/server/src/main/java/com/cloud/usage/UsageServiceImpl.java @@ -16,6 +16,28 @@ // under the License. package com.cloud.usage; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.api.command.admin.usage.GenerateUsageRecordsCmd; +import org.apache.cloudstack.api.command.admin.usage.GetUsageRecordsCmd; +import org.apache.cloudstack.api.command.admin.usage.RemoveRawUsageRecordsCmd; +import org.apache.cloudstack.api.response.UsageTypeResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.usage.Usage; +import org.apache.cloudstack.usage.UsageService; +import org.apache.cloudstack.usage.UsageTypes; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + import com.cloud.configuration.Config; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; @@ -55,26 +77,6 @@ import com.cloud.utils.db.TransactionLegacy; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.VMInstanceDao; -import org.apache.cloudstack.api.command.admin.usage.GenerateUsageRecordsCmd; -import org.apache.cloudstack.api.command.admin.usage.GetUsageRecordsCmd; -import org.apache.cloudstack.api.command.admin.usage.RemoveRawUsageRecordsCmd; -import org.apache.cloudstack.api.response.UsageTypeResponse; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.usage.Usage; -import org.apache.cloudstack.usage.UsageService; -import org.apache.cloudstack.usage.UsageTypes; -import org.apache.log4j.Logger; -import org.springframework.stereotype.Component; - -import javax.inject.Inject; -import javax.naming.ConfigurationException; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.TimeZone; @Component public class UsageServiceImpl extends ManagerBase implements UsageService, Manager { @@ -267,6 +269,7 @@ public Pair, Integer> getUsageRecords(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(); 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..b7bf5762235f --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -0,0 +1,589 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.api.command.admin.backup.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.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.ListVMBackupRestorePoints; +import org.apache.cloudstack.api.command.user.backup.ListVMBackupsCmd; +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; +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.dc.DataCenter; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.event.ActionEvent; +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventUtils; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.storage.ScopeType; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.usage.dao.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; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.VMInstanceDao; +import com.google.common.base.Strings; + +@Component +public class BackupManagerImpl extends ManagerBase implements BackupManager { + private static final Logger LOG = Logger.getLogger(BackupManagerImpl.class); + + @Inject + private BackupPolicyDao backupPolicyDao; + @Inject + private VMInstanceDao vmInstanceDao; + @Inject + private AccountService accountService; + @Inject + private AccountManager accountManager; + @Inject + private VMBackupDao backupDao; + @Inject + private UsageVMBackupDao usageVMBackupDao; + @Inject + private VolumeDao volumeDao; + @Inject + private DataCenterDao dataCenterDao; + @Inject + private BackgroundPollManager backgroundPollManager; + @Inject + private HostDao hostDao; + @Inject + private HypervisorGuruManager hypervisorGuruManager; + @Inject + private PrimaryDataStoreDao primaryDataStoreDao; + @Inject + private DiskOfferingDao diskOfferingDao; + + private static Map backupProvidersMap = new HashMap<>(); + private List backupProviders; + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_IMPORT_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) { + 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; + } + + /** + * 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) { + 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"); + } + return Collections.singletonList(policy); + } + + @Override + public List listBackupPolicies(final Long zoneId, final Boolean external, final Long policyId) { + if (policyId != null) { + return listInternalPolicyById(policyId); + } else { + if (BooleanUtils.isTrue(external) && accountService.isRootAdmin(CallContext.current().getCallingAccountId())) { + return listExternalPolicies(zoneId); + } else { + return listInternalPolicies(zoneId); + } + } + } + + @Override + public List listVMBackups(final Long vmId) { + final Account callerAccount = CallContext.current().getCallingAccount(); + final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + if (accountService.isRootAdmin(callerAccount.getId())) { + return new ArrayList<>(backupDao.listAll()); + } else { + return new ArrayList<>(backupDao.listByAccountId(callerAccount.getId())); + } + } + accountManager.checkAccess(callerAccount, null, true, vm); + return backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + } + + @Override + @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"); + } + 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) { + 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()); + 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 + @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) { + 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); + } + + @Override + @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"); + } + final Long vmId = backup.getVmId(); + final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM " + vmId + " does not exist"); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + final BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + boolean result = backupProvider.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_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true) + 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"); + } + Long vmId = backup.getVmId(); + BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM " + vmId + " couldn't be found on existing or removed VMs"); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + if (vm.getRemoved() == null && !vm.getState().equals(VirtualMachine.State.Stopped) && + !vm.getState().equals(VirtualMachine.State.Destroyed)) { + throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup"); + } + if (!backupProvider.restoreVMFromBackup(vm, backup.getExternalId(), restorePointId)) { + throw new CloudRuntimeException("Error restoring VM " + vm.getId() + " from backup " + backup.getId()); + } + importVM(vm.getDataCenterId(), vm.getDomainId(), vm.getAccountId(), vm.getUserId(), + vm.getInstanceName(), vm.getHypervisorType(), backup); + return true; + } + + private 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_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"); + } + VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM " + vmId + " does not exist"); + } + + VMInstanceVO vmFromBackup = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); + if (vmFromBackup != null) { + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vmFromBackup); + } + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + + Pair restoreInfo = getRestoreVolumeHostAndDatastore(vm); + String hostIp = restoreInfo.first(); + String datastoreUuid = restoreInfo.second(); + + LOG.debug("Asking provider to restore volume " + backedUpVolumeUuid + " from backup " + backupId + + " and restore point " + restorePointId + " and attach it to VM: " + vm.getUuid()); + BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + Pair result = backupProvider.restoreBackedUpVolume(backup.getZoneId(), backup.getUuid(), + restorePointId, backedUpVolumeUuid, hostIp, datastoreUuid); + if (!result.first()) { + throw new CloudRuntimeException("Error restoring volume " + backedUpVolumeUuid); + } + if (!attachVolumeToVM(backup.getZoneId(), result.second(), backup.getBackedUpVolumes(), + backedUpVolumeUuid, vm, datastoreUuid, backup)) { + throw new CloudRuntimeException("Error attaching volume " + backedUpVolumeUuid + " to VM " + vm.getUuid()); + } + return true; + } + + /** + * Get the pair: hostIp, datastoreUuid in which to restore the volume, based on the VM to be attached information + */ + private Pair getRestoreVolumeHostAndDatastore(VMInstanceVO vm) { + List rootVmVolume = volumeDao.findIncludingRemovedByInstanceAndType(vm.getId(), Volume.Type.ROOT); + Long poolId = rootVmVolume.get(0).getPoolId(); + StoragePoolVO storagePoolVO = primaryDataStoreDao.findById(poolId); + String datastoreUuid = storagePoolVO.getUuid(); + String hostIp = vm.getHostId() == null ? + getHostIp(storagePoolVO) : + hostDao.findById(vm.getHostId()).getPrivateIpAddress(); + return new Pair<>(hostIp, datastoreUuid); + } + + /** + * Find a host IP from storage pool access + */ + private String getHostIp(StoragePoolVO storagePoolVO) { + List hosts = null; + if (storagePoolVO.getScope().equals(ScopeType.CLUSTER)) { + hosts = hostDao.findByClusterId(storagePoolVO.getClusterId()); + + } else if (storagePoolVO.getScope().equals(ScopeType.ZONE)) { + hosts = hostDao.findByDataCenterId(storagePoolVO.getDataCenterId()); + } + return hosts.get(0).getPrivateIpAddress(); + } + + /** + * Attach volume to VM + */ + private boolean attachVolumeToVM(Long zoneId, String restoredVolumeLocation, List backedUpVolumes, + String volumeUuid, VMInstanceVO vm, String datastoreUuid, VMBackup backup) throws Exception { + HypervisorGuru guru = hypervisorGuruManager.getGuru(vm.getHypervisorType()); + VMBackup.VolumeInfo volumeInfo = getVolumeInfo(backedUpVolumes, volumeUuid); + if (volumeInfo == null) { + throw new CloudRuntimeException("Failed to find volume in the backedup volumes of ID " + volumeUuid); + } + volumeInfo.setType(Volume.Type.DATADISK); + + LOG.debug("Attaching the restored volume to VM " + vm.getId()); + StoragePoolVO pool = primaryDataStoreDao.findByUuid(datastoreUuid); + try { + return guru.attachRestoredVolumeToVirtualMachine(zoneId, restoredVolumeLocation, volumeInfo, vm, pool.getId(), backup); + } catch (Exception e) { + throw new CloudRuntimeException("Error attach restored volume to VM " + vm.getUuid() + " due to: " + e.getMessage()); + } + } + + @Override + public boolean 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); + } + 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.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); + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + backgroundPollManager.submitTask(new BackupSyncTask(this)); + return true; + } + + public boolean isEnabled(final Long zoneId) { + return BackupFrameworkEnabled.valueIn(zoneId); + } + + @Override + public List listBackupProviders() { + return backupProviders; + } + + @Override + public BackupProvider getBackupProvider(final Long zoneId) { + final String name = BackupProviderPlugin.valueIn(zoneId); + if (Strings.isNullOrEmpty(name)) { + throw new CloudRuntimeException("Invalid backup provider name configured in zone id: " + zoneId); + } + if (!backupProvidersMap.containsKey(name)) { + throw new CloudRuntimeException("Failed to find backup provider for zone id:" + zoneId); + } + return backupProvidersMap.get(name); + } + + @Override + public List> getCommands() { + final List> cmdList = new ArrayList>(); + cmdList.add(ListBackupProvidersCmd.class); + cmdList.add(ListBackupPoliciesCmd.class); + cmdList.add(ImportBackupPolicyCmd.class); + cmdList.add(DeleteBackupPolicyCmd.class); + cmdList.add(ListVMBackupsCmd.class); + cmdList.add(CreateVMBackupCmd.class); + cmdList.add(StartVMBackupCmd.class); + cmdList.add(DeleteVMBackupCmd.class); + cmdList.add(ListVMBackupRestorePoints.class); + cmdList.add(RestoreVMBackupCmd.class); + cmdList.add(RestoreVolumeFromBackupAndAttachToVMCmd.class); + cmdList.add(ImportVMCmdByAdmin.class); + return cmdList; + } + + @Override + public String getConfigComponentName() { + return BackupService.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[]{ + BackupFrameworkEnabled, + BackupProviderPlugin, + BackupSyncPollingInterval + }; + } + + public void setBackupProviders(final List backupProviders) { + this.backupProviders = backupProviders; + } + + @Override + public boolean start() { + initializeBackupProviderMap(); + return true; + } + + private void initializeBackupProviderMap() { + if (backupProviders != null) { + for (final BackupProvider backupProvider : backupProviders) { + backupProvidersMap.put(backupProvider.getName().toLowerCase(), backupProvider); + } + } + } + + //////////////////////////////////////////////////// + /////////////// Background Tasks /////////////////// + //////////////////////////////////////////////////// + + /** + * This background task syncs backups from providers side in CloudStack db + * along with creation of usage records + */ + private final class BackupSyncTask extends ManagedContextRunnable implements BackgroundPollTask { + private BackupManager backupManager; + + public BackupSyncTask(final BackupManager backupManager) { + this.backupManager = backupManager; + } + + @Override + protected void runInContext() { + try { + if (LOG.isTraceEnabled()) { + LOG.trace("Backup sync background task is running..."); + } + for (final DataCenter dataCenter : dataCenterDao.listAllZones()) { + if (dataCenter == null || !isEnabled(dataCenter.getId())) { + continue; + } + + // GC and expunge removed backups + for (final 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/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index 2f67c4248d35..0c9cb7ee440f 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -300,4 +300,9 @@ + + + + + diff --git a/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java b/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java index 11f3f81f357d..8a9431fd653e 100644 --- a/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java +++ b/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java @@ -235,4 +235,9 @@ public int getNonSystemNetworkCountByVpcId(final long vpcId) { public List listNetworkVO(List idset) { return null; } + + @Override + public NetworkVO findByVlan(String vlan) { + return null; + } } \ No newline at end of file diff --git a/test/integration/smoke/test_backup_recovery.py b/test/integration/smoke/test_backup_recovery.py new file mode 100644 index 000000000000..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 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"); + } } 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); } }