diff --git a/agent/conf/agent.properties b/agent/conf/agent.properties index 0dc5b8211e0..777bd4bf5cd 100644 --- a/agent/conf/agent.properties +++ b/agent/conf/agent.properties @@ -457,3 +457,6 @@ iscsi.session.cleanup.enabled=false # Instance conversion VIRT_V2V_TMPDIR env var #convert.instance.env.virtv2v.tmpdir= + +# Time, in seconds, to wait before retrying to rebase during the incremental snapshot process. +# incremental.snapshot.retry.rebase.wait=60 diff --git a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java index 3364f9708cf..b0926b23a1e 100644 --- a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java +++ b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java @@ -885,6 +885,11 @@ public class AgentProperties{ */ public static final Property CREATE_FULL_CLONE = new Property<>("create.full.clone", false); + /** + * Time, in seconds, to wait before retrying to rebase during the incremental snapshot process. + * */ + public static final Property INCREMENTAL_SNAPSHOT_RETRY_REBASE_WAIT = new Property<>("incremental.snapshot.retry.rebase.wait", 60); + public static class Property { private String name; diff --git a/api/src/main/java/com/cloud/agent/api/to/BucketTO.java b/api/src/main/java/com/cloud/agent/api/to/BucketTO.java index f7e4bfea80f..fd8237998a7 100644 --- a/api/src/main/java/com/cloud/agent/api/to/BucketTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/BucketTO.java @@ -26,10 +26,13 @@ public final class BucketTO { private String secretKey; + private long accountId; + public BucketTO(Bucket bucket) { this.name = bucket.getName(); this.accessKey = bucket.getAccessKey(); this.secretKey = bucket.getSecretKey(); + this.accountId = bucket.getAccountId(); } public BucketTO(String name) { @@ -47,4 +50,8 @@ public final class BucketTO { public String getSecretKey() { return this.secretKey; } + + public long getAccountId() { + return this.accountId; + } } diff --git a/api/src/main/java/com/cloud/projects/ProjectService.java b/api/src/main/java/com/cloud/projects/ProjectService.java index 5080cb5a781..d11e9ae0446 100644 --- a/api/src/main/java/com/cloud/projects/ProjectService.java +++ b/api/src/main/java/com/cloud/projects/ProjectService.java @@ -82,7 +82,7 @@ public interface ProjectService { Project updateProject(long id, String name, String displayText, String newOwnerName, Long userId, Role newRole) throws ResourceAllocationException; - boolean addAccountToProject(long projectId, String accountName, String email, Long projectRoleId, Role projectRoleType); + boolean addAccountToProject(long projectId, String accountName, String email, Long projectRoleId, Role projectRoleType) throws ResourceAllocationException; boolean deleteAccountFromProject(long projectId, String accountName); @@ -100,6 +100,6 @@ public interface ProjectService { Project findByProjectAccountIdIncludingRemoved(long projectAccountId); - boolean addUserToProject(Long projectId, String username, String email, Long projectRoleId, Role projectRole); + boolean addUserToProject(Long projectId, String username, String email, Long projectRoleId, Role projectRole) throws ResourceAllocationException; } diff --git a/api/src/main/java/com/cloud/storage/VMTemplateStorageResourceAssoc.java b/api/src/main/java/com/cloud/storage/VMTemplateStorageResourceAssoc.java index db702a61f2b..7d5b2d7c57d 100644 --- a/api/src/main/java/com/cloud/storage/VMTemplateStorageResourceAssoc.java +++ b/api/src/main/java/com/cloud/storage/VMTemplateStorageResourceAssoc.java @@ -23,9 +23,10 @@ import org.apache.cloudstack.api.InternalIdentity; public interface VMTemplateStorageResourceAssoc extends InternalIdentity { public static enum Status { - UNKNOWN, DOWNLOAD_ERROR, NOT_DOWNLOADED, DOWNLOAD_IN_PROGRESS, DOWNLOADED, ABANDONED, UPLOADED, NOT_UPLOADED, UPLOAD_ERROR, UPLOAD_IN_PROGRESS, CREATING, CREATED, BYPASSED + UNKNOWN, DOWNLOAD_ERROR, NOT_DOWNLOADED, DOWNLOAD_IN_PROGRESS, DOWNLOADED, ABANDONED, LIMIT_REACHED, UPLOADED, NOT_UPLOADED, UPLOAD_ERROR, UPLOAD_IN_PROGRESS, CREATING, CREATED, BYPASSED } + List ERROR_DOWNLOAD_STATES = List.of(Status.DOWNLOAD_ERROR, Status.ABANDONED, Status.LIMIT_REACHED, Status.UNKNOWN); List PENDING_DOWNLOAD_STATES = List.of(Status.NOT_DOWNLOADED, Status.DOWNLOAD_IN_PROGRESS); String getInstallPath(); diff --git a/api/src/main/java/com/cloud/user/ResourceLimitService.java b/api/src/main/java/com/cloud/user/ResourceLimitService.java index 738e593582b..9c493fb383c 100644 --- a/api/src/main/java/com/cloud/user/ResourceLimitService.java +++ b/api/src/main/java/com/cloud/user/ResourceLimitService.java @@ -30,6 +30,7 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.offering.DiskOffering; import com.cloud.offering.ServiceOffering; import com.cloud.template.VirtualMachineTemplate; +import org.apache.cloudstack.resourcelimit.Reserver; public interface ResourceLimitService { @@ -191,6 +192,7 @@ public interface ResourceLimitService { */ public void checkResourceLimit(Account account, ResourceCount.ResourceType type, long... count) throws ResourceAllocationException; public void checkResourceLimitWithTag(Account account, ResourceCount.ResourceType type, String tag, long... count) throws ResourceAllocationException; + public void checkResourceLimitWithTag(Account account, Long domainId, boolean considerSystemAccount, ResourceCount.ResourceType type, String tag, long... count) throws ResourceAllocationException; /** * Gets the count of resources for a resource type and account @@ -251,12 +253,12 @@ public interface ResourceLimitService { List getResourceLimitStorageTags(DiskOffering diskOffering); void updateTaggedResourceLimitsAndCountsForAccounts(List responses, String tag); void updateTaggedResourceLimitsAndCountsForDomains(List responses, String tag); - void checkVolumeResourceLimit(Account owner, Boolean display, Long size, DiskOffering diskOffering) throws ResourceAllocationException; - + void checkVolumeResourceLimit(Account owner, Boolean display, Long size, DiskOffering diskOffering, List reservations) throws ResourceAllocationException; + List getResourceLimitStorageTagsForResourceCountOperation(Boolean display, DiskOffering diskOffering); void checkVolumeResourceLimitForDiskOfferingChange(Account owner, Boolean display, Long currentSize, Long newSize, - DiskOffering currentOffering, DiskOffering newOffering) throws ResourceAllocationException; + DiskOffering currentOffering, DiskOffering newOffering, List reservations) throws ResourceAllocationException; - void checkPrimaryStorageResourceLimit(Account owner, Boolean display, Long size, DiskOffering diskOffering) throws ResourceAllocationException; + void checkPrimaryStorageResourceLimit(Account owner, Boolean display, Long size, DiskOffering diskOffering, List reservations) throws ResourceAllocationException; void incrementVolumeResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering); void decrementVolumeResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering); @@ -273,25 +275,23 @@ public interface ResourceLimitService { void incrementVolumePrimaryStorageResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering); void decrementVolumePrimaryStorageResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering); - void checkVmResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template) throws ResourceAllocationException; + void checkVmResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, List reservations) throws ResourceAllocationException; void incrementVmResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template); void decrementVmResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template); void checkVmResourceLimitsForServiceOfferingChange(Account owner, Boolean display, Long currentCpu, Long newCpu, - Long currentMemory, Long newMemory, ServiceOffering currentOffering, ServiceOffering newOffering, VirtualMachineTemplate template) throws ResourceAllocationException; + Long currentMemory, Long newMemory, ServiceOffering currentOffering, ServiceOffering newOffering, VirtualMachineTemplate template, List reservations) throws ResourceAllocationException; void checkVmResourceLimitsForTemplateChange(Account owner, Boolean display, ServiceOffering offering, - VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate) throws ResourceAllocationException; + VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate, List reservations) throws ResourceAllocationException; - void checkVmCpuResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long cpu) throws ResourceAllocationException; void incrementVmCpuResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long cpu); void decrementVmCpuResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long cpu); - void checkVmMemoryResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long memory) throws ResourceAllocationException; void incrementVmMemoryResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long memory); void decrementVmMemoryResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long memory); - void checkVmGpuResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long gpu) throws ResourceAllocationException; void incrementVmGpuResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long gpu); void decrementVmGpuResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long gpu); + long recalculateDomainResourceCount(final long domainId, final ResourceType type, String tag); } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java b/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java index 4d33ba859a5..e2ebb242cbf 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java @@ -127,8 +127,8 @@ public enum ApiCommandResourceType { } public static ApiCommandResourceType fromString(String value) { - if (StringUtils.isNotEmpty(value) && EnumUtils.isValidEnum(ApiCommandResourceType.class, value)) { - return valueOf(value); + if (StringUtils.isNotBlank(value) && EnumUtils.isValidEnumIgnoreCase(ApiCommandResourceType.class, value)) { + return EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, value); } return null; } 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 05c6098bc72..9ec85734233 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -509,6 +509,7 @@ public class ApiConstants { public static final String REPAIR = "repair"; public static final String REPETITION_ALLOWED = "repetitionallowed"; public static final String REQUIRES_HVM = "requireshvm"; + public static final String RESERVED_RESOURCE_DETAILS = "reservedresourcedetails"; public static final String RESOURCES = "resources"; public static final String RESOURCE_COUNT = "resourcecount"; public static final String RESOURCE_NAME = "resourcename"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java index 85e7b0af319..63a0a6ca51e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.api.command.user.account; import java.util.List; +import com.cloud.exception.ResourceAllocationException; import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.BaseCmd; @@ -106,7 +107,7 @@ public class AddAccountToProjectCmd extends BaseAsyncCmd { ///////////////////////////////////////////////////// @Override - public void execute() { + public void execute() throws ResourceAllocationException { if (accountName == null && email == null) { throw new InvalidParameterValueException("Either accountName or email is required"); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java index 69832d4dfdc..683522039b1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.api.command.user.account; +import com.cloud.exception.ResourceAllocationException; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiArgValidator; @@ -111,7 +112,7 @@ public class AddUserToProjectCmd extends BaseAsyncCmd { ///////////////////////////////////////////////////// @Override - public void execute() { + public void execute() throws ResourceAllocationException { validateInput(); boolean result = _projectService.addUserToProject(getProjectId(), getUsername(), getEmail(), getProjectRoleId(), getRoleType()); if (result) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java index 703a1b2e880..4644687817d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java @@ -20,6 +20,7 @@ package org.apache.cloudstack.api.command.user.backup; import javax.inject.Inject; import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; @@ -53,6 +54,7 @@ public class RestoreVolumeFromBackupAndAttachToVMCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// + @ACL @Parameter(name = ApiConstants.BACKUP_ID, type = CommandType.UUID, entityType = BackupResponse.class, @@ -60,12 +62,14 @@ public class RestoreVolumeFromBackupAndAttachToVMCmd extends BaseAsyncCmd { description = "ID of the Instance backup") private Long backupId; + @ACL @Parameter(name = ApiConstants.VOLUME_ID, type = CommandType.STRING, required = true, description = "ID of the volume backed up") private String volumeUuid; + @ACL @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/job/ListAsyncJobsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/job/ListAsyncJobsCmd.java index b55d1b234f1..2c840183113 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/job/ListAsyncJobsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/job/ListAsyncJobsCmd.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.api.command.user.job; import java.util.Date; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseListAccountResourcesCmd; import org.apache.cloudstack.api.Parameter; @@ -40,6 +41,12 @@ public class ListAsyncJobsCmd extends BaseListAccountResourcesCmd { @Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID, type = CommandType.UUID, entityType = ManagementServerResponse.class, description = "The id of the management server", since="4.19") private Long managementServerId; + @Parameter(name = ApiConstants.RESOURCE_ID, validations = {ApiArgValidator.UuidString}, type = CommandType.STRING, description = "the ID of the resource associated with the job", since="4.22.1") + private String resourceId; + + @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, description = "the type of the resource associated with the job", since="4.22.1") + private String resourceType; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -52,6 +59,14 @@ public class ListAsyncJobsCmd extends BaseListAccountResourcesCmd { return managementServerId; } + public String getResourceId() { + return resourceId; + } + + public String getResourceType() { + return resourceType; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/job/QueryAsyncJobResultCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/job/QueryAsyncJobResultCmd.java index 93a44375721..5c3b0084574 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/job/QueryAsyncJobResultCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/job/QueryAsyncJobResultCmd.java @@ -16,8 +16,8 @@ // under the License. package org.apache.cloudstack.api.command.user.job; - import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; @@ -34,9 +34,15 @@ public class QueryAsyncJobResultCmd extends BaseCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.JOB_ID, type = CommandType.UUID, entityType = AsyncJobResponse.class, required = true, description = "The ID of the asynchronous job") + @Parameter(name = ApiConstants.JOB_ID, type = CommandType.UUID, entityType = AsyncJobResponse.class, description = "The ID of the asynchronous job") private Long id; + @Parameter(name = ApiConstants.RESOURCE_ID, validations = {ApiArgValidator.UuidString}, type = CommandType.STRING, description = "the ID of the resource associated with the job", since="4.22.1") + private String resourceId; + + @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, description = "the type of the resource associated with the job", since="4.22.1") + private String resourceType; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -45,6 +51,14 @@ public class QueryAsyncJobResultCmd extends BaseCmd { return id; } + public String getResourceId() { + return resourceId; + } + + public String getResourceType() { + return resourceType; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMFromBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMFromBackupCmd.java index e17ba9c2d70..a719062e1bc 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMFromBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMFromBackupCmd.java @@ -51,6 +51,7 @@ public class CreateVMFromBackupCmd extends BaseDeployVMCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// + @ACL @Parameter(name = ApiConstants.BACKUP_ID, type = CommandType.UUID, entityType = BackupResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java index 5bcf3a14117..78de0564848 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java @@ -32,6 +32,7 @@ import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; @@ -109,6 +110,13 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd implements UserC description = "The ID of the Instance; to be used with snapshot Id, Instance to which the volume gets attached after creation") private Long virtualMachineId; + @Parameter(name = ApiConstants.STORAGE_ID, + type = CommandType.UUID, + entityType = StoragePoolResponse.class, + description = "Storage pool ID to create the volume in. Cannot be used with the snapshotid parameter.", + authorized = {RoleType.Admin}) + private Long storageId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -153,6 +161,13 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd implements UserC return projectId; } + public Long getStorageId() { + if (snapshotId != null && storageId != null) { + throw new IllegalArgumentException("StorageId parameter cannot be specified with the SnapshotId parameter."); + } + return storageId; + } + public Boolean getDisplayVolume() { return displayVolume; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ExtensionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ExtensionResponse.java index fdf1e87df50..911839da405 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ExtensionResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ExtensionResponse.java @@ -85,6 +85,12 @@ public class ExtensionResponse extends BaseResponse { @Param(description = "Removal timestamp of the extension, if applicable") private Date removed; + @SerializedName(ApiConstants.RESERVED_RESOURCE_DETAILS) + @Param(description = "Resource detail names as comma separated string that should be reserved and not visible " + + "to end users", + since = "4.22.1") + protected String reservedResourceDetails; + public ExtensionResponse(String id, String name, String description, String type) { this.id = id; this.name = name; @@ -179,4 +185,8 @@ public class ExtensionResponse extends BaseResponse { public void setRemoved(Date removed) { this.removed = removed; } + + public void setReservedResourceDetails(String reservedResourceDetails) { + this.reservedResourceDetails = reservedResourceDetails; + } } diff --git a/api/src/main/java/org/apache/cloudstack/extension/ExtensionHelper.java b/api/src/main/java/org/apache/cloudstack/extension/ExtensionHelper.java index f50f841ed74..a01131278a7 100644 --- a/api/src/main/java/org/apache/cloudstack/extension/ExtensionHelper.java +++ b/api/src/main/java/org/apache/cloudstack/extension/ExtensionHelper.java @@ -17,8 +17,11 @@ package org.apache.cloudstack.extension; +import java.util.List; + public interface ExtensionHelper { Long getExtensionIdForCluster(long clusterId); Extension getExtension(long id); Extension getExtensionForCluster(long clusterId); + List getExtensionReservedResourceDetails(long extensionId); } diff --git a/api/src/main/java/org/apache/cloudstack/resourcelimit/Reserver.java b/api/src/main/java/org/apache/cloudstack/resourcelimit/Reserver.java new file mode 100644 index 00000000000..6b3f57b6aa5 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/resourcelimit/Reserver.java @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.resourcelimit; + +/** + * Interface implemented by CheckedReservation. + *

+ * This is defined in cloud-api to allow methods declared in modules that do not depend on cloud-server + * to receive CheckedReservations as parameters. + */ +public interface Reserver extends AutoCloseable { + + void close(); + +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/AddAccountToProjectCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/AddAccountToProjectCmdTest.java index f100822b8c7..cd0390aa268 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/test/AddAccountToProjectCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/test/AddAccountToProjectCmdTest.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.test; +import com.cloud.exception.ResourceAllocationException; import junit.framework.Assert; import junit.framework.TestCase; @@ -149,6 +150,8 @@ public class AddAccountToProjectCmdTest extends TestCase { addAccountToProjectCmd.execute(); } catch (InvalidParameterValueException exception) { Assert.assertEquals("Either accountName or email is required", exception.getLocalizedMessage()); + } catch (ResourceAllocationException exception) { + Assert.fail(); } } diff --git a/core/src/main/java/com/cloud/agent/api/storage/DownloadAnswer.java b/core/src/main/java/com/cloud/agent/api/storage/DownloadAnswer.java index 0c6373134b1..1c5eb7b9a9a 100644 --- a/core/src/main/java/com/cloud/agent/api/storage/DownloadAnswer.java +++ b/core/src/main/java/com/cloud/agent/api/storage/DownloadAnswer.java @@ -140,7 +140,7 @@ public class DownloadAnswer extends Answer { } public Long getTemplateSize() { - return templateSize; + return templateSize == 0 ? templatePhySicalSize : templateSize; } public void setTemplatePhySicalSize(long templatePhySicalSize) { diff --git a/core/src/main/java/org/apache/cloudstack/direct/download/DirectTemplateDownloaderImpl.java b/core/src/main/java/org/apache/cloudstack/direct/download/DirectTemplateDownloaderImpl.java index a1485463eaa..05619e5632b 100644 --- a/core/src/main/java/org/apache/cloudstack/direct/download/DirectTemplateDownloaderImpl.java +++ b/core/src/main/java/org/apache/cloudstack/direct/download/DirectTemplateDownloaderImpl.java @@ -21,6 +21,7 @@ package org.apache.cloudstack.direct.download; import com.cloud.utils.UriUtils; import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.utils.security.DigestHelper; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; @@ -33,6 +34,7 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.UUID; public abstract class DirectTemplateDownloaderImpl implements DirectTemplateDownloader { @@ -128,15 +130,14 @@ public abstract class DirectTemplateDownloaderImpl implements DirectTemplateDown */ protected File createTemporaryDirectoryAndFile(String downloadDir) { createFolder(downloadDir); - return new File(downloadDir + File.separator + getFileNameFromUrl()); + return new File(downloadDir + File.separator + getTemporaryFileName()); } /** - * Return filename from url + * Return filename from the temporary download file */ - public String getFileNameFromUrl() { - String[] urlParts = url.split("/"); - return urlParts[urlParts.length - 1]; + public String getTemporaryFileName() { + return String.format("%s.%s", UUID.randomUUID(), FilenameUtils.getExtension(url)); } @Override diff --git a/core/src/main/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloader.java b/core/src/main/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloader.java index 2050b9ef09f..854c310cde9 100644 --- a/core/src/main/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloader.java +++ b/core/src/main/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloader.java @@ -97,7 +97,7 @@ public class MetalinkDirectTemplateDownloader extends DirectTemplateDownloaderIm DirectTemplateDownloader urlDownloader = createDownloaderForMetalinks(getUrl(), getTemplateId(), getDestPoolPath(), getChecksum(), headers, connectTimeout, soTimeout, null, temporaryDownloadPath); try { - setDownloadedFilePath(downloadDir + File.separator + getFileNameFromUrl()); + setDownloadedFilePath(downloadDir + File.separator + getTemporaryFileName()); File f = new File(getDownloadedFilePath()); if (f.exists()) { f.delete(); diff --git a/core/src/main/java/org/apache/cloudstack/direct/download/NfsDirectTemplateDownloader.java b/core/src/main/java/org/apache/cloudstack/direct/download/NfsDirectTemplateDownloader.java index 21184ef07fe..6b0959b78ff 100644 --- a/core/src/main/java/org/apache/cloudstack/direct/download/NfsDirectTemplateDownloader.java +++ b/core/src/main/java/org/apache/cloudstack/direct/download/NfsDirectTemplateDownloader.java @@ -69,7 +69,7 @@ public class NfsDirectTemplateDownloader extends DirectTemplateDownloaderImpl { String mount = String.format(mountCommand, srcHost + ":" + srcPath, "/mnt/" + mountSrcUuid); Script.runSimpleBashScript(mount); String downloadDir = getDestPoolPath() + File.separator + getDirectDownloadTempPath(getTemplateId()); - setDownloadedFilePath(downloadDir + File.separator + getFileNameFromUrl()); + setDownloadedFilePath(downloadDir + File.separator + getTemporaryFileName()); Script.runSimpleBashScript("cp /mnt/" + mountSrcUuid + srcPath + " " + getDownloadedFilePath()); Script.runSimpleBashScript("umount /mnt/" + mountSrcUuid); return new Pair<>(true, getDownloadedFilePath()); diff --git a/core/src/main/java/org/apache/cloudstack/storage/command/TemplateOrVolumePostUploadCommand.java b/core/src/main/java/org/apache/cloudstack/storage/command/TemplateOrVolumePostUploadCommand.java index 253a2607a72..98db75f3902 100644 --- a/core/src/main/java/org/apache/cloudstack/storage/command/TemplateOrVolumePostUploadCommand.java +++ b/core/src/main/java/org/apache/cloudstack/storage/command/TemplateOrVolumePostUploadCommand.java @@ -19,6 +19,9 @@ package org.apache.cloudstack.storage.command; +import com.cloud.configuration.Resource; +import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; + public class TemplateOrVolumePostUploadCommand { long entityId; @@ -185,6 +188,11 @@ public class TemplateOrVolumePostUploadCommand { this.description = description; } + public void setDefaultMaxSecondaryStorageInBytes(long defaultMaxSecondaryStorageInBytes) { + this.defaultMaxSecondaryStorageInGB = defaultMaxSecondaryStorageInBytes != Resource.RESOURCE_UNLIMITED ? + ByteScaleUtils.bytesToGibibytes(defaultMaxSecondaryStorageInBytes) : Resource.RESOURCE_UNLIMITED; + } + public void setDefaultMaxSecondaryStorageInGB(long defaultMaxSecondaryStorageInGB) { this.defaultMaxSecondaryStorageInGB = defaultMaxSecondaryStorageInGB; } diff --git a/core/src/main/java/org/apache/cloudstack/storage/command/UploadStatusCommand.java b/core/src/main/java/org/apache/cloudstack/storage/command/UploadStatusCommand.java index 9e6b76e467f..f78744046f7 100644 --- a/core/src/main/java/org/apache/cloudstack/storage/command/UploadStatusCommand.java +++ b/core/src/main/java/org/apache/cloudstack/storage/command/UploadStatusCommand.java @@ -28,6 +28,7 @@ public class UploadStatusCommand extends Command { } private String entityUuid; private EntityType entityType; + private Boolean abort; protected UploadStatusCommand() { } @@ -37,6 +38,11 @@ public class UploadStatusCommand extends Command { this.entityType = entityType; } + public UploadStatusCommand(String entityUuid, EntityType entityType, Boolean abort) { + this(entityUuid, entityType); + this.abort = abort; + } + public String getEntityUuid() { return entityUuid; } @@ -45,6 +51,10 @@ public class UploadStatusCommand extends Command { return entityType; } + public Boolean getAbort() { + return abort; + } + @Override public boolean executeInSequence() { return false; diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java index 947cbd8e618..615320e1788 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java @@ -310,7 +310,7 @@ public interface NetworkOrchestrationService { void removeDhcpServiceInSubnet(Nic nic); - boolean resourceCountNeedsUpdate(NetworkOffering ntwkOff, ACLType aclType); + boolean isResourceCountUpdateNeeded(NetworkOffering networkOffering); void prepareAllNicsForMigration(VirtualMachineProfile vm, DeployDestination dest); diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java index 6f8c4630456..a55219511cb 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java @@ -120,7 +120,7 @@ public interface VolumeOrchestrationService { void destroyVolume(Volume volume); DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template, - Account owner, Long deviceId); + Account owner, Long deviceId, boolean incrementResourceCount); VolumeInfo createVolumeOnPrimaryStorage(VirtualMachine vm, VolumeInfo volume, HypervisorType rootDiskHyperType, StoragePool storagePool) throws NoTransitionException; diff --git a/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java b/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java index ddc8153d739..3ae94479cea 100644 --- a/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java +++ b/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java @@ -21,6 +21,7 @@ import static org.apache.cloudstack.framework.config.ConfigKey.Scope.Cluster; import com.cloud.deploy.DeploymentPlanner; import com.cloud.host.HostVO; import com.cloud.host.Status; +import com.cloud.storage.Storage.StoragePoolType; import com.cloud.utils.component.Manager; import com.cloud.vm.VMInstanceVO; import org.apache.cloudstack.framework.config.ConfigKey; @@ -32,6 +33,8 @@ import java.util.List; */ public interface HighAvailabilityManager extends Manager { + List LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT = List.of(StoragePoolType.NetworkFilesystem, StoragePoolType.SharedMountPoint); + ConfigKey ForceHA = new ConfigKey<>("Advanced", Boolean.class, "force.ha", "false", "Force High-Availability to happen even if the VM says no.", true, Cluster); diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index b20c06fc2c3..71ecc73f325 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -50,7 +50,6 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import javax.persistence.EntityExistsException; - import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -310,7 +309,6 @@ import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; import com.google.gson.Gson; - public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable { public static final String VM_WORK_JOB_HANDLER = VirtualMachineManagerImpl.class.getSimpleName(); @@ -588,7 +586,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac Long deviceId = dataDiskDeviceIds.get(index++); String volumeName = deviceId == null ? "DATA-" + persistedVm.getId() : "DATA-" + persistedVm.getId() + "-" + String.valueOf(deviceId); volumeMgr.allocateRawVolume(Type.DATADISK, volumeName, dataDiskOfferingInfo.getDiskOffering(), dataDiskOfferingInfo.getSize(), - dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), persistedVm, template, owner, deviceId); + dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), persistedVm, template, owner, deviceId, true); } } if (datadiskTemplateToDiskOfferingMap != null && !datadiskTemplateToDiskOfferingMap.isEmpty()) { @@ -598,7 +596,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac long diskOfferingSize = diskOffering.getDiskSize() / (1024 * 1024 * 1024); VMTemplateVO dataDiskTemplate = _templateDao.findById(dataDiskTemplateToDiskOfferingMap.getKey()); volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId() + "-" + String.valueOf( diskNumber), diskOffering, diskOfferingSize, null, null, - persistedVm, dataDiskTemplate, owner, diskNumber); + persistedVm, dataDiskTemplate, owner, diskNumber, true); diskNumber++; } } @@ -628,7 +626,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac String rootVolumeName = String.format("ROOT-%s", vm.getId()); if (template.getFormat() == ImageFormat.ISO) { volumeMgr.allocateRawVolume(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskOfferingInfo.getSize(), - rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vm, template, owner, null); + rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vm, template, owner, null, true); } else if (Arrays.asList(ImageFormat.BAREMETAL, ImageFormat.EXTERNAL).contains(template.getFormat())) { logger.debug("{} has format [{}]. Skipping ROOT volume [{}] allocation.", template, template.getFormat(), rootVolumeName); } else { @@ -2219,7 +2217,6 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac protected boolean sendStop(final VirtualMachineGuru guru, final VirtualMachineProfile profile, final boolean force, final boolean checkBeforeCleanup) { final VirtualMachine vm = profile.getVirtualMachine(); Map vlanToPersistenceMap = getVlanToPersistenceMapForVM(vm.getId()); - StopCommand stpCmd = new StopCommand(vm, getExecuteInSequence(vm.getHypervisorType()), checkBeforeCleanup); updateStopCommandForExternalHypervisorType(vm.getHypervisorType(), profile, stpCmd); if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { @@ -5278,9 +5275,20 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac private void saveCustomOfferingDetails(long vmId, ServiceOffering serviceOffering) { Map details = vmInstanceDetailsDao.listDetailsKeyPairs(vmId); - details.put(UsageEventVO.DynamicParameters.cpuNumber.name(), serviceOffering.getCpu().toString()); - details.put(UsageEventVO.DynamicParameters.cpuSpeed.name(), serviceOffering.getSpeed().toString()); - details.put(UsageEventVO.DynamicParameters.memory.name(), serviceOffering.getRamSize().toString()); + + // We need to restore only the customizable parameters. If we save a parameter that is not customizable and attempt + // to restore a VM snapshot, com.cloud.vm.UserVmManagerImpl.validateCustomParameters will fail. + ServiceOffering unfilledOffering = _serviceOfferingDao.findByIdIncludingRemoved(serviceOffering.getId()); + if (unfilledOffering.getCpu() == null) { + details.put(UsageEventVO.DynamicParameters.cpuNumber.name(), serviceOffering.getCpu().toString()); + } + if (unfilledOffering.getSpeed() == null) { + details.put(UsageEventVO.DynamicParameters.cpuSpeed.name(), serviceOffering.getSpeed().toString()); + } + if (unfilledOffering.getRamSize() == null) { + details.put(UsageEventVO.DynamicParameters.memory.name(), serviceOffering.getRamSize().toString()); + } + List detailList = new ArrayList<>(); for (Map.Entry entry: details.entrySet()) { VMInstanceDetailVO detailVO = new VMInstanceDetailVO(vmId, entry.getKey(), entry.getValue(), true); diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index 77ea965e50a..2ae228adba9 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -58,6 +58,7 @@ import org.apache.cloudstack.framework.messagebus.PublishScope; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.apache.cloudstack.network.dao.NetworkPermissionDao; +import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.ObjectUtils; @@ -86,6 +87,7 @@ import com.cloud.api.query.dao.DomainRouterJoinDao; import com.cloud.api.query.vo.DomainRouterJoinVO; import com.cloud.bgp.BGPService; import com.cloud.configuration.ConfigurationManager; +import com.cloud.configuration.Resource; import com.cloud.configuration.Resource.ResourceType; import com.cloud.dc.ASNumberVO; import com.cloud.dc.ClusterVO; @@ -214,6 +216,7 @@ import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.offerings.dao.NetworkOfferingDetailsDao; import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; import com.cloud.resource.ResourceManager; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.server.ManagementServer; import com.cloud.user.Account; import com.cloud.user.ResourceLimitService; @@ -447,6 +450,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra ClusterDao clusterDao; @Inject RoutedIpv4Manager routedIpv4Manager; + @Inject + private ReservationDao reservationDao; protected StateMachine2 _stateMachine; ScheduledExecutorService _executor; @@ -2752,12 +2757,6 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra return null; } - final boolean updateResourceCount = resourceCountNeedsUpdate(ntwkOff, aclType); - //check resource limits - if (updateResourceCount) { - _resourceLimitMgr.checkResourceLimit(owner, ResourceType.network, isDisplayNetworkEnabled); - } - // Validate network offering if (ntwkOff.getState() != NetworkOffering.State.Enabled) { // see NetworkOfferingVO @@ -2776,6 +2775,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra boolean ipv6 = false; + try (CheckedReservation networkReservation = new CheckedReservation(owner, domainId, Resource.ResourceType.network, null, null, 1L, reservationDao, _resourceLimitMgr)) { + if (StringUtils.isNoneBlank(ip6Gateway, ip6Cidr)) { ipv6 = true; } @@ -3115,8 +3116,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra } } - if (updateResourceCount) { - _resourceLimitMgr.incrementResourceCount(owner.getId(), ResourceType.network, isDisplayNetworkEnabled); + if (isResourceCountUpdateNeeded(ntwkOff)) { + changeAccountResourceCountOrRecalculateDomainResourceCount(owner.getAccountId(), domainId, isDisplayNetworkEnabled, true); } UsageEventUtils.publishNetworkCreation(network); @@ -3127,6 +3128,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra CallContext.current().setEventDetails("Network ID: " + network.getUuid()); CallContext.current().putContextParameter(Network.class, network.getUuid()); return network; + } } @Override @@ -3492,9 +3494,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra } final NetworkOffering ntwkOff = _entityMgr.findById(NetworkOffering.class, networkFinal.getNetworkOfferingId()); - final boolean updateResourceCount = resourceCountNeedsUpdate(ntwkOff, networkFinal.getAclType()); - if (updateResourceCount) { - _resourceLimitMgr.decrementResourceCount(networkFinal.getAccountId(), ResourceType.network, networkFinal.getDisplayNetwork()); + if (isResourceCountUpdateNeeded(ntwkOff)) { + changeAccountResourceCountOrRecalculateDomainResourceCount(networkFinal.getAccountId(), networkFinal.getDomainId(), networkFinal.getDisplayNetwork(), false); } } return deletedVlans.second(); @@ -3517,6 +3518,23 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra return success; } + /** + * If it is a shared network with {@link ACLType#Domain}, it will belong to account {@link Account#ACCOUNT_ID_SYSTEM} and the resources will be not incremented for the + * domain. Therefore, we force the recalculation of the domain's resource count in this case. Otherwise, it will change the count for the account owner. + * @param incrementAccountResourceCount If true, the account resource count will be incremented by 1; otherwise, it will decremented by 1. + */ + private void changeAccountResourceCountOrRecalculateDomainResourceCount(Long accountId, Long domainId, boolean displayNetwork, boolean incrementAccountResourceCount) { + if (Account.ACCOUNT_ID_SYSTEM == accountId && ObjectUtils.isNotEmpty(domainId)) { + _resourceLimitMgr.recalculateDomainResourceCount(domainId, ResourceType.network, null); + } else { + if (incrementAccountResourceCount) { + _resourceLimitMgr.incrementResourceCount(accountId, ResourceType.network, displayNetwork); + } else { + _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.network, displayNetwork); + } + } + } + private void publishDeletedVlanRanges(List deletedVlanRangeToPublish) { if (CollectionUtils.isNotEmpty(deletedVlanRangeToPublish)) { for (VlanVO vlan : deletedVlanRangeToPublish) { @@ -3526,10 +3544,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra } @Override - public boolean resourceCountNeedsUpdate(final NetworkOffering ntwkOff, final ACLType aclType) { - //Update resource count only for Isolated account specific non-system networks - final boolean updateResourceCount = ntwkOff.getGuestType() == GuestType.Isolated && !ntwkOff.isSystemOnly() && aclType == ACLType.Account; - return updateResourceCount; + public boolean isResourceCountUpdateNeeded(NetworkOffering networkOffering) { + return !networkOffering.isSystemOnly(); } protected Pair> deleteVlansInNetwork(final NetworkVO network, final long userId, final Account callerAccount) { diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index e8c75afa81c..bf3985d3ce7 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -40,6 +40,7 @@ import javax.naming.ConfigurationException; import com.cloud.deploy.DeploymentClusterPlanner; import com.cloud.exception.ResourceAllocationException; +import com.cloud.resourcelimit.ReservationHelper; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.dao.VMTemplateDao; @@ -82,6 +83,7 @@ import org.apache.cloudstack.framework.jobs.AsyncJobManager; import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO; import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; +import org.apache.cloudstack.resourcelimit.Reserver; import org.apache.cloudstack.secret.PassphraseVO; import org.apache.cloudstack.secret.dao.PassphraseDao; import org.apache.cloudstack.snapshot.SnapshotHelper; @@ -862,7 +864,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating volume", create = true) @Override public DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template, Account owner, - Long deviceId) { + Long deviceId, boolean incrementResourceCount) { if (size == null) { size = offering.getDiskSize(); } else { @@ -901,7 +903,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati saveVolumeDetails(offering.getId(), vol.getId()); // Save usage event and update resource count for user vm volumes - if (vm.getType() == VirtualMachine.Type.User) { + if (vm.getType() == VirtualMachine.Type.User && incrementResourceCount) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, vol.getAccountId(), vol.getDataCenterId(), vol.getId(), vol.getName(), offering.getId(), null, size, Volume.class.getName(), vol.getUuid(), vol.getInstanceId(), vol.isDisplayVolume()); _resourceLimitMgr.incrementVolumeResourceCount(vm.getAccountId(), vol.isDisplayVolume(), vol.getSize(), offering); @@ -1938,14 +1940,20 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati template == null ? null : template.getSize(), vol.getPassphraseId() != null); - if (newSize != vol.getSize()) { - DiskOfferingVO diskOffering = diskOfferingDao.findByIdIncludingRemoved(vol.getDiskOfferingId()); + if (newSize == vol.getSize()) { + return; + } + + DiskOfferingVO diskOffering = diskOfferingDao.findByIdIncludingRemoved(vol.getDiskOfferingId()); + + List reservations = new ArrayList<>(); + try { VMInstanceVO vm = vol.getInstanceId() != null ? vmInstanceDao.findById(vol.getInstanceId()) : null; if (vm == null || vm.getType() == VirtualMachine.Type.User) { // Update resource count for user vm volumes when volume is attached if (newSize > vol.getSize()) { _resourceLimitMgr.checkPrimaryStorageResourceLimit(_accountMgr.getActiveAccountById(vol.getAccountId()), - vol.isDisplay(), newSize - vol.getSize(), diskOffering); + vol.isDisplay(), newSize - vol.getSize(), diskOffering, reservations); _resourceLimitMgr.incrementVolumePrimaryStorageResourceCount(vol.getAccountId(), vol.isDisplay(), newSize - vol.getSize(), diskOffering); } else { @@ -1953,9 +1961,11 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati vol.getSize() - newSize, diskOffering); } } - vol.setSize(newSize); - _volsDao.persist(vol); + } finally { + ReservationHelper.closeAll(reservations); } + vol.setSize(newSize); + _volsDao.persist(vol); } @Override diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/AccountVlanMapDao.java b/engine/schema/src/main/java/com/cloud/dc/dao/AccountVlanMapDao.java index 01afd0780f7..e4047cf7973 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/AccountVlanMapDao.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/AccountVlanMapDao.java @@ -27,6 +27,6 @@ public interface AccountVlanMapDao extends GenericDao { public List listAccountVlanMapsByVlan(long vlanDbId); - public AccountVlanMapVO findAccountVlanMap(long accountId, long vlanDbId); + public AccountVlanMapVO findAccountVlanMap(Long accountId, long vlanDbId); } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/AccountVlanMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/AccountVlanMapDaoImpl.java index 12114770f11..0844bb77caa 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/AccountVlanMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/AccountVlanMapDaoImpl.java @@ -48,9 +48,9 @@ public class AccountVlanMapDaoImpl extends GenericDaoBase sc = AccountVlanSearch.create(); - sc.setParameters("accountId", accountId); + sc.setParametersIfNotNull("accountId", accountId); sc.setParameters("vlanDbId", vlanDbId); return findOneIncludingRemovedBy(sc); } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/DomainVlanMapDao.java b/engine/schema/src/main/java/com/cloud/dc/dao/DomainVlanMapDao.java index 6af16bbace9..d14ccbe86ca 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/DomainVlanMapDao.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/DomainVlanMapDao.java @@ -24,5 +24,5 @@ import com.cloud.utils.db.GenericDao; public interface DomainVlanMapDao extends GenericDao { public List listDomainVlanMapsByDomain(long domainId); public List listDomainVlanMapsByVlan(long vlanDbId); - public DomainVlanMapVO findDomainVlanMap(long domainId, long vlanDbId); + public DomainVlanMapVO findDomainVlanMap(Long domainId, long vlanDbId); } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/DomainVlanMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/DomainVlanMapDaoImpl.java index f789721d5fd..0b4c781349f 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/DomainVlanMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/DomainVlanMapDaoImpl.java @@ -46,9 +46,9 @@ public class DomainVlanMapDaoImpl extends GenericDaoBase } @Override - public DomainVlanMapVO findDomainVlanMap(long domainId, long vlanDbId) { + public DomainVlanMapVO findDomainVlanMap(Long domainId, long vlanDbId) { SearchCriteria sc = DomainVlanSearch.create(); - sc.setParameters("domainId", domainId); + sc.setParametersIfNotNull("domainId", domainId); sc.setParameters("vlanDbId", vlanDbId); return findOneIncludingRemovedBy(sc); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java index 23541c2431e..4fd3e729e0d 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java @@ -21,6 +21,7 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import com.cloud.hypervisor.Hypervisor; import com.cloud.utils.Pair; @@ -192,4 +193,8 @@ public interface VMInstanceDao extends GenericDao, StateDao< int getVmCountByOfferingNotInDomain(Long serviceOfferingId, List domainIds); List listByIdsIncludingRemoved(List ids); + + List listDeleteProtectedVmsByAccountId(long accountId); + + List listDeleteProtectedVmsByDomainIds(Set domainIds); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java index 589a63ea0d8..6ffa7cd5962 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -25,11 +25,13 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import javax.annotation.PostConstruct; import javax.inject.Inject; +import org.apache.cloudstack.api.ApiConstants; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; @@ -106,6 +108,8 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem protected SearchBuilder IdsPowerStateSelectSearch; GenericSearchBuilder CountByOfferingId; GenericSearchBuilder CountUserVmNotInDomain; + SearchBuilder DeleteProtectedVmSearchByAccount; + SearchBuilder DeleteProtectedVmSearchByDomainIds; @Inject ResourceTagDao tagsDao; @@ -368,6 +372,19 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem CountUserVmNotInDomain.and("domainIdsNotIn", CountUserVmNotInDomain.entity().getDomainId(), Op.NIN); CountUserVmNotInDomain.done(); + DeleteProtectedVmSearchByAccount = createSearchBuilder(); + DeleteProtectedVmSearchByAccount.selectFields(DeleteProtectedVmSearchByAccount.entity().getUuid()); + DeleteProtectedVmSearchByAccount.and(ApiConstants.ACCOUNT_ID, DeleteProtectedVmSearchByAccount.entity().getAccountId(), Op.EQ); + DeleteProtectedVmSearchByAccount.and(ApiConstants.DELETE_PROTECTION, DeleteProtectedVmSearchByAccount.entity().isDeleteProtection(), Op.EQ); + DeleteProtectedVmSearchByAccount.and(ApiConstants.REMOVED, DeleteProtectedVmSearchByAccount.entity().getRemoved(), Op.NULL); + DeleteProtectedVmSearchByAccount.done(); + + DeleteProtectedVmSearchByDomainIds = createSearchBuilder(); + DeleteProtectedVmSearchByDomainIds.selectFields(DeleteProtectedVmSearchByDomainIds.entity().getUuid()); + DeleteProtectedVmSearchByDomainIds.and(ApiConstants.DOMAIN_IDS, DeleteProtectedVmSearchByDomainIds.entity().getDomainId(), Op.IN); + DeleteProtectedVmSearchByDomainIds.and(ApiConstants.DELETE_PROTECTION, DeleteProtectedVmSearchByDomainIds.entity().isDeleteProtection(), Op.EQ); + DeleteProtectedVmSearchByDomainIds.and(ApiConstants.REMOVED, DeleteProtectedVmSearchByDomainIds.entity().getRemoved(), Op.NULL); + DeleteProtectedVmSearchByDomainIds.done(); } @Override @@ -1296,4 +1313,22 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem sc.setParameters("ids", ids.toArray()); return listIncludingRemovedBy(sc); } + + @Override + public List listDeleteProtectedVmsByAccountId(long accountId) { + SearchCriteria sc = DeleteProtectedVmSearchByAccount.create(); + sc.setParameters(ApiConstants.ACCOUNT_ID, accountId); + sc.setParameters(ApiConstants.DELETE_PROTECTION, true); + Filter filter = new Filter(VMInstanceVO.class, null, false, 0L, 10L); + return listBy(sc, filter); + } + + @Override + public List listDeleteProtectedVmsByDomainIds(Set domainIds) { + SearchCriteria sc = DeleteProtectedVmSearchByDomainIds.create(); + sc.setParameters(ApiConstants.DOMAIN_IDS, domainIds.toArray()); + sc.setParameters(ApiConstants.DELETE_PROTECTION, true); + Filter filter = new Filter(VMInstanceVO.class, null, false, 0L, 10L); + return listBy(sc, filter); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java index ee1783a9c89..87b7dab1ff7 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java @@ -21,20 +21,14 @@ import java.util.Date; import java.util.List; import com.cloud.utils.DateUtil; -import org.apache.cloudstack.api.response.BackupScheduleResponse; -import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.backup.BackupScheduleVO; import com.cloud.utils.db.GenericDao; public interface BackupScheduleDao extends GenericDao { - BackupScheduleVO findByVM(Long vmId); - List listByVM(Long vmId); BackupScheduleVO findByVMAndIntervalType(Long vmId, DateUtil.IntervalType intervalType); List getSchedulesToExecute(Date currentTimestamp); - - BackupScheduleResponse newBackupScheduleResponse(BackupSchedule schedule); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java index d9cf7b63680..972af73391a 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java @@ -17,28 +17,23 @@ package org.apache.cloudstack.backup.dao; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.Date; import java.util.List; import javax.annotation.PostConstruct; -import javax.inject.Inject; import com.cloud.utils.DateUtil; -import org.apache.cloudstack.api.response.BackupScheduleResponse; -import org.apache.cloudstack.backup.BackupSchedule; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.TransactionLegacy; import org.apache.cloudstack.backup.BackupScheduleVO; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; -import com.cloud.vm.VMInstanceVO; -import com.cloud.vm.dao.VMInstanceDao; public class BackupScheduleDaoImpl extends GenericDaoBase implements BackupScheduleDao { - - @Inject - VMInstanceDao vmInstanceDao; - private SearchBuilder backupScheduleSearch; private SearchBuilder executableSchedulesSearch; @@ -59,13 +54,6 @@ public class BackupScheduleDaoImpl extends GenericDaoBase sc = backupScheduleSearch.create(); - sc.setParameters("vm_id", vmId); - return findOneBy(sc); - } - @Override public List listByVM(Long vmId) { SearchCriteria sc = backupScheduleSearch.create(); @@ -88,21 +76,19 @@ public class BackupScheduleDaoImpl extends GenericDaoBase forRollback = new ArrayList<>(); + List snapshotsForRollback = new ArrayList<>(); long startFreeze = 0; try { vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshotVO, VMSnapshot.Event.CreateRequested); @@ -165,7 +165,7 @@ public class StorageVMSnapshotStrategy extends DefaultVMSnapshotStrategy { logger.info("The virtual machine is frozen"); for (VolumeInfo vol : vinfos) { long startSnapshtot = System.nanoTime(); - SnapshotInfo snapInfo = createDiskSnapshot(vmSnapshot, forRollback, vol); + SnapshotInfo snapInfo = createDiskSnapshot(vmSnapshot, snapshotsForRollback, vol); if (snapInfo == null) { thawAnswer = (FreezeThawVMAnswer) agentMgr.send(hostId, thawCmd); @@ -222,7 +222,7 @@ public class StorageVMSnapshotStrategy extends DefaultVMSnapshotStrategy { } } if (!result) { - for (SnapshotInfo snapshotInfo : forRollback) { + for (SnapshotInfo snapshotInfo : snapshotsForRollback) { rollbackDiskSnapshot(snapshotInfo); } try { @@ -388,10 +388,16 @@ public class StorageVMSnapshotStrategy extends DefaultVMSnapshotStrategy { //Rollback if one of disks snapshot fails protected void rollbackDiskSnapshot(SnapshotInfo snapshotInfo) { + if (snapshotInfo == null) { + return; + } Long snapshotID = snapshotInfo.getId(); SnapshotVO snapshot = snapshotDao.findById(snapshotID); + if (snapshot == null) { + return; + } deleteSnapshotByStrategy(snapshot); - logger.debug("Rollback is executed: deleting snapshot with id:" + snapshotID); + logger.debug("Rollback is executed: deleting snapshot with id: {}", snapshotID); } protected void deleteSnapshotByStrategy(SnapshotVO snapshot) { @@ -434,7 +440,7 @@ public class StorageVMSnapshotStrategy extends DefaultVMSnapshotStrategy { } } - protected SnapshotInfo createDiskSnapshot(VMSnapshot vmSnapshot, List forRollback, VolumeInfo vol) { + protected SnapshotInfo createDiskSnapshot(VMSnapshot vmSnapshot, List snapshotsForRollback, VolumeInfo vol) { String snapshotName = vmSnapshot.getId() + "_" + vol.getUuid(); SnapshotVO snapshot = new SnapshotVO(vol.getDataCenterId(), vol.getAccountId(), vol.getDomainId(), vol.getId(), vol.getDiskOfferingId(), snapshotName, (short) Snapshot.Type.GROUP.ordinal(), Snapshot.Type.GROUP.name(), vol.getSize(), vol.getMinIops(), vol.getMaxIops(), Hypervisor.HypervisorType.KVM, null); @@ -448,6 +454,7 @@ public class StorageVMSnapshotStrategy extends DefaultVMSnapshotStrategy { vol.addPayload(setPayload(vol, snapshot, quiescevm)); SnapshotInfo snapshotInfo = snapshotDataFactory.getSnapshot(snapshot.getId(), vol.getDataStore()); snapshotInfo.addPayload(vol.getpayload()); + snapshotsForRollback.add(snapshotInfo); SnapshotStrategy snapshotStrategy = storageStrategyFactory.getSnapshotStrategy(snapshotInfo, SnapshotOperation.TAKE); if (snapshotStrategy == null) { throw new CloudRuntimeException("Could not find strategy for snapshot uuid:" + snapshotInfo.getUuid()); @@ -455,8 +462,6 @@ public class StorageVMSnapshotStrategy extends DefaultVMSnapshotStrategy { snapshotInfo = snapshotStrategy.takeSnapshot(snapshotInfo); if (snapshotInfo == null) { throw new CloudRuntimeException("Failed to create snapshot"); - } else { - forRollback.add(snapshotInfo); } vmSnapshotDetailsDao.persist(new VMSnapshotDetailsVO(vmSnapshot.getId(), STORAGE_SNAPSHOT, String.valueOf(snapshot.getId()), true)); snapshotInfo.markBackedUp(); diff --git a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyKVMTest.java b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyKVMTest.java index 050c0246aba..7d5d3c786e8 100644 --- a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyKVMTest.java +++ b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyKVMTest.java @@ -155,7 +155,7 @@ public class VMSnapshotStrategyKVMTest extends TestCase{ @Test public void testCreateDiskSnapshotBasedOnStrategy() throws Exception { VMSnapshotVO vmSnapshot = Mockito.mock(VMSnapshotVO.class); - List forRollback = new ArrayList<>(); + List snapshotsForRollback = new ArrayList<>(); VolumeInfo vol = Mockito.mock(VolumeInfo.class); SnapshotInfo snapshotInfo = Mockito.mock(SnapshotInfo.class); SnapshotStrategy strategy = Mockito.mock(SnapshotStrategy.class); @@ -179,7 +179,7 @@ public class VMSnapshotStrategyKVMTest extends TestCase{ VMSnapshotDetailsVO vmDetails = new VMSnapshotDetailsVO(vmSnapshot.getId(), volUuid, String.valueOf(snapshot.getId()), false); when(vmSnapshotDetailsDao.persist(any())).thenReturn(vmDetails); - info = vmStrategy.createDiskSnapshot(vmSnapshot, forRollback, vol); + info = vmStrategy.createDiskSnapshot(vmSnapshot, snapshotsForRollback, vol); assertNotNull(info); } diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java index cd9ab3adb21..4057f7a051b 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java @@ -145,10 +145,10 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement storageType = "shared"; } - logger.debug(String.format( - "Filtering storage pools by capacity type [%s] as the first storage pool of the list, with name [%s] and ID [%s], is a [%s] storage.", + logger.info( + "Filtering storage pools by capacity type [{}] as the first storage pool of the list, with name [{}] and ID [{}], is a [{}] storage.", capacityType, storagePool.getName(), storagePool.getUuid(), storageType - )); + ); Pair, Map> result = capacityDao.orderHostsByFreeCapacity(zoneId, clusterId, capacityType); List poolIdsByCapacity = result.first(); @@ -185,7 +185,7 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement Long clusterId = plan.getClusterId(); List poolIdsByVolCount = volumeDao.listPoolIdsByVolumeCount(dcId, podId, clusterId, account.getAccountId()); - logger.debug(String.format("List of pools in ascending order of number of volumes for account [%s] is [%s].", account, poolIdsByVolCount)); + logger.debug("List of pools in ascending order of number of volumes for account [{}] is [{}].", account, poolIdsByVolCount); // now filter the given list of Pools by this ordered list Map poolMap = new HashMap<>(); @@ -206,16 +206,11 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement @Override public List reorderPools(List pools, VirtualMachineProfile vmProfile, DeploymentPlan plan, DiskProfile dskCh) { - if (logger.isTraceEnabled()) { - logger.trace("reordering pools"); - } if (pools == null) { - logger.trace("There are no pools to reorder; returning null."); + logger.info("There are no pools to reorder."); return null; } - if (logger.isTraceEnabled()) { - logger.trace(String.format("reordering %d pools", pools.size())); - } + logger.info("Reordering [{}] pools", pools.size()); Account account = null; if (vmProfile.getVirtualMachine() != null) { account = vmProfile.getOwner(); @@ -224,9 +219,7 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement pools = reorderStoragePoolsBasedOnAlgorithm(pools, plan, account); if (vmProfile.getVirtualMachine() == null) { - if (logger.isTraceEnabled()) { - logger.trace("The VM is null, skipping pools reordering by disk provisioning type."); - } + logger.info("The VM is null, skipping pool reordering by disk provisioning type."); return pools; } @@ -240,14 +233,10 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement List reorderStoragePoolsBasedOnAlgorithm(List pools, DeploymentPlan plan, Account account) { String volumeAllocationAlgorithm = VolumeOrchestrationService.VolumeAllocationAlgorithm.value(); - logger.debug("Using volume allocation algorithm {} to reorder pools.", volumeAllocationAlgorithm); + logger.info("Using volume allocation algorithm {} to reorder pools.", volumeAllocationAlgorithm); if (volumeAllocationAlgorithm.equals("random") || (account == null)) { reorderRandomPools(pools); } else if (StringUtils.equalsAny(volumeAllocationAlgorithm, "userdispersing", "firstfitleastconsumed")) { - if (logger.isTraceEnabled()) { - logger.trace("Using reordering algorithm {}", volumeAllocationAlgorithm); - } - if (volumeAllocationAlgorithm.equals("userdispersing")) { pools = reorderPoolsByNumberOfVolumes(plan, pools, account); } else { @@ -259,16 +248,15 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement void reorderRandomPools(List pools) { StorageUtil.traceLogStoragePools(pools, logger, "pools to choose from: "); - if (logger.isTraceEnabled()) { - logger.trace("Shuffle this so that we don't check the pools in the same order. Algorithm == 'random' (or no account?)"); - } - StorageUtil.traceLogStoragePools(pools, logger, "pools to shuffle: "); + logger.trace("Shuffle this so that we don't check the pools in the same order. Algorithm == 'random' (or no account?)"); + logger.debug("Pools to shuffle: [{}]", pools); Collections.shuffle(pools, secureRandom); - StorageUtil.traceLogStoragePools(pools, logger, "shuffled list of pools to choose from: "); + logger.debug("Shuffled list of pools to choose from: [{}]", pools); } private List reorderPoolsByDiskProvisioningType(List pools, DiskProfile diskProfile) { if (diskProfile != null && diskProfile.getProvisioningType() != null && !diskProfile.getProvisioningType().equals(Storage.ProvisioningType.THIN)) { + logger.info("Reordering [{}] pools by disk provisioning type [{}].", pools.size(), diskProfile.getProvisioningType()); List reorderedPools = new ArrayList<>(); int preferredIndex = 0; for (StoragePool pool : pools) { @@ -282,22 +270,28 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement reorderedPools.add(preferredIndex++, pool); } } + logger.debug("Reordered list of pools by disk provisioning type [{}]: [{}]", diskProfile.getProvisioningType(), reorderedPools); return reorderedPools; } else { + if (diskProfile == null) { + logger.info("Reordering pools by disk provisioning type wasn't necessary, since no disk profile was found."); + } else { + logger.debug("Reordering pools by disk provisioning type wasn't necessary, since the provisioning type is [{}].", diskProfile.getProvisioningType()); + } return pools; } } protected boolean filter(ExcludeList avoid, StoragePool pool, DiskProfile dskCh, DeploymentPlan plan) { - logger.debug(String.format("Checking if storage pool [%s] is suitable to disk [%s].", pool, dskCh)); + logger.debug("Checking if storage pool [{}] is suitable to disk [{}].", pool, dskCh); if (avoid.shouldAvoid(pool)) { - logger.debug(String.format("StoragePool [%s] is in avoid set, skipping this pool to allocation of disk [%s].", pool, dskCh)); + logger.debug("StoragePool [{}] is in avoid set, skipping this pool to allocation of disk [{}].", pool, dskCh); return false; } if (dskCh.requiresEncryption() && !pool.getPoolType().supportsEncryption()) { if (logger.isDebugEnabled()) { - logger.debug(String.format("Storage pool type '%s' doesn't support encryption required for volume, skipping this pool", pool.getPoolType())); + logger.debug("Storage pool type '[{}]' doesn't support encryption required for volume, skipping this pool", pool.getPoolType()); } return false; } @@ -319,8 +313,8 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement } if (!checkDiskProvisioningSupport(dskCh, pool)) { - logger.debug(String.format("Storage pool [%s] does not have support to disk provisioning of disk [%s].", pool, ReflectionToStringBuilderUtils.reflectOnlySelectedFields(dskCh, - "type", "name", "diskOfferingId", "templateId", "volumeId", "provisioningType", "hyperType"))); + logger.debug("Storage pool [{}] does not have support to disk provisioning of disk [{}].", pool, ReflectionToStringBuilderUtils.reflectOnlySelectedFields(dskCh, + "type", "name", "diskOfferingId", "templateId", "volumeId", "provisioningType", "hyperType")); return false; } @@ -332,7 +326,7 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement HostVO plannedHost = hostDao.findById(plan.getHostId()); if (!storageMgr.checkIfHostAndStoragePoolHasCommonStorageAccessGroups(plannedHost, pool)) { if (logger.isDebugEnabled()) { - logger.debug(String.format("StoragePool %s and host %s does not have matching storage access groups", pool, plannedHost)); + logger.debug("StoragePool [{}] and host [{}] does not have matching storage access groups", pool, plannedHost); } return false; } @@ -343,13 +337,13 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement if (!isTempVolume) { volume = volumeDao.findById(dskCh.getVolumeId()); if (!storageMgr.storagePoolCompatibleWithVolumePool(pool, volume)) { - logger.debug(String.format("Pool [%s] is not compatible with volume [%s], skipping it.", pool, volume)); + logger.debug("Pool [{}] is not compatible with volume [{}], skipping it.", pool, volume); return false; } } if (pool.isManaged() && !storageUtil.managedStoragePoolCanScale(pool, plan.getClusterId(), plan.getHostId())) { - logger.debug(String.format("Cannot allocate pool [%s] to volume [%s] because the max number of managed clustered filesystems has been exceeded.", pool, volume)); + logger.debug("Cannot allocate pool [{}] to volume [{}] because the max number of managed clustered filesystems has been exceeded.", pool, volume); return false; } @@ -358,13 +352,13 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement requestVolumeDiskProfilePairs.add(new Pair<>(volume, dskCh)); if (dskCh.getHypervisorType() == HypervisorType.VMware) { if (pool.getPoolType() == Storage.StoragePoolType.DatastoreCluster && storageMgr.isStoragePoolDatastoreClusterParent(pool)) { - logger.debug(String.format("Skipping allocation of pool [%s] to volume [%s] because this pool is a parent datastore cluster.", pool, volume)); + logger.debug("Skipping allocation of pool [{}] to volume [{}] because this pool is a parent datastore cluster.", pool, volume); return false; } if (pool.getParent() != 0L) { StoragePoolVO datastoreCluster = storagePoolDao.findById(pool.getParent()); if (datastoreCluster == null || (datastoreCluster != null && datastoreCluster.getStatus() != StoragePoolStatus.Up)) { - logger.debug(String.format("Skipping allocation of pool [%s] to volume [%s] because this pool is not in [%s] state.", datastoreCluster, volume, StoragePoolStatus.Up)); + logger.debug("Skipping allocation of pool [{}] to volume [{}] because this pool is not in [{}] state.", datastoreCluster, volume, StoragePoolStatus.Up); return false; } } @@ -374,11 +368,11 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement storageMgr.isStoragePoolCompliantWithStoragePolicy(dskCh.getDiskOfferingId(), pool) : storageMgr.isStoragePoolCompliantWithStoragePolicy(requestVolumeDiskProfilePairs, pool); if (!isStoragePoolStoragePolicyCompliance) { - logger.debug(String.format("Skipping allocation of pool [%s] to volume [%s] because this pool is not compliant with the storage policy required by the volume.", pool, volume)); + logger.debug("Skipping allocation of pool [{}] to volume [{}] because this pool is not compliant with the storage policy required by the volume.", pool, volume); return false; } } catch (StorageUnavailableException e) { - logger.warn(String.format("Could not verify storage policy compliance against storage pool %s due to exception %s", pool.getUuid(), e.getMessage())); + logger.warn("Could not verify storage policy compliance against storage pool [{}] due to exception [{}]", pool.getUuid(), e.getMessage()); return false; } } @@ -427,19 +421,19 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement protected void logDisabledStoragePools(long dcId, Long podId, Long clusterId, ScopeType scope) { List disabledPools = storagePoolDao.findDisabledPoolsByScope(dcId, podId, clusterId, scope); if (disabledPools != null && !disabledPools.isEmpty()) { - logger.trace(String.format("Ignoring pools [%s] as they are in disabled state.", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(disabledPools))); + logger.trace("Ignoring pools [{}] as they are in disabled state.", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(disabledPools)); } } protected void logStartOfSearch(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, int returnUpTo, boolean bypassStorageTypeCheck){ - logger.trace(String.format("%s is looking for storage pools that match the VM's disk profile [%s], virtual machine profile [%s] and " - + "deployment plan [%s]. Returning up to [%d] and bypassStorageTypeCheck [%s].", this.getClass().getSimpleName(), dskCh, vmProfile, plan, returnUpTo, bypassStorageTypeCheck)); + logger.trace("[{}] is looking for storage pools that match the VM's disk profile [{}], virtual machine profile [{}] and " + + "deployment plan [{}]. Returning up to [{}] and bypassStorageTypeCheck [{}].", this.getClass().getSimpleName(), dskCh, vmProfile, plan, returnUpTo, bypassStorageTypeCheck); } protected void logEndOfSearch(List storagePoolList) { - logger.debug(String.format("%s is returning [%s] suitable storage pools [%s].", this.getClass().getSimpleName(), storagePoolList.size(), - Arrays.toString(storagePoolList.toArray()))); + logger.debug("[{}] is returning [{}] suitable storage pools [{}].", this.getClass().getSimpleName(), storagePoolList.size(), + Arrays.toString(storagePoolList.toArray())); } } diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java index a2e9eff2a08..26b39e30776 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java @@ -230,8 +230,10 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver { updateBuilder.setJobId(answer.getJobId()); updateBuilder.setLocalDownloadPath(answer.getDownloadPath()); updateBuilder.setInstallPath(answer.getInstallPath()); - updateBuilder.setSize(answer.getTemplateSize()); - updateBuilder.setPhysicalSize(answer.getTemplatePhySicalSize()); + if (!VMTemplateStorageResourceAssoc.ERROR_DOWNLOAD_STATES.contains(answer.getDownloadStatus())) { + updateBuilder.setSize(answer.getTemplateSize()); + updateBuilder.setPhysicalSize(answer.getTemplatePhySicalSize()); + } _templateStoreDao.update(tmpltStoreVO.getId(), updateBuilder); // update size in vm_template table VMTemplateVO tmlptUpdater = _templateDao.createForUpdate(); @@ -241,8 +243,7 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver { AsyncCompletionCallback caller = context.getParentCallback(); - if (answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR || - answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.ABANDONED || answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.UNKNOWN) { + if (VMTemplateStorageResourceAssoc.ERROR_DOWNLOAD_STATES.contains(answer.getDownloadStatus())) { CreateCmdResult result = new CreateCmdResult(null, null); result.setSuccess(false); result.setResult(answer.getErrorString()); @@ -285,19 +286,22 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver { updateBuilder.setJobId(answer.getJobId()); updateBuilder.setLocalDownloadPath(answer.getDownloadPath()); updateBuilder.setInstallPath(answer.getInstallPath()); - updateBuilder.setSize(answer.getTemplateSize()); - updateBuilder.setPhysicalSize(answer.getTemplatePhySicalSize()); + if (!VMTemplateStorageResourceAssoc.ERROR_DOWNLOAD_STATES.contains(answer.getDownloadStatus())) { + updateBuilder.setSize(answer.getTemplateSize()); + updateBuilder.setPhysicalSize(answer.getTemplatePhySicalSize()); + } _volumeStoreDao.update(volStoreVO.getId(), updateBuilder); // update size in volume table - VolumeVO volUpdater = volumeDao.createForUpdate(); - volUpdater.setSize(answer.getTemplateSize()); - volumeDao.update(obj.getId(), volUpdater); + if (!VMTemplateStorageResourceAssoc.ERROR_DOWNLOAD_STATES.contains(answer.getDownloadStatus())) { + VolumeVO volUpdater = volumeDao.createForUpdate(); + volUpdater.setSize(answer.getTemplateSize()); + volumeDao.update(obj.getId(), volUpdater); + } } AsyncCompletionCallback caller = context.getParentCallback(); - if (answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR || - answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.ABANDONED || answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.UNKNOWN) { + if (VMTemplateStorageResourceAssoc.ERROR_DOWNLOAD_STATES.contains(answer.getDownloadStatus())) { CreateCmdResult result = new CreateCmdResult(null, null); result.setSuccess(false); result.setResult(answer.getErrorString()); diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmd.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmd.java index 5ab54149645..9d76a7e6ec2 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmd.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmd.java @@ -83,6 +83,12 @@ public class CreateExtensionCmd extends BaseCmd { description = "Details in key/value pairs using format details[i].keyname=keyvalue. Example: details[0].endpoint.url=urlvalue") protected Map details; + @Parameter(name = ApiConstants.RESERVED_RESOURCE_DETAILS, type = CommandType.STRING, + description = "Resource detail names as comma separated string that should be reserved and not visible " + + "to end users", + since = "4.22.1") + protected String reservedResourceDetails; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -115,6 +121,10 @@ public class CreateExtensionCmd extends BaseCmd { return convertDetailsToMap(details); } + public String getReservedResourceDetails() { + return reservedResourceDetails; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmd.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmd.java index ded07d2dd32..5baaea1709d 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmd.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmd.java @@ -78,6 +78,12 @@ public class UpdateExtensionCmd extends BaseCmd { "if false or not set, no action)") private Boolean cleanupDetails; + @Parameter(name = ApiConstants.RESERVED_RESOURCE_DETAILS, type = CommandType.STRING, + description = "Resource detail names as comma separated string that should be reserved and not visible " + + "to end users", + since = "4.22.1") + protected String reservedResourceDetails; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -106,6 +112,10 @@ public class UpdateExtensionCmd extends BaseCmd { return cleanupDetails; } + public String getReservedResourceDetails() { + return reservedResourceDetails; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java index 4171b9615fe..1422338ddc9 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java @@ -216,6 +216,11 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana @Inject AccountService accountService; + // Map of in-built extension names and their reserved resource details that shouldn't be accessible to end-users + protected static final Map> INBUILT_RESERVED_RESOURCE_DETAILS = Map.of( + "proxmox", List.of("proxmox_vmid") + ); + private ScheduledExecutorService extensionPathStateCheckExecutor; protected String getDefaultExtensionRelativePath(String name) { @@ -563,6 +568,25 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana updateExtensionPathReady(extension, true); } + protected void addInbuiltExtensionReservedResourceDetails(long extensionId, List reservedResourceDetails) { + ExtensionVO vo = extensionDao.findById(extensionId); + if (vo == null || vo.isUserDefined()) { + return; + } + String lowerName = StringUtils.defaultString(vo.getName()).toLowerCase(); + Optional>> match = INBUILT_RESERVED_RESOURCE_DETAILS.entrySet().stream() + .filter(e -> lowerName.contains(e.getKey().toLowerCase())) + .findFirst(); + if (match.isPresent()) { + Set existing = new HashSet<>(reservedResourceDetails); + for (String detailKey : match.get().getValue()) { + if (existing.add(detailKey)) { + reservedResourceDetails.add(detailKey); + } + } + } + } + @Override public String getExtensionsPath() { return externalProvisioner.getExtensionsPath(); @@ -577,6 +601,7 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana String relativePath = cmd.getPath(); final Boolean orchestratorRequiresPrepareVm = cmd.isOrchestratorRequiresPrepareVm(); final String stateStr = cmd.getState(); + final String reservedResourceDetails = cmd.getReservedResourceDetails(); ExtensionVO extensionByName = extensionDao.findByName(name); if (extensionByName != null) { throw new CloudRuntimeException("Extension by name already exists"); @@ -624,6 +649,10 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM, String.valueOf(orchestratorRequiresPrepareVm), false)); } + if (StringUtils.isNotBlank(reservedResourceDetails)) { + detailsVOList.add(new ExtensionDetailsVO(extension.getId(), + ApiConstants.RESERVED_RESOURCE_DETAILS, reservedResourceDetails, false)); + } if (CollectionUtils.isNotEmpty(detailsVOList)) { extensionDetailsDao.saveDetails(detailsVOList); } @@ -704,6 +733,7 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana final String stateStr = cmd.getState(); final Map details = cmd.getDetails(); final Boolean cleanupDetails = cmd.isCleanupDetails(); + final String reservedResourceDetails = cmd.getReservedResourceDetails(); final ExtensionVO extensionVO = extensionDao.findById(id); if (extensionVO == null) { throw new InvalidParameterValueException("Failed to find the extension"); @@ -732,7 +762,8 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana throw new CloudRuntimeException(String.format("Failed to updated the extension: %s", extensionVO.getName())); } - updateExtensionsDetails(cleanupDetails, details, orchestratorRequiresPrepareVm, id); + updateExtensionsDetails(cleanupDetails, details, orchestratorRequiresPrepareVm, reservedResourceDetails, + id); return extensionVO; }); if (StringUtils.isNotBlank(stateStr)) { @@ -748,9 +779,11 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana return result; } - protected void updateExtensionsDetails(Boolean cleanupDetails, Map details, Boolean orchestratorRequiresPrepareVm, long id) { + protected void updateExtensionsDetails(Boolean cleanupDetails, Map details, + Boolean orchestratorRequiresPrepareVm, String reservedResourceDetails, long id) { final boolean needToUpdateAllDetails = Boolean.TRUE.equals(cleanupDetails) || MapUtils.isNotEmpty(details); - if (!needToUpdateAllDetails && orchestratorRequiresPrepareVm == null) { + if (!needToUpdateAllDetails && orchestratorRequiresPrepareVm == null && + StringUtils.isBlank(reservedResourceDetails)) { return; } if (needToUpdateAllDetails) { @@ -761,6 +794,9 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana hiddenDetails.put(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM, String.valueOf(orchestratorRequiresPrepareVm)); } + if (StringUtils.isNotBlank(reservedResourceDetails)) { + hiddenDetails.put(ApiConstants.RESERVED_RESOURCE_DETAILS, reservedResourceDetails); + } if (MapUtils.isNotEmpty(hiddenDetails)) { hiddenDetails.forEach((key, value) -> detailsVOList.add( new ExtensionDetailsVO(id, key, value, false))); @@ -775,15 +811,29 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana extensionDetailsDao.removeDetails(id); } } else { - ExtensionDetailsVO detailsVO = extensionDetailsDao.findDetail(id, - ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM); - if (detailsVO == null) { - extensionDetailsDao.persist(new ExtensionDetailsVO(id, - ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM, - String.valueOf(orchestratorRequiresPrepareVm), false)); - } else if (Boolean.parseBoolean(detailsVO.getValue()) != orchestratorRequiresPrepareVm) { - detailsVO.setValue(String.valueOf(orchestratorRequiresPrepareVm)); - extensionDetailsDao.update(detailsVO.getId(), detailsVO); + if (orchestratorRequiresPrepareVm != null) { + ExtensionDetailsVO detailsVO = extensionDetailsDao.findDetail(id, + ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM); + if (detailsVO == null) { + extensionDetailsDao.persist(new ExtensionDetailsVO(id, + ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM, + String.valueOf(orchestratorRequiresPrepareVm), false)); + } else if (Boolean.parseBoolean(detailsVO.getValue()) != orchestratorRequiresPrepareVm) { + detailsVO.setValue(String.valueOf(orchestratorRequiresPrepareVm)); + extensionDetailsDao.update(detailsVO.getId(), detailsVO); + } + } + if (StringUtils.isNotBlank(reservedResourceDetails)) { + ExtensionDetailsVO detailsVO = extensionDetailsDao.findDetail(id, + ApiConstants.RESERVED_RESOURCE_DETAILS); + if (detailsVO == null) { + extensionDetailsDao.persist(new ExtensionDetailsVO(id, + ApiConstants.RESERVED_RESOURCE_DETAILS, + reservedResourceDetails, false)); + } else if (!reservedResourceDetails.equals(detailsVO.getValue())) { + detailsVO.setValue(reservedResourceDetails); + extensionDetailsDao.update(detailsVO.getId(), detailsVO); + } } } } @@ -961,12 +1011,16 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana hiddenDetails = extensionDetails.second(); } else { hiddenDetails = extensionDetailsDao.listDetailsKeyPairs(extension.getId(), - List.of(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM)); + List.of(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM, + ApiConstants.RESERVED_RESOURCE_DETAILS)); } if (hiddenDetails.containsKey(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM)) { response.setOrchestratorRequiresPrepareVm(Boolean.parseBoolean( hiddenDetails.get(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM))); } + if (hiddenDetails.containsKey(ApiConstants.RESERVED_RESOURCE_DETAILS)) { + response.setReservedResourceDetails(hiddenDetails.get(ApiConstants.RESERVED_RESOURCE_DETAILS)); + } response.setObjectName(Extension.class.getSimpleName().toLowerCase()); return response; } @@ -1605,6 +1659,24 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana return extensionDao.findById(extensionId); } + @Override + public List getExtensionReservedResourceDetails(long extensionId) { + ExtensionDetailsVO detailsVO = extensionDetailsDao.findDetail(extensionId, + ApiConstants.RESERVED_RESOURCE_DETAILS); + if (detailsVO == null || !StringUtils.isNotBlank(detailsVO.getValue())) { + return Collections.emptyList(); + } + List reservedDetails = new ArrayList<>(); + String[] parts = detailsVO.getValue().split(","); + for (String part : parts) { + if (StringUtils.isNotBlank(part)) { + reservedDetails.add(part.trim()); + } + } + addInbuiltExtensionReservedResourceDetails(extensionId, reservedDetails); + return reservedDetails; + } + @Override public boolean start() { long pathStateCheckInterval = PathStateCheckInterval.value(); diff --git a/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmdTest.java b/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmdTest.java index 2edb6ea48e3..2f630966056 100644 --- a/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmdTest.java +++ b/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmdTest.java @@ -94,4 +94,18 @@ public class CreateExtensionCmdTest { setField(cmd, "details", details); assertTrue(MapUtils.isNotEmpty(cmd.getDetails())); } + + @Test + public void getReservedResourceDetailsReturnsValueWhenSet() { + setField(cmd, "reservedResourceDetails", "detail1,detail2,detail3"); + String result = cmd.getReservedResourceDetails(); + assertEquals("detail1,detail2,detail3", result); + } + + @Test + public void getReservedResourceDetailsReturnsNullWhenNotSet() { + setField(cmd, "reservedResourceDetails", null); + String result = cmd.getReservedResourceDetails(); + assertNull(result); + } } diff --git a/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmdTest.java b/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmdTest.java index f0a3a6fcf21..5c5c2014a52 100644 --- a/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmdTest.java +++ b/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmdTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.test.util.ReflectionTestUtils.setField; import java.util.EnumSet; import java.util.HashMap; @@ -134,6 +135,20 @@ public class UpdateExtensionCmdTest { assertTrue(cmd.isCleanupDetails()); } + @Test + public void getReservedResourceDetailsReturnsValueWhenSet() { + setField(cmd, "reservedResourceDetails", "detail1,detail2,detail3"); + String result = cmd.getReservedResourceDetails(); + assertEquals("detail1,detail2,detail3", result); + } + + @Test + public void getReservedResourceDetailsReturnsNullWhenNotSet() { + setField(cmd, "reservedResourceDetails", null); + String result = cmd.getReservedResourceDetails(); + assertNull(result); + } + @Test public void executeSetsExtensionResponseWhenManagerSucceeds() { Extension extension = mock(Extension.class); diff --git a/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImplTest.java b/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImplTest.java index 085ae212b28..ff3fce06b00 100644 --- a/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImplTest.java +++ b/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImplTest.java @@ -23,11 +23,13 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; @@ -40,6 +42,7 @@ import static org.mockito.Mockito.when; import java.io.File; import java.security.InvalidParameterException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; @@ -49,8 +52,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import com.cloud.exception.PermissionDeniedException; -import com.cloud.user.AccountService; import org.apache.cloudstack.acl.Role; import org.apache.cloudstack.acl.RoleService; import org.apache.cloudstack.acl.RoleType; @@ -85,9 +86,11 @@ import org.apache.cloudstack.framework.extensions.dao.ExtensionResourceMapDao; import org.apache.cloudstack.framework.extensions.dao.ExtensionResourceMapDetailsDao; import org.apache.cloudstack.framework.extensions.vo.ExtensionCustomActionDetailsVO; import org.apache.cloudstack.framework.extensions.vo.ExtensionCustomActionVO; +import org.apache.cloudstack.framework.extensions.vo.ExtensionDetailsVO; import org.apache.cloudstack.framework.extensions.vo.ExtensionResourceMapVO; import org.apache.cloudstack.framework.extensions.vo.ExtensionVO; import org.apache.cloudstack.utils.identity.ManagementServerNode; +import org.apache.commons.collections.CollectionUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -113,6 +116,7 @@ import com.cloud.dc.dao.ClusterDao; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.PermissionDeniedException; import com.cloud.host.Host; import com.cloud.host.dao.HostDao; import com.cloud.host.dao.HostDetailsDao; @@ -122,6 +126,7 @@ import com.cloud.org.Cluster; import com.cloud.serializer.GsonHelper; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.user.Account; +import com.cloud.user.AccountService; import com.cloud.utils.Pair; import com.cloud.utils.UuidUtils; import com.cloud.utils.db.EntityManager; @@ -664,6 +669,8 @@ public class ExtensionsManagerImplTest { when(cmd.getPath()).thenReturn(null); when(cmd.isOrchestratorRequiresPrepareVm()).thenReturn(null); when(cmd.getState()).thenReturn(null); + String reservedResourceDetails = "abc,xyz"; + when(cmd.getReservedResourceDetails()).thenReturn(reservedResourceDetails); when(extensionDao.findByName("ext1")).thenReturn(null); when(extensionDao.persist(any())).thenAnswer(inv -> { ExtensionVO extensionVO = inv.getArgument(0); @@ -671,11 +678,20 @@ public class ExtensionsManagerImplTest { return extensionVO; }); when(managementServerHostDao.listBy(any())).thenReturn(Collections.emptyList()); - + List detailsList = new ArrayList<>(); + doAnswer(inv -> { + List detailsVO = inv.getArgument(0); + detailsList.addAll(detailsVO); + return null; + }).when(extensionDetailsDao).saveDetails(anyList()); Extension ext = extensionsManager.createExtension(cmd); assertEquals("ext1", ext.getName()); verify(extensionDao).persist(any()); + assertTrue(CollectionUtils.isNotEmpty(detailsList)); + assertTrue(detailsList.stream() + .anyMatch(detail -> ApiConstants.RESERVED_RESOURCE_DETAILS.equals(detail.getName()) + && reservedResourceDetails.equals(detail.getValue()))); } @Test @@ -938,14 +954,32 @@ public class ExtensionsManagerImplTest { public void updateExtensionsDetails_SavesDetails_WhenDetailsProvided() { long extensionId = 10L; Map details = Map.of("foo", "bar", "baz", "qux"); - extensionsManager.updateExtensionsDetails(false, details, null, extensionId); + extensionsManager.updateExtensionsDetails(false, details, null, null, extensionId); verify(extensionDetailsDao).saveDetails(any()); } + @Test + public void updateExtensionsDetails_PersistReservedDetail_WhenProvided() { + long extensionId = 10L; + when(extensionDetailsDao.persist(any())).thenReturn(mock(ExtensionDetailsVO.class)); + extensionsManager.updateExtensionsDetails(false, null, null, "abc,xyz", extensionId); + verify(extensionDetailsDao).persist(any()); + } + + @Test + public void updateExtensionsDetails_UpdateReservedDetail_WhenProvided() { + long extensionId = 10L; + when(extensionDetailsDao.findDetail(anyLong(), eq(ApiConstants.RESERVED_RESOURCE_DETAILS))) + .thenReturn(mock(ExtensionDetailsVO.class)); + when(extensionDetailsDao.update(anyLong(), any())).thenReturn(true); + extensionsManager.updateExtensionsDetails(false, null, null, "abc,xyz", extensionId); + verify(extensionDetailsDao).update(anyLong(), any()); + } + @Test public void updateExtensionsDetails_DoesNothing_WhenDetailsAndCleanupAreNull() { long extensionId = 11L; - extensionsManager.updateExtensionsDetails(null, null, null, extensionId); + extensionsManager.updateExtensionsDetails(null, null, null, null, extensionId); verify(extensionDetailsDao, never()).removeDetails(anyLong()); verify(extensionDetailsDao, never()).saveDetails(any()); } @@ -953,7 +987,7 @@ public class ExtensionsManagerImplTest { @Test public void updateExtensionsDetails_RemovesDetailsOnly_WhenCleanupIsTrue() { long extensionId = 12L; - extensionsManager.updateExtensionsDetails(true, null, null, extensionId); + extensionsManager.updateExtensionsDetails(true, null, null, null, extensionId); verify(extensionDetailsDao).removeDetails(extensionId); verify(extensionDetailsDao, never()).saveDetails(any()); } @@ -961,7 +995,7 @@ public class ExtensionsManagerImplTest { @Test public void updateExtensionsDetails_PersistsOrchestratorFlag_WhenFlagIsNotNull() { long extensionId = 13L; - extensionsManager.updateExtensionsDetails(false, null, true, extensionId); + extensionsManager.updateExtensionsDetails(false, null, true, null, extensionId); verify(extensionDetailsDao).persist(any()); } @@ -970,7 +1004,7 @@ public class ExtensionsManagerImplTest { long extensionId = 14L; Map details = Map.of("foo", "bar"); doThrow(CloudRuntimeException.class).when(extensionDetailsDao).saveDetails(any()); - extensionsManager.updateExtensionsDetails(false, details, null, extensionId); + extensionsManager.updateExtensionsDetails(false, details, null, null, extensionId); } @Test @@ -1161,7 +1195,8 @@ public class ExtensionsManagerImplTest { when(externalProvisioner.getExtensionPath("entry2.sh")).thenReturn("/some/path/entry2.sh"); Map hiddenDetails = Map.of(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM, "false"); - when(extensionDetailsDao.listDetailsKeyPairs(2L, List.of(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM))) + when(extensionDetailsDao.listDetailsKeyPairs(2L, List.of( + ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM, ApiConstants.RESERVED_RESOURCE_DETAILS))) .thenReturn(hiddenDetails); EnumSet viewDetails = EnumSet.noneOf(ApiConstants.ExtensionDetails.class); @@ -2069,4 +2104,118 @@ public class ExtensionsManagerImplTest { } } + @Test + public void getExtensionReservedResourceDetailsReturnsEmptyListWhenDetailsNotFound() { + long extensionId = 1L; + when(extensionDetailsDao.findDetail(extensionId, ApiConstants.RESERVED_RESOURCE_DETAILS)).thenReturn(null); + + List result = extensionsManager.getExtensionReservedResourceDetails(extensionId); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void getExtensionReservedResourceDetailsReturnsEmptyListWhenValueIsBlank() { + long extensionId = 2L; + ExtensionDetailsVO detailsVO = mock(ExtensionDetailsVO.class); + when(detailsVO.getValue()).thenReturn(" "); + when(extensionDetailsDao.findDetail(extensionId, ApiConstants.RESERVED_RESOURCE_DETAILS)).thenReturn(detailsVO); + + List result = extensionsManager.getExtensionReservedResourceDetails(extensionId); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void getExtensionReservedResourceDetailsReturnsListOfTrimmedDetails() { + long extensionId = 3L; + ExtensionDetailsVO detailsVO = mock(ExtensionDetailsVO.class); + when(detailsVO.getValue()).thenReturn(" detail1 , detail2,detail3 "); + when(extensionDetailsDao.findDetail(extensionId, ApiConstants.RESERVED_RESOURCE_DETAILS)).thenReturn(detailsVO); + + List result = extensionsManager.getExtensionReservedResourceDetails(extensionId); + + assertNotNull(result); + assertEquals(3, result.size()); + assertEquals("detail1", result.get(0)); + assertEquals("detail2", result.get(1)); + assertEquals("detail3", result.get(2)); + } + + @Test + public void getExtensionReservedResourceDetailsHandlesEmptyPartsGracefully() { + long extensionId = 4L; + ExtensionDetailsVO detailsVO = mock(ExtensionDetailsVO.class); + when(detailsVO.getValue()).thenReturn("detail1,,detail2, ,detail3"); + when(extensionDetailsDao.findDetail(extensionId, ApiConstants.RESERVED_RESOURCE_DETAILS)).thenReturn(detailsVO); + + List result = extensionsManager.getExtensionReservedResourceDetails(extensionId); + + assertNotNull(result); + assertEquals(3, result.size()); + assertEquals("detail1", result.get(0)); + assertEquals("detail2", result.get(1)); + assertEquals("detail3", result.get(2)); + } + + @Test + public void getExtensionReservedResourceDetailsReturnsEmptyListWhenSplitResultsInNoParts() { + long extensionId = 5L; + ExtensionDetailsVO detailsVO = mock(ExtensionDetailsVO.class); + when(detailsVO.getValue()).thenReturn(","); + when(extensionDetailsDao.findDetail(extensionId, ApiConstants.RESERVED_RESOURCE_DETAILS)).thenReturn(detailsVO); + + List result = extensionsManager.getExtensionReservedResourceDetails(extensionId); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void addInbuiltExtensionReservedResourceDetailsDoesNothingWhenExtensionNotFound() { + when(extensionDao.findById(1L)).thenReturn(null); + List reservedResourceDetails = new ArrayList<>(); + extensionsManager.addInbuiltExtensionReservedResourceDetails(1L, reservedResourceDetails); + assertTrue(reservedResourceDetails.isEmpty()); + } + + @Test + public void addInbuiltExtensionReservedResourceDetailsDoesNothingForUserDefinedExtension() { + ExtensionVO extension = mock(ExtensionVO.class); + when(extension.isUserDefined()).thenReturn(true); + when(extensionDao.findById(2L)).thenReturn(extension); + List reservedResourceDetails = new ArrayList<>(); + reservedResourceDetails.add("existing-detail"); + extensionsManager.addInbuiltExtensionReservedResourceDetails(2L, reservedResourceDetails); + assertEquals(1, reservedResourceDetails.size()); + assertTrue(reservedResourceDetails.contains("existing-detail")); + } + + @Test + public void addInbuiltExtensionReservedResourceDetailsDoesNothingWhenNoMatchFound() { + ExtensionVO extension = mock(ExtensionVO.class); + when(extension.isUserDefined()).thenReturn(false); + when(extension.getName()).thenReturn("no-such-inbuilt-key-expected"); + when(extensionDao.findById(3L)).thenReturn(extension); + List reservedResourceDetails = new ArrayList<>(); + extensionsManager.addInbuiltExtensionReservedResourceDetails(3L, reservedResourceDetails); + assertTrue(reservedResourceDetails.isEmpty()); + } + + @Test + public void addInbuiltExtensionReservedResourceDetailsAddedDetails() { + ExtensionVO extension = mock(ExtensionVO.class); + when(extension.isUserDefined()).thenReturn(false); + Map.Entry> entry = + ExtensionsManagerImpl.INBUILT_RESERVED_RESOURCE_DETAILS.entrySet().iterator().next(); + when(extension.getName()).thenReturn(entry.getKey()); + when(extensionDao.findById(3L)).thenReturn(extension); + List reservedResourceDetails = new ArrayList<>(); + extensionsManager.addInbuiltExtensionReservedResourceDetails(3L, reservedResourceDetails); + assertFalse(reservedResourceDetails.isEmpty()); + assertEquals(reservedResourceDetails.size(), entry.getValue().size()); + assertTrue(reservedResourceDetails.containsAll(entry.getValue())); + } } diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDao.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDao.java index 9f7a4ad6e05..926280bfead 100644 --- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDao.java +++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDao.java @@ -23,12 +23,30 @@ import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import com.cloud.utils.db.GenericDao; +import javax.annotation.Nullable; + public interface AsyncJobDao extends GenericDao { AsyncJobVO findInstancePendingAsyncJob(String instanceType, long instanceId); List findInstancePendingAsyncJobs(String instanceType, Long accountId); + /** + * Finds async job matching the given parameters. + * Non-null parameters are added to search criteria. + * Returns the most recent job by creation date. + *

+ * When searching by resourceId and resourceType, only one active job + * is expected per resource, so returning a single result is sufficient. + * + * @param id job ID + * @param resourceId resource ID (instanceId) + * @param resourceType resource type (instanceType) + * @return matching job or null + */ + @Nullable + AsyncJobVO findJob(Long id, Long resourceId, String resourceType); + AsyncJobVO findPseudoJob(long threadId, long msid); void cleanupPseduoJobs(long msid); diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDaoImpl.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDaoImpl.java index a2f1f36b863..81cc5d4f2a8 100644 --- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDaoImpl.java +++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDaoImpl.java @@ -22,6 +22,8 @@ import java.util.Date; import java.util.List; import org.apache.cloudstack.api.ApiConstants; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import org.apache.cloudstack.jobs.JobInfo; @@ -45,6 +47,7 @@ public class AsyncJobDaoImpl extends GenericDaoBase implements private final SearchBuilder expiringUnfinishedAsyncJobSearch; private final SearchBuilder expiringCompletedAsyncJobSearch; private final SearchBuilder failureMsidAsyncJobSearch; + private final SearchBuilder byIdResourceIdResourceTypeSearch; private final GenericSearchBuilder asyncJobTypeSearch; private final GenericSearchBuilder pendingNonPseudoAsyncJobsSearch; @@ -95,6 +98,12 @@ public class AsyncJobDaoImpl extends GenericDaoBase implements failureMsidAsyncJobSearch.and("job_cmd", failureMsidAsyncJobSearch.entity().getCmd(), Op.IN); failureMsidAsyncJobSearch.done(); + byIdResourceIdResourceTypeSearch = createSearchBuilder(); + byIdResourceIdResourceTypeSearch.and("id", byIdResourceIdResourceTypeSearch.entity().getId(), SearchCriteria.Op.EQ); + byIdResourceIdResourceTypeSearch.and("instanceId", byIdResourceIdResourceTypeSearch.entity().getInstanceId(), SearchCriteria.Op.EQ); + byIdResourceIdResourceTypeSearch.and("instanceType", byIdResourceIdResourceTypeSearch.entity().getInstanceType(), SearchCriteria.Op.EQ); + byIdResourceIdResourceTypeSearch.done(); + asyncJobTypeSearch = createSearchBuilder(Long.class); asyncJobTypeSearch.select(null, SearchCriteria.Func.COUNT, asyncJobTypeSearch.entity().getId()); asyncJobTypeSearch.and("job_info", asyncJobTypeSearch.entity().getCmdInfo(),Op.LIKE); @@ -140,6 +149,30 @@ public class AsyncJobDaoImpl extends GenericDaoBase implements return listBy(sc); } + @Override + public AsyncJobVO findJob(Long id, Long resourceId, String resourceType) { + SearchCriteria sc = byIdResourceIdResourceTypeSearch.create(); + + if (id == null && resourceId == null && StringUtils.isBlank(resourceType)) { + logger.debug("findJob called with all null parameters"); + return null; + } + + if (id != null) { + sc.setParameters("id", id); + } + if (resourceId != null && StringUtils.isNotBlank(resourceType)) { + sc.setParameters("instanceType", resourceType); + sc.setParameters("instanceId", resourceId); + } + Filter filter = new Filter(AsyncJobVO.class, "created", false, 0L, 1L); + List result = searchIncludingRemoved(sc, filter, Boolean.FALSE, false); + if (CollectionUtils.isNotEmpty(result)) { + return result.get(0); + } + return null; + } + @Override public AsyncJobVO findPseudoJob(long threadId, long msid) { SearchCriteria sc = pseudoJobSearch.create(); diff --git a/packaging/el8/cloud.spec b/packaging/el8/cloud.spec index 705959336f1..3dee161bf27 100644 --- a/packaging/el8/cloud.spec +++ b/packaging/el8/cloud.spec @@ -83,6 +83,8 @@ Requires: (iptables-services or iptables) Requires: rng-tools Requires: (qemu-img or qemu-tools) Requires: python3-pip +Requires: python3-six +Requires: python3-protobuf Requires: python3-setuptools Requires: (libgcrypt > 1.8.3 or libgcrypt20) Group: System Environment/Libraries @@ -334,11 +336,11 @@ cp -r ui/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/ rm -f ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/config.json ln -sf /etc/%{name}/ui/config.json ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/config.json -# Package mysql-connector-python -wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/ee/ff/48bde5c0f013094d729fe4b0316ba2a24774b3ff1c52d924a8a4cb04078a/six-1.15.0-py2.py3-none-any.whl -wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/e9/93/4860cebd5ad3ff2664ad3c966490ccb46e3b88458b2095145bca11727ca4/setuptools-47.3.1-py3-none-any.whl -wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/32/27/1141a8232723dcb10a595cc0ce4321dcbbd5215300bf4acfc142343205bf/protobuf-3.19.6-py2.py3-none-any.whl +# Package mysql-connector-python (bundled to avoid dependency on external community repo) +# Version 8.0.31 is the last version supporting Python 3.6 (EL8) wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/08/1f/42d74bae9dd6dcfec67c9ed0f3fa482b1ae5ac5f117ca82ab589ecb3ca19/mysql_connector_python-8.0.31-py2.py3-none-any.whl +# Version 8.3.0 supports Python 3.8 to 3.12 (EL9, EL10) +wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/53/ed/26a4b8cacb8852c6fd97d2d58a7f2591c41989807ea82bd8d9725a4e6937/mysql_connector_python-8.3.0-py2.py3-none-any.whl chmod 440 ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d/%{name}-management chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/mnt @@ -455,8 +457,13 @@ then fi %post management -# Install mysql-connector-python -pip3 install %{_datadir}/%{name}-management/setup/wheel/six-1.15.0-py2.py3-none-any.whl %{_datadir}/%{name}-management/setup/wheel/setuptools-47.3.1-py3-none-any.whl %{_datadir}/%{name}-management/setup/wheel/protobuf-3.19.6-py2.py3-none-any.whl %{_datadir}/%{name}-management/setup/wheel/mysql_connector_python-8.0.31-py2.py3-none-any.whl +# Install mysql-connector-python wheel +# Detect Python version to install compatible wheel +if python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3, 7) else 1)'; then + pip3 install %{_datadir}/%{name}-management/setup/wheel/mysql_connector_python-8.3.0-py2.py3-none-any.whl +else + pip3 install %{_datadir}/%{name}-management/setup/wheel/mysql_connector_python-8.0.31-py2.py3-none-any.whl +fi /usr/bin/systemctl enable cloudstack-management > /dev/null 2>&1 || true /usr/bin/systemctl enable --now rngd > /dev/null 2>&1 || true diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalTemplateAdapter.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalTemplateAdapter.java index 940897de3c9..c6c38a39809 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalTemplateAdapter.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalTemplateAdapter.java @@ -106,7 +106,6 @@ public class BareMetalTemplateAdapter extends TemplateAdapterBase implements Tem } } - _resourceLimitMgr.incrementResourceCount(profile.getAccountId(), ResourceType.template); return template; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java index 896426addca..e9a7ac8951c 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java @@ -35,7 +35,6 @@ import com.cloud.agent.properties.AgentPropertiesFileHandler; public class KVMHABase { protected Logger logger = LogManager.getLogger(getClass()); private long _timeout = 60000; /* 1 minutes */ - protected static String s_heartBeatPath; protected long _heartBeatUpdateTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HEARTBEAT_UPDATE_TIMEOUT); protected long _heartBeatUpdateFreq = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_FREQUENCY); protected long _heartBeatUpdateMaxTries = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_MAX_TRIES); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java index cf407bfc08a..aa868ff1d3f 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java @@ -18,7 +18,7 @@ package com.cloud.hypervisor.kvm.resource; import com.cloud.agent.properties.AgentProperties; import com.cloud.agent.properties.AgentPropertiesFileHandler; -import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.ha.HighAvailabilityManager; import com.cloud.utils.script.Script; import org.libvirt.Connect; import org.libvirt.LibvirtException; @@ -39,20 +39,15 @@ public class KVMHAMonitor extends KVMHABase implements Runnable { private final String hostPrivateIp; - public KVMHAMonitor(HAStoragePool pool, String host, String scriptPath) { + public KVMHAMonitor(HAStoragePool pool, String host) { if (pool != null) { storagePool.put(pool.getPoolUUID(), pool); } hostPrivateIp = host; - configureHeartBeatPath(scriptPath); rebootHostAndAlertManagementOnHeartbeatTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.REBOOT_HOST_AND_ALERT_MANAGEMENT_ON_HEARTBEAT_TIMEOUT); } - private static synchronized void configureHeartBeatPath(String scriptPath) { - KVMHABase.s_heartBeatPath = scriptPath; - } - public void addStoragePool(HAStoragePool pool) { synchronized (storagePool) { storagePool.put(pool.getPoolUUID(), pool); @@ -86,8 +81,8 @@ public class KVMHAMonitor extends KVMHABase implements Runnable { Set removedPools = new HashSet<>(); for (String uuid : storagePool.keySet()) { HAStoragePool primaryStoragePool = storagePool.get(uuid); - if (primaryStoragePool.getPool().getType() == StoragePoolType.NetworkFilesystem) { - checkForNotExistingPools(removedPools, uuid); + if (HighAvailabilityManager.LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT.contains(primaryStoragePool.getPool().getType())) { + checkForNotExistingLibvirtStoragePools(removedPools, uuid); if (removedPools.contains(uuid)) { continue; } @@ -127,7 +122,7 @@ public class KVMHAMonitor extends KVMHABase implements Runnable { return result; } - private void checkForNotExistingPools(Set removedPools, String uuid) { + private void checkForNotExistingLibvirtStoragePools(Set removedPools, String uuid) { try { Connect conn = LibvirtConnection.getConnection(); StoragePool storage = conn.storagePoolLookupByUUIDString(uuid); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 46cf1da461e..67ea7a1d0dc 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -1065,11 +1065,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv throw new ConfigurationException("Unable to find patch.sh"); } - heartBeatPath = Script.findScript(kvmScriptsDir, "kvmheartbeat.sh"); - if (heartBeatPath == null) { - throw new ConfigurationException("Unable to find kvmheartbeat.sh"); - } - createVmPath = Script.findScript(storageScriptsDir, "createvm.sh"); if (createVmPath == null) { throw new ConfigurationException("Unable to find the createvm.sh"); @@ -1332,7 +1327,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv final String[] info = NetUtils.getNetworkParams(privateNic); - kvmhaMonitor = new KVMHAMonitor(null, info[0], heartBeatPath); + kvmhaMonitor = new KVMHAMonitor(null, info[0]); final Thread ha = new Thread(kvmhaMonitor); ha.start(); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java index a708d441be5..d3f537dc917 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java @@ -48,7 +48,7 @@ public final class LibvirtCheckVMActivityOnStoragePoolCommandWrapper extends Com KVMStoragePool primaryPool = storagePoolMgr.getStoragePool(pool.getType(), pool.getUuid()); - if (primaryPool.isPoolSupportHA()){ + if (primaryPool.isPoolSupportHA()) { final HAStoragePool nfspool = monitor.getStoragePool(pool.getUuid()); final KVMHAVMActivityChecker ha = new KVMHAVMActivityChecker(nfspool, command.getHost(), command.getVolumeList(), libvirtComputingResource.getVmActivityCheckPath(), command.getSuspectTimeInSeconds()); final Future future = executors.submit(ha); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVirtualMachineCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVirtualMachineCommandWrapper.java index 99005755cbb..aa6a37b6561 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVirtualMachineCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVirtualMachineCommandWrapper.java @@ -45,11 +45,10 @@ public final class LibvirtCheckVirtualMachineCommandWrapper extends CommandWrapp Integer vncPort = null; if (state == PowerState.PowerOn) { vncPort = libvirtComputingResource.getVncPort(conn, command.getVmName()); - } - - Domain vm = conn.domainLookupByName(command.getVmName()); - if (state == PowerState.PowerOn && DomainInfo.DomainState.VIR_DOMAIN_PAUSED.equals(vm.getInfo().state)) { - return new CheckVirtualMachineAnswer(command, PowerState.PowerUnknown, vncPort); + Domain vm = conn.domainLookupByName(command.getVmName()); + if (DomainInfo.DomainState.VIR_DOMAIN_PAUSED.equals(vm.getInfo().state)) { + return new CheckVirtualMachineAnswer(command, PowerState.PowerUnknown, vncPort); + } } return new CheckVirtualMachineAnswer(command, state, vncPort); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java index 6665cf625e2..35cc864268c 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java @@ -289,6 +289,7 @@ public class KVMStoragePoolManager { if (pool instanceof LibvirtStoragePool) { addPoolDetails(uuid, (LibvirtStoragePool) pool); + ((LibvirtStoragePool) pool).setType(type); } return pool; @@ -390,6 +391,9 @@ public class KVMStoragePoolManager { private synchronized KVMStoragePool createStoragePool(String name, String host, int port, String path, String userInfo, StoragePoolType type, Map details, boolean primaryStorage) { StorageAdaptor adaptor = getStorageAdaptor(type); KVMStoragePool pool = adaptor.createStoragePool(name, host, port, path, userInfo, type, details, primaryStorage); + if (pool instanceof LibvirtStoragePool) { + ((LibvirtStoragePool) pool).setType(type); + } // LibvirtStorageAdaptor-specific statement if (pool.isPoolSupportHA() && primaryStorage) { diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java index 030d9747d6c..6e03b84d20c 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java @@ -185,6 +185,8 @@ public class KVMStorageProcessor implements StorageProcessor { private int incrementalSnapshotTimeout; + private int incrementalSnapshotRetryRebaseWait; + private static final String CHECKPOINT_XML_TEMP_DIR = "/tmp/cloudstack/checkpointXMLs"; private static final String BACKUP_XML_TEMP_DIR = "/tmp/cloudstack/backupXMLs"; @@ -252,6 +254,7 @@ public class KVMStorageProcessor implements StorageProcessor { _cmdsTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.CMDS_TIMEOUT) * 1000; incrementalSnapshotTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.INCREMENTAL_SNAPSHOT_TIMEOUT) * 1000; + incrementalSnapshotRetryRebaseWait = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.INCREMENTAL_SNAPSHOT_RETRY_REBASE_WAIT) * 1000; return true; } @@ -2093,8 +2096,25 @@ public class KVMStorageProcessor implements StorageProcessor { QemuImg qemuImg = new QemuImg(wait); qemuImg.rebase(snapshotFile, parentSnapshotFile, PhysicalDiskFormat.QCOW2.toString(), false); } catch (LibvirtException | QemuImgException e) { - logger.error("Exception while rebasing incremental snapshot [{}] due to: [{}].", snapshotName, e.getMessage(), e); - throw new CloudRuntimeException(e); + if (!StringUtils.contains(e.getMessage(), "Is another process using the image")) { + logger.error("Exception while rebasing incremental snapshot [{}] due to: [{}].", snapshotName, e.getMessage(), e); + throw new CloudRuntimeException(e); + } + retryRebase(snapshotName, wait, e, snapshotFile, parentSnapshotFile); + } + } + + private void retryRebase(String snapshotName, int wait, Exception e, QemuImgFile snapshotFile, QemuImgFile parentSnapshotFile) { + logger.warn("Libvirt still has not released the lock, will wait [{}] milliseconds and try again later.", incrementalSnapshotRetryRebaseWait); + try { + Thread.sleep(incrementalSnapshotRetryRebaseWait); + QemuImg qemuImg = new QemuImg(wait); + qemuImg.rebase(snapshotFile, parentSnapshotFile, PhysicalDiskFormat.QCOW2.toString(), false); + } catch (LibvirtException | QemuImgException | InterruptedException ex) { + logger.error("Unable to rebase snapshot [{}].", snapshotName, ex); + CloudRuntimeException cre = new CloudRuntimeException(ex); + cre.addSuppressed(e); + throw cre; } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java index ab39f7bc6ff..45c22d3ac75 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java @@ -31,6 +31,7 @@ import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat; import com.cloud.agent.api.to.HostTO; import com.cloud.agent.properties.AgentProperties; import com.cloud.agent.properties.AgentPropertiesFileHandler; +import com.cloud.ha.HighAvailabilityManager; import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool; import com.cloud.storage.Storage; import com.cloud.storage.Storage.StoragePoolType; @@ -320,13 +321,24 @@ public class LibvirtStoragePool implements KVMStoragePool { @Override public boolean isPoolSupportHA() { - return type == StoragePoolType.NetworkFilesystem; + return HighAvailabilityManager.LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT.contains(type); } public String getHearthBeatPath() { - if (type == StoragePoolType.NetworkFilesystem) { + if (StoragePoolType.NetworkFilesystem.equals(type)) { String kvmScriptsDir = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_SCRIPTS_DIR); - return Script.findScript(kvmScriptsDir, "kvmheartbeat.sh"); + String scriptPath = Script.findScript(kvmScriptsDir, "kvmheartbeat.sh"); + if (scriptPath == null) { + throw new CloudRuntimeException("Unable to find heartbeat script 'kvmheartbeat.sh' in directory: " + kvmScriptsDir); + } + return scriptPath; + } else if (StoragePoolType.SharedMountPoint.equals(type)) { + String kvmScriptsDir = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_SCRIPTS_DIR); + String scriptPath = Script.findScript(kvmScriptsDir, "kvmsmpheartbeat.sh"); + if (scriptPath == null) { + throw new CloudRuntimeException("Unable to find heartbeat script 'kvmsmpheartbeat.sh' in directory: " + kvmScriptsDir); + } + return scriptPath; } return null; } @@ -410,4 +422,8 @@ public class LibvirtStoragePool implements KVMStoragePool { return true; } } + + public void setType(StoragePoolType type) { + this.type = type; + } } diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVirtualMachineCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVirtualMachineCommandWrapperTest.java new file mode 100644 index 00000000000..a06a9c50bc1 --- /dev/null +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVirtualMachineCommandWrapperTest.java @@ -0,0 +1,191 @@ +// 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.hypervisor.kvm.resource.wrapper; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.libvirt.Connect; +import org.libvirt.Domain; +import org.libvirt.DomainInfo; +import org.libvirt.DomainInfo.DomainState; +import org.libvirt.LibvirtException; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.api.CheckVirtualMachineAnswer; +import com.cloud.agent.api.CheckVirtualMachineCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.vm.VirtualMachine.PowerState; + +@RunWith(MockitoJUnitRunner.class) +public class LibvirtCheckVirtualMachineCommandWrapperTest { + + private static final String VM_NAME = "i-2-3-VM"; + + @Mock + private LibvirtComputingResource libvirtComputingResource; + @Mock + private LibvirtUtilitiesHelper libvirtUtilitiesHelper; + @Mock + private Connect conn; + @Mock + private Domain domain; + + private LibvirtCheckVirtualMachineCommandWrapper wrapper; + private CheckVirtualMachineCommand command; + + @Before + public void setUp() throws LibvirtException { + wrapper = new LibvirtCheckVirtualMachineCommandWrapper(); + command = new CheckVirtualMachineCommand(VM_NAME); + + when(libvirtComputingResource.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper); + when(libvirtUtilitiesHelper.getConnectionByVmName(VM_NAME)).thenReturn(conn); + } + + @Test + public void testExecuteVmPoweredOnReturnsStateAndVncPort() throws LibvirtException { + DomainInfo domainInfo = new DomainInfo(); + domainInfo.state = DomainState.VIR_DOMAIN_RUNNING; + + when(libvirtComputingResource.getVmState(conn, VM_NAME)).thenReturn(PowerState.PowerOn); + when(libvirtComputingResource.getVncPort(conn, VM_NAME)).thenReturn(5900); + when(conn.domainLookupByName(VM_NAME)).thenReturn(domain); + when(domain.getInfo()).thenReturn(domainInfo); + + CheckVirtualMachineAnswer answer = (CheckVirtualMachineAnswer) wrapper.execute(command, libvirtComputingResource); + + assertTrue(answer.getResult()); + assertEquals(PowerState.PowerOn, answer.getState()); + assertEquals(Integer.valueOf(5900), answer.getVncPort()); + } + + @Test + public void testExecuteVmPausedReturnsPowerUnknown() throws LibvirtException { + DomainInfo domainInfo = new DomainInfo(); + domainInfo.state = DomainState.VIR_DOMAIN_PAUSED; + + when(libvirtComputingResource.getVmState(conn, VM_NAME)).thenReturn(PowerState.PowerOn); + when(libvirtComputingResource.getVncPort(conn, VM_NAME)).thenReturn(5901); + when(conn.domainLookupByName(VM_NAME)).thenReturn(domain); + when(domain.getInfo()).thenReturn(domainInfo); + + CheckVirtualMachineAnswer answer = (CheckVirtualMachineAnswer) wrapper.execute(command, libvirtComputingResource); + + assertTrue(answer.getResult()); + assertEquals(PowerState.PowerUnknown, answer.getState()); + assertEquals(Integer.valueOf(5901), answer.getVncPort()); + } + + @Test + public void testExecuteVmPoweredOffReturnsStateWithNullVncPort() throws LibvirtException { + when(libvirtComputingResource.getVmState(conn, VM_NAME)).thenReturn(PowerState.PowerOff); + + CheckVirtualMachineAnswer answer = (CheckVirtualMachineAnswer) wrapper.execute(command, libvirtComputingResource); + + assertTrue(answer.getResult()); + assertEquals(PowerState.PowerOff, answer.getState()); + assertNull(answer.getVncPort()); + } + + @Test + public void testExecuteVmStateUnknownReturnsStateWithNullVncPort() throws LibvirtException { + when(libvirtComputingResource.getVmState(conn, VM_NAME)).thenReturn(PowerState.PowerUnknown); + + CheckVirtualMachineAnswer answer = (CheckVirtualMachineAnswer) wrapper.execute(command, libvirtComputingResource); + + assertTrue(answer.getResult()); + assertEquals(PowerState.PowerUnknown, answer.getState()); + assertNull(answer.getVncPort()); + } + + @Test + public void testExecuteVmPoweredOnWithNullVncPort() throws LibvirtException { + DomainInfo domainInfo = new DomainInfo(); + domainInfo.state = DomainState.VIR_DOMAIN_RUNNING; + + when(libvirtComputingResource.getVmState(conn, VM_NAME)).thenReturn(PowerState.PowerOn); + when(libvirtComputingResource.getVncPort(conn, VM_NAME)).thenReturn(null); + when(conn.domainLookupByName(VM_NAME)).thenReturn(domain); + when(domain.getInfo()).thenReturn(domainInfo); + + CheckVirtualMachineAnswer answer = (CheckVirtualMachineAnswer) wrapper.execute(command, libvirtComputingResource); + + assertTrue(answer.getResult()); + assertEquals(PowerState.PowerOn, answer.getState()); + assertNull(answer.getVncPort()); + } + + @Test + public void testExecuteLibvirtExceptionOnGetConnectionReturnsFailure() throws LibvirtException { + LibvirtException libvirtException = mock(LibvirtException.class); + when(libvirtException.getMessage()).thenReturn("Connection refused"); + when(libvirtUtilitiesHelper.getConnectionByVmName(VM_NAME)).thenThrow(libvirtException); + + CheckVirtualMachineAnswer answer = (CheckVirtualMachineAnswer) wrapper.execute(command, libvirtComputingResource); + + assertFalse(answer.getResult()); + assertEquals("Connection refused", answer.getDetails()); + } + + @Test + public void testExecuteLibvirtExceptionOnGetVncPortReturnsFailure() throws LibvirtException { + LibvirtException libvirtException = mock(LibvirtException.class); + when(libvirtException.getMessage()).thenReturn("VNC port error"); + when(libvirtComputingResource.getVmState(conn, VM_NAME)).thenReturn(PowerState.PowerOn); + when(libvirtComputingResource.getVncPort(conn, VM_NAME)).thenThrow(libvirtException); + + CheckVirtualMachineAnswer answer = (CheckVirtualMachineAnswer) wrapper.execute(command, libvirtComputingResource); + + assertFalse(answer.getResult()); + assertEquals("VNC port error", answer.getDetails()); + } + + @Test + public void testExecuteLibvirtExceptionOnDomainLookupReturnsFailure() throws LibvirtException { + LibvirtException libvirtException = mock(LibvirtException.class); + when(libvirtException.getMessage()).thenReturn("Domain not found"); + when(libvirtComputingResource.getVmState(conn, VM_NAME)).thenReturn(PowerState.PowerOn); + when(libvirtComputingResource.getVncPort(conn, VM_NAME)).thenReturn(5900); + when(conn.domainLookupByName(VM_NAME)).thenThrow(libvirtException); + + CheckVirtualMachineAnswer answer = (CheckVirtualMachineAnswer) wrapper.execute(command, libvirtComputingResource); + + assertFalse(answer.getResult()); + assertEquals("Domain not found", answer.getDetails()); + } + + @Test + public void testExecuteCallsGetLibvirtUtilitiesHelper() throws LibvirtException { + when(libvirtComputingResource.getVmState(conn, VM_NAME)).thenReturn(PowerState.PowerOff); + + wrapper.execute(command, libvirtComputingResource); + + verify(libvirtComputingResource).getLibvirtUtilitiesHelper(); + verify(libvirtUtilitiesHelper).getConnectionByVmName(VM_NAME); + } +} diff --git a/plugins/storage/object/minio/src/main/java/org/apache/cloudstack/storage/datastore/driver/MinIOObjectStoreDriverImpl.java b/plugins/storage/object/minio/src/main/java/org/apache/cloudstack/storage/datastore/driver/MinIOObjectStoreDriverImpl.java index 9dc4b30414e..28e3b85e1a5 100644 --- a/plugins/storage/object/minio/src/main/java/org/apache/cloudstack/storage/datastore/driver/MinIOObjectStoreDriverImpl.java +++ b/plugins/storage/object/minio/src/main/java/org/apache/cloudstack/storage/datastore/driver/MinIOObjectStoreDriverImpl.java @@ -24,6 +24,8 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @@ -98,6 +100,51 @@ public class MinIOObjectStoreDriverImpl extends BaseObjectStoreDriverImpl { return String.format("%s-%s", ACS_PREFIX, account.getUuid()); } + private void updateCannedPolicy(long storeId, Account account, String excludeBucket) { + List buckets = _bucketDao.listByObjectStoreIdAndAccountId(storeId, account.getId()); + + String resources = buckets.stream() + .map(BucketVO::getName) + .filter(name -> !Objects.equals(name, excludeBucket)) + .map(name -> "\"arn:aws:s3:::" + name + "/*\"") + .collect(Collectors.joining(",\n")); + String policy; + if (resources.isEmpty()) { + // Resource cannot be empty in a canned Policy so deny access to all resources if the user has no buckets + policy = " {\n" + + " \"Statement\": [\n" + + " {\n" + + " \"Action\": \"s3:*\",\n" + + " \"Effect\": \"Deny\",\n" + + " \"Resource\": [\"arn:aws:s3:::*\", \"arn:aws:s3:::*/*\"]\n" + + " }\n" + + " ],\n" + + " \"Version\": \"2012-10-17\"\n" + + " }"; + } else { + policy = " {\n" + + " \"Statement\": [\n" + + " {\n" + + " \"Action\": \"s3:*\",\n" + + " \"Effect\": \"Allow\",\n" + + " \"Resource\": [" + resources + "]\n" + + " }\n" + + " ],\n" + + " \"Version\": \"2012-10-17\"\n" + + " }"; + } + + MinioAdminClient minioAdminClient = getMinIOAdminClient(storeId); + String policyName = getUserOrAccessKeyForAccount(account) + "-policy"; + String userName = getUserOrAccessKeyForAccount(account); + try { + minioAdminClient.addCannedPolicy(policyName, policy); + minioAdminClient.setPolicy(userName, false, policyName); + } catch (NoSuchAlgorithmException | IOException | InvalidKeyException e) { + throw new CloudRuntimeException(e); + } + } + @Override public Bucket createBucket(Bucket bucket, boolean objectLock) { //ToDo Client pool mgmt @@ -125,33 +172,8 @@ public class MinIOObjectStoreDriverImpl extends BaseObjectStoreDriverImpl { throw new CloudRuntimeException(e); } - List buckets = _bucketDao.listByObjectStoreIdAndAccountId(storeId, accountId); - StringBuilder resources_builder = new StringBuilder(); - for(BucketVO exitingBucket : buckets) { - resources_builder.append("\"arn:aws:s3:::"+exitingBucket.getName()+"/*\",\n"); - } - resources_builder.append("\"arn:aws:s3:::"+bucketName+"/*\"\n"); + updateCannedPolicy(storeId, account,null); - String policy = " {\n" + - " \"Statement\": [\n" + - " {\n" + - " \"Action\": \"s3:*\",\n" + - " \"Effect\": \"Allow\",\n" + - " \"Principal\": \"*\",\n" + - " \"Resource\": ["+resources_builder+"]" + - " }\n" + - " ],\n" + - " \"Version\": \"2012-10-17\"\n" + - " }"; - MinioAdminClient minioAdminClient = getMinIOAdminClient(storeId); - String policyName = getUserOrAccessKeyForAccount(account) + "-policy"; - String userName = getUserOrAccessKeyForAccount(account); - try { - minioAdminClient.addCannedPolicy(policyName, policy); - minioAdminClient.setPolicy(userName, false, policyName); - } catch (Exception e) { - throw new CloudRuntimeException(e); - } String accessKey = _accountDetailsDao.findDetail(accountId, MINIO_ACCESS_KEY).getValue(); String secretKey = _accountDetailsDao.findDetail(accountId, MINIO_SECRET_KEY).getValue(); ObjectStoreVO store = _storeDao.findById(storeId); @@ -183,6 +205,8 @@ public class MinIOObjectStoreDriverImpl extends BaseObjectStoreDriverImpl { @Override public boolean deleteBucket(BucketTO bucket, long storeId) { String bucketName = bucket.getName(); + long accountId = bucket.getAccountId(); + Account account = _accountDao.findById(accountId); MinioClient minioClient = getMinIOClient(storeId); try { if(!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) { @@ -197,6 +221,9 @@ public class MinIOObjectStoreDriverImpl extends BaseObjectStoreDriverImpl { } catch (Exception e) { throw new CloudRuntimeException(e); } + + updateCannedPolicy(storeId, account, bucketName); + return true; } diff --git a/plugins/storage/object/minio/src/test/java/org/apache/cloudstack/storage/datastore/driver/MinIOObjectStoreDriverImplTest.java b/plugins/storage/object/minio/src/test/java/org/apache/cloudstack/storage/datastore/driver/MinIOObjectStoreDriverImplTest.java index 1a8b3d9663a..d3298a235ca 100644 --- a/plugins/storage/object/minio/src/test/java/org/apache/cloudstack/storage/datastore/driver/MinIOObjectStoreDriverImplTest.java +++ b/plugins/storage/object/minio/src/test/java/org/apache/cloudstack/storage/datastore/driver/MinIOObjectStoreDriverImplTest.java @@ -129,10 +129,15 @@ public class MinIOObjectStoreDriverImplTest { @Test public void testDeleteBucket() throws Exception { String bucketName = "test-bucket"; - BucketTO bucket = new BucketTO(bucketName); + BucketVO bucketVO = new BucketVO(1L, 1L, 1L, bucketName, 1, false, false, false, null); + BucketTO bucket = new BucketTO(bucketVO); + when(accountDao.findById(1L)).thenReturn(account); + when(account.getUuid()).thenReturn(UUID.randomUUID().toString()); + when(bucketDao.listByObjectStoreIdAndAccountId(anyLong(), anyLong())).thenReturn(new ArrayList()); doReturn(minioClient).when(minioObjectStoreDriverImpl).getMinIOClient(anyLong()); when(minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())).thenReturn(true); doNothing().when(minioClient).removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); + doReturn(minioAdminClient).when(minioObjectStoreDriverImpl).getMinIOAdminClient(anyLong()); boolean success = minioObjectStoreDriverImpl.deleteBucket(bucket, 1L); assertTrue(success); verify(minioClient, times(1)).bucketExists(any()); diff --git a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java index 5faa377ce3d..6a7f1d58043 100644 --- a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java @@ -27,6 +27,7 @@ import java.util.UUID; import javax.inject.Inject; import com.cloud.agent.api.to.DiskTO; +import com.cloud.ha.HighAvailabilityManager; import com.cloud.storage.VolumeVO; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; @@ -587,7 +588,7 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri @Override public boolean isStorageSupportHA(StoragePoolType type) { - return StoragePoolType.NetworkFilesystem == type; + return type != null && HighAvailabilityManager.LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT.contains(type); } @Override diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraAdapter.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraAdapter.java index b574ccdb164..3f92f8e4014 100644 --- a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraAdapter.java +++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraAdapter.java @@ -384,6 +384,7 @@ public class PrimeraAdapter implements ProviderAdapter { // Online copy configuration: immediate clone with deduplication and compression parms.setOnline(true); parms.setDestCPG(cpg); + parms.setSnapCPG(snapCpg); parms.setTpvv(false); parms.setReduce(true); logger.debug("PrimeraAdapter: Configuring online copy - destination CPG: '{}', deduplication enabled, thin provisioning disabled", cpg); diff --git a/scripts/vm/hypervisor/kvm/kvmsmpheartbeat.sh b/scripts/vm/hypervisor/kvm/kvmsmpheartbeat.sh new file mode 100755 index 00000000000..b102a1a866b --- /dev/null +++ b/scripts/vm/hypervisor/kvm/kvmsmpheartbeat.sh @@ -0,0 +1,218 @@ +#!/bin/bash +# 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. + +help() { + printf "Usage: $0 + -i identifier (required for CLI compatibility; value ignored by local-only heartbeat) + -p path (required for CLI compatibility; value ignored by local-only heartbeat) + -m mount point (local path where heartbeat will be written) + -h host (host IP/name to include in heartbeat filename) + -r write/read hb log (read-check mode) + -c cleanup (trigger emergency reboot) + -t interval between read hb log\n" + exit 1 +} + +#set -x +NfsSvrIP= +NfsSvrPath= +MountPoint= +HostIP= +interval= +rflag=0 +cflag=0 + +while getopts 'i:p:m:h:t:rc' OPTION +do + case $OPTION in + i) + NfsSvrIP="$OPTARG" + ;; # retained for CLI compatibility but unused for this script + p) + NfsSvrPath="$OPTARG" + ;; # retained for CLI compatibility but unused for this script + m) + MountPoint="$OPTARG" + ;; + h) + HostIP="$OPTARG" + ;; + r) + rflag=1 + ;; + t) + interval="$OPTARG" + ;; + c) + cflag=1 + ;; + *) + help + ;; + esac +done + +# For heartbeat we require a mountpoint +if [ -z "$MountPoint" ] +then + echo "Mount point (-m) is required" + help +fi + +# Validate mount point exists, is (if possible) a mounted filesystem, and is writable +if [ ! -d "$MountPoint" ]; then + echo "Mount point directory does not exist: $MountPoint" >&2 + exit 1 +fi + +# If the 'mountpoint' utility is available, ensure this is an actual mount +if command -v mountpoint >/dev/null 2>&1; then + if ! mountpoint -q "$MountPoint"; then + echo "Mount point is not a mounted filesystem: $MountPoint" >&2 + exit 1 + fi +fi + +# Ensure the mount point is writable +if [ ! -w "$MountPoint" ]; then + echo "Mount point is not writable: $MountPoint" >&2 + exit 1 +fi +#delete VMs on this mountpoint (best-effort) +deleteVMs() { + local mountPoint=$1 + # ensure it ends with a single trailing slash + mountPoint="${mountPoint%/}/" + + vmPids=$(ps aux | grep qemu | grep "$mountPoint" | awk '{print $2}' 2> /dev/null) + + if [ -z "$vmPids" ] + then + return + fi + + for pid in $vmPids + do + kill -9 $pid &> /dev/null + done +} + +#checking is there the mount point present under $MountPoint? +if grep -q "^[^ ]\+ $MountPoint " /proc/mounts +then + # mount exists; nothing to do here; keep for compatibility with original flow + : +else + # mount point not present + # if not in read-check mode, consider deleting VMs similar to original behavior + if [ "$rflag" == "0" ] + then + deleteVMs $MountPoint + fi +fi + +hbFolder="$MountPoint/KVMHA" +hbFile="$hbFolder/hb-$HostIP" + +write_hbLog() { +#write the heart beat log + stat "$hbFile" &> /dev/null + if [ $? -gt 0 ] + then + # create a new one + mkdir -p "$hbFolder" &> /dev/null + # touch will be done by atomic write below; ensure folder is writable + if [ ! -w "$hbFolder" ]; then + printf "Folder not writable: $hbFolder" >&2 + return 2 + fi + fi + + timestamp=$(date +%s) + # Write atomically to avoid partial writes (write to tmp then mv) + tmpfile="${hbFile}.$$" + printf "%s\n" "$timestamp" > "$tmpfile" 2>/dev/null + if [ $? -ne 0 ]; then + printf "Failed to write heartbeat to $tmpfile" >&2 + return 2 + fi + mv -f "$tmpfile" "$hbFile" 2>/dev/null + return $? +} + +check_hbLog() { + hb_diff=0 + if [ ! -f "$hbFile" ]; then + # signal large difference if file missing + hb_diff=999999 + return 1 + fi + now=$(date +%s) + hb=$(cat "$hbFile" 2>/dev/null) + if [ -z "$hb" ]; then + hb_diff=999998 + return 1 + fi + diff=`expr $now - $hb 2>/dev/null` + if [ $? -ne 0 ] + then + hb_diff=999997 + return 1 + fi + if [ -z "$interval" ]; then + # if no interval provided, consider 0 as success + if [ $diff -gt 0 ]; then + hb_diff=$diff + return 1 + else + hb_diff=0 + return 0 + fi + fi + if [ $diff -gt $interval ] + then + hb_diff=$diff + return 1 + fi + hb_diff=0 + return 0 +} + +if [ "$rflag" == "1" ] +then + check_hbLog + status=$? + diff="${hb_diff:-0}" + if [ $status -eq 0 ] + then + echo "=====> ALIVE <=====" + else + echo "=====> Considering host as DEAD because last write on [$hbFile] was [$diff] seconds ago, but the max interval is [$interval] <======" + fi + exit 0 +elif [ "$cflag" == "1" ] +then + /usr/bin/logger -t heartbeat "kvmsmpheartbeat.sh will reboot system because it was unable to write the heartbeat to the storage." + sync & + sleep 5 + echo b > /proc/sysrq-trigger + exit $? +else + write_hbLog + exit $? +fi diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index 702b3afddf1..1d00e9ec16b 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -45,7 +45,6 @@ import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.AsyncJobResponse; import org.apache.cloudstack.api.response.BackupOfferingResponse; -import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.DomainRouterResponse; @@ -76,7 +75,6 @@ import org.apache.cloudstack.api.response.VpcOfferingResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.BackupRepository; -import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupRepositoryDao; @@ -2302,6 +2300,10 @@ public class ApiDBUtils { return s_accountService.isAdmin(account.getId()); } + public static Account getSystemAccount() { + return s_accountService.getSystemAccount(); + } + public static List listResourceTagViewByResourceUUID(String resourceUUID, ResourceObjectType resourceType) { return s_tagJoinDao.listBy(resourceUUID, resourceType); } @@ -2310,10 +2312,6 @@ public class ApiDBUtils { return s_resourceIconDao.findByResourceUuid(resourceUUID, resourceType); } - public static BackupScheduleResponse newBackupScheduleResponse(BackupSchedule schedule) { - return s_backupScheduleDao.newBackupScheduleResponse(schedule); - } - public static BackupOfferingResponse newBackupOfferingResponse(BackupOffering offering) { BackupRepository repository = s_backupRepositoryDao.findByUuid(offering.getExternalId()); Boolean crossZoneInstanceCreationEnabled = repository != null ? Boolean.TRUE.equals(repository.crossZoneInstanceCreationEnabled()) : false; diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index bd09d1de48b..11ac3d639e9 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -32,6 +32,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.TimeZone; import java.util.function.Consumer; @@ -39,10 +40,6 @@ import java.util.stream.Collectors; import javax.inject.Inject; -import com.cloud.domain.dao.DomainDao; -import com.cloud.user.AccountVO; -import com.cloud.user.ApiKeyPairState; -import com.cloud.user.dao.AccountDao; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.RoleVO; @@ -53,6 +50,7 @@ import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants.DomainDetails; import org.apache.cloudstack.api.ApiConstants.HostDetails; @@ -217,6 +215,7 @@ 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.framework.jobs.dao.AsyncJobDao; import org.apache.cloudstack.gui.theme.GuiThemeJoin; import org.apache.cloudstack.management.ManagementServerHost; import org.apache.cloudstack.network.BgpPeerVO; @@ -264,6 +263,7 @@ import com.cloud.api.query.vo.NetworkOfferingJoinVO; import com.cloud.api.query.vo.ProjectAccountJoinVO; import com.cloud.api.query.vo.ProjectInvitationJoinVO; import com.cloud.api.query.vo.ProjectJoinVO; +import com.cloud.api.query.ResourceIdSupport; import com.cloud.api.query.vo.ResourceTagJoinVO; import com.cloud.api.query.vo.SecurityGroupJoinVO; import com.cloud.api.query.vo.ServiceOfferingJoinVO; @@ -304,6 +304,7 @@ import com.cloud.dc.dao.ASNumberRangeDao; import com.cloud.dc.dao.VlanDetailsDao; import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; import com.cloud.event.Event; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; @@ -421,11 +422,14 @@ import com.cloud.tags.dao.ResourceTagDao; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.ApiKeyPairState; import com.cloud.user.SSHKeyPair; import com.cloud.user.User; import com.cloud.user.UserAccount; import com.cloud.user.UserData; import com.cloud.user.UserStatisticsVO; +import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.UserDataDao; import com.cloud.user.dao.UserStatisticsDao; import com.cloud.uservm.UserVm; @@ -458,7 +462,7 @@ import com.cloud.vm.snapshot.dao.VMSnapshotDao; import sun.security.x509.X509CertImpl; -public class ApiResponseHelper implements ResponseGenerator { +public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport { protected Logger logger = LogManager.getLogger(ApiResponseHelper.class); private static final DecimalFormat s_percentFormat = new DecimalFormat("##.##"); @@ -542,6 +546,8 @@ public class ApiResponseHelper implements ResponseGenerator { Site2SiteVpnManager site2SiteVpnManager; @Inject ResourceIconManager resourceIconManager; + @Inject + AsyncJobDao asyncJobDao; public static String getPrettyDomainPath(String path) { if (path == null) { @@ -814,7 +820,7 @@ public class ApiResponseHelper implements ResponseGenerator { if (mapCapabilities != null) { String value = mapCapabilities.get(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString()); - Boolean supportsStorageSystemSnapshots = new Boolean(value); + boolean supportsStorageSystemSnapshots = Boolean.getBoolean(value); if (supportsStorageSystemSnapshots) { return DataStoreRole.Primary; @@ -2335,16 +2341,26 @@ public class ApiResponseHelper implements ResponseGenerator { @Override public AsyncJobResponse queryJobResult(final QueryAsyncJobResultCmd cmd) { - final Account caller = CallContext.current().getCallingAccount(); + ApiCommandResourceType resourceType = getResourceType(cmd.getResourceType()); + String resourceTypeName = Optional.ofNullable(resourceType).map(ApiCommandResourceType::name).orElse(null); - final AsyncJob job = _entityMgr.findByIdIncludingRemoved(AsyncJob.class, cmd.getId()); - if (job == null) { - throw new InvalidParameterValueException("Unable to find a job by id " + cmd.getId()); + Long resourceId = getResourceId(resourceType, cmd.getResourceId()); + Long jobId = cmd.getId(); + if (jobId == null && resourceId == null) { + throw new InvalidParameterValueException("Expected parameter job id or parameters resource type and resource id"); } + final AsyncJob job = asyncJobDao.findJob(jobId, resourceId, resourceTypeName); + if (job == null) { + throw new InvalidParameterValueException("Unable to find a job by id " + jobId + " resource type " + + cmd.getResourceType() + " resource id " + cmd.getResourceId()); + } + jobId = job.getId(); + final User userJobOwner = _accountMgr.getUserIncludingRemoved(job.getUserId()); final Account jobOwner = _accountMgr.getAccount(userJobOwner.getAccountId()); + final Account caller = CallContext.current().getCallingAccount(); //check permissions if (_accountMgr.isNormalUser(caller.getId())) { //regular users can see only jobs they own @@ -2355,7 +2371,7 @@ public class ApiResponseHelper implements ResponseGenerator { _accountMgr.checkAccess(caller, null, true, jobOwner); } - return createAsyncJobResponse(_jobMgr.queryJob(cmd.getId(), true)); + return createAsyncJobResponse(_jobMgr.queryJob(jobId, true)); } public AsyncJobResponse createAsyncJobResponse(AsyncJob job) { @@ -5104,7 +5120,25 @@ public class ApiResponseHelper implements ResponseGenerator { @Override public BackupScheduleResponse createBackupScheduleResponse(BackupSchedule schedule) { - return ApiDBUtils.newBackupScheduleResponse(schedule); + BackupScheduleResponse response = new BackupScheduleResponse(); + response.setId(schedule.getUuid()); + response.setIntervalType(schedule.getScheduleType()); + response.setSchedule(schedule.getSchedule()); + response.setTimezone(schedule.getTimezone()); + response.setMaxBackups(schedule.getMaxBackups()); + + if (schedule.getQuiesceVM() != null) { + response.setQuiesceVM(schedule.getQuiesceVM()); + } + + VMInstanceVO vm = ApiDBUtils.findVMInstanceById(schedule.getVmId()); + if (vm != null) { + response.setVmId(vm.getUuid()); + response.setVmName(vm.getHostName()); + } + + response.setObjectName("backupschedule"); + return response; } @Override @@ -5823,4 +5857,14 @@ protected Map getResourceIconsUsingOsCategory(List[] {AllowUserViewDestroyedVM, UserVMDeniedDetails, UserVMReadOnlyDetails, SortKeyAscending, AllowUserViewAllDomainAccounts, AllowUserViewAllDataCenters, SharePublicTemplatesWithOtherDomains, ReturnVmStatsOnVmList}; } + + @Override + public EntityManager getEntityManager() { + return entityManager; + } + + @Override + public AccountManager getAccountManager() { + return accountMgr; + } } diff --git a/server/src/main/java/com/cloud/api/query/ResourceIdSupport.java b/server/src/main/java/com/cloud/api/query/ResourceIdSupport.java new file mode 100644 index 00000000000..2c32df22f0c --- /dev/null +++ b/server/src/main/java/com/cloud/api/query/ResourceIdSupport.java @@ -0,0 +1,123 @@ +// 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.api.query; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.utils.db.EntityManager; +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.context.CallContext; +import org.apache.commons.lang3.StringUtils; + +import java.util.Optional; +import java.util.UUID; +import static org.apache.cloudstack.acl.SecurityChecker.AccessType; + +/** + * Support interface for converting resource UUIDs to internal IDs + * with validation and access control. + * + * @author mprokopchuk + */ +public interface ResourceIdSupport { + + EntityManager getEntityManager(); + + AccountManager getAccountManager(); + + /** + * Converts resource UUID to internal database ID with access control checks. + * + * @param resourceType type of the resource + * @param resourceUuid UUID of the resource + * @return internal resource ID or null if parameters are null + * @throws InvalidParameterValueException if only one parameter provided or resource not found + */ + default Long getResourceId(ApiCommandResourceType resourceType, String resourceUuid) { + String uuid = getResourceUuid(resourceUuid); + + if (resourceType == null && uuid == null) { + return null; + } else if ((resourceType == null) ^ (uuid == null)) { + throw new InvalidParameterValueException(String.format("Both %s and %s required", + ApiConstants.RESOURCE_ID, ApiConstants.RESOURCE_TYPE)); + } + + Object object = getEntityManager().findByUuidIncludingRemoved(resourceType.getAssociatedClass(), resourceUuid); + if (!(object instanceof InternalIdentity)) { + throw new InvalidParameterValueException(String.format("Invalid %s", ApiConstants.RESOURCE_ID)); + } + Long resourceId = ((InternalIdentity) object).getId(); + + Account caller = CallContext.current().getCallingAccount(); + boolean isRootAdmin = getAccountManager().isRootAdmin(caller.getId()); + + if (!isRootAdmin && object instanceof ControlledEntity) { + ControlledEntity entity = (ControlledEntity) object; + boolean sameOwner = entity.getAccountId() == caller.getId(); + getAccountManager().checkAccess(caller, AccessType.ListEntry, sameOwner, entity); + } + + return resourceId; + } + + /** + * Parses and validates resource type string. + * + * @param resourceType resource type as string + * @return parsed resource type or null if not provided + * @throws InvalidParameterValueException if provided type is invalid + */ + default ApiCommandResourceType getResourceType(String resourceType) { + Optional resourceTypeOpt = Optional.ofNullable(resourceType).filter(StringUtils::isNotBlank); + // return null if resource type was not provided + if (resourceTypeOpt.isEmpty()) { + return null; + } + // return value or throw exception if provided resource type is invalid + return resourceTypeOpt + .map(ApiCommandResourceType::fromString) + .orElseThrow(() -> new InvalidParameterValueException(String.format("Invalid %s", + ApiConstants.RESOURCE_TYPE))); + } + + /** + * Validates resource UUID format. + * + * @param resourceUuid UUID string to validate + * @return validated UUID or null if not provided + * @throws InvalidParameterValueException if UUID format is invalid + */ + default String getResourceUuid(String resourceUuid) { + if (StringUtils.isBlank(resourceUuid)) { + return null; + } + + try { + UUID.fromString(resourceUuid); + } catch (IllegalArgumentException ex) { + throw new InvalidParameterValueException(String.format("Invalid %s", ApiConstants.RESOURCE_ID)); + } + + return resourceUuid; + } + +} diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index 687fea1c4e3..72690091e40 100644 --- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java @@ -17,14 +17,13 @@ package com.cloud.api.query.dao; import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Collections; import java.time.LocalDate; import java.time.ZoneId; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; import java.util.Date; - import java.util.HashMap; import java.util.Hashtable; import java.util.List; @@ -34,8 +33,6 @@ import java.util.stream.Collectors; import javax.inject.Inject; -import com.cloud.gpu.dao.VgpuProfileDao; -import com.cloud.service.dao.ServiceOfferingDao; import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -49,6 +46,7 @@ import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.VnfNicResponse; import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.extension.ExtensionHelper; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.query.QueryService; import org.apache.cloudstack.vm.lease.VMLeaseManager; @@ -61,11 +59,13 @@ import com.cloud.api.ApiDBUtils; import com.cloud.api.ApiResponseHelper; import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.gpu.GPU; +import com.cloud.gpu.dao.VgpuProfileDao; import com.cloud.host.ControlState; import com.cloud.network.IpAddress; import com.cloud.network.vpc.VpcVO; import com.cloud.network.vpc.dao.VpcDao; import com.cloud.service.ServiceOfferingDetailsVO; +import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.GuestOS; import com.cloud.storage.Storage.TemplateType; @@ -94,7 +94,6 @@ import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.VmStats; import com.cloud.vm.dao.NicExtraDhcpOptionDao; import com.cloud.vm.dao.NicSecondaryIpVO; - import com.cloud.vm.dao.VMInstanceDetailsDao; @Component @@ -128,6 +127,8 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation VmDetailSearch; private final SearchBuilder activeVmByIsoSearch; @@ -461,7 +462,16 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation userVmSettingsToHide = new ArrayList<>(); + String[] parts = QueryService.UserVMDeniedDetails.value().split(","); + if (parts.length > 0) { + Collections.addAll(userVmSettingsToHide, parts); + } + if (userVm.getTemplateExtensionId() != null) { + userVmSettingsToHide.addAll(extensionHelper.getExtensionReservedResourceDetails( + userVm.getTemplateExtensionId())); + } + for (String key : userVmSettingsToHide) { resourceDetails.remove(key.trim()); } @@ -521,7 +531,6 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation maps = _accountVlanMapDao.listAccountVlanMapsByVlan(id); - if (maps != null && !maps.isEmpty()) { + if (account != null) { throw new InvalidParameterValueException("Specified Public IP range has already been dedicated to an account"); } - - List domainmaps = _domainVlanMapDao.listDomainVlanMapsByVlan(id); - if (domainmaps != null && !domainmaps.isEmpty()) { + if (domainId != null) { throw new InvalidParameterValueException("Specified Public IP range has already been dedicated to a domain"); } } if (ipv4) { - updateVlanAndIpv4Range(id, vlanRange, startIp, endIp, gateway, netmask, isRangeForSystemVM, forSystemVms); + long existingIpAddressAmount = 0L; + long newIpAddressAmount = 0L; + + if (account != null) { + // IPv4 public range is dedicated to an account (IPv6 cannot be dedicated at the moment). + // We need to update the resource count. + existingIpAddressAmount = _publicIpAddressDao.countIPs(vlanRange.getDataCenterId(), id, false); + newIpAddressAmount = NetUtils.ip2Long(endIp) - NetUtils.ip2Long(startIp) + 1; + } + + try (CheckedReservation publicIpReservation = new CheckedReservation(account, ResourceType.public_ip, null, null, null, newIpAddressAmount, existingIpAddressAmount, reservationDao, _resourceLimitMgr)) { + + updateVlanAndIpv4Range(id, vlanRange, startIp, endIp, gateway, netmask, isRangeForSystemVM, forSystemVms); + + if (account != null) { + long countDiff = newIpAddressAmount - existingIpAddressAmount; + if (countDiff > 0) { + _resourceLimitMgr.incrementResourceCount(account.getId(), ResourceType.public_ip, countDiff); + } else if (countDiff < 0) { + _resourceLimitMgr.decrementResourceCount(account.getId(), ResourceType.public_ip, Math.abs(countDiff)); + } + } + } } if (ipv6) { updateVlanAndIpv6Range(id, vlanRange, startIpv6, endIpv6, ip6Gateway, ip6Cidr, isRangeForSystemVM, forSystemVms); @@ -6423,7 +6449,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } finally { _vlanDao.releaseFromLockTable(vlanDbId); if (resourceCountToBeDecrement > 0) { //Making sure to decrement the count of only success operations above. For any reaason if disassociation fails then this number will vary from original range length. - _resourceLimitMgr.decrementResourceCount(acctVln.get(0).getAccountId(), ResourceType.public_ip, new Long(resourceCountToBeDecrement)); + _resourceLimitMgr.decrementResourceCount(acctVln.get(0).getAccountId(), ResourceType.public_ip, (long)resourceCountToBeDecrement); } } } else { // !isAccountSpecific @@ -6531,12 +6557,6 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati throw new InvalidParameterValueException("Public IP range can be dedicated to an account only in the zone of type " + NetworkType.Advanced); } - // Check Public IP resource limits - if (vlanOwner != null) { - final int accountPublicIpRange = _publicIpAddressDao.countIPs(zoneId, vlanDbId, false); - _resourceLimitMgr.checkResourceLimit(vlanOwner, ResourceType.public_ip, accountPublicIpRange); - } - // Check if any of the Public IP addresses is allocated to another // account final List ips = _publicIpAddressDao.listByVlanId(vlanDbId); @@ -6557,29 +6577,35 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } - if (vlanOwner != null) { - // Create an AccountVlanMapVO entry - final AccountVlanMapVO accountVlanMapVO = new AccountVlanMapVO(vlanOwner.getId(), vlan.getId()); - _accountVlanMapDao.persist(accountVlanMapVO); + // Check Public IP resource limits + long reservedIpAddressesAmount = vlanOwner != null ? _publicIpAddressDao.countIPs(zoneId, vlanDbId, false) : 0L; + try (CheckedReservation publicIpReservation = new CheckedReservation(vlanOwner, ResourceType.public_ip, null, null, null, reservedIpAddressesAmount, null, reservationDao, _resourceLimitMgr)) { - // generate usage event for dedication of every ip address in the range - for (final IPAddressVO ip : ips) { - final boolean usageHidden = _ipAddrMgr.isUsageHidden(ip); - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NET_IP_ASSIGN, vlanOwner.getId(), ip.getDataCenterId(), ip.getId(), ip.getAddress().toString(), ip.isSourceNat(), - vlan.getVlanType().toString(), ip.getSystem(), usageHidden, ip.getClass().getName(), ip.getUuid()); + if (vlanOwner != null) { + // Create an AccountVlanMapVO entry + final AccountVlanMapVO accountVlanMapVO = new AccountVlanMapVO(vlanOwner.getId(), vlan.getId()); + _accountVlanMapDao.persist(accountVlanMapVO); + + // generate usage event for dedication of every ip address in the range + for (final IPAddressVO ip : ips) { + final boolean usageHidden = _ipAddrMgr.isUsageHidden(ip); + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NET_IP_ASSIGN, vlanOwner.getId(), ip.getDataCenterId(), ip.getId(), ip.getAddress().toString(), ip.isSourceNat(), + vlan.getVlanType().toString(), ip.getSystem(), usageHidden, ip.getClass().getName(), ip.getUuid()); + } + } else if (domain != null) { + // Create an DomainVlanMapVO entry + DomainVlanMapVO domainVlanMapVO = new DomainVlanMapVO(domain.getId(), vlan.getId()); + _domainVlanMapDao.persist(domainVlanMapVO); } - } else if (domain != null) { - // Create an DomainVlanMapVO entry - DomainVlanMapVO domainVlanMapVO = new DomainVlanMapVO(domain.getId(), vlan.getId()); - _domainVlanMapDao.persist(domainVlanMapVO); - } - // increment resource count for dedicated public ip's - if (vlanOwner != null) { - _resourceLimitMgr.incrementResourceCount(vlanOwner.getId(), ResourceType.public_ip, new Long(ips.size())); - } + // increment resource count for dedicated public ip's + if (vlanOwner != null) { + _resourceLimitMgr.incrementResourceCount(vlanOwner.getId(), ResourceType.public_ip, (long)ips.size()); + } - return vlan; + return vlan; + + } } @Override @@ -6668,7 +6694,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } // decrement resource count for dedicated public ip's - _resourceLimitMgr.decrementResourceCount(acctVln.get(0).getAccountId(), ResourceType.public_ip, new Long(ips.size())); + _resourceLimitMgr.decrementResourceCount(acctVln.get(0).getAccountId(), ResourceType.public_ip, (long)ips.size()); success = true; } else if (isDomainSpecific && _domainVlanMapDao.remove(domainVlan.get(0).getId())) { logger.debug("Remove the vlan from domain_vlan_map successfully."); @@ -6837,7 +6863,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final List newCidrPair = new ArrayList<>(); newCidrPair.add(0, getCidrAddress(cidr)); newCidrPair.add(1, (long)getCidrSize(cidr)); - currentPodCidrSubnets.put(new Long(-1), newCidrPair); + currentPodCidrSubnets.put(-1L, newCidrPair); final DataCenterVO dcVo = _zoneDao.findById(dcId); final String guestNetworkCidr = dcVo.getGuestNetworkCidr(); @@ -6937,7 +6963,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } private String getPodName(final long podId) { - return _podDao.findById(new Long(podId)).getName(); + return _podDao.findById(podId).getName(); } private boolean validZone(final String zoneName) { @@ -6949,7 +6975,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } private String getZoneName(final long zoneId) { - final DataCenterVO zone = _zoneDao.findById(new Long(zoneId)); + final DataCenterVO zone = _zoneDao.findById(zoneId); if (zone != null) { return zone.getName(); } else { @@ -6976,11 +7002,14 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final boolean ipv4 = deletedVlan.getVlanGateway() != null; final boolean ipv6 = deletedVlan.getIp6Gateway() != null; final long networkId = deletedVlan.getNetworkId(); + final NetworkVO networkVO = _networkDao.findById(networkId); - if (ipv4) { - removeCidrAndGatewayForIpv4(networkId, deletedVlan); - } else if (ipv6) { - removeCidrAndGatewayForIpv6(networkId, deletedVlan); + if (networkVO != null && networkVO.getTrafficType() != TrafficType.Public) { + if (ipv4) { + removeCidrAndGatewayForIpv4(networkId, deletedVlan); + } else if (ipv6) { + removeCidrAndGatewayForIpv6(networkId, deletedVlan); + } } messageBus.publish(_name, MESSAGE_DELETE_VLAN_IP_RANGE_EVENT, PublishScope.LOCAL, deletedVlan); diff --git a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java index f344b1b7c46..47d1a306e4d 100644 --- a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java +++ b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java @@ -69,9 +69,6 @@ public interface ConsoleProxyManager extends Manager, ConsoleProxyService { ConfigKey ConsoleProxyCapacityScanInterval = new ConfigKey<>(String.class, "consoleproxy.capacityscan.interval", "Console Proxy", "30000", "The time interval(in millisecond) to scan whether or not system needs more console proxy to ensure minimal standby capacity", false, null); - ConfigKey ConsoleProxyCmdPort = new ConfigKey<>(Integer.class, "consoleproxy.cmd.port", "Console Proxy", String.valueOf(DEFAULT_PROXY_CMD_PORT), - "Console proxy command port that is used to communicate with management server", false, ConfigKey.Scope.Zone, null); - ConfigKey ConsoleProxyRestart = new ConfigKey<>(Boolean.class, "consoleproxy.restart", "Console Proxy", "true", "Console proxy restart flag, defaults to true", true, ConfigKey.Scope.Zone, null); diff --git a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java index db15a440ba3..b3da6af2113 100644 --- a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java +++ b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java @@ -1589,7 +1589,7 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy @Override public ConfigKey[] getConfigKeys() { return new ConfigKey[] {ConsoleProxySslEnabled, NoVncConsoleDefault, NoVncConsoleSourceIpCheckEnabled, ConsoleProxyServiceOffering, - ConsoleProxyCapacityStandby, ConsoleProxyCapacityScanInterval, ConsoleProxyCmdPort, ConsoleProxyRestart, ConsoleProxyUrlDomain, ConsoleProxySessionMax, ConsoleProxySessionTimeout, ConsoleProxyDisableRpFilter, ConsoleProxyLaunchMax, + ConsoleProxyCapacityStandby, ConsoleProxyCapacityScanInterval, ConsoleProxyRestart, ConsoleProxyUrlDomain, ConsoleProxySessionMax, ConsoleProxySessionTimeout, ConsoleProxyDisableRpFilter, ConsoleProxyLaunchMax, ConsoleProxyManagementLastState, ConsoleProxyServiceManagementState, NoVncConsoleShowDot, ConsoleProxyVmUserData}; } diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index 0a2e679b723..24526e1aee0 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -41,6 +41,7 @@ import java.util.UUID; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.resourcelimit.CheckedReservation; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.alert.AlertService; @@ -76,6 +77,7 @@ import org.apache.cloudstack.network.NetworkPermissionVO; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.apache.cloudstack.network.dao.NetworkPermissionDao; import org.apache.cloudstack.network.element.InternalLoadBalancerElementService; +import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.BooleanUtils; @@ -338,6 +340,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C @Inject ResourceLimitService _resourceLimitMgr; @Inject + ReservationDao reservationDao; + @Inject DomainManager _domainMgr; @Inject ProjectManager _projectMgr; @@ -1155,15 +1159,10 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C if (ipDedicatedAccountId != null && !ipDedicatedAccountId.equals(account.getAccountId())) { throw new InvalidParameterValueException("Unable to reserve a IP because it is dedicated to another Account."); } - if (ipDedicatedAccountId == null) { - // Check that the maximum number of public IPs for the given accountId will not be exceeded - try { - _resourceLimitMgr.checkResourceLimit(account, Resource.ResourceType.public_ip); - } catch (ResourceAllocationException ex) { - logger.warn("Failed to allocate resource of type " + ex.getResourceType() + " for account " + account); - throw new AccountLimitException("Maximum number of public IP addresses for account: " + account.getAccountName() + " has been exceeded."); - } - } + + long reservedIpAddressesAmount = ipDedicatedAccountId == null ? 1L : 0L; + try (CheckedReservation publicIpAddressReservation = new CheckedReservation(account, Resource.ResourceType.public_ip, reservedIpAddressesAmount, reservationDao, _resourceLimitMgr)) { + List maps = _accountVlanMapDao.listAccountVlanMapsByVlan(ipVO.getVlanId()); ipVO.setAllocatedTime(new Date()); ipVO.setAllocatedToAccountId(account.getAccountId()); @@ -1173,10 +1172,15 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C ipVO.setDisplay(displayIp); } ipVO = _ipAddressDao.persist(ipVO); - if (ipDedicatedAccountId == null) { + if (reservedIpAddressesAmount > 0) { _resourceLimitMgr.incrementResourceCount(account.getId(), Resource.ResourceType.public_ip); } return ipVO; + + } catch (ResourceAllocationException ex) { + logger.warn("Failed to allocate resource of type " + ex.getResourceType() + " for account " + account); + throw new AccountLimitException("Maximum number of public IP addresses for account: " + account.getAccountName() + " has been exceeded."); + } } @Override @@ -3216,7 +3220,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C if (displayNetwork != null && displayNetwork != network.getDisplayNetwork()) { // Update resource count if it needs to be updated NetworkOffering networkOffering = _networkOfferingDao.findById(network.getNetworkOfferingId()); - if (_networkMgr.resourceCountNeedsUpdate(networkOffering, network.getAclType())) { + if (_networkMgr.isResourceCountUpdateNeeded(networkOffering)) { _resourceLimitMgr.changeResourceCount(network.getAccountId(), Resource.ResourceType.network, displayNetwork); } diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index 156e71e72b8..1203404c494 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -42,30 +42,8 @@ import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.configuration.ConfigurationManager; -import com.cloud.configuration.ConfigurationManagerImpl; -import com.cloud.bgp.BGPService; -import com.cloud.dc.ASNumberVO; -import com.cloud.dc.dao.ASNumberDao; -import com.cloud.dc.Vlan; -import com.cloud.network.RemoteAccessVpn; -import com.cloud.network.Site2SiteVpnConnection; -import com.cloud.network.dao.NetrisProviderDao; -import com.cloud.network.dao.NsxProviderDao; -import com.cloud.network.dao.RemoteAccessVpnDao; -import com.cloud.network.dao.RemoteAccessVpnVO; -import com.cloud.network.dao.Site2SiteCustomerGatewayDao; -import com.cloud.network.dao.Site2SiteCustomerGatewayVO; -import com.cloud.network.dao.Site2SiteVpnConnectionDao; -import com.cloud.network.dao.Site2SiteVpnConnectionVO; -import com.cloud.network.element.NetrisProviderVO; -import com.cloud.network.element.NetworkACLServiceProvider; -import com.cloud.network.element.NsxProviderVO; -import com.cloud.network.rules.RulesManager; -import com.cloud.network.vpn.RemoteAccessVpnService; -import com.cloud.utils.DomainHelper; -import com.cloud.vm.dao.VMInstanceDao; import com.google.common.collect.Sets; + import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.alert.AlertService; import org.apache.cloudstack.annotation.AnnotationService; @@ -108,12 +86,18 @@ import com.cloud.agent.manager.Commands; import com.cloud.alert.AlertManager; import com.cloud.api.query.dao.VpcOfferingJoinDao; import com.cloud.api.query.vo.VpcOfferingJoinVO; +import com.cloud.bgp.BGPService; import com.cloud.configuration.Config; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.configuration.ConfigurationManagerImpl; import com.cloud.configuration.Resource.ResourceType; +import com.cloud.dc.ASNumberVO; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.Vlan.VlanType; +import com.cloud.dc.Vlan; import com.cloud.dc.VlanVO; +import com.cloud.dc.dao.ASNumberDao; import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.VlanDao; import com.cloud.deploy.DeployDestination; @@ -143,18 +127,33 @@ import com.cloud.network.NetworkService; import com.cloud.network.Networks.BroadcastDomainType; import com.cloud.network.Networks.TrafficType; import com.cloud.network.PhysicalNetwork; +import com.cloud.network.RemoteAccessVpn; +import com.cloud.network.Site2SiteVpnConnection; import com.cloud.network.addr.PublicIp; import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.NetrisProviderDao; +import com.cloud.network.dao.NsxProviderDao; +import com.cloud.network.dao.RemoteAccessVpnDao; +import com.cloud.network.dao.RemoteAccessVpnVO; +import com.cloud.network.dao.Site2SiteCustomerGatewayDao; +import com.cloud.network.dao.Site2SiteCustomerGatewayVO; +import com.cloud.network.dao.Site2SiteVpnConnectionDao; +import com.cloud.network.dao.Site2SiteVpnConnectionVO; +import com.cloud.network.element.NetrisProviderVO; +import com.cloud.network.element.NetworkACLServiceProvider; import com.cloud.network.element.NetworkElement; +import com.cloud.network.element.NsxProviderVO; import com.cloud.network.element.StaticNatServiceProvider; import com.cloud.network.element.VpcProvider; import com.cloud.network.router.CommandSetupHelper; import com.cloud.network.router.NetworkHelper; import com.cloud.network.router.VpcVirtualNetworkApplianceManager; +import com.cloud.network.rules.RulesManager; +import com.cloud.network.vpn.RemoteAccessVpnService; import com.cloud.network.vpc.VpcOffering.State; import com.cloud.network.vpc.dao.NetworkACLDao; import com.cloud.network.vpc.dao.PrivateIpDao; @@ -173,6 +172,7 @@ import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; import com.cloud.org.Grouping; import com.cloud.projects.Project.ListProjectResourcesCriteria; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.tags.ResourceTagVO; import com.cloud.tags.dao.ResourceTagDao; @@ -180,6 +180,7 @@ import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.ResourceLimitService; import com.cloud.user.User; +import com.cloud.utils.DomainHelper; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; import com.cloud.utils.StringUtils; @@ -209,6 +210,7 @@ import com.cloud.vm.ReservationContextImpl; import com.cloud.vm.VirtualMachine; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.VMInstanceDao; import static com.cloud.offering.NetworkOffering.RoutingMode.Dynamic; @@ -1573,9 +1575,6 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis // Verify that caller can perform actions in behalf of vpc owner _accountMgr.checkAccess(caller, null, false, owner); - // check resource limit - _resourceLimitMgr.checkResourceLimit(owner, ResourceType.vpc); - // Validate zone final DataCenter zone = _dcDao.findById(zoneId); if (zone == null) { @@ -1670,6 +1669,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis vpc.setDisplay(Boolean.TRUE.equals(displayVpc)); vpc.setUseRouterIpResolver(Boolean.TRUE.equals(useVrIpResolver)); + try (CheckedReservation vpcReservation = new CheckedReservation(owner, ResourceType.vpc, null, null, 1L, reservationDao, _resourceLimitMgr)) { if (vpc.getCidr() == null && cidrSize != null) { // Allocate a CIDR for VPC Ipv4GuestSubnetNetworkMap subnet = routedIpv4Manager.getOrCreateIpv4SubnetForVpc(vpc, cidrSize); @@ -1689,6 +1689,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis routedIpv4Manager.persistBgpPeersForVpc(newVpc.getId(), bgpPeerIds); } return newVpc; + } } private void validateVpcCidrSize(Account caller, long accountId, VpcOffering vpcOffering, String cidr, Integer cidrSize, long zoneId) { diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index 95fdfa0fde8..06397f5a81b 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -36,6 +36,7 @@ import javax.inject.Inject; import javax.mail.MessagingException; import javax.naming.ConfigurationException; +import com.cloud.resourcelimit.CheckedReservation; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ProjectRole; import org.apache.cloudstack.acl.SecurityChecker.AccessType; @@ -47,6 +48,7 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.PublishScope; import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.cloudstack.utils.mailing.MailAddress; import org.apache.cloudstack.utils.mailing.SMTPMailProperties; import org.apache.cloudstack.utils.mailing.SMTPMailSender; @@ -159,6 +161,8 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C private VpcManager _vpcMgr; @Inject MessageBus messageBus; + @Inject + private ReservationDao reservationDao; protected boolean _invitationRequired = false; protected long _invitationTimeOut = 86400000; @@ -272,8 +276,7 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C owner = _accountDao.findById(user.getAccountId()); } - //do resource limit check - _resourceLimitMgr.checkResourceLimit(owner, ResourceType.project); + try (CheckedReservation projectReservation = new CheckedReservation(owner, ResourceType.project, null, null, 1L, reservationDao, _resourceLimitMgr)) { final Account ownerFinal = owner; User finalUser = user; @@ -308,6 +311,7 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C messageBus.publish(_name, ProjectManager.MESSAGE_CREATE_TUNGSTEN_PROJECT_EVENT, PublishScope.LOCAL, project); return project; + } } @Override @@ -491,6 +495,9 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C //remove account ProjectAccountVO projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, account.getId()); success = _projectAccountDao.remove(projectAccount.getId()); + if (projectAccount.getAccountRole() == Role.Admin) { + _resourceLimitMgr.decrementResourceCount(account.getId(), ResourceType.project); + } //remove all invitations for account if (success) { @@ -537,7 +544,7 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C @Override @ActionEvent(eventType = EventTypes.EVENT_PROJECT_USER_ADD, eventDescription = "adding user to project", async = true) - public boolean addUserToProject(Long projectId, String username, String email, Long projectRoleId, Role projectRole) { + public boolean addUserToProject(Long projectId, String username, String email, Long projectRoleId, Role projectRole) throws ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); Project project = getProject(projectId); @@ -594,12 +601,20 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C if (username == null) { throw new InvalidParameterValueException("User information (ID) is required to add user to the project"); } + + boolean shouldIncrementResourceCount = projectRole != null && Role.Admin == projectRole; + try (CheckedReservation cr = new CheckedReservation(userAccount, ResourceType.project, shouldIncrementResourceCount ? 1L : 0L, reservationDao, _resourceLimitMgr)) { if (assignUserToProject(project, user.getId(), user.getAccountId(), projectRole, Optional.ofNullable(role).map(ProjectRole::getId).orElse(null)) != null) { + if (shouldIncrementResourceCount) { + _resourceLimitMgr.incrementResourceCount(userAccount.getId(), ResourceType.project); + } return true; + } else { + logger.warn("Failed to add user to project: {}", project); + return false; + } } - logger.warn("Failed to add user to project: {}", project); - return false; } } @@ -652,14 +667,18 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C } private void updateProjectAccount(ProjectAccountVO futureOwner, Role newAccRole, Long accountId) throws ResourceAllocationException { - _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(accountId), ResourceType.project); + Account account = _accountMgr.getAccount(accountId); + boolean shouldIncrementResourceCount = Role.Admin == newAccRole; + + try (CheckedReservation checkedReservation = new CheckedReservation(account, ResourceType.project, shouldIncrementResourceCount ? 1L : 0L, reservationDao, _resourceLimitMgr)) { futureOwner.setAccountRole(newAccRole); _projectAccountDao.update(futureOwner.getId(), futureOwner); - if (newAccRole != null && Role.Admin == newAccRole) { + if (shouldIncrementResourceCount) { _resourceLimitMgr.incrementResourceCount(accountId, ResourceType.project); } else { _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.project); } + } } @Override @@ -701,9 +720,7 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C " doesn't belong to the project. Add it to the project first and then change the project's ownership"); } - //do resource limit check - _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(futureOwnerAccount.getId()), ResourceType.project); - + try (CheckedReservation checkedReservation = new CheckedReservation(futureOwnerAccount, ResourceType.project, null, null, 1L, reservationDao, _resourceLimitMgr)) { //unset the role for the old owner ProjectAccountVO currentOwner = _projectAccountDao.findByProjectIdAccountId(projectId, currentOwnerAccount.getId()); currentOwner.setAccountRole(Role.Regular); @@ -714,7 +731,7 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C futureOwner.setAccountRole(Role.Admin); _projectAccountDao.update(futureOwner.getId(), futureOwner); _resourceLimitMgr.incrementResourceCount(futureOwnerAccount.getId(), ResourceType.project); - + } } else { logger.trace("Future owner {}is already the owner of the project {}", newOwnerName, project); } @@ -792,7 +809,7 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C @Override @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ACCOUNT_ADD, eventDescription = "adding account to project", async = true) - public boolean addAccountToProject(long projectId, String accountName, String email, Long projectRoleId, Role projectRoleType) { + public boolean addAccountToProject(long projectId, String accountName, String email, Long projectRoleId, Role projectRoleType) throws ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); //check that the project exists @@ -857,13 +874,20 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C if (account == null) { throw new InvalidParameterValueException("Account information is required for assigning account to the project"); } + + boolean shouldIncrementResourceCount = projectRoleType != null && Role.Admin == projectRoleType; + try (CheckedReservation cr = new CheckedReservation(account, ResourceType.project, shouldIncrementResourceCount ? 1L : 0L, reservationDao, _resourceLimitMgr)) { if (assignAccountToProject(project, account.getId(), projectRoleType, null, Optional.ofNullable(projectRole).map(ProjectRole::getId).orElse(null)) != null) { + if (shouldIncrementResourceCount) { + _resourceLimitMgr.incrementResourceCount(account.getId(), ResourceType.project); + } return true; } else { logger.warn("Failed to add account {} to project {}", accountName, project); return false; } + } } } @@ -1042,7 +1066,9 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C boolean success = true; ProjectAccountVO projectAccount = _projectAccountDao.findByProjectIdUserId(projectId, user.getAccountId(), user.getId()); success = _projectAccountDao.remove(projectAccount.getId()); - + if (projectAccount.getAccountRole() == Role.Admin) { + _resourceLimitMgr.decrementResourceCount(user.getAccountId(), ResourceType.project); + } if (success) { logger.debug("Removed user {} from project. Removing any invite sent to the user", user); ProjectInvitation invite = _projectInvitationDao.findByUserIdProjectId(user.getId(), user.getAccountId(), projectId); diff --git a/server/src/main/java/com/cloud/resourcelimit/CheckedReservation.java b/server/src/main/java/com/cloud/resourcelimit/CheckedReservation.java index d66e1eb912a..cab77ccf16a 100644 --- a/server/src/main/java/com/cloud/resourcelimit/CheckedReservation.java +++ b/server/src/main/java/com/cloud/resourcelimit/CheckedReservation.java @@ -20,13 +20,17 @@ package com.cloud.resourcelimit; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; +import com.cloud.api.ApiDBUtils; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.reservation.ReservationVO; import org.apache.cloudstack.reservation.dao.ReservationDao; +import org.apache.cloudstack.resourcelimit.Reserver; import org.apache.cloudstack.user.ResourceReservation; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -40,7 +44,7 @@ import com.cloud.utils.db.GlobalLock; import com.cloud.utils.exception.CloudRuntimeException; -public class CheckedReservation implements AutoCloseable { +public class CheckedReservation implements Reserver { protected Logger logger = LogManager.getLogger(getClass()); private static final int TRY_TO_GET_LOCK_TIME = 120; @@ -48,11 +52,15 @@ public class CheckedReservation implements AutoCloseable { ReservationDao reservationDao; ResourceLimitService resourceLimitService; - private final Account account; - private final ResourceType resourceType; - private Long amount; + private Account account; + private Long domainId; + private ResourceType resourceType; + private Long resourceId; + private Long reservationAmount; + private List reservationTags; + private Long existingAmount; + private List existingLimitTags; private List reservations; - private List resourceLimitTags; private String getContextParameterKey() { return getResourceReservationContextParameterKey(resourceType); @@ -73,12 +81,12 @@ public class CheckedReservation implements AutoCloseable { this.reservations = null; } - protected void checkLimitAndPersistReservations(Account account, ResourceType resourceType, Long resourceId, List resourceLimitTags, Long amount) throws ResourceAllocationException { + protected void checkLimitAndPersistReservations(Account account, Long domainId, ResourceType resourceType, Long resourceId, List resourceLimitTags, Long amount) throws ResourceAllocationException { try { - checkLimitAndPersistReservation(account, resourceType, resourceId, null, amount); + checkLimitAndPersistReservation(account, domainId, resourceType, resourceId, null, amount); if (CollectionUtils.isNotEmpty(resourceLimitTags)) { for (String tag : resourceLimitTags) { - checkLimitAndPersistReservation(account, resourceType, resourceId, tag, amount); + checkLimitAndPersistReservation(account, domainId, resourceType, resourceId, tag, amount); } } } catch (ResourceAllocationException rae) { @@ -87,11 +95,11 @@ public class CheckedReservation implements AutoCloseable { } } - protected void checkLimitAndPersistReservation(Account account, ResourceType resourceType, Long resourceId, String tag, Long amount) throws ResourceAllocationException { + protected void checkLimitAndPersistReservation(Account account, Long domainId, ResourceType resourceType, Long resourceId, String tag, Long amount) throws ResourceAllocationException { if (amount > 0) { - resourceLimitService.checkResourceLimitWithTag(account, resourceType, tag, amount); + resourceLimitService.checkResourceLimitWithTag(account, domainId, true, resourceType, tag, amount); } - ReservationVO reservationVO = new ReservationVO(account.getAccountId(), account.getDomainId(), resourceType, tag, amount); + ReservationVO reservationVO = new ReservationVO(account.getAccountId(), domainId, resourceType, tag, amount); if (resourceId != null) { reservationVO.setResourceId(resourceId); } @@ -99,9 +107,25 @@ public class CheckedReservation implements AutoCloseable { this.reservations.add(reservation); } - public CheckedReservation(Account account, ResourceType resourceType, List resourceLimitTags, Long amount, - ReservationDao reservationDao, ResourceLimitService resourceLimitService) throws ResourceAllocationException { - this(account, resourceType, null, resourceLimitTags, amount, reservationDao, resourceLimitService); + // TODO: refactor these into a Builder to avoid having so many constructors + public CheckedReservation(Account account, ResourceType resourceType, List resourceLimitTags, Long reservationAmount, + ReservationDao reservationDao, ResourceLimitService resourceLimitService) throws ResourceAllocationException { + this(account, resourceType, null, resourceLimitTags, null, reservationAmount, null, reservationDao, resourceLimitService); + } + + public CheckedReservation(Account account, ResourceType resourceType, Long resourceId, List reservedTags, + List existingTags, Long reservationAmount, Long existingAmount, ReservationDao reservationDao, + ResourceLimitService resourceLimitService) throws ResourceAllocationException { + this(account, null, resourceType, resourceId, reservedTags, existingTags, reservationAmount, existingAmount, reservationDao, resourceLimitService); + } + + public CheckedReservation(Account account, Long domainId, ResourceType resourceType, Long resourceId, List reservedTags, + Long reservationAmount, ReservationDao reservationDao, ResourceLimitService resourceLimitService) throws ResourceAllocationException { + this(account, domainId, resourceType, resourceId, reservedTags, null, reservationAmount, null, reservationDao, resourceLimitService); + } + + public CheckedReservation(Account account, ResourceType resourceType, Long resourceId, List reservedTags, Long reservationAmount, ReservationDao reservationDao, ResourceLimitService resourceLimitService) throws ResourceAllocationException { + this(account, null, resourceType, resourceId, reservedTags, null, reservationAmount, null, reservationDao, resourceLimitService); } /** @@ -109,25 +133,48 @@ public class CheckedReservation implements AutoCloseable { * - create DB entry for reservation * - hold the id of this record as a ticket for implementation * - * @param amount positive number of the resource type to reserve + * @param reservationAmount positive number of the resource type to reserve * @throws ResourceAllocationException */ - public CheckedReservation(Account account, ResourceType resourceType, Long resourceId, List resourceLimitTags, Long amount, - ReservationDao reservationDao, ResourceLimitService resourceLimitService) throws ResourceAllocationException { + public CheckedReservation(Account account, Long domainId, ResourceType resourceType, Long resourceId, List reservedTags, + List existingTags, Long reservationAmount, Long existingAmount, ReservationDao reservationDao, + ResourceLimitService resourceLimitService) throws ResourceAllocationException { + + if (ObjectUtils.allNull(account, domainId)) { + logger.debug("Not reserving any {} resources, as no account/domain was provided.", resourceType); + return; + } + this.reservationDao = reservationDao; this.resourceLimitService = resourceLimitService; - this.account = account; - this.resourceType = resourceType; - this.amount = amount; - this.reservations = new ArrayList<>(); - this.resourceLimitTags = resourceLimitTags; - if (this.amount != null && this.amount != 0) { - if (amount > 0) { + // When allocating to a domain instead of a specific account, consider the system account as the owner for the validations here. + if (account == null) { + account = ApiDBUtils.getSystemAccount(); + } + this.account = account; + + if (domainId == null) { + domainId = account.getDomainId(); + } + this.domainId = domainId; + + this.resourceType = resourceType; + this.reservationAmount = reservationAmount; + this.existingAmount = existingAmount; + this.reservations = new ArrayList<>(); + + this.reservationTags = getTagsWithoutNull(reservedTags); + this.existingLimitTags = getTagsWithoutNull(existingTags); + + // TODO: refactor me + if (this.reservationAmount != null && this.reservationAmount != 0) { + if (reservationAmount > 0) { setGlobalLock(); if (quotaLimitLock.lock(TRY_TO_GET_LOCK_TIME)) { try { - checkLimitAndPersistReservations(account, resourceType, resourceId, resourceLimitTags, amount); + adjustCountToNotConsiderExistingAmount(); + checkLimitAndPersistReservations(account, this.domainId, resourceType, resourceId, reservationTags, reservationAmount); CallContext.current().putContextParameter(getContextParameterKey(), getIds()); } catch (NullPointerException npe) { throw new CloudRuntimeException("not enough means to check limits", npe); @@ -138,14 +185,28 @@ public class CheckedReservation implements AutoCloseable { throw new ResourceAllocationException(String.format("unable to acquire resource reservation \"%s\"", quotaLimitLock.getName()), resourceType); } } else { - checkLimitAndPersistReservations(account, resourceType, resourceId, resourceLimitTags, amount); + checkLimitAndPersistReservations(account, this.domainId, resourceType, resourceId, reservationTags, reservationAmount); } } else { logger.debug("not reserving any amount of resources for {} in domain {}, type: {}, tag: {}", - account.getAccountName(), account.getDomainId(), resourceType, getResourceLimitTagsAsString()); + account.getAccountName(), this.domainId, resourceType, getResourceLimitTagsAsString()); } } + protected List getTagsWithoutNull(List tags) { + if (tags == null) { + return null; + } + return tags.stream().filter(Objects::nonNull).collect(Collectors.toList()); + } + + protected void adjustCountToNotConsiderExistingAmount() throws ResourceAllocationException { + if (existingAmount == null || existingAmount == 0) { + return; + } + checkLimitAndPersistReservations(account, domainId, resourceType, resourceId, existingLimitTags, -1 * existingAmount); + } + public CheckedReservation(Account account, ResourceType resourceType, Long amount, ReservationDao reservationDao, ResourceLimitService resourceLimitService) throws ResourceAllocationException { this(account, resourceType, null, amount, reservationDao, resourceLimitService); @@ -153,7 +214,7 @@ public class CheckedReservation implements AutoCloseable { @NotNull private void setGlobalLock() { - String lockName = String.format("CheckedReservation-%s/%d", account.getDomainId(), resourceType.getOrdinal()); + String lockName = String.format("CheckedReservation-%s/%d", this.domainId, resourceType.getOrdinal()); setQuotaLimitLock(GlobalLock.getInternLock(lockName)); } @@ -162,7 +223,7 @@ public class CheckedReservation implements AutoCloseable { } @Override - public void close() throws Exception { + public void close() { removeAllReservations(); } @@ -171,11 +232,11 @@ public class CheckedReservation implements AutoCloseable { } public String getResourceLimitTagsAsString() { - return CollectionUtils.isNotEmpty(resourceLimitTags) ? StringUtils.join(resourceLimitTags) : null; + return CollectionUtils.isNotEmpty(reservationTags) ? StringUtils.join(reservationTags) : null; } public Long getReservedAmount() { - return amount; + return reservationAmount; } public List getReservations() { diff --git a/server/src/main/java/com/cloud/resourcelimit/ReservationHelper.java b/server/src/main/java/com/cloud/resourcelimit/ReservationHelper.java new file mode 100644 index 00000000000..cffa17176fa --- /dev/null +++ b/server/src/main/java/com/cloud/resourcelimit/ReservationHelper.java @@ -0,0 +1,35 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.resourcelimit; + +import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.cloudstack.resourcelimit.Reserver; + +import java.util.List; + +public class ReservationHelper { + + public static void closeAll(List reservations) throws CloudRuntimeException { + for (Reserver reservation : reservations) { + reservation.close(); + } + } + +} diff --git a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java index 0da403c35f2..729da7caa33 100644 --- a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java +++ b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java @@ -54,6 +54,7 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.reservation.ReservationVO; import org.apache.cloudstack.reservation.dao.ReservationDao; +import org.apache.cloudstack.resourcelimit.Reserver; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; @@ -92,6 +93,7 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkDomainDao; import com.cloud.network.vpc.dao.VpcDao; import com.cloud.offering.DiskOffering; import com.cloud.offering.ServiceOffering; @@ -175,6 +177,8 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim @Inject private ReservationDao reservationDao; @Inject + private ResourceLimitService resourceLimitService; + @Inject protected SnapshotDao _snapshotDao; @Inject protected BackupDao backupDao; @@ -204,6 +208,8 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim DiskOfferingDao diskOfferingDao; @Inject BucketDao bucketDao; + @Inject + private NetworkDomainDao networkDomainDao; protected GenericSearchBuilder templateSizeSearch; protected GenericSearchBuilder snapshotSizeSearch; @@ -267,7 +273,7 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim templateSizeSearch = _vmTemplateStoreDao.createSearchBuilder(SumCount.class); templateSizeSearch.select("sum", Func.SUM, templateSizeSearch.entity().getSize()); - templateSizeSearch.and("downloadState", templateSizeSearch.entity().getDownloadState(), Op.EQ); + templateSizeSearch.and("downloadState", templateSizeSearch.entity().getDownloadState(), Op.IN); templateSizeSearch.and("destroyed", templateSizeSearch.entity().getDestroyed(), Op.EQ); SearchBuilder join1 = _vmTemplateDao.createSearchBuilder(); join1.and("accountId", join1.entity().getAccountId(), Op.EQ); @@ -515,15 +521,7 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim return max; } - protected void checkDomainResourceLimit(final Account account, final Project project, final ResourceType type, String tag, long numResources) throws ResourceAllocationException { - // check all domains in the account's domain hierarchy - Long domainId; - if (project != null) { - domainId = project.getDomainId(); - } else { - domainId = account.getDomainId(); - } - + protected void checkDomainResourceLimit(Long domainId, final ResourceType type, String tag, long numResources) throws ResourceAllocationException { while (domainId != null) { DomainVO domain = _domainDao.findById(domainId); // no limit check if it is ROOT domain @@ -645,11 +643,16 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim @Override public void checkResourceLimitWithTag(final Account account, final ResourceType type, String tag, long... count) throws ResourceAllocationException { + checkResourceLimitWithTag(account, null, false, type, tag, count); + } + + @Override + public void checkResourceLimitWithTag(final Account account, Long domainId, boolean considerSystemAccount, final ResourceType type, String tag, long... count) throws ResourceAllocationException { final long numResources = ((count.length == 0) ? 1 : count[0]); Project project = null; // Don't place any limits on system or root admin accounts - if (_accountMgr.isRootAdmin(account.getId())) { + if (_accountMgr.isRootAdmin(account.getId()) && !(considerSystemAccount && Account.ACCOUNT_ID_SYSTEM == account.getId())) { return; } @@ -657,6 +660,14 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim project = _projectDao.findByProjectAccountId(account.getId()); } + if (domainId == null) { + if (project != null) { + domainId = project.getDomainId(); + } else { + domainId = account.getDomainId(); + } + } + Long domainIdFinal = domainId; final Project projectFinal = project; Transaction.execute(new TransactionCallbackWithExceptionNoReturn() { @Override @@ -666,7 +677,7 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim // Check account limits checkAccountResourceLimit(account, projectFinal, type, tag, numResources); // check all domains in the account's domain hierarchy - checkDomainResourceLimit(account, projectFinal, type, tag, numResources); + checkDomainResourceLimit(domainIdFinal, type, tag, numResources); } }); } @@ -1208,7 +1219,7 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim * @param type the resource type to do the recalculation for * @return the resulting new resource count */ - protected long recalculateDomainResourceCount(final long domainId, final ResourceType type, String tag) { + public long recalculateDomainResourceCount(final long domainId, final ResourceType type, String tag) { List accounts = _accountDao.findActiveAccountsForDomain(domainId); List childDomains = _domainDao.findImmediateChildrenForParent(domainId); @@ -1244,9 +1255,8 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim long newResourceCount = 0L; ResourceCountVO domainRC = null; - // calculate project count here - if (type == ResourceType.project) { - newResourceCount += _projectDao.countProjectsForDomain(domainId); + if (type == ResourceType.network) { + newResourceCount += networkDomainDao.listDomainNetworkMapByDomain(domainId).size(); } // TODO make sure that the resource counts are not null @@ -1493,7 +1503,7 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim long totalTemplatesSize = 0; SearchCriteria sc = templateSizeSearch.create(); - sc.setParameters("downloadState", Status.DOWNLOADED); + sc.setParameters("downloadState", Status.DOWNLOADED, Status.DOWNLOAD_IN_PROGRESS); sc.setParameters("destroyed", false); sc.setJoinParameters("templates", "accountId", accountId); List templates = _vmTemplateStoreDao.customSearch(sc, null); @@ -1730,7 +1740,8 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim return tags; } - protected List getResourceLimitStorageTagsForResourceCountOperation(Boolean display, DiskOffering diskOffering) { + @Override + public List getResourceLimitStorageTagsForResourceCountOperation(Boolean display, DiskOffering diskOffering) { if (Boolean.FALSE.equals(display)) { return new ArrayList<>(); } @@ -1744,54 +1755,53 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim } @Override - public void checkVolumeResourceLimit(Account owner, Boolean display, Long size, DiskOffering diskOffering) throws ResourceAllocationException { + public void checkVolumeResourceLimit(Account owner, Boolean display, Long size, DiskOffering diskOffering, List reservations) throws ResourceAllocationException { List tags = getResourceLimitStorageTagsForResourceCountOperation(display, diskOffering); if (CollectionUtils.isEmpty(tags)) { return; } - for (String tag : tags) { - checkResourceLimitWithTag(owner, ResourceType.volume, tag); - if (size != null) { - checkResourceLimitWithTag(owner, ResourceType.primary_storage, tag, size); - } + + CheckedReservation volumeReservation = new CheckedReservation(owner, ResourceType.volume, tags, 1L, reservationDao, resourceLimitService); + reservations.add(volumeReservation); + + if (size != null) { + CheckedReservation primaryStorageReservation = new CheckedReservation(owner, ResourceType.primary_storage, tags, size, reservationDao, resourceLimitService); + reservations.add(primaryStorageReservation); } } @Override - public void checkPrimaryStorageResourceLimit(Account owner, Boolean display, Long size, DiskOffering diskOffering) throws ResourceAllocationException { + public void checkPrimaryStorageResourceLimit(Account owner, Boolean display, Long size, DiskOffering diskOffering, List reservations) throws ResourceAllocationException { List tags = getResourceLimitStorageTagsForResourceCountOperation(display, diskOffering); if (CollectionUtils.isEmpty(tags)) { return; } - if (size != null) { - for (String tag : tags) { - checkResourceLimitWithTag(owner, ResourceType.primary_storage, tag, size); - } - } + CheckedReservation primaryStorageReservation = new CheckedReservation(owner, ResourceType.primary_storage, tags, size, reservationDao, resourceLimitService); + reservations.add(primaryStorageReservation); } @Override public void checkVolumeResourceLimitForDiskOfferingChange(Account owner, Boolean display, Long currentSize, Long newSize, - DiskOffering currentOffering, DiskOffering newOffering + DiskOffering currentOffering, DiskOffering newOffering, List reservations ) throws ResourceAllocationException { Ternary, Set, Set> updatedResourceLimitStorageTags = getResourceLimitStorageTagsForDiskOfferingChange(display, currentOffering, newOffering); if (updatedResourceLimitStorageTags == null) { return; } - Set sameTags = updatedResourceLimitStorageTags.first(); - Set newTags = updatedResourceLimitStorageTags.second(); - - if (newSize > currentSize) { - for (String tag : sameTags) { - checkResourceLimitWithTag(owner, ResourceType.primary_storage, tag, newSize - currentSize); - } + List currentTags = getResourceLimitStorageTagsForResourceCountOperation(true, currentOffering); + List tagsAfterUpdate = getResourceLimitStorageTagsForResourceCountOperation(true, newOffering); + if (currentTags.isEmpty() && tagsAfterUpdate.isEmpty()) { + return; } - for (String tag : newTags) { - checkResourceLimitWithTag(owner, ResourceType.volume, tag, 1L); - checkResourceLimitWithTag(owner, ResourceType.primary_storage, tag, newSize); - } + CheckedReservation volumeReservation = new CheckedReservation(owner, ResourceType.volume, null, tagsAfterUpdate, + currentTags, 1L, 1L, reservationDao, resourceLimitService); + reservations.add(volumeReservation); + + CheckedReservation primaryStorageReservation = new CheckedReservation(owner, ResourceType.primary_storage, null, + tagsAfterUpdate, currentTags, newSize, currentSize, reservationDao, resourceLimitService); + reservations.add(primaryStorageReservation); } @DB @@ -2018,20 +2028,27 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim } @Override - public void checkVmResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template) throws ResourceAllocationException { + public void checkVmResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, List reservations) throws ResourceAllocationException { List tags = getResourceLimitHostTagsForResourceCountOperation(display, serviceOffering, template); if (CollectionUtils.isEmpty(tags)) { return; } + + CheckedReservation vmReservation = new CheckedReservation(owner, ResourceType.user_vm, tags, 1L, reservationDao, resourceLimitService); + reservations.add(vmReservation); + Long cpu = serviceOffering.getCpu() != null ? Long.valueOf(serviceOffering.getCpu()) : 0L; + CheckedReservation cpuReservation = new CheckedReservation(owner, ResourceType.cpu, tags, cpu, reservationDao, resourceLimitService); + reservations.add(cpuReservation); + Long ram = serviceOffering.getRamSize() != null ? Long.valueOf(serviceOffering.getRamSize()) : 0L; + CheckedReservation memReservation = new CheckedReservation(owner, ResourceType.memory, tags, ram, reservationDao, resourceLimitService); + reservations.add(memReservation); + Long gpu = serviceOffering.getGpuCount() != null ? Long.valueOf(serviceOffering.getGpuCount()) : 0L; - for (String tag : tags) { - checkResourceLimitWithTag(owner, ResourceType.user_vm, tag); - checkResourceLimitWithTag(owner, ResourceType.cpu, tag, cpu); - checkResourceLimitWithTag(owner, ResourceType.memory, tag, ram); - checkResourceLimitWithTag(owner, ResourceType.gpu, tag, gpu); - } + CheckedReservation gpuReservation = new CheckedReservation(owner, ResourceType.gpu, tags, gpu, reservationDao, resourceLimitService); + reservations.add(gpuReservation); + } @Override @@ -2081,83 +2098,60 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim @Override public void checkVmResourceLimitsForTemplateChange(Account owner, Boolean display, ServiceOffering offering, - VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate) throws ResourceAllocationException { + VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate, List reservations) throws ResourceAllocationException { checkVmResourceLimitsForServiceOfferingAndTemplateChange(owner, display, null, null, - null, null, offering, offering, currentTemplate, newTemplate); + null, null, offering, offering, currentTemplate, newTemplate, reservations); } @Override public void checkVmResourceLimitsForServiceOfferingChange(Account owner, Boolean display, Long currentCpu, Long newCpu, Long currentMemory, Long newMemory, - ServiceOffering currentOffering, ServiceOffering newOffering, VirtualMachineTemplate template + ServiceOffering currentOffering, ServiceOffering newOffering, VirtualMachineTemplate template, List reservations ) throws ResourceAllocationException { checkVmResourceLimitsForServiceOfferingAndTemplateChange(owner, display, currentCpu, newCpu, currentMemory, newMemory, currentOffering, - newOffering != null ? newOffering : currentOffering, template, template); + newOffering != null ? newOffering : currentOffering, template, template, reservations); } private void checkVmResourceLimitsForServiceOfferingAndTemplateChange(Account owner, Boolean display, Long currentCpu, Long newCpu, Long currentMemory, Long newMemory, ServiceOffering currentOffering, ServiceOffering newOffering, - VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate + VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate, List reservations ) throws ResourceAllocationException { - Ternary, Set, Set> updatedResourceLimitHostTags = getResourceLimitHostTagsForVmServiceOfferingAndTemplateChange(display, currentOffering, newOffering, currentTemplate, newTemplate); - if (updatedResourceLimitHostTags == null) { + List currentTags = getResourceLimitHostTagsForResourceCountOperation(true, currentOffering, currentTemplate); + List tagsAfterUpdate = getResourceLimitHostTagsForResourceCountOperation(true, newOffering, newTemplate); + if (currentTags.isEmpty() && tagsAfterUpdate.isEmpty()) { return; } + CheckedReservation vmReservation = new CheckedReservation(owner, ResourceType.user_vm, null, tagsAfterUpdate, + currentTags, 1L, 1L, reservationDao, resourceLimitService); + reservations.add(vmReservation); + if (currentCpu == null) { currentCpu = currentOffering.getCpu() != null ? Long.valueOf(currentOffering.getCpu()) : 0L; } if (newCpu == null) { newCpu = newOffering.getCpu() != null ? Long.valueOf(newOffering.getCpu()) : 0L; } + CheckedReservation cpuReservation = new CheckedReservation(owner, ResourceType.cpu, null, tagsAfterUpdate, + currentTags, newCpu, currentCpu, reservationDao, resourceLimitService); + reservations.add(cpuReservation); + if (currentMemory == null) { currentMemory = currentOffering.getRamSize() != null ? Long.valueOf(currentOffering.getRamSize()) : 0L; } if (newMemory == null) { newMemory = newOffering.getRamSize() != null ? Long.valueOf(newOffering.getRamSize()) : 0L; } + CheckedReservation memReservation = new CheckedReservation(owner, ResourceType.memory, null, tagsAfterUpdate, + currentTags, newMemory, currentMemory, reservationDao, resourceLimitService); + reservations.add(memReservation); + Long currentGpu = currentOffering.getGpuCount() != null ? Long.valueOf(currentOffering.getGpuCount()) : 0L; Long newGpu = newOffering.getGpuCount() != null ? Long.valueOf(newOffering.getGpuCount()) : 0L; - Set sameTags = updatedResourceLimitHostTags.first(); - Set newTags = updatedResourceLimitHostTags.second(); - - if (newCpu - currentCpu > 0 || newMemory - currentMemory > 0 || newGpu - currentGpu > 0) { - for (String tag : sameTags) { - if (newCpu - currentCpu > 0) { - checkResourceLimitWithTag(owner, ResourceType.cpu, tag, newCpu - currentCpu); - } - - if (newMemory - currentMemory > 0) { - checkResourceLimitWithTag(owner, ResourceType.memory, tag, newMemory - currentMemory); - } - - if (newGpu - currentGpu > 0) { - checkResourceLimitWithTag(owner, ResourceType.gpu, tag, newGpu - currentGpu); - } - } - } - - for (String tag : newTags) { - checkResourceLimitWithTag(owner, ResourceType.user_vm, tag, 1L); - checkResourceLimitWithTag(owner, ResourceType.cpu, tag, newCpu); - checkResourceLimitWithTag(owner, ResourceType.memory, tag, newMemory); - checkResourceLimitWithTag(owner, ResourceType.gpu, tag, newGpu); - } - } - - @Override - public void checkVmCpuResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long cpu) throws ResourceAllocationException { - List tags = getResourceLimitHostTagsForResourceCountOperation(display, serviceOffering, template); - if (CollectionUtils.isEmpty(tags)) { - return; - } - if (cpu == null) { - cpu = serviceOffering.getCpu() != null ? Long.valueOf(serviceOffering.getCpu()) : 0L; - } - for (String tag : tags) { - checkResourceLimitWithTag(owner, ResourceType.cpu, tag, cpu); - } + CheckedReservation gpuReservation = new CheckedReservation(owner, ResourceType.gpu, null, tagsAfterUpdate, + currentTags, newGpu, currentGpu, reservationDao, resourceLimitService); + reservations.add(gpuReservation); } @Override @@ -2188,20 +2182,6 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim } } - @Override - public void checkVmMemoryResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long memory) throws ResourceAllocationException { - List tags = getResourceLimitHostTagsForResourceCountOperation(display, serviceOffering, template); - if (CollectionUtils.isEmpty(tags)) { - return; - } - if (memory == null) { - memory = serviceOffering.getRamSize() != null ? Long.valueOf(serviceOffering.getRamSize()) : 0L; - } - for (String tag : tags) { - checkResourceLimitWithTag(owner, ResourceType.memory, tag, memory); - } - } - @Override public void incrementVmMemoryResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long memory) { List tags = getResourceLimitHostTagsForResourceCountOperation(display, serviceOffering, template); @@ -2230,20 +2210,6 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim } } - @Override - public void checkVmGpuResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long gpu) throws ResourceAllocationException { - List tags = getResourceLimitHostTagsForResourceCountOperation(display, serviceOffering, template); - if (CollectionUtils.isEmpty(tags)) { - return; - } - if (gpu == null) { - gpu = serviceOffering.getGpuCount() != null ? Long.valueOf(serviceOffering.getGpuCount()) : 0L; - } - for (String tag : tags) { - checkResourceLimitWithTag(owner, ResourceType.gpu, tag, gpu); - } - } - @Override public void incrementVmGpuResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long gpu) { List tags = getResourceLimitHostTagsForResourceCountOperation(display, serviceOffering, template); diff --git a/server/src/main/java/com/cloud/storage/ImageStoreUploadMonitorImpl.java b/server/src/main/java/com/cloud/storage/ImageStoreUploadMonitorImpl.java index 334e9f10835..5c6f8dc6599 100755 --- a/server/src/main/java/com/cloud/storage/ImageStoreUploadMonitorImpl.java +++ b/server/src/main/java/com/cloud/storage/ImageStoreUploadMonitorImpl.java @@ -25,7 +25,6 @@ import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.agent.api.to.OVFInformationTO; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; @@ -37,6 +36,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.cloudstack.storage.command.UploadStatusAnswer; import org.apache.cloudstack.storage.command.UploadStatusAnswer.UploadStatus; import org.apache.cloudstack.storage.command.UploadStatusCommand; @@ -55,6 +55,7 @@ import com.cloud.agent.api.AgentControlCommand; import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.to.OVFInformationTO; import com.cloud.alert.AlertManager; import com.cloud.api.query.dao.TemplateJoinDao; import com.cloud.api.query.vo.TemplateJoinVO; @@ -62,15 +63,20 @@ import com.cloud.configuration.Resource; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; import com.cloud.exception.ConnectionException; +import com.cloud.exception.ResourceAllocationException; import com.cloud.host.Host; import com.cloud.host.Status; import com.cloud.host.dao.HostDao; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.storage.Volume.Event; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateZoneDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.template.VirtualMachineTemplate; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; import com.cloud.user.ResourceLimitService; +import com.cloud.user.dao.AccountDao; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.Transaction; @@ -117,6 +123,12 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto private TemplateJoinDao templateJoinDao; @Inject private DeployAsIsHelper deployAsIsHelper; + @Inject + private AccountDao accountDao; + @Inject + private AccountManager _accountMgr; + @Inject + private ReservationDao reservationDao; private long _nodeId; private ScheduledExecutorService _executor = null; @@ -205,6 +217,36 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto public UploadStatusCheck() { } + private Answer sendUploadStatusCommandForVolume(EndPoint ep, UploadStatusCommand cmd, VolumeVO volume) { + Answer answer = null; + try { + answer = ep.sendMessage(cmd); + } catch (CloudRuntimeException e) { + logger.warn("Unable to get upload status for volume {}. Error details: {}", volume, e.getMessage()); + answer = new UploadStatusAnswer(cmd, UploadStatus.UNKNOWN, e.getMessage()); + } + if (answer == null || !(answer instanceof UploadStatusAnswer)) { + logger.warn("No or invalid answer corresponding to UploadStatusCommand for volume {}", volume); + return null; + } + return answer; + } + + private Answer sendUploadStatusCommandForTemplate(EndPoint ep, UploadStatusCommand cmd, VMTemplateVO template) { + Answer answer = null; + try { + answer = ep.sendMessage(cmd); + } catch (CloudRuntimeException e) { + logger.warn("Unable to get upload status for template {}. Error details: {}", template, e.getMessage()); + answer = new UploadStatusAnswer(cmd, UploadStatus.UNKNOWN, e.getMessage()); + } + if (answer == null || !(answer instanceof UploadStatusAnswer)) { + logger.warn("No or invalid answer corresponding to UploadStatusCommand for template {}", template); + return null; + } + return answer; + } + @Override protected void runInContext() { // 1. Select all entries with download_state = Not_Downloaded or Download_In_Progress @@ -231,18 +273,17 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto UploadStatusCommand cmd = new UploadStatusCommand(volume.getUuid(), EntityType.Volume); if (host != null && host.getManagementServerId() != null) { if (_nodeId == host.getManagementServerId().longValue()) { - Answer answer = null; - try { - answer = ep.sendMessage(cmd); - } catch (CloudRuntimeException e) { - logger.warn("Unable to get upload status for volume {}. Error details: {}", volume, e.getMessage()); - answer = new UploadStatusAnswer(cmd, UploadStatus.UNKNOWN, e.getMessage()); - } - if (answer == null || !(answer instanceof UploadStatusAnswer)) { - logger.warn("No or invalid answer corresponding to UploadStatusCommand for volume {}", volume); + Answer answer = sendUploadStatusCommandForVolume(ep, cmd, volume); + if (answer == null) { continue; } - handleVolumeStatusResponse((UploadStatusAnswer)answer, volume, volumeDataStore); + if (!handleVolumeStatusResponse((UploadStatusAnswer)answer, volume, volumeDataStore)) { + cmd = new UploadStatusCommand(volume.getUuid(), EntityType.Volume, true); + answer = sendUploadStatusCommandForVolume(ep, cmd, volume); + if (answer == null) { + logger.warn("Unable to abort upload for volume {}", volume); + } + } } } else { String error = "Volume " + volume.getUuid() + " failed to upload as SSVM is either destroyed or SSVM agent not in 'Up' state"; @@ -275,18 +316,17 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto UploadStatusCommand cmd = new UploadStatusCommand(template.getUuid(), EntityType.Template); if (host != null && host.getManagementServerId() != null) { if (_nodeId == host.getManagementServerId().longValue()) { - Answer answer = null; - try { - answer = ep.sendMessage(cmd); - } catch (CloudRuntimeException e) { - logger.warn("Unable to get upload status for template {}. Error details: {}", template, e.getMessage()); - answer = new UploadStatusAnswer(cmd, UploadStatus.UNKNOWN, e.getMessage()); - } - if (answer == null || !(answer instanceof UploadStatusAnswer)) { - logger.warn("No or invalid answer corresponding to UploadStatusCommand for template {}", template); + Answer answer = sendUploadStatusCommandForTemplate(ep, cmd, template); + if (answer == null) { continue; } - handleTemplateStatusResponse((UploadStatusAnswer)answer, template, templateDataStore); + if (!handleTemplateStatusResponse((UploadStatusAnswer) answer, template, templateDataStore)) { + cmd = new UploadStatusCommand(template.getUuid(), EntityType.Template, true); + answer = sendUploadStatusCommandForTemplate(ep, cmd, template); + if (answer == null) { + logger.warn("Unable to abort upload for template {}", template); + } + } } } else { String error = String.format( @@ -303,7 +343,41 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto } } - private void handleVolumeStatusResponse(final UploadStatusAnswer answer, final VolumeVO volume, final VolumeDataStoreVO volumeDataStore) { + private Boolean checkAndUpdateSecondaryStorageResourceLimit(Long accountId, Long lastSize, Long currentSize) { + if (lastSize >= currentSize) { + return true; + } + Long usage = currentSize - lastSize; + try (CheckedReservation secStorageReservation = new CheckedReservation(_accountMgr.getAccount(accountId), Resource.ResourceType.secondary_storage, null, null, usage, reservationDao, _resourceLimitMgr)) { + _resourceLimitMgr.incrementResourceCount(accountId, Resource.ResourceType.secondary_storage, usage); + return true; + } catch (Exception e) { + _resourceLimitMgr.decrementResourceCount(accountId, Resource.ResourceType.secondary_storage, lastSize); + return false; + } + } + + private Boolean checkAndUpdateVolumeResourceLimit(VolumeVO volume, VolumeDataStoreVO volumeDataStore, UploadStatusAnswer answer) { + boolean success = true; + Long currentSize = answer.getVirtualSize() != 0 ? answer.getVirtualSize() : answer.getPhysicalSize(); + Long lastSize = volume.getSize() != null ? volume.getSize() : 0L; + if (!checkAndUpdateSecondaryStorageResourceLimit(volume.getAccountId(), volume.getSize(), currentSize)) { + volumeDataStore.setDownloadState(VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR); + volumeDataStore.setState(State.Failed); + volumeDataStore.setErrorString("Storage Limit Reached"); + Account owner = accountDao.findById(volume.getAccountId()); + String msg = String.format("Upload of volume [%s] failed because its owner [%s] does not have enough secondary storage space available.", volume.getUuid(), owner.getUuid()); + logger.error(msg); + success = false; + } + VolumeVO volumeUpdate = _volumeDao.findById(volume.getId()); + volumeUpdate.setSize(currentSize); + _volumeDao.update(volumeUpdate.getId(), volumeUpdate); + return success; + } + + private boolean handleVolumeStatusResponse(final UploadStatusAnswer answer, final VolumeVO volume, final VolumeDataStoreVO volumeDataStore) { + final boolean[] needAbort = new boolean[]{false}; final StateMachine2 stateMachine = Volume.State.getStateMachine(); Transaction.execute(new TransactionCallbackNoReturn() { @Override @@ -315,6 +389,11 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto try { switch (answer.getStatus()) { case COMPLETED: + if (!checkAndUpdateVolumeResourceLimit(tmpVolume, tmpVolumeDataStore, answer)) { + stateMachine.transitTo(tmpVolume, Event.OperationFailed, null, _volumeDao); + sendAlert = true; + break; + } tmpVolumeDataStore.setDownloadState(VMTemplateStorageResourceAssoc.Status.DOWNLOADED); tmpVolumeDataStore.setState(State.Ready); tmpVolumeDataStore.setInstallPath(answer.getInstallPath()); @@ -326,7 +405,6 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto volumeUpdate.setSize(answer.getVirtualSize()); _volumeDao.update(tmpVolume.getId(), volumeUpdate); stateMachine.transitTo(tmpVolume, Event.OperationSucceeded, null, _volumeDao); - _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), Resource.ResourceType.secondary_storage, answer.getVirtualSize()); // publish usage events UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_UPLOAD, tmpVolume.getAccountId(), @@ -339,6 +417,12 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto } break; case IN_PROGRESS: + if (!checkAndUpdateVolumeResourceLimit(tmpVolume, tmpVolumeDataStore, answer)) { + stateMachine.transitTo(tmpVolume, Event.OperationFailed, null, _volumeDao); + sendAlert = true; + needAbort[0] = true; + break; + } if (tmpVolume.getState() == Volume.State.NotUploaded) { tmpVolumeDataStore.setDownloadState(VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS); tmpVolumeDataStore.setDownloadPercent(answer.getDownloadPercent()); @@ -387,10 +471,29 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto } } }); + return !needAbort[0]; } - private void handleTemplateStatusResponse(final UploadStatusAnswer answer, final VMTemplateVO template, final TemplateDataStoreVO templateDataStore) { + private Boolean checkAndUpdateTemplateResourceLimit(VMTemplateVO template, TemplateDataStoreVO templateDataStore, UploadStatusAnswer answer) { + boolean success = true; + Long currentSize = answer.getVirtualSize() != 0 ? answer.getVirtualSize() : answer.getPhysicalSize(); + Long lastSize = template.getSize() != null ? template.getSize() : 0L; + if (!checkAndUpdateSecondaryStorageResourceLimit(template.getAccountId(), lastSize, currentSize)) { + templateDataStore.setDownloadState(VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR); + templateDataStore.setErrorString("Storage Limit Reached"); + templateDataStore.setState(State.Failed); + Account owner = accountDao.findById(template.getAccountId()); + String msg = String.format("Upload of template [%s] failed because its owner [%s] does not have enough secondary storage space available.", template.getUuid(), owner.getUuid()); + logger.error(msg); + success = false; + } + templateDataStore.setSize(currentSize); + return success; + } + + private boolean handleTemplateStatusResponse(final UploadStatusAnswer answer, final VMTemplateVO template, final TemplateDataStoreVO templateDataStore) { final StateMachine2 stateMachine = VirtualMachineTemplate.State.getStateMachine(); + final boolean[] needAbort = new boolean[]{false}; Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { @@ -401,6 +504,11 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto try { switch (answer.getStatus()) { case COMPLETED: + if (!checkAndUpdateTemplateResourceLimit(tmpTemplate, tmpTemplateDataStore, answer)) { + stateMachine.transitTo(tmpTemplate, VirtualMachineTemplate.Event.OperationFailed, null, _templateDao); + sendAlert = true; + break; + } tmpTemplateDataStore.setDownloadState(VMTemplateStorageResourceAssoc.Status.DOWNLOADED); tmpTemplateDataStore.setState(State.Ready); tmpTemplateDataStore.setInstallPath(answer.getInstallPath()); @@ -436,8 +544,23 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto break; } } + + Account owner = accountDao.findById(template.getAccountId()); + long templateSize = answer.getVirtualSize(); + + try (CheckedReservation secondaryStorageReservation = new CheckedReservation(owner, Resource.ResourceType.secondary_storage, null, null, templateSize, reservationDao, _resourceLimitMgr)) { + _resourceLimitMgr.incrementResourceCount(owner.getId(), Resource.ResourceType.secondary_storage, templateSize); + } catch (ResourceAllocationException e) { + tmpTemplateDataStore.setDownloadState(VMTemplateStorageResourceAssoc.Status.UPLOAD_ERROR); + tmpTemplateDataStore.setState(State.Failed); + stateMachine.transitTo(tmpTemplate, VirtualMachineTemplate.Event.OperationFailed, null, _templateDao); + msg = String.format("Upload of template [%s] failed because its owner [%s] does not have enough secondary storage space available.", template.getUuid(), owner.getUuid()); + logger.warn(msg); + sendAlert = true; + break; + } + stateMachine.transitTo(tmpTemplate, VirtualMachineTemplate.Event.OperationSucceeded, null, _templateDao); - _resourceLimitMgr.incrementResourceCount(template.getAccountId(), Resource.ResourceType.secondary_storage, answer.getVirtualSize()); //publish usage event String etype = EventTypes.EVENT_TEMPLATE_CREATE; if (tmpTemplate.getFormat() == Storage.ImageFormat.ISO) { @@ -453,6 +576,12 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto } break; case IN_PROGRESS: + if (!checkAndUpdateTemplateResourceLimit(tmpTemplate, tmpTemplateDataStore, answer)) { + stateMachine.transitTo(tmpTemplate, VirtualMachineTemplate.Event.OperationFailed, null, _templateDao); + sendAlert = true; + needAbort[0] = true; + break; + } if (tmpTemplate.getState() == VirtualMachineTemplate.State.NotUploaded) { tmpTemplateDataStore.setDownloadState(VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS); stateMachine.transitTo(tmpTemplate, VirtualMachineTemplate.Event.UploadRequested, null, _templateDao); @@ -502,6 +631,7 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto } } }); + return !needAbort[0]; } } diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 17961dbd955..c5d85e6eef6 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -35,16 +35,11 @@ import java.util.stream.Collectors; import javax.inject.Inject; -import com.cloud.projects.Project; -import com.cloud.projects.ProjectManager; -import com.cloud.vm.snapshot.VMSnapshot; -import com.cloud.vm.snapshot.VMSnapshotDetailsVO; -import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao; -import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.InternalIdentity; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd; import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd; import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd; import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd; @@ -94,10 +89,12 @@ import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import org.apache.cloudstack.framework.jobs.impl.OutcomeImpl; import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; import org.apache.cloudstack.jobs.JobInfo; +import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO; import org.apache.cloudstack.resourcedetail.SnapshotPolicyDetailVO; import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; import org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDao; +import org.apache.cloudstack.resourcelimit.Reserver; import org.apache.cloudstack.snapshot.SnapshotHelper; import org.apache.cloudstack.storage.command.AttachAnswer; import org.apache.cloudstack.storage.command.AttachCommand; @@ -166,8 +163,12 @@ import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao; import com.cloud.offering.DiskOffering; import com.cloud.org.Cluster; import com.cloud.org.Grouping; +import com.cloud.projects.Project; +import com.cloud.projects.ProjectManager; import com.cloud.resource.ResourceManager; import com.cloud.resource.ResourceState; +import com.cloud.resourcelimit.CheckedReservation; +import com.cloud.resourcelimit.ReservationHelper; import com.cloud.serializer.GsonHelper; import com.cloud.server.ManagementService; import com.cloud.server.ResourceTag; @@ -242,8 +243,12 @@ import com.cloud.vm.VmWorkTakeVolumeSnapshot; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.VMSnapshot; +import com.cloud.vm.snapshot.VMSnapshotDetailsVO; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; @@ -351,10 +356,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic private ManagementService managementService; @Inject protected SnapshotHelper snapshotHelper; - @Inject protected DomainDao domainDao; - @Inject protected ProjectManager projectManager; @Inject @@ -367,7 +370,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic HostPodDao podDao; @Inject EndPointSelector _epSelector; - + @Inject + private ReservationDao reservationDao; @Inject private VMSnapshotDetailsDao vmSnapshotDetailsDao; @@ -440,9 +444,17 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic Long diskOfferingId = cmd.getDiskOfferingId(); String imageStoreUuid = cmd.getImageStoreUuid(); - validateVolume(caller, ownerId, zoneId, volumeName, url, format, diskOfferingId); + VolumeVO volume; - VolumeVO volume = persistVolume(owner, zoneId, volumeName, url, format, diskOfferingId, Volume.State.Allocated); + List reservations = new ArrayList<>(); + try { + + validateVolume(caller, ownerId, zoneId, volumeName, url, format, diskOfferingId, reservations); + volume = persistVolume(owner, zoneId, volumeName, url, format, diskOfferingId, Volume.State.Allocated); + + } finally { + ReservationHelper.closeAll(reservations); + } VolumeInfo vol = volFactory.getVolume(volume.getId()); @@ -482,80 +494,85 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic final Long diskOfferingId = cmd.getDiskOfferingId(); String imageStoreUuid = cmd.getImageStoreUuid(); - validateVolume(caller, ownerId, zoneId, volumeName, null, format, diskOfferingId); + List reservations = new ArrayList<>(); + try { + validateVolume(caller, ownerId, zoneId, volumeName, null, format, diskOfferingId, reservations); - return Transaction.execute(new TransactionCallbackWithException() { - @Override - public GetUploadParamsResponse doInTransaction(TransactionStatus status) throws MalformedURLException { + return Transaction.execute(new TransactionCallbackWithException() { + @Override + public GetUploadParamsResponse doInTransaction(TransactionStatus status) throws MalformedURLException { - VolumeVO volume = persistVolume(owner, zoneId, volumeName, null, format, diskOfferingId, Volume.State.NotUploaded); + VolumeVO volume = persistVolume(owner, zoneId, volumeName, null, format, diskOfferingId, Volume.State.NotUploaded); - final DataStore store = _tmpltMgr.getImageStore(imageStoreUuid, zoneId, volume); + final DataStore store = _tmpltMgr.getImageStore(imageStoreUuid, zoneId, volume); - VolumeInfo vol = volFactory.getVolume(volume.getId()); + VolumeInfo vol = volFactory.getVolume(volume.getId()); - RegisterVolumePayload payload = new RegisterVolumePayload(null, cmd.getChecksum(), format); - vol.addPayload(payload); + RegisterVolumePayload payload = new RegisterVolumePayload(null, cmd.getChecksum(), format); + vol.addPayload(payload); - Pair pair = volService.registerVolumeForPostUpload(vol, store); - EndPoint ep = pair.first(); - DataObject dataObject = pair.second(); + Pair pair = volService.registerVolumeForPostUpload(vol, store); + EndPoint ep = pair.first(); + DataObject dataObject = pair.second(); - GetUploadParamsResponse response = new GetUploadParamsResponse(); + GetUploadParamsResponse response = new GetUploadParamsResponse(); - String ssvmUrlDomain = _configDao.getValue(Config.SecStorageSecureCopyCert.key()); - String protocol = UseHttpsToUpload.valueIn(zoneId) ? "https" : "http"; + String ssvmUrlDomain = _configDao.getValue(Config.SecStorageSecureCopyCert.key()); + String protocol = UseHttpsToUpload.value() ? "https" : "http"; - String url = ImageStoreUtil.generatePostUploadUrl(ssvmUrlDomain, ep.getPublicAddr(), vol.getUuid(), - protocol); - response.setPostURL(new URL(url)); + String url = ImageStoreUtil.generatePostUploadUrl(ssvmUrlDomain, ep.getPublicAddr(), vol.getUuid(), protocol); + response.setPostURL(new URL(url)); - // set the post url, this is used in the monitoring thread to determine the SSVM - VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(vol.getId()); - assert (volumeStore != null) : "sincle volume is registered, volumestore cannot be null at this stage"; - volumeStore.setExtractUrl(url); - _volumeStoreDao.persist(volumeStore); + // set the post url, this is used in the monitoring thread to determine the SSVM + VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(vol.getId()); + assert (volumeStore != null) : "sincle volume is registered, volumestore cannot be null at this stage"; + volumeStore.setExtractUrl(url); + _volumeStoreDao.persist(volumeStore); - response.setId(UUID.fromString(vol.getUuid())); + response.setId(UUID.fromString(vol.getUuid())); - int timeout = ImageStoreUploadMonitorImpl.getUploadOperationTimeout(); - DateTime currentDateTime = new DateTime(DateTimeZone.UTC); - String expires = currentDateTime.plusMinutes(timeout).toString(); - response.setTimeout(expires); + int timeout = ImageStoreUploadMonitorImpl.getUploadOperationTimeout(); + DateTime currentDateTime = new DateTime(DateTimeZone.UTC); + String expires = currentDateTime.plusMinutes(timeout).toString(); + response.setTimeout(expires); - String key = _configDao.getValue(Config.SSVMPSK.key()); - /* - * encoded metadata using the post upload config key - */ - TemplateOrVolumePostUploadCommand command = new TemplateOrVolumePostUploadCommand(vol.getId(), - vol.getUuid(), volumeStore.getInstallPath(), cmd.getChecksum(), vol.getType().toString(), - vol.getName(), vol.getFormat().toString(), dataObject.getDataStore().getUri(), - dataObject.getDataStore().getRole().toString(), zoneId); - command.setLocalPath(volumeStore.getLocalDownloadPath()); - //using the existing max upload size configuration - command.setProcessTimeout(NumbersUtil.parseLong(_configDao.getValue("vmware.package.ova.timeout"), 3600)); - command.setMaxUploadSize(_configDao.getValue(Config.MaxUploadVolumeSize.key())); + String key = _configDao.getValue(Config.SSVMPSK.key()); + /* + * encoded metadata using the post upload config key + */ + TemplateOrVolumePostUploadCommand command = new TemplateOrVolumePostUploadCommand(vol.getId(), + vol.getUuid(), volumeStore.getInstallPath(), cmd.getChecksum(), vol.getType().toString(), + vol.getName(), vol.getFormat().toString(), dataObject.getDataStore().getUri(), + dataObject.getDataStore().getRole().toString(), zoneId); + command.setLocalPath(volumeStore.getLocalDownloadPath()); + //using the existing max upload size configuration + command.setProcessTimeout(NumbersUtil.parseLong(_configDao.getValue("vmware.package.ova.timeout"), 3600)); + command.setMaxUploadSize(_configDao.getValue(Config.MaxUploadVolumeSize.key())); - long accountId = vol.getAccountId(); - Account account = _accountDao.findById(accountId); - Domain domain = domainDao.findById(account.getDomainId()); + long accountId = vol.getAccountId(); + Account account = _accountDao.findById(accountId); + Domain domain = domainDao.findById(account.getDomainId()); - command.setDefaultMaxSecondaryStorageInGB(_resourceLimitMgr.findCorrectResourceLimitForAccountAndDomain(account, domain, ResourceType.secondary_storage, null)); - command.setAccountId(accountId); - Gson gson = new GsonBuilder().create(); - String metadata = EncryptionUtil.encodeData(gson.toJson(command), key); - response.setMetadata(metadata); + command.setDefaultMaxSecondaryStorageInBytes(_resourceLimitMgr.findCorrectResourceLimitForAccountAndDomain(account, domain, ResourceType.secondary_storage, null)); + command.setAccountId(accountId); + Gson gson = new GsonBuilder().create(); + String metadata = EncryptionUtil.encodeData(gson.toJson(command), key); + response.setMetadata(metadata); - /* - * signature calculated on the url, expiry, metadata. - */ - response.setSignature(EncryptionUtil.generateSignature(metadata + url + expires, key)); - return response; - } - }); + /* + * signature calculated on the url, expiry, metadata. + */ + response.setSignature(EncryptionUtil.generateSignature(metadata + url + expires, key)); + return response; + } + }); + + } finally { + ReservationHelper.closeAll(reservations); + } } - private boolean validateVolume(Account caller, long ownerId, Long zoneId, String volumeName, String url, String format, Long diskOfferingId) throws ResourceAllocationException { + private boolean validateVolume(Account caller, long ownerId, Long zoneId, String volumeName, String url, String format, Long diskOfferingId, List reservations) throws ResourceAllocationException { // permission check Account volumeOwner = _accountMgr.getActiveAccountById(ownerId); @@ -566,7 +583,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic _accountMgr.checkAccess(caller, null, true, volumeOwner); // Check that the resource limit for volumes won't be exceeded - _resourceLimitMgr.checkVolumeResourceLimit(volumeOwner, true, null, diskOffering); + _resourceLimitMgr.checkVolumeResourceLimit(volumeOwner, true, null, diskOffering, reservations); // Verify that zone exists DataCenterVO zone = _dcDao.findById(zoneId); @@ -940,30 +957,39 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic Storage.ProvisioningType provisioningType = diskOffering.getProvisioningType(); - // Check that the resource limit for volume & primary storage won't be exceeded - _resourceLimitMgr.checkVolumeResourceLimit(owner,displayVolume, size, diskOffering); - - // Verify that zone exists - DataCenterVO zone = _dcDao.findById(zoneId); - if (zone == null) { - throw new InvalidParameterValueException("Unable to find zone by id " + zoneId); + List tags = _resourceLimitMgr.getResourceLimitStorageTagsForResourceCountOperation(displayVolume, diskOffering); + if (tags.size() == 1 && tags.get(0) == null) { + tags = new ArrayList<>(); } - // Check if zone is disabled - if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())) { - throw new PermissionDeniedException(String.format("Cannot perform this operation, Zone: %s is currently disabled", zone)); + List reservations = new ArrayList<>(); + try { + _resourceLimitMgr.checkVolumeResourceLimit(owner, displayVolume, size, diskOffering, reservations); + + // Verify that zone exists + DataCenterVO zone = _dcDao.findById(zoneId); + if (zone == null) { + throw new InvalidParameterValueException("Unable to find zone by id " + zoneId); + } + + // Check if zone is disabled + if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())) { + throw new PermissionDeniedException(String.format("Cannot perform this operation, Zone: %s is currently disabled", zone)); + } + + // If local storage is disabled then creation of volume with local disk + // offering not allowed + if (!zone.isLocalStorageEnabled() && diskOffering.isUseLocalStorage()) { + throw new InvalidParameterValueException("Zone is not configured to use local storage but volume's disk offering " + diskOffering.getName() + " uses it"); + } + + String userSpecifiedName = getVolumeNameFromCommand(cmd); + + return commitVolume(cmd.getSnapshotId(), caller, owner, displayVolume, zoneId, diskOfferingId, provisioningType, size, minIops, maxIops, parentVolume, userSpecifiedName, + _uuidMgr.generateUuid(Volume.class, cmd.getCustomId()), details); + } finally { + ReservationHelper.closeAll(reservations); } - - // If local storage is disabled then creation of volume with local disk - // offering not allowed - if (!zone.isLocalStorageEnabled() && diskOffering.isUseLocalStorage()) { - throw new InvalidParameterValueException("Zone is not configured to use local storage but volume's disk offering " + diskOffering.getName() + " uses it"); - } - - String userSpecifiedName = getVolumeNameFromCommand(cmd); - - return commitVolume(cmd.getSnapshotId(), caller, owner, displayVolume, zoneId, diskOfferingId, provisioningType, size, minIops, maxIops, parentVolume, userSpecifiedName, - _uuidMgr.generateUuid(Volume.class, cmd.getCustomId()), details); } @Override @@ -981,7 +1007,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic return Transaction.execute(new TransactionCallback() { @Override public VolumeVO doInTransaction(TransactionStatus status) { - VolumeVO volume = new VolumeVO(userSpecifiedName, -1, -1, -1, -1, new Long(-1), null, null, provisioningType, 0, Volume.Type.DATADISK); + VolumeVO volume = new VolumeVO(userSpecifiedName, -1, -1, -1, -1, -1L, null, null, provisioningType, 0, Volume.Type.DATADISK); volume.setPoolId(null); volume.setUuid(uuid); volume.setDataCenterId(zoneId); @@ -1046,6 +1072,36 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic return true; } + private VolumeVO createVolumeOnStoragePool(Long volumeId, Long storageId) throws ExecutionException, InterruptedException { + VolumeVO volume = _volsDao.findById(volumeId); + StoragePool storagePool = (StoragePool) dataStoreMgr.getDataStore(storageId, DataStoreRole.Primary); + if (storagePool == null) { + throw new InvalidParameterValueException("Failed to find the storage pool: " + storageId); + } else if (!storagePool.getStatus().equals(StoragePoolStatus.Up)) { + throw new InvalidParameterValueException(String.format("Cannot create volume %s on storage pool %s as the storage pool is not in Up state.", + volume.getUuid(), storagePool.getName())); + } + + if (storagePool.getDataCenterId() != volume.getDataCenterId()) { + throw new InvalidParameterValueException(String.format("Cannot create volume %s in zone %s on storage pool %s in zone %s.", + volume.getUuid(), volume.getDataCenterId(), storagePool.getUuid(), storagePool.getDataCenterId())); + } + + DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); + if (!doesStoragePoolSupportDiskOffering(storagePool, diskOffering)) { + throw new InvalidParameterValueException(String.format("Disk offering: %s is not compatible with the storage pool", diskOffering.getUuid())); + } + + DataStore dataStore = dataStoreMgr.getDataStore(storageId, DataStoreRole.Primary); + VolumeInfo volumeInfo = volFactory.getVolume(volumeId, dataStore); + AsyncCallFuture createVolumeFuture = volService.createVolumeAsync(volumeInfo, dataStore); + VolumeApiResult createVolumeResult = createVolumeFuture.get(); + if (createVolumeResult.isFailed()) { + throw new CloudRuntimeException("Volume creation on storage failed: " + createVolumeResult.getResult()); + } + return _volsDao.findById(volumeInfo.getId()); + } + @Override @DB @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating volume", async = true) @@ -1077,6 +1133,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic throw new CloudRuntimeException(message.toString()); } } + } else if (cmd.getStorageId() != null) { + volume = createVolumeOnStoragePool(cmd.getEntityId(), cmd.getStorageId()); } return volume; } catch (Exception e) { @@ -1288,134 +1346,141 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic if (dataStore != null && dataStore.getDriver() instanceof PrimaryDataStoreDriver) { newSize = ((PrimaryDataStoreDriver) dataStore.getDriver()).getVolumeSizeRequiredOnPool(newSize, null, isEncryptionRequired); } - validateVolumeResizeWithSize(volume, currentSize, newSize, shrinkOk, diskOffering, newDiskOffering); - // Note: The storage plug-in in question should perform validation on the IOPS to check if a sufficient number of IOPS is available to perform - // the requested change + List reservations = new ArrayList<>(); + try { + validateVolumeResizeWithSize(volume, currentSize, newSize, shrinkOk, diskOffering, newDiskOffering, reservations); - /* If this volume has never been beyond allocated state, short circuit everything and simply update the database. */ - // We need to publish this event to usage_volume table - if (volume.getState() == Volume.State.Allocated) { - logger.debug("Volume is in the allocated state, but has never been created. Simply updating database with new size and IOPS."); + // Note: The storage plug-in in question should perform validation on the IOPS to check if a sufficient number of IOPS is available to perform + // the requested change - volume.setSize(newSize); - volume.setMinIops(newMinIops); - volume.setMaxIops(newMaxIops); - volume.setHypervisorSnapshotReserve(newHypervisorSnapshotReserve); + /* If this volume has never been beyond allocated state, short circuit everything and simply update the database. */ + // We need to publish this event to usage_volume table + if (volume.getState() == Volume.State.Allocated) { + logger.debug("Volume is in the allocated state, but has never been created. Simply updating database with new size and IOPS."); - if (newDiskOffering != null) { - volume.setDiskOfferingId(cmd.getNewDiskOfferingId()); - } + volume.setSize(newSize); + volume.setMinIops(newMinIops); + volume.setMaxIops(newMaxIops); + volume.setHypervisorSnapshotReserve(newHypervisorSnapshotReserve); - _volsDao.update(volume.getId(), volume); - _resourceLimitMgr.updateVolumeResourceCountForDiskOfferingChange(volume.getAccountId(), volume.isDisplayVolume(), currentSize, newSize, - diskOffering, newDiskOffering); - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_RESIZE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), - volume.getDiskOfferingId(), volume.getTemplateId(), volume.getSize(), Volume.class.getName(), volume.getUuid()); - return volume; - } - - Long newDiskOfferingId = newDiskOffering != null ? newDiskOffering.getId() : diskOffering.getId(); - - boolean volumeMigrateRequired = false; - List suitableStoragePoolsWithEnoughSpace = null; - StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId()); - if (!storageMgr.storagePoolHasEnoughSpaceForResize(storagePool, currentSize, newSize)) { - if (!autoMigrateVolume) { - throw new CloudRuntimeException(String.format("Failed to resize volume %s since the storage pool does not have enough space to accommodate new size for the volume %s, try with automigrate set to true in order to check in the other suitable pools for the new size and then migrate & resize volume there.", volume.getUuid(), volume.getName())); - } - Pair, List> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(volume.getId(), newDiskOfferingId, currentSize, newMinIops, newMaxIops, true, false); - List suitableStoragePools = poolsPair.second(); - if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume resize failed for volume ID: %s as no suitable pool(s) found for migrating to support new disk offering or new size", volume.getUuid())); - } - final Long newSizeFinal = newSize; - suitableStoragePoolsWithEnoughSpace = suitableStoragePools.stream().filter(pool -> storageMgr.storagePoolHasEnoughSpaceForResize(pool, 0L, newSizeFinal)).collect(Collectors.toList()); - if (CollectionUtils.isEmpty(suitableStoragePoolsWithEnoughSpace)) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume resize failed for volume ID: %s as no suitable pool(s) with enough space found.", volume.getUuid())); - } - Collections.shuffle(suitableStoragePoolsWithEnoughSpace); - volumeMigrateRequired = true; - } - - boolean volumeResizeRequired = false; - if (currentSize != newSize || !compareEqualsIncludingNullOrZero(newMaxIops, volume.getMaxIops()) || !compareEqualsIncludingNullOrZero(newMinIops, volume.getMinIops())) { - volumeResizeRequired = true; - } - if (!volumeMigrateRequired && !volumeResizeRequired && newDiskOffering != null) { - _volsDao.updateDiskOffering(volume.getId(), newDiskOffering.getId()); - volume = _volsDao.findById(volume.getId()); - updateStorageWithTheNewDiskOffering(volume, newDiskOffering); - - return volume; - } - - if (volumeMigrateRequired) { - MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePoolsWithEnoughSpace.get(0).getId(), newDiskOfferingId, true); - try { - Volume result = migrateVolume(migrateVolumeCmd); - volume = (result != null) ? _volsDao.findById(result.getId()) : null; - if (volume == null) { - throw new CloudRuntimeException(String.format("Volume resize operation failed for volume ID: %s as migration failed to storage pool %s accommodating new size", volume.getUuid(), suitableStoragePoolsWithEnoughSpace.get(0).getId())); - } - } catch (Exception e) { - throw new CloudRuntimeException(String.format("Volume resize operation failed for volume ID: %s as migration failed to storage pool %s accommodating new size", volume.getUuid(), suitableStoragePoolsWithEnoughSpace.get(0).getId())); - } - } - - UserVmVO userVm = _userVmDao.findById(volume.getInstanceId()); - - if (userVm != null) { - // serialize VM operation - AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); - - if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { - // avoid re-entrance - - VmWorkJobVO placeHolder = null; - - placeHolder = createPlaceHolderWork(userVm.getId()); - - try { - return orchestrateResizeVolume(volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, - newDiskOffering != null ? cmd.getNewDiskOfferingId() : null, shrinkOk); - } finally { - _workJobDao.expunge(placeHolder.getId()); - } - } else { - Outcome outcome = resizeVolumeThroughJobQueue(userVm.getId(), volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, - newDiskOffering != null ? cmd.getNewDiskOfferingId() : null, shrinkOk); - - try { - outcome.get(); - } catch (InterruptedException e) { - throw new RuntimeException("Operation was interrupted", e); - } catch (ExecutionException e) { - throw new RuntimeException("Execution exception", e); + if (newDiskOffering != null) { + volume.setDiskOfferingId(cmd.getNewDiskOfferingId()); } - Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob()); + _volsDao.update(volume.getId(), volume); + _resourceLimitMgr.updateVolumeResourceCountForDiskOfferingChange(volume.getAccountId(), volume.isDisplayVolume(), currentSize, newSize, + diskOffering, newDiskOffering); + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_RESIZE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), + volume.getDiskOfferingId(), volume.getTemplateId(), volume.getSize(), Volume.class.getName(), volume.getUuid()); + return volume; + } - if (jobResult != null) { - if (jobResult instanceof ConcurrentOperationException) { - throw (ConcurrentOperationException) jobResult; - } else if (jobResult instanceof ResourceAllocationException) { - throw (ResourceAllocationException) jobResult; - } else if (jobResult instanceof RuntimeException) { - throw (RuntimeException) jobResult; - } else if (jobResult instanceof Throwable) { - throw new RuntimeException("Unexpected exception", (Throwable) jobResult); - } else if (jobResult instanceof Long) { - return _volsDao.findById((Long) jobResult); - } + Long newDiskOfferingId = newDiskOffering != null ? newDiskOffering.getId() : diskOffering.getId(); + + boolean volumeMigrateRequired = false; + List suitableStoragePoolsWithEnoughSpace = null; + StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId()); + if (!storageMgr.storagePoolHasEnoughSpaceForResize(storagePool, currentSize, newSize)) { + if (!autoMigrateVolume) { + throw new CloudRuntimeException(String.format("Failed to resize volume %s since the storage pool does not have enough space to accommodate new size for the volume %s, try with automigrate set to true in order to check in the other suitable pools for the new size and then migrate & resize volume there.", volume.getUuid(), volume.getName())); } + Pair, List> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(volume.getId(), newDiskOfferingId, currentSize, newMinIops, newMaxIops, true, false); + List suitableStoragePools = poolsPair.second(); + if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume resize failed for volume ID: %s as no suitable pool(s) found for migrating to support new disk offering or new size", volume.getUuid())); + } + final Long newSizeFinal = newSize; + suitableStoragePoolsWithEnoughSpace = suitableStoragePools.stream().filter(pool -> storageMgr.storagePoolHasEnoughSpaceForResize(pool, 0L, newSizeFinal)).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(suitableStoragePoolsWithEnoughSpace)) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume resize failed for volume ID: %s as no suitable pool(s) with enough space found.", volume.getUuid())); + } + Collections.shuffle(suitableStoragePoolsWithEnoughSpace); + volumeMigrateRequired = true; + } + + boolean volumeResizeRequired = false; + if (currentSize != newSize || !compareEqualsIncludingNullOrZero(newMaxIops, volume.getMaxIops()) || !compareEqualsIncludingNullOrZero(newMinIops, volume.getMinIops())) { + volumeResizeRequired = true; + } + if (!volumeMigrateRequired && !volumeResizeRequired && newDiskOffering != null) { + _volsDao.updateDiskOffering(volume.getId(), newDiskOffering.getId()); + volume = _volsDao.findById(volume.getId()); + updateStorageWithTheNewDiskOffering(volume, newDiskOffering); return volume; } - } - return orchestrateResizeVolume(volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, newDiskOffering != null ? cmd.getNewDiskOfferingId() : null, - shrinkOk); + if (volumeMigrateRequired) { + MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePoolsWithEnoughSpace.get(0).getId(), newDiskOfferingId, true); + try { + Volume result = migrateVolume(migrateVolumeCmd); + volume = (result != null) ? _volsDao.findById(result.getId()) : null; + if (volume == null) { + throw new CloudRuntimeException(String.format("Volume resize operation failed for volume ID: %s as migration failed to storage pool %s accommodating new size", volume.getUuid(), suitableStoragePoolsWithEnoughSpace.get(0).getId())); + } + } catch (Exception e) { + throw new CloudRuntimeException(String.format("Volume resize operation failed for volume ID: %s as migration failed to storage pool %s accommodating new size", volume.getUuid(), suitableStoragePoolsWithEnoughSpace.get(0).getId())); + } + } + + UserVmVO userVm = _userVmDao.findById(volume.getInstanceId()); + + if (userVm != null) { + // serialize VM operation + AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); + + if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { + // avoid re-entrance + + VmWorkJobVO placeHolder = null; + + placeHolder = createPlaceHolderWork(userVm.getId()); + + try { + return orchestrateResizeVolume(volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, + newDiskOffering != null ? cmd.getNewDiskOfferingId() : null, shrinkOk); + } finally { + _workJobDao.expunge(placeHolder.getId()); + } + } else { + Outcome outcome = resizeVolumeThroughJobQueue(userVm.getId(), volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, + newDiskOffering != null ? cmd.getNewDiskOfferingId() : null, shrinkOk); + + try { + outcome.get(); + } catch (InterruptedException e) { + throw new RuntimeException("Operation was interrupted", e); + } catch (ExecutionException e) { + throw new RuntimeException("Execution exception", e); + } + + Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob()); + + if (jobResult != null) { + if (jobResult instanceof ConcurrentOperationException) { + throw (ConcurrentOperationException) jobResult; + } else if (jobResult instanceof ResourceAllocationException) { + throw (ResourceAllocationException) jobResult; + } else if (jobResult instanceof RuntimeException) { + throw (RuntimeException) jobResult; + } else if (jobResult instanceof Throwable) { + throw new RuntimeException("Unexpected exception", (Throwable) jobResult); + } else if (jobResult instanceof Long) { + return _volsDao.findById((Long) jobResult); + } + } + + return volume; + } + } + + return orchestrateResizeVolume(volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, newDiskOffering != null ? cmd.getNewDiskOfferingId() : null, + shrinkOk); + + } finally { + ReservationHelper.closeAll(reservations); + } } protected void validateNoVmSnapshots(VolumeVO volume) { @@ -1699,10 +1764,10 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic * * A volume can be destroyed if it is not in any of the following states. *
    - *
  • {@value Volume.State#Destroy}; - *
  • {@value Volume.State#Expunging}; - *
  • {@value Volume.State#Expunged}; - *
  • {@value Volume.State#Allocated}. + *
  • {code}Volume.State#Destroy{code}; + *
  • {code}Volume.State#Expunging{code}; + *
  • {code}Volume.State#Expunged{code}; + *
  • {code}Volume.State#Allocated{code}. *
* * The volume is destroyed via {@link VolumeService#destroyVolume(long)} method. @@ -1872,24 +1937,29 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic throw new InvalidParameterValueException("Please specify a volume in Destroy state."); } + DiskOffering diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); + + List reservations = new ArrayList<>(); try { - _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(volume.getAccountId()), ResourceType.primary_storage, volume.isDisplayVolume(), volume.getSize()); + _resourceLimitMgr.checkVolumeResourceLimit(_accountMgr.getAccount(volume.getAccountId()), volume.isDisplayVolume(), volume.getSize(), diskOffering, reservations); + + try { + _volsDao.detachVolume(volume.getId()); + stateTransitTo(volume, Volume.Event.RecoverRequested); + } catch (NoTransitionException e) { + logger.debug("Failed to recover volume {}", volume, e); + throw new CloudRuntimeException(String.format("Failed to recover volume %s", volume), e); + } + _resourceLimitMgr.incrementVolumeResourceCount(volume.getAccountId(), volume.isDisplay(), + volume.getSize(), _diskOfferingDao.findById(volume.getDiskOfferingId())); + } catch (ResourceAllocationException e) { logger.error("primary storage resource limit check failed", e); throw new InvalidParameterValueException(e.getMessage()); + } finally { + ReservationHelper.closeAll(reservations); } - try { - _volsDao.detachVolume(volume.getId()); - stateTransitTo(volume, Volume.Event.RecoverRequested); - } catch (NoTransitionException e) { - logger.debug("Failed to recover volume {}", volume, e); - throw new CloudRuntimeException(String.format("Failed to recover volume %s", volume), e); - } - _resourceLimitMgr.incrementVolumeResourceCount(volume.getAccountId(), volume.isDisplay(), - volume.getSize(), _diskOfferingDao.findById(volume.getDiskOfferingId())); - - publishVolumeCreationUsageEvent(volume); return volume; @@ -2118,98 +2188,145 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic newSize = ((PrimaryDataStoreDriver) dataStore.getDriver()).getVolumeSizeRequiredOnPool(newSize, null, newDiskOffering.getEncrypt()); } - validateVolumeResizeWithSize(volume, currentSize, newSize, shrinkOk, existingDiskOffering, newDiskOffering); + List reservations = new ArrayList<>(); + try { + validateVolumeResizeWithSize(volume, currentSize, newSize, shrinkOk, existingDiskOffering, newDiskOffering, reservations); - /* If this volume has never been beyond allocated state, short circuit everything and simply update the database. */ - // We need to publish this event to usage_volume table - if (volume.getState() == Volume.State.Allocated) { - logger.debug("Volume {} is in the allocated state, but has never been created. Simply updating database with new size and IOPS.", volume); + /* If this volume has never been beyond allocated state, short circuit everything and simply update the database. */ + // We need to publish this event to usage_volume table + if (volume.getState() == Volume.State.Allocated) { + logger.debug("Volume {} is in the allocated state, but has never been created. Simply updating database with new size and IOPS.", volume); - volume.setSize(newSize); - volume.setMinIops(newMinIops); - volume.setMaxIops(newMaxIops); - volume.setHypervisorSnapshotReserve(newHypervisorSnapshotReserve); + volume.setSize(newSize); + volume.setMinIops(newMinIops); + volume.setMaxIops(newMaxIops); + volume.setHypervisorSnapshotReserve(newHypervisorSnapshotReserve); - if (newDiskOffering != null) { - volume.setDiskOfferingId(newDiskOfferingId); - _volumeMgr.saveVolumeDetails(newDiskOfferingId, volume.getId()); - } - - _volsDao.update(volume.getId(), volume); - _resourceLimitMgr.updateVolumeResourceCountForDiskOfferingChange(volume.getAccountId(), volume.isDisplayVolume(), currentSize, newSize, - existingDiskOffering, newDiskOffering); - - if (currentSize != newSize) { - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_RESIZE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), - volume.getDiskOfferingId(), volume.getTemplateId(), volume.getSize(), Volume.class.getName(), volume.getUuid()); - } - return volume; - } - - if (currentSize != newSize || !compareEqualsIncludingNullOrZero(newMaxIops, volume.getMaxIops()) || !compareEqualsIncludingNullOrZero(newMinIops, volume.getMinIops())) { - volumeResizeRequired = true; - validateVolumeReadyStateAndHypervisorChecks(volume, currentSize, newSize); - } - - StoragePoolVO existingStoragePool = _storagePoolDao.findById(volume.getPoolId()); - - Pair, List> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(volume.getId(), newDiskOffering.getId(), currentSize, newMinIops, newMaxIops, true, false); - List suitableStoragePools = poolsPair.second(); - - if (!suitableStoragePools.stream().anyMatch(p -> (p.getId() == existingStoragePool.getId()))) { - volumeMigrateRequired = true; - if (!autoMigrateVolume) { - throw new InvalidParameterValueException(String.format("Failed to change offering for volume %s since automigrate is set to false but volume needs to migrated", volume.getUuid())); - } - } - - if (!volumeMigrateRequired && !volumeResizeRequired) { - _volsDao.updateDiskOffering(volume.getId(), newDiskOffering.getId()); - volume = _volsDao.findById(volume.getId()); - updateStorageWithTheNewDiskOffering(volume, newDiskOffering); - - return volume; - } - - if (volumeMigrateRequired) { - if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume change offering operation failed for volume: %s as no suitable pool(s) found for migrating to support new disk offering", volume)); - } - final Long newSizeFinal = newSize; - List suitableStoragePoolsWithEnoughSpace = suitableStoragePools.stream().filter(pool -> storageMgr.storagePoolHasEnoughSpaceForResize(pool, 0L, newSizeFinal)).collect(Collectors.toList()); - if (CollectionUtils.isEmpty(suitableStoragePoolsWithEnoughSpace)) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume change offering operation failed for volume: %s as no suitable pool(s) with enough space found for volume migration.", volume)); - } - Collections.shuffle(suitableStoragePoolsWithEnoughSpace); - MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePoolsWithEnoughSpace.get(0).getId(), newDiskOffering.getId(), true); - String volumeUuid = volume.getUuid(); - try { - Volume result = migrateVolume(migrateVolumeCmd); - volume = (result != null) ? _volsDao.findById(result.getId()) : null; - if (volume == null) { - throw new CloudRuntimeException("Change offering for the volume failed."); + if (newDiskOffering != null) { + volume.setDiskOfferingId(newDiskOfferingId); + _volumeMgr.saveVolumeDetails(newDiskOfferingId, volume.getId()); } - } catch (Exception e) { - logger.error("Volume change offering operation failed for volume ID: {} migration failed to storage pool {} due to {}", volumeUuid, suitableStoragePoolsWithEnoughSpace.get(0).getId(), e.getMessage()); - throw new CloudRuntimeException("Change offering for the volume failed.", e); - } - } - if (volumeResizeRequired) { - // refresh volume data - volume = _volsDao.findById(volume.getId()); - try { - volume = resizeVolumeInternal(volume, newDiskOffering, currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, shrinkOk); - } catch (Exception e) { - if (volumeMigrateRequired) { - logger.warn(String.format("Volume change offering operation succeeded for volume ID: %s but volume resize operation failed, so please try resize volume operation separately", volume.getUuid())); - } else { - throw new CloudRuntimeException(String.format("Volume disk offering change operation failed for volume ID [%s] because the volume resize operation failed.", volume.getUuid())); + _volsDao.update(volume.getId(), volume); + _resourceLimitMgr.updateVolumeResourceCountForDiskOfferingChange(volume.getAccountId(), volume.isDisplayVolume(), currentSize, newSize, + existingDiskOffering, newDiskOffering); + + if (currentSize != newSize) { + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_RESIZE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), + volume.getDiskOfferingId(), volume.getTemplateId(), volume.getSize(), Volume.class.getName(), volume.getUuid()); + } + return volume; + } + if (currentSize != newSize || !compareEqualsIncludingNullOrZero(newMaxIops, volume.getMaxIops()) || !compareEqualsIncludingNullOrZero(newMinIops, volume.getMinIops())) { + volumeResizeRequired = true; + validateVolumeReadyStateAndHypervisorChecks(volume, currentSize, newSize); + } + StoragePoolVO existingStoragePool = _storagePoolDao.findById(volume.getPoolId()); + + Pair, List> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(volume.getId(), newDiskOffering.getId(), currentSize, newMinIops, newMaxIops, true, false); + List suitableStoragePools = poolsPair.second(); + if (!suitableStoragePools.stream().anyMatch(p -> (p.getId() == existingStoragePool.getId()))) { + volumeMigrateRequired = true; + if (!autoMigrateVolume) { + throw new InvalidParameterValueException(String.format("Failed to change offering for volume %s since automigrate is set to false but volume needs to migrated", volume.getUuid())); } } - } - return volume; + if (!volumeMigrateRequired && !volumeResizeRequired) { + _volsDao.updateDiskOffering(volume.getId(), newDiskOffering.getId()); + volume = _volsDao.findById(volume.getId()); + updateStorageWithTheNewDiskOffering(volume, newDiskOffering); + + return volume; + } + + if (volumeMigrateRequired) { + if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume change offering operation failed for volume: %s as no suitable pool(s) found for migrating to support new disk offering", volume)); + } + final Long newSizeFinal = newSize; + List suitableStoragePoolsWithEnoughSpace = suitableStoragePools.stream().filter(pool -> storageMgr.storagePoolHasEnoughSpaceForResize(pool, 0L, newSizeFinal)).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(suitableStoragePoolsWithEnoughSpace)) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume change offering operation failed for volume: %s as no suitable pool(s) with enough space found for volume migration.", volume)); + } + Collections.shuffle(suitableStoragePoolsWithEnoughSpace); + MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePoolsWithEnoughSpace.get(0).getId(), newDiskOffering.getId(), true); + String volumeUuid = volume.getUuid(); + try { + Volume result = migrateVolume(migrateVolumeCmd); + volume = (result != null) ? _volsDao.findById(result.getId()) : null; + if (volume == null) { + throw new CloudRuntimeException("Change offering for the volume failed."); + } + } catch (Exception e) { + logger.error("Volume change offering operation failed for volume ID: {} migration failed to storage pool {} due to {}", volumeUuid, suitableStoragePoolsWithEnoughSpace.get(0).getId(), e.getMessage()); + throw new CloudRuntimeException("Change offering for the volume failed.", e); + } + } + + if (volumeResizeRequired) { + // refresh volume data + volume = _volsDao.findById(volume.getId()); + try { + volume = resizeVolumeInternal(volume, newDiskOffering, currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, shrinkOk); + } catch (Exception e) { + if (volumeMigrateRequired) { + logger.warn(String.format("Volume change offering operation succeeded for volume ID: %s but volume resize operation failed, so please try resize volume operation separately", volume.getUuid())); + } else { + throw new CloudRuntimeException(String.format("Volume disk offering change operation failed for volume ID [%s] because the volume resize operation failed.", volume.getUuid())); + } + } + } + + if (!volumeMigrateRequired && !volumeResizeRequired) { + _volsDao.updateDiskOffering(volume.getId(), newDiskOffering.getId()); + volume = _volsDao.findById(volume.getId()); + updateStorageWithTheNewDiskOffering(volume, newDiskOffering); + + return volume; + } + + if (volumeMigrateRequired) { + if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume change offering operation failed for volume: %s as no suitable pool(s) found for migrating to support new disk offering", volume)); + } + final Long newSizeFinal = newSize; + List suitableStoragePoolsWithEnoughSpace = suitableStoragePools.stream().filter(pool -> storageMgr.storagePoolHasEnoughSpaceForResize(pool, 0L, newSizeFinal)).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(suitableStoragePoolsWithEnoughSpace)) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume change offering operation failed for volume: %s as no suitable pool(s) with enough space found for volume migration.", volume)); + } + Collections.shuffle(suitableStoragePoolsWithEnoughSpace); + MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePoolsWithEnoughSpace.get(0).getId(), newDiskOffering.getId(), true); + try { + Volume result = migrateVolume(migrateVolumeCmd); + volume = (result != null) ? _volsDao.findById(result.getId()) : null; + if (volume == null) { + throw new CloudRuntimeException(String.format("Volume change offering operation failed for volume: %s migration failed to storage pool %s", volume, suitableStoragePools.get(0))); + } + } catch (Exception e) { + throw new CloudRuntimeException(String.format("Volume change offering operation failed for volume: %s migration failed to storage pool %s due to %s", volume, suitableStoragePools.get(0), e.getMessage())); + } + } + + if (volumeResizeRequired) { + // refresh volume data + volume = _volsDao.findById(volume.getId()); + try { + volume = resizeVolumeInternal(volume, newDiskOffering, currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, shrinkOk); + } catch (Exception e) { + if (volumeMigrateRequired) { + logger.warn(String.format("Volume change offering operation succeeded for volume ID: %s but volume resize operation failed, so please try resize volume operation separately", volume.getUuid())); + } else { + throw new CloudRuntimeException(String.format("Volume change offering operation failed for volume ID: %s due to resize volume operation failed", volume.getUuid())); + } + } + } + + return volume; + + } finally { + ReservationHelper.closeAll(reservations); + } } private void updateStorageWithTheNewDiskOffering(VolumeVO volume, DiskOfferingVO newDiskOffering) { @@ -2415,7 +2532,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } private void validateVolumeResizeWithSize(VolumeVO volume, long currentSize, Long newSize, boolean shrinkOk, - DiskOfferingVO existingDiskOffering, DiskOfferingVO newDiskOffering) throws ResourceAllocationException { + DiskOfferingVO existingDiskOffering, DiskOfferingVO newDiskOffering, List reservations) throws ResourceAllocationException { // if the caller is looking to change the size of the volume if (newSize != null && currentSize != newSize) { @@ -2480,7 +2597,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic /* Check resource limit for this account */ _resourceLimitMgr.checkVolumeResourceLimitForDiskOfferingChange(_accountMgr.getAccount(volume.getAccountId()), volume.isDisplayVolume(), currentSize, newSize != null ? newSize : currentSize, - existingDiskOffering, newDiskOffering); + existingDiskOffering, newDiskOffering, reservations); } @Override @@ -2659,7 +2776,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic checkForBackups(vm, true); - checkRightsToAttach(caller, volumeToAttach, vm); + _accountMgr.checkAccess(caller, null, true, volumeToAttach, vm); StoragePoolVO volumeToAttachStoragePool = _storagePoolDao.findById(volumeToAttach.getPoolId()); if (logger.isTraceEnabled() && volumeToAttachStoragePool != null) { @@ -2683,6 +2800,12 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic throw new InvalidParameterValueException("Volume's disk offering has encryption enabled, but volume encryption is not supported for hypervisor type " + rootDiskHyperType); } + Account owner = _accountDao.findById(volumeToAttach.getAccountId()); + List resourceLimitStorageTags = _resourceLimitMgr.getResourceLimitStorageTagsForResourceCountOperation(true, diskOffering); + Long requiredPrimaryStorageSpace = getRequiredPrimaryStorageSizeForVolumeAttach(resourceLimitStorageTags, volumeToAttach); + + try (CheckedReservation primaryStorageReservation = new CheckedReservation(owner, ResourceType.primary_storage, resourceLimitStorageTags, requiredPrimaryStorageSpace, reservationDao, _resourceLimitMgr)) { + _jobMgr.updateAsyncJobAttachment(job.getId(), "Volume", volumeId); if (asyncExecutionContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { @@ -2690,9 +2813,21 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } else { return getVolumeAttachJobResult(vmId, volumeId, deviceId); } + + } catch (ResourceAllocationException e) { + logger.error("primary storage resource limit check failed", e); + throw new InvalidParameterValueException(e.getMessage()); + } } - @Nullable private Volume getVolumeAttachJobResult(Long vmId, Long volumeId, Long deviceId) { + protected Long getRequiredPrimaryStorageSizeForVolumeAttach(List resourceLimitStorageTags, VolumeInfo volumeToAttach) { + if (CollectionUtils.isEmpty(resourceLimitStorageTags) || Arrays.asList(Volume.State.Allocated, Volume.State.Ready).contains(volumeToAttach.getState())) { + return 0L; + } + return volumeToAttach.getSize(); + } + + @Nullable protected Volume getVolumeAttachJobResult(Long vmId, Long volumeId, Long deviceId) { Outcome outcome = attachVolumeToVmThroughJobQueue(vmId, volumeId, deviceId); Volume vol = null; @@ -2741,21 +2876,6 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } } - private void checkRightsToAttach(Account caller, VolumeInfo volumeToAttach, UserVmVO vm) { - _accountMgr.checkAccess(caller, null, true, volumeToAttach, vm); - - Account owner = _accountDao.findById(volumeToAttach.getAccountId()); - - if (!Arrays.asList(Volume.State.Allocated, Volume.State.Ready).contains(volumeToAttach.getState())) { - try { - _resourceLimitMgr.checkResourceLimit(owner, ResourceType.primary_storage, volumeToAttach.getSize()); - } catch (ResourceAllocationException e) { - logger.error("primary storage resource limit check failed", e); - throw new InvalidParameterValueException(e.getMessage()); - } - } - } - private void checkForVMSnapshots(Long vmId, UserVmVO vm) { // if target VM has associated VM snapshots List vmSnapshots = _vmSnapshotDao.findByVm(vmId); @@ -4321,7 +4441,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic _accountMgr.checkAccess(caller, null, true, oldAccount); _accountMgr.checkAccess(caller, null, true, newAccount); - _resourceLimitMgr.checkVolumeResourceLimit(newAccount, true, volume.getSize(), _diskOfferingDao.findById(volume.getDiskOfferingId())); + List reservations = new ArrayList<>(); + try { + _resourceLimitMgr.checkVolumeResourceLimit(newAccount, true, volume.getSize(), _diskOfferingDao.findById(volume.getDiskOfferingId()), reservations); Transaction.execute(new TransactionCallbackNoReturn() { @Override @@ -4331,6 +4453,10 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic }); return volume; + + } finally { + ReservationHelper.closeAll(reservations); + } } protected void updateVolumeAccount(Account oldAccount, VolumeVO volume, Account newAccount) { @@ -5287,20 +5413,20 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic private Pair orchestrateAttachVolumeToVM(VmWorkAttachVolume work) throws Exception { Volume vol = orchestrateAttachVolumeToVM(work.getVmId(), work.getVolumeId(), work.getDeviceId()); - return new Pair(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(new Long(vol.getId()))); + return new Pair(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(vol.getId())); } @ReflectionUse private Pair orchestrateDetachVolumeFromVM(VmWorkDetachVolume work) throws Exception { Volume vol = orchestrateDetachVolumeFromVM(work.getVmId(), work.getVolumeId()); - return new Pair(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(new Long(vol.getId()))); + return new Pair(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(vol.getId())); } @ReflectionUse private Pair orchestrateResizeVolume(VmWorkResizeVolume work) throws Exception { Volume vol = orchestrateResizeVolume(work.getVolumeId(), work.getCurrentSize(), work.getNewSize(), work.getNewMinIops(), work.getNewMaxIops(), work.getNewHypervisorSnapshotReserve(), work.getNewServiceOfferingId(), work.isShrinkOk()); - return new Pair(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(new Long(vol.getId()))); + return new Pair(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(vol.getId())); } @ReflectionUse diff --git a/server/src/main/java/com/cloud/storage/download/DownloadActiveState.java b/server/src/main/java/com/cloud/storage/download/DownloadActiveState.java index 889ffbc0b1c..35b23985dd2 100644 --- a/server/src/main/java/com/cloud/storage/download/DownloadActiveState.java +++ b/server/src/main/java/com/cloud/storage/download/DownloadActiveState.java @@ -95,6 +95,11 @@ public abstract class DownloadActiveState extends DownloadState { return Status.ABANDONED.toString(); } + @Override + public String handleLimitReached() { + return Status.LIMIT_REACHED.toString(); + } + @Override public String handleDisconnect() { diff --git a/server/src/main/java/com/cloud/storage/download/DownloadErrorState.java b/server/src/main/java/com/cloud/storage/download/DownloadErrorState.java index a0834456a2d..5dddefb0192 100644 --- a/server/src/main/java/com/cloud/storage/download/DownloadErrorState.java +++ b/server/src/main/java/com/cloud/storage/download/DownloadErrorState.java @@ -60,6 +60,11 @@ public class DownloadErrorState extends DownloadInactiveState { return Status.ABANDONED.toString(); } + @Override + public String handleLimitReached() { + return Status.LIMIT_REACHED.toString(); + } + @Override public String getName() { return Status.DOWNLOAD_ERROR.toString(); diff --git a/server/src/main/java/com/cloud/storage/download/DownloadInactiveState.java b/server/src/main/java/com/cloud/storage/download/DownloadInactiveState.java index 8fee3d0437c..69d46879ebe 100644 --- a/server/src/main/java/com/cloud/storage/download/DownloadInactiveState.java +++ b/server/src/main/java/com/cloud/storage/download/DownloadInactiveState.java @@ -36,6 +36,12 @@ public abstract class DownloadInactiveState extends DownloadState { return getName(); } + @Override + public String handleLimitReached() { + // ignore and stay put + return getName(); + } + @Override public String handleDisconnect() { //ignore and stay put diff --git a/server/src/main/java/com/cloud/storage/download/DownloadLimitReachedState.java b/server/src/main/java/com/cloud/storage/download/DownloadLimitReachedState.java new file mode 100644 index 00000000000..8ce5668299e --- /dev/null +++ b/server/src/main/java/com/cloud/storage/download/DownloadLimitReachedState.java @@ -0,0 +1,54 @@ +// 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.download; + +import org.apache.cloudstack.storage.command.DownloadProgressCommand.RequestType; +import org.apache.logging.log4j.Level; + +import com.cloud.agent.api.storage.DownloadAnswer; +import com.cloud.storage.VMTemplateStorageResourceAssoc.Status; + +public class DownloadLimitReachedState extends DownloadInactiveState { + + public DownloadLimitReachedState(DownloadListener dl) { + super(dl); + } + + @Override + public String getName() { + return Status.LIMIT_REACHED.toString(); + } + + @Override + public String handleEvent(DownloadEvent event, Object eventObj) { + if (logger.isTraceEnabled()) { + getDownloadListener().log("handleEvent, event type=" + event + ", curr state=" + getName(), Level.TRACE); + } + return Status.LIMIT_REACHED.toString(); + } + + @Override + public void onEntry(String prevState, DownloadEvent event, Object evtObj) { + if (!prevState.equalsIgnoreCase(getName())) { + DownloadAnswer answer = new DownloadAnswer("Storage Limit Reached", Status.LIMIT_REACHED); + getDownloadListener().callback(answer); + getDownloadListener().cancelStatusTask(); + getDownloadListener().cancelTimeoutTask(); + getDownloadListener().scheduleImmediateStatusCheck(RequestType.PURGE); + } + } +} diff --git a/server/src/main/java/com/cloud/storage/download/DownloadListener.java b/server/src/main/java/com/cloud/storage/download/DownloadListener.java index 42b0e394db4..058881fdb54 100644 --- a/server/src/main/java/com/cloud/storage/download/DownloadListener.java +++ b/server/src/main/java/com/cloud/storage/download/DownloadListener.java @@ -25,6 +25,15 @@ import java.util.Timer; import javax.inject.Inject; +import com.cloud.configuration.Resource; +import com.cloud.resourcelimit.CheckedReservation; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.ResourceLimitService; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; @@ -34,10 +43,13 @@ import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; import org.apache.cloudstack.framework.async.AsyncCompletionCallback; import org.apache.cloudstack.managed.context.ManagedContextTimerTask; +import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.cloudstack.storage.command.DownloadCommand; import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType; import org.apache.cloudstack.storage.command.DownloadProgressCommand; import org.apache.cloudstack.storage.command.DownloadProgressCommand.RequestType; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import org.apache.cloudstack.utils.cache.LazyCache; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; @@ -107,6 +119,7 @@ public class DownloadListener implements Listener { public static final String DOWNLOAD_ERROR = Status.DOWNLOAD_ERROR.toString(); public static final String DOWNLOAD_IN_PROGRESS = Status.DOWNLOAD_IN_PROGRESS.toString(); public static final String DOWNLOAD_ABANDONED = Status.ABANDONED.toString(); + public static final String DOWNLOAD_LIMIT_REACHED = Status.LIMIT_REACHED.toString(); private EndPoint _ssAgent; @@ -137,6 +150,18 @@ public class DownloadListener implements Listener { private DataStoreManager _storeMgr; @Inject private VolumeService _volumeSrv; + @Inject + private VMTemplateDao _templateDao; + @Inject + private TemplateDataStoreDao _templateDataStoreDao; + @Inject + private VolumeDao _volumeDao; + @Inject + private ResourceLimitService _resourceLimitMgr; + @Inject + private AccountManager _accountMgr; + @Inject + ReservationDao _reservationDao; private LazyCache> zoneHypervisorsCache; @@ -160,7 +185,7 @@ public class DownloadListener implements Listener { _downloadMonitor = downloadMonitor; _cmd = cmd; initStateMachine(); - _currState = getState(Status.NOT_DOWNLOADED.toString()); + _currState = getState(NOT_DOWNLOADED); this._timer = timer; _timeoutTask = new TimeoutTask(this); this._timer.schedule(_timeoutTask, 3 * STATUS_POLL_INTERVAL); @@ -184,11 +209,12 @@ public class DownloadListener implements Listener { } private void initStateMachine() { - _stateMap.put(Status.NOT_DOWNLOADED.toString(), new NotDownloadedState(this)); - _stateMap.put(Status.DOWNLOADED.toString(), new DownloadCompleteState(this)); - _stateMap.put(Status.DOWNLOAD_ERROR.toString(), new DownloadErrorState(this)); - _stateMap.put(Status.DOWNLOAD_IN_PROGRESS.toString(), new DownloadInProgressState(this)); - _stateMap.put(Status.ABANDONED.toString(), new DownloadAbandonedState(this)); + _stateMap.put(NOT_DOWNLOADED, new NotDownloadedState(this)); + _stateMap.put(DOWNLOADED, new DownloadCompleteState(this)); + _stateMap.put(DOWNLOAD_ERROR, new DownloadErrorState(this)); + _stateMap.put(DOWNLOAD_IN_PROGRESS, new DownloadInProgressState(this)); + _stateMap.put(DOWNLOAD_ABANDONED, new DownloadAbandonedState(this)); + _stateMap.put(DOWNLOAD_LIMIT_REACHED, new DownloadLimitReachedState(this)); } private DownloadState getState(String stateName) { @@ -239,10 +265,53 @@ public class DownloadListener implements Listener { return false; } + private Long getAccountIdForDataObject() { + if (object == null) { + return null; + } + if (DataObjectType.TEMPLATE.equals(object.getType())) { + VMTemplateVO t = _templateDao.findById(object.getId()); + return t != null ? t.getAccountId() : null; + } else if (DataObjectType.VOLUME.equals(object.getType())) { + VolumeVO v = _volumeDao.findById(object.getId()); + return v != null ? v.getAccountId() : null; + } + return null; + } + + private Long getSizeFromDB() { + Long lastSize = 0L; + if (DataObjectType.TEMPLATE.equals(object.getType())) { + TemplateDataStoreVO t = _templateDataStoreDao.findByStoreTemplate(object.getDataStore().getId(), object.getId()); + lastSize = t.getSize(); + } else if (DataObjectType.VOLUME.equals(object.getType())) { + VolumeVO v = _volumeDao.findById(object.getId()); + lastSize = v.getSize(); + } + return lastSize; + } + + private Boolean checkAndUpdateResourceLimits(DownloadAnswer answer) { + Long lastSize = getSizeFromDB(); + Long currentSize = answer.getTemplateSize(); + + if (currentSize > lastSize) { + Long accountId = getAccountIdForDataObject(); + Account account = _accountMgr.getAccount(accountId); + Long usage = currentSize - lastSize; + try (CheckedReservation secStorageReservation = new CheckedReservation(account, Resource.ResourceType.secondary_storage, usage, _reservationDao, _resourceLimitMgr)) { + _resourceLimitMgr.incrementResourceCount(accountId, Resource.ResourceType.secondary_storage, usage); + } catch (Exception e) { + return false; + } + } + return true; + } + @Override public boolean processAnswers(long agentId, long seq, Answer[] answers) { boolean processed = false; - if (answers != null & answers.length > 0) { + if (answers != null && answers.length > 0) { if (answers[0] instanceof DownloadAnswer) { final DownloadAnswer answer = (DownloadAnswer)answers[0]; if (getJobId() == null) { @@ -250,7 +319,11 @@ public class DownloadListener implements Listener { } else if (!getJobId().equalsIgnoreCase(answer.getJobId())) { return false;//TODO } - transition(DownloadEvent.DOWNLOAD_ANSWER, answer); + if (!checkAndUpdateResourceLimits(answer)) { + transition(DownloadEvent.LIMIT_REACHED, answer); + } else { + transition(DownloadEvent.DOWNLOAD_ANSWER, answer); + } processed = true; } } diff --git a/server/src/main/java/com/cloud/storage/download/DownloadState.java b/server/src/main/java/com/cloud/storage/download/DownloadState.java index 68723b53e35..e58d1f3f39f 100644 --- a/server/src/main/java/com/cloud/storage/download/DownloadState.java +++ b/server/src/main/java/com/cloud/storage/download/DownloadState.java @@ -26,7 +26,7 @@ import com.cloud.agent.api.storage.DownloadAnswer; public abstract class DownloadState { public static enum DownloadEvent { - DOWNLOAD_ANSWER, ABANDON_DOWNLOAD, TIMEOUT_CHECK, DISCONNECT + DOWNLOAD_ANSWER, ABANDON_DOWNLOAD, LIMIT_REACHED, TIMEOUT_CHECK, DISCONNECT }; protected Logger logger = LogManager.getLogger(getClass()); @@ -51,6 +51,8 @@ public abstract class DownloadState { return handleAnswer(answer); case ABANDON_DOWNLOAD: return handleAbort(); + case LIMIT_REACHED: + return handleLimitReached(); case TIMEOUT_CHECK: Date now = new Date(); long update = now.getTime() - dl.getLastUpdated().getTime(); @@ -78,6 +80,8 @@ public abstract class DownloadState { public abstract String handleAbort(); + public abstract String handleLimitReached(); + public abstract String handleDisconnect(); public abstract String handleAnswer(DownloadAnswer answer); diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java index b5a03d1836b..10c0373598b 100755 --- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -16,8 +16,6 @@ // under the License. package com.cloud.storage.snapshot; - -import com.cloud.storage.StoragePoolStatus; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -36,9 +34,6 @@ import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.host.dao.HostDao; -import com.cloud.storage.Upload; -import com.cloud.storage.dao.SnapshotDetailsDao; import org.apache.cloudstack.acl.SecurityChecker; import com.cloud.api.ApiDBUtils; import org.apache.cloudstack.annotation.AnnotationService; @@ -77,6 +72,7 @@ import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.jobs.AsyncJob; import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.cloudstack.resourcedetail.SnapshotPolicyDetailVO; import org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDao; import org.apache.cloudstack.snapshot.SnapshotHelper; @@ -119,10 +115,12 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.StorageUnavailableException; import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.org.Grouping; import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.resource.ResourceManager; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.server.TaggedResourceService; import com.cloud.storage.CreateSnapshotPayload; @@ -135,15 +133,18 @@ import com.cloud.storage.SnapshotPolicyVO; import com.cloud.storage.SnapshotScheduleVO; import com.cloud.storage.SnapshotVO; import com.cloud.storage.Storage; +import com.cloud.storage.StoragePoolStatus; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePool; +import com.cloud.storage.Upload; 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.SnapshotDetailsDao; import com.cloud.storage.dao.SnapshotDao; import com.cloud.storage.dao.SnapshotPolicyDao; import com.cloud.storage.dao.SnapshotScheduleDao; @@ -251,7 +252,8 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement public TaggedResourceService taggedResourceService; @Inject private AnnotationDao annotationDao; - + @Inject + private ReservationDao reservationDao; @Inject protected SnapshotHelper snapshotHelper; @Inject @@ -321,7 +323,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement hostIdsToTryFirst = new long[] {vmHostId}; } - List hostIdsToAvoid = new ArrayList(); + List hostIdsToAvoid = new ArrayList<>(); for (int retry = _totalRetries; retry >= 0; retry--) { try { Pair result = _storageMgr.sendToPool(pool, hostIdsToTryFirst, hostIdsToAvoid, cmd); @@ -421,7 +423,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement } public void updateVolumeSizeAndPrimaryStorageCount(VolumeVO volume, SnapshotVO snapshot) { - Long differenceBetweenVolumeAndSnapshotSize = new Long(volume.getSize() - snapshot.getSize()); + long differenceBetweenVolumeAndSnapshotSize = volume.getSize() - snapshot.getSize(); if (differenceBetweenVolumeAndSnapshotSize != 0) { if (differenceBetweenVolumeAndSnapshotSize > 0) { _resourceLimitMgr.decrementResourceCount(snapshot.getAccountId(), ResourceType.primary_storage, differenceBetweenVolumeAndSnapshotSize); @@ -696,7 +698,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement } catch (Exception e) { logger.debug("Failed to backup Snapshot from Instance Snapshot", e); _resourceLimitMgr.decrementResourceCount(snapshotOwnerId, ResourceType.snapshot); - _resourceLimitMgr.decrementResourceCount(snapshotOwnerId, ResourceType.secondary_storage, new Long(volume.getSize())); + _resourceLimitMgr.decrementResourceCount(snapshotOwnerId, ResourceType.secondary_storage, volume.getSize()); throw new CloudRuntimeException("Failed to backup Snapshot from Instance Snapshot", e); } finally { if (snapshotOnPrimaryStore != null) { @@ -911,43 +913,36 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement List snapshotStoreRefs = storeRefAndZones.first(); List zoneIds = storeRefAndZones.second(); - boolean result = snapshotStrategy.deleteSnapshot(snapshotId, zoneId); - if (result) { - for (Long zId : zoneIds) { - if (snapshotCheck.getState() == Snapshot.State.BackedUp) { - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_DELETE, snapshotCheck.getAccountId(), zId, snapshotId, - snapshotCheck.getName(), null, null, 0L, snapshotCheck.getClass().getName(), snapshotCheck.getUuid()); + try { + boolean result = snapshotStrategy.deleteSnapshot(snapshotId, zoneId); + if (result) { + for (Long zId : zoneIds) { + if (snapshotCheck.getState() == Snapshot.State.BackedUp) { + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_DELETE, snapshotCheck.getAccountId(), zId, snapshotId, + snapshotCheck.getName(), null, null, 0L, snapshotCheck.getClass().getName(), snapshotCheck.getUuid()); + } + } + final SnapshotVO postDeleteSnapshotEntry = _snapshotDao.findById(snapshotId); + if (postDeleteSnapshotEntry == null || Snapshot.State.Destroyed.equals(postDeleteSnapshotEntry.getState())) { + annotationDao.removeByEntityType(AnnotationService.EntityType.SNAPSHOT.name(), snapshotCheck.getUuid()); + + if (snapshotCheck.getState() != Snapshot.State.Error && snapshotCheck.getState() != Snapshot.State.Destroyed) { + _resourceLimitMgr.decrementResourceCount(snapshotCheck.getAccountId(), ResourceType.snapshot); + } + } + for (SnapshotDataStoreVO snapshotStoreRef : snapshotStoreRefs) { + if (ObjectInDataStoreStateMachine.State.Ready.equals(snapshotStoreRef.getState()) && !DataStoreRole.Primary.equals(snapshotStoreRef.getRole())) { + _resourceLimitMgr.decrementResourceCount(snapshotCheck.getAccountId(), ResourceType.secondary_storage, snapshotStoreRef.getPhysicalSize()); + } } } - final SnapshotVO postDeleteSnapshotEntry = _snapshotDao.findById(snapshotId); - if (postDeleteSnapshotEntry == null || Snapshot.State.Destroyed.equals(postDeleteSnapshotEntry.getState())) { - annotationDao.removeByEntityType(AnnotationService.EntityType.SNAPSHOT.name(), snapshotCheck.getUuid()); - if (snapshotCheck.getState() != Snapshot.State.Error && snapshotCheck.getState() != Snapshot.State.Destroyed) { - _resourceLimitMgr.decrementResourceCount(snapshotCheck.getAccountId(), ResourceType.snapshot); - } - } - for (SnapshotDataStoreVO snapshotStoreRef : snapshotStoreRefs) { - if (ObjectInDataStoreStateMachine.State.Ready.equals(snapshotStoreRef.getState()) && !DataStoreRole.Primary.equals(snapshotStoreRef.getRole())) { - _resourceLimitMgr.decrementResourceCount(snapshotCheck.getAccountId(), ResourceType.secondary_storage, new Long(snapshotStoreRef.getPhysicalSize())); - } - } - } - final SnapshotVO postDeleteSnapshotEntry = _snapshotDao.findById(snapshotId); - if (postDeleteSnapshotEntry == null || Snapshot.State.Destroyed.equals(postDeleteSnapshotEntry.getState())) { - annotationDao.removeByEntityType(AnnotationService.EntityType.SNAPSHOT.name(), snapshotCheck.getUuid()); + return result; + } catch (Exception e) { + logger.debug("Failed to delete snapshot {}:{}", snapshotCheck, e.toString()); - if (snapshotCheck.getState() != Snapshot.State.Error && snapshotCheck.getState() != Snapshot.State.Destroyed) { - _resourceLimitMgr.decrementResourceCount(snapshotCheck.getAccountId(), ResourceType.snapshot); - } + throw new CloudRuntimeException("Failed to delete snapshot:" + e.toString()); } - for (SnapshotDataStoreVO snapshotStoreRef : snapshotStoreRefs) { - if (ObjectInDataStoreStateMachine.State.Ready.equals(snapshotStoreRef.getState()) && !DataStoreRole.Primary.equals(snapshotStoreRef.getRole())) { - _resourceLimitMgr.decrementResourceCount(snapshotCheck.getAccountId(), ResourceType.secondary_storage, new Long(snapshotStoreRef.getPhysicalSize())); - } - } - - return result; } @Override @@ -961,7 +956,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement Map tags = cmd.getTags(); Long zoneId = cmd.getZoneId(); Account caller = CallContext.current().getCallingAccount(); - List permittedAccounts = new ArrayList(); + List permittedAccounts = new ArrayList<>(); // Verify parameters if (volumeId != null) { @@ -973,7 +968,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement List ids = getIdsListFromCmd(cmd.getId(), cmd.getIds()); - Ternary domainIdRecursiveListProject = new Ternary(cmd.getDomainId(), + Ternary domainIdRecursiveListProject = new Ternary<>(cmd.getDomainId(), cmd.isRecursive(), null); _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false); Long domainId = domainIdRecursiveListProject.first(); @@ -1066,7 +1061,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement } Pair, Integer> result = _snapshotDao.searchAndCount(sc, searchFilter); - return new Pair, Integer>(result.first(), result.second()); + return new Pair<>(result.first(), result.second()); } @Override @@ -1082,7 +1077,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement if (volume.getPoolId() == null) { continue; } - Long volumeId = volume.getId(); + long volumeId = volume.getId(); Long dcId = volume.getDataCenterId(); if (_snapshotDao.listByVolumeIdIncludingRemoved(volumeId).isEmpty()) { // This volume doesn't have any snapshots. Nothing do delete. @@ -1126,7 +1121,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement if (Type.MANUAL == snapshot.getRecurringType()) { _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.snapshot); for (SnapshotDataStoreVO snapshotStoreRef : snapshotStoreRefs) { - _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.secondary_storage, new Long(snapshotStoreRef.getPhysicalSize())); + _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.secondary_storage, snapshotStoreRef.getPhysicalSize()); } } @@ -1155,7 +1150,6 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement throw new InvalidParameterValueException("Backing up of snapshot is not supported by the zone of the volume. Snapshots can not be taken for multiple zones"); } boolean isRootAdminCaller = _accountMgr.isRootAdmin(caller.getId()); - if (hasZones) { for (Long zoneId : zoneIds) { getCheckedDestinationZoneForSnapshotCopy(zoneId, isRootAdminCaller); @@ -1291,7 +1285,6 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement logger.debug("Acquired lock for creating snapshot policy [{}] for volume {}.", intervalType, volume); - try { SnapshotPolicyVO policy = _snapshotPolicyDao.findOneByVolumeInterval(volumeId, intervalType); @@ -1498,7 +1491,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement } // List only future schedules, not past ones. - List snapshotSchedules = new ArrayList(); + List snapshotSchedules = new ArrayList<>(); if (policyId == null) { List policyInstances = listPoliciesforVolume(volumeId); for (SnapshotPolicyVO policyInstance : policyInstances) { @@ -1632,7 +1625,6 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement Long snapshotId = payload.getSnapshotId(); Account snapshotOwner = payload.getAccount(); - SnapshotInfo snapshot = snapshotFactory.getSnapshot(snapshotId, volume.getDataStore()); StoragePool storagePool = _storagePoolDao.findById(volume.getPoolId()); @@ -1698,7 +1690,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement ResourceType storeResourceType = dataStoreRole == DataStoreRole.Image ? ResourceType.secondary_storage : ResourceType.primary_storage; // Correct the resource count of snapshot in case of delta snapshots. - _resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), storeResourceType, new Long(volume.getSize() - snapshotStoreRef.getPhysicalSize())); + _resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), storeResourceType, volume.getSize() - snapshotStoreRef.getPhysicalSize()); if (!payload.getAsyncBackup()) { if (backupSnapToSecondary) { @@ -1717,7 +1709,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement } ResourceType storeResourceType = getStoreResourceType(volume.getDataCenterId(), payload.getLocationType()); _resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), ResourceType.snapshot); - _resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), storeResourceType, new Long(volume.getSize())); + _resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), storeResourceType, volume.getSize()); throw cre; } catch (Exception e) { if (logger.isDebugEnabled()) { @@ -1725,7 +1717,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement } ResourceType storeResourceType = getStoreResourceType(volume.getDataCenterId(), payload.getLocationType()); _resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), ResourceType.snapshot); - _resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), storeResourceType, new Long(volume.getSize())); + _resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), storeResourceType, volume.getSize()); throw new CloudRuntimeException("Failed to create snapshot", e); } return snapshot; @@ -1935,7 +1927,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement } if (policyIds == null) { - policyIds = new ArrayList(); + policyIds = new ArrayList<>(); policyIds.add(policyId); } else if (policyIds.size() <= 0) { // Not even sure how this is even possible @@ -2018,20 +2010,6 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement Type snapshotType = getSnapshotType(policyId); Account owner = _accountMgr.getAccount(volume.getAccountId()); - ResourceType storeResourceType = getStoreResourceType(volume.getDataCenterId(), locationType); - try { - _resourceLimitMgr.checkResourceLimit(owner, ResourceType.snapshot); - _resourceLimitMgr.checkResourceLimit(owner, storeResourceType, volume.getSize()); - } catch (ResourceAllocationException e) { - if (snapshotType != Type.MANUAL) { - String msg = String.format("Snapshot resource limit exceeded for account %s. Failed to create recurring snapshots", owner); - logger.warn(msg); - _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_UPDATE_RESOURCE_COUNT, 0L, 0L, msg, "Snapshot resource limit exceeded for account id : " + owner.getId() - + ". Failed to create recurring snapshots; please use updateResourceLimit to increase the limit"); - } - throw e; - } - // Determine the name for this snapshot // Snapshot Name: VMInstancename + volumeName + timeString String timeString = DateUtil.getDateDisplayString(DateUtil.GMT_TIMEZONE, new Date(), DateUtil.YYYYMMDD_FORMAT); @@ -2063,6 +2041,14 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement hypervisorType = volume.getHypervisorType(); } + ResourceType storeResourceType = ResourceType.secondary_storage; + if (!isBackupSnapshotToSecondaryForZone(volume.getDataCenterId()) || + Snapshot.LocationType.PRIMARY.equals(locationType)) { + storeResourceType = ResourceType.primary_storage; + } + + try (CheckedReservation volumeSnapshotReservation = new CheckedReservation(owner, ResourceType.snapshot, null, null, 1L, reservationDao, _resourceLimitMgr); + CheckedReservation storageReservation = new CheckedReservation(owner, storeResourceType, null, null, volume.getSize(), reservationDao, _resourceLimitMgr)) { SnapshotVO snapshotVO = new SnapshotVO(volume.getDataCenterId(), volume.getAccountId(), volume.getDomainId(), volume.getId(), volume.getDiskOfferingId(), snapshotName, (short)snapshotType.ordinal(), snapshotType.name(), volume.getSize(), volume.getMinIops(), volume.getMaxIops(), hypervisorType, locationType); @@ -2074,6 +2060,14 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.snapshot); _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), storeResourceType, volume.getSize()); return snapshot; + } catch (ResourceAllocationException e) { + if (snapshotType != Type.MANUAL) { + String msg = String.format("Snapshot resource limit exceeded for account id : %s. Failed to create recurring snapshots", owner.getId()); + logger.warn(msg); + _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_UPDATE_RESOURCE_COUNT, 0L, 0L, msg, msg + ". Please, use updateResourceLimit to increase the limit"); + } + throw e; + } } @Override @@ -2123,57 +2117,61 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement @DB private boolean copySnapshotToZone(SnapshotDataStoreVO snapshotDataStoreVO, DataStore srcSecStore, - DataCenterVO dstZone, DataStore dstSecStore, Account account, boolean kvmIncrementalSnapshot) + DataCenterVO dstZone, DataStore dstSecStore, Account account, boolean kvmIncrementalSnapshot, boolean shouldCheckResourceLimits) throws ResourceAllocationException { final long snapshotId = snapshotDataStoreVO.getSnapshotId(); final long dstZoneId = dstZone.getId(); if (checkAndProcessSnapshotAlreadyExistInStore(snapshotId, dstSecStore)) { return true; } - _resourceLimitMgr.checkResourceLimit(account, ResourceType.secondary_storage, snapshotDataStoreVO.getSize()); - // snapshotId may refer to ID of a removed parent snapshot - SnapshotInfo snapshotOnSecondary = snapshotFactory.getSnapshot(snapshotId, srcSecStore); - if (kvmIncrementalSnapshot) { - snapshotOnSecondary = snapshotHelper.convertSnapshotIfNeeded(snapshotOnSecondary); - } - String copyUrl = null; - try { - AsyncCallFuture future = snapshotSrv.queryCopySnapshot(snapshotOnSecondary); - CreateCmdResult result = future.get(); - if (!result.isFailed()) { - copyUrl = result.getPath(); + // Resource limit checks are not performed here at the moment, but they were added in case this method is used + // in the future to copy a standalone snapshot + long requiredSecondaryStorageSpace = shouldCheckResourceLimits ? snapshotDataStoreVO.getSize() : 0L; + try (CheckedReservation secondaryStorageReservation = new CheckedReservation(account, ResourceType.secondary_storage, null, null, null, requiredSecondaryStorageSpace, null, reservationDao, _resourceLimitMgr)) { + // snapshotId may refer to ID of a removed parent snapshot + SnapshotInfo snapshotOnSecondary = snapshotFactory.getSnapshot(snapshotId, srcSecStore); + if (kvmIncrementalSnapshot) { + snapshotOnSecondary = snapshotHelper.convertSnapshotIfNeeded(snapshotOnSecondary); } - } catch (InterruptedException | ExecutionException | ResourceUnavailableException ex) { - logger.error("Failed to prepare URL for copy for snapshot ID: {} on store: {}", snapshotId, srcSecStore, ex); - } - if (StringUtils.isEmpty(copyUrl)) { - logger.error("Unable to prepare URL for copy for snapshot ID: {} on store: {}", snapshotId, srcSecStore); - return false; - } - logger.debug(String.format("Copying snapshot ID: %d to destination zones using download URL: %s", snapshotId, copyUrl)); - try { - AsyncCallFuture future = snapshotSrv.copySnapshot(snapshotOnSecondary, copyUrl, dstSecStore); - SnapshotResult result = future.get(); - if (result.isFailed()) { - logger.debug("Copy snapshot ID: {} failed for image store {}: {}", snapshotId, dstSecStore, result.getResult()); + String copyUrl = null; + try { + AsyncCallFuture future = snapshotSrv.queryCopySnapshot(snapshotOnSecondary); + CreateCmdResult result = future.get(); + if (!result.isFailed()) { + copyUrl = result.getPath(); + } + } catch (InterruptedException | ExecutionException | ResourceUnavailableException ex) { + logger.error("Failed to prepare URL for copy for snapshot ID: {} on store: {}", snapshotId, srcSecStore, ex); + } + if (StringUtils.isEmpty(copyUrl)) { + logger.error("Unable to prepare URL for copy for snapshot ID: {} on store: {}", snapshotId, srcSecStore); return false; } - snapshotZoneDao.addSnapshotToZone(snapshotId, dstZoneId); - _resourceLimitMgr.incrementResourceCount(account.getId(), ResourceType.secondary_storage, snapshotDataStoreVO.getSize()); - if (account.getId() != Account.ACCOUNT_ID_SYSTEM) { - SnapshotVO snapshotVO = _snapshotDao.findByIdIncludingRemoved(snapshotId); - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_COPY, account.getId(), dstZoneId, snapshotId, null, null, null, snapshotVO.getSize(), - snapshotVO.getSize(), snapshotVO.getClass().getName(), snapshotVO.getUuid()); - } - if (kvmIncrementalSnapshot) { - ((ImageStoreEntity) srcSecStore).deleteExtractUrl(snapshotOnSecondary.getPath(), copyUrl, Upload.Type.SNAPSHOT); - } + logger.debug(String.format("Copying snapshot ID: %d to destination zones using download URL: %s", snapshotId, copyUrl)); + try { + AsyncCallFuture future = snapshotSrv.copySnapshot(snapshotOnSecondary, copyUrl, dstSecStore); + SnapshotResult result = future.get(); + if (result.isFailed()) { + logger.debug("Copy snapshot ID: {} failed for image store {}: {}", snapshotId, dstSecStore, result.getResult()); + return false; + } + snapshotZoneDao.addSnapshotToZone(snapshotId, dstZoneId); + _resourceLimitMgr.incrementResourceCount(account.getId(), ResourceType.secondary_storage, snapshotDataStoreVO.getSize()); + if (account.getId() != Account.ACCOUNT_ID_SYSTEM) { + SnapshotVO snapshotVO = _snapshotDao.findByIdIncludingRemoved(snapshotId); + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_COPY, account.getId(), dstZoneId, snapshotId, null, null, null, snapshotVO.getSize(), + snapshotVO.getSize(), snapshotVO.getClass().getName(), snapshotVO.getUuid()); + } + if (kvmIncrementalSnapshot) { + ((ImageStoreEntity) srcSecStore).deleteExtractUrl(snapshotOnSecondary.getPath(), copyUrl, Upload.Type.SNAPSHOT); + } - return true; - } catch (InterruptedException | ExecutionException | ResourceUnavailableException ex) { - logger.debug("Failed to copy snapshot ID: {} to image store: {}", snapshotId, dstSecStore); + return true; + } catch (InterruptedException | ExecutionException | ResourceUnavailableException ex) { + logger.debug("Failed to copy snapshot ID: {} to image store: {}", snapshotId, dstSecStore); + } + return false; } - return false; } @DB @@ -2205,13 +2203,6 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement if (CollectionUtils.isEmpty(snapshotChain)) { return true; } - try { - _resourceLimitMgr.checkResourceLimit(account, ResourceType.secondary_storage, size); - } catch (ResourceAllocationException e) { - logger.error(String.format("Unable to allocate secondary storage resources for snapshot chain for %s with size: %d", snapshotVO, size), e); - return false; - } - Collections.reverse(snapshotChain); if (dstSecStore == null) { // find all eligible image stores for the destination zone List dstSecStores = dataStoreMgr.getImageStoresByScopeExcludingReadOnly(new ZoneScope(destZoneId)); @@ -2223,16 +2214,23 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement throw new StorageUnavailableException("Destination zone is not ready, no image store with free capacity", DataCenter.class, destZoneId); } } - logger.debug("Copying snapshot chain for snapshot ID: {} on secondary store: {} of zone ID: {}", snapshotVO, dstSecStore, destZone); - for (SnapshotDataStoreVO snapshotDataStoreVO : snapshotChain) { - if (!copySnapshotToZone(snapshotDataStoreVO, srcSecStore, destZone, dstSecStore, account, kvmIncrementalSnapshot)) { - logger.error("Failed to copy snapshot: {} to zone: {} due to failure to copy snapshot ID: {} from snapshot chain", - snapshotVO, destZone, snapshotDataStoreVO.getSnapshotId()); - return false; + try (CheckedReservation secondaryStorageReservation = new CheckedReservation(account, ResourceType.secondary_storage, null, null, null, size, null, reservationDao, _resourceLimitMgr)) { + logger.debug("Copying snapshot chain for snapshot ID: {} on secondary store: {} of zone ID: {}", snapshotVO, dstSecStore, destZone); + Collections.reverse(snapshotChain); + for (SnapshotDataStoreVO snapshotDataStoreVO : snapshotChain) { + if (!copySnapshotToZone(snapshotDataStoreVO, srcSecStore, destZone, dstSecStore, account, kvmIncrementalSnapshot, false)) { + logger.error("Failed to copy snapshot: {} to zone: {} due to failure to copy snapshot ID: {} from snapshot chain", + snapshotVO, destZone, snapshotDataStoreVO.getSnapshotId()); + return false; + } } + return true; + } catch (ResourceAllocationException e) { + logger.error(String.format("Unable to allocate secondary storage resources for snapshot chain for %s with size: %d", snapshotVO, size), e); + return false; } - return true; } + @DB private List copySnapshotToZones(SnapshotVO snapshotVO, DataStore srcSecStore, List dstZones) throws StorageUnavailableException, ResourceAllocationException { AccountVO account = _accountDao.findById(snapshotVO.getAccountId()); diff --git a/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java b/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java index a7a6cea7d90..fdde8f47a67 100644 --- a/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java +++ b/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java @@ -34,10 +34,8 @@ import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.command.user.iso.DeleteIsoCmd; -import org.apache.cloudstack.api.command.user.iso.GetUploadParamsForIsoCmd; import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd; import org.apache.cloudstack.api.command.user.template.DeleteTemplateCmd; -import org.apache.cloudstack.api.command.user.template.GetUploadParamsForTemplateCmd; import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.direct.download.DirectDownloadManager; @@ -197,19 +195,6 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { profile.setSize(templateSize); } profile.setUrl(url); - // Check that the resource limit for secondary storage won't be exceeded - _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(cmd.getEntityOwnerId()), - ResourceType.secondary_storage, - UriUtils.getRemoteSize(url, followRedirects)); - return profile; - } - - @Override - public TemplateProfile prepare(GetUploadParamsForIsoCmd cmd) throws ResourceAllocationException { - TemplateProfile profile = super.prepare(cmd); - - // Check that the resource limit for secondary storage won't be exceeded - _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(cmd.getEntityOwnerId()), ResourceType.secondary_storage); return profile; } @@ -228,19 +213,7 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { profile.setForCks(cmd.isForCks()); } profile.setUrl(url); - // Check that the resource limit for secondary storage won't be exceeded - _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(cmd.getEntityOwnerId()), - ResourceType.secondary_storage, - UriUtils.getRemoteSize(url, followRedirects)); - return profile; - } - @Override - public TemplateProfile prepare(GetUploadParamsForTemplateCmd cmd) throws ResourceAllocationException { - TemplateProfile profile = super.prepare(cmd); - - // Check that the resource limit for secondary storage won't be exceeded - _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(cmd.getEntityOwnerId()), ResourceType.secondary_storage); return profile; } @@ -268,7 +241,6 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { persistDirectDownloadTemplate(template.getId(), profile.getSize()); } - _resourceLimitMgr.incrementResourceCount(profile.getAccountId(), ResourceType.template); return template; } @@ -377,7 +349,7 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { if(payloads.isEmpty()) { throw new CloudRuntimeException("unable to find zone or an image store with enough capacity"); } - _resourceLimitMgr.incrementResourceCount(profile.getAccountId(), ResourceType.template); + return payloads; } }); @@ -396,13 +368,12 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { CreateTemplateContext context) { TemplateApiResult result = callback.getResult(); TemplateInfo template = context.template; + VMTemplateVO tmplt = _tmpltDao.findById(template.getId()); if (result.isSuccess()) { - VMTemplateVO tmplt = _tmpltDao.findById(template.getId()); // need to grant permission for public templates if (tmplt.isPublicTemplate()) { _messageBus.publish(_name, TemplateManager.MESSAGE_REGISTER_PUBLIC_TEMPLATE_EVENT, PublishScope.LOCAL, tmplt.getId()); } - long accountId = tmplt.getAccountId(); if (template.getSize() != null) { // publish usage event String etype = EventTypes.EVENT_TEMPLATE_CREATE; @@ -431,9 +402,13 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { UsageEventUtils.publishUsageEvent(etype, template.getAccountId(), -1, template.getId(), template.getName(), null, null, physicalSize, template.getSize(), VirtualMachineTemplate.class.getName(), template.getUuid()); } - _resourceLimitMgr.incrementResourceCount(accountId, ResourceType.secondary_storage, template.getSize()); } } + if (tmplt != null) { + long accountId = tmplt.getAccountId(); + Account account = _accountDao.findById(accountId); + _resourceLimitMgr.recalculateResourceCount(accountId, account.getDomainId(), ResourceType.secondary_storage.getOrdinal()); + } return null; } diff --git a/server/src/main/java/com/cloud/template/TemplateAdapterBase.java b/server/src/main/java/com/cloud/template/TemplateAdapterBase.java index 97c6e590359..8f508135605 100644 --- a/server/src/main/java/com/cloud/template/TemplateAdapterBase.java +++ b/server/src/main/java/com/cloud/template/TemplateAdapterBase.java @@ -245,7 +245,7 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat Account account = _accountDao.findById(accountId); Domain domain = _domainDao.findById(account.getDomainId()); - payload.setDefaultMaxSecondaryStorageInGB(_resourceLimitMgr.findCorrectResourceLimitForAccountAndDomain(account, domain, ResourceType.secondary_storage, null)); + payload.setDefaultMaxSecondaryStorageInBytes(_resourceLimitMgr.findCorrectResourceLimitForAccountAndDomain(account, domain, ResourceType.secondary_storage, null)); payload.setAccountId(accountId); payload.setRemoteEndPoint(ep.getPublicAddr()); payload.setRequiresHvm(template.requiresHvm()); @@ -364,8 +364,6 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat throw new IllegalArgumentException("Unable to find user with id " + userId); } - _resourceLimitMgr.checkResourceLimit(templateOwner, ResourceType.template); - // If a zoneId is specified, make sure it is valid if (zoneIdList != null) { for (Long zoneId :zoneIdList) { diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java index 8593fb131f4..b62ec87d349 100755 --- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java @@ -86,6 +86,7 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.PublishScope; import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDao; import org.apache.cloudstack.secstorage.heuristics.HeuristicType; import org.apache.cloudstack.snapshot.SnapshotHelper; @@ -152,6 +153,7 @@ import com.cloud.hypervisor.HypervisorGuru; import com.cloud.hypervisor.HypervisorGuruManager; import com.cloud.projects.Project; import com.cloud.projects.ProjectManager; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.storage.DataStoreRole; import com.cloud.storage.GuestOSVO; import com.cloud.storage.ImageStoreUploadMonitorImpl; @@ -201,6 +203,7 @@ import com.cloud.utils.EncryptionUtil; import com.cloud.utils.EnumUtils; import com.cloud.utils.Pair; import com.cloud.utils.StringUtils; +import com.cloud.utils.UriUtils; import com.cloud.utils.component.AdapterBase; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; @@ -302,29 +305,27 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, private VMTemplateDetailsDao _tmpltDetailsDao; @Inject private HypervisorGuruManager _hvGuruMgr; - - private List _adapters; - - ExecutorService _preloadExecutor; - + @Inject + ReservationDao reservationDao; @Inject private StorageCacheManager cacheMgr; @Inject private EndPointSelector selector; - @Inject protected SnapshotHelper snapshotHelper; @Inject VnfTemplateManager vnfTemplateManager; @Inject TemplateDeployAsIsDetailsDao templateDeployAsIsDetailsDao; - @Inject private SecondaryStorageHeuristicDao secondaryStorageHeuristicDao; - @Inject private HeuristicRuleHelper heuristicRuleHelper; + private List _adapters; + + ExecutorService _preloadExecutor; + protected boolean backupSnapshotAfterTakingSnapshot = SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value(); private TemplateAdapter getAdapter(HypervisorType type) { @@ -353,14 +354,30 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, @ActionEvent(eventType = EventTypes.EVENT_ISO_CREATE, eventDescription = "Creating ISO") public VirtualMachineTemplate registerIso(RegisterIsoCmd cmd) throws ResourceAllocationException { TemplateAdapter adapter = getAdapter(HypervisorType.None); - TemplateProfile profile = adapter.prepare(cmd); - VMTemplateVO template = adapter.create(profile); + Account owner = _accountMgr.getAccount(cmd.getEntityOwnerId()); - if (template != null) { - CallContext.current().putContextParameter(VirtualMachineTemplate.class, template.getUuid()); - return template; - } else { - throw new CloudRuntimeException("Failed to create ISO"); + // Secondary storage resource count is not incremented for BareMetalTemplateAdapter + // Note: checking the file size before registering will require the Management Server host to have access to the Internet and a DNS server + // If it does not, UriUtils.getRemoteSize will return 0L. + long secondaryStorageUsage = adapter instanceof HypervisorTemplateAdapter && !cmd.isDirectDownload() ? + UriUtils.getRemoteSize(cmd.getUrl(), StorageManager.DataStoreDownloadFollowRedirects.value()) : 0L; + + try (CheckedReservation templateReservation = new CheckedReservation(owner, ResourceType.template, null, null, 1L, reservationDao, _resourceLimitMgr); + CheckedReservation secondaryStorageReservation = new CheckedReservation(owner, ResourceType.secondary_storage, null, null, secondaryStorageUsage, reservationDao, _resourceLimitMgr)) { + TemplateProfile profile = adapter.prepare(cmd); + VMTemplateVO template = adapter.create(profile); + + // Secondary storage resource usage will be incremented in com.cloud.template.HypervisorTemplateAdapter.createTemplateAsyncCallBack + _resourceLimitMgr.incrementResourceCount(profile.getAccountId(), ResourceType.template); + if (secondaryStorageUsage > 0) { + _resourceLimitMgr.incrementResourceCount(profile.getAccountId(), ResourceType.secondary_storage, secondaryStorageUsage); + } + if (template != null) { + CallContext.current().putContextParameter(VirtualMachineTemplate.class, template.getUuid()); + return template; + } else { + throw new CloudRuntimeException("Failed to create ISO"); + } } } @@ -380,17 +397,32 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, } TemplateAdapter adapter = getAdapter(HypervisorType.getType(cmd.getHypervisor())); - TemplateProfile profile = adapter.prepare(cmd); - VMTemplateVO template = adapter.create(profile); + Account owner = _accountMgr.getAccount(cmd.getEntityOwnerId()); - if (template != null) { - CallContext.current().putContextParameter(VirtualMachineTemplate.class, template.getUuid()); - if (cmd instanceof RegisterVnfTemplateCmd) { - vnfTemplateManager.persistVnfTemplate(template.getId(), (RegisterVnfTemplateCmd) cmd); + long secondaryStorageUsage = adapter instanceof HypervisorTemplateAdapter && !cmd.isDirectDownload() ? + UriUtils.getRemoteSize(cmd.getUrl(), StorageManager.DataStoreDownloadFollowRedirects.value()) : 0L; + + try (CheckedReservation templateReservation = new CheckedReservation(owner, ResourceType.template, null, null, 1L, reservationDao, _resourceLimitMgr); + CheckedReservation secondaryStorageReservation = new CheckedReservation(owner, ResourceType.secondary_storage, null, null, secondaryStorageUsage, reservationDao, _resourceLimitMgr)) { + TemplateProfile profile = adapter.prepare(cmd); + VMTemplateVO template = adapter.create(profile); + + // Secondary storage resource usage will be recalculated in com.cloud.template.HypervisorTemplateAdapter.createTemplateAsyncCallBack + // for HypervisorTemplateAdapter + _resourceLimitMgr.incrementResourceCount(profile.getAccountId(), ResourceType.template); + if (secondaryStorageUsage > 0) { + _resourceLimitMgr.incrementResourceCount(profile.getAccountId(), ResourceType.secondary_storage, secondaryStorageUsage); + } + + if (template != null) { + CallContext.current().putContextParameter(VirtualMachineTemplate.class, template.getUuid()); + if (cmd instanceof RegisterVnfTemplateCmd) { + vnfTemplateManager.persistVnfTemplate(template.getId(), (RegisterVnfTemplateCmd) cmd); + } + return template; + } else { + throw new CloudRuntimeException("Failed to create a Template"); } - return template; - } else { - throw new CloudRuntimeException("Failed to create a Template"); } } @@ -455,17 +487,35 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, @Override @ActionEvent(eventType = EventTypes.EVENT_ISO_CREATE, eventDescription = "Creating post upload ISO") public GetUploadParamsResponse registerIsoForPostUpload(GetUploadParamsForIsoCmd cmd) throws ResourceAllocationException, MalformedURLException { - TemplateAdapter adapter = getAdapter(HypervisorType.None); - TemplateProfile profile = adapter.prepare(cmd); - return registerPostUploadInternal(adapter, profile); + Account owner = _accountMgr.getAccount(cmd.getEntityOwnerId()); + + try (CheckedReservation templateReservation = new CheckedReservation(owner, ResourceType.template, null, null, 1L, reservationDao, _resourceLimitMgr)) { + TemplateAdapter adapter = getAdapter(HypervisorType.None); + TemplateProfile profile = adapter.prepare(cmd); + + GetUploadParamsResponse response = registerPostUploadInternal(adapter, profile); + + _resourceLimitMgr.incrementResourceCount(profile.getAccountId(), ResourceType.template); + + return response; + } } @Override @ActionEvent(eventType = EventTypes.EVENT_TEMPLATE_CREATE, eventDescription = "Creating post upload Template") public GetUploadParamsResponse registerTemplateForPostUpload(GetUploadParamsForTemplateCmd cmd) throws ResourceAllocationException, MalformedURLException { - TemplateAdapter adapter = getAdapter(HypervisorType.getType(cmd.getHypervisor())); - TemplateProfile profile = adapter.prepare(cmd); - return registerPostUploadInternal(adapter, profile); + Account owner = _accountMgr.getAccount(cmd.getEntityOwnerId()); + + try (CheckedReservation templateReservation = new CheckedReservation(owner, ResourceType.template, null, null, 1L, reservationDao, _resourceLimitMgr)) { + TemplateAdapter adapter = getAdapter(HypervisorType.getType(cmd.getHypervisor())); + TemplateProfile profile = adapter.prepare(cmd); + + GetUploadParamsResponse response = registerPostUploadInternal(adapter, profile); + + _resourceLimitMgr.incrementResourceCount(profile.getAccountId(), ResourceType.template); + + return response; + } } @Override @@ -527,7 +577,7 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, VMTemplateVO vmTemplate = _tmpltDao.findById(templateId); if (vmTemplate == null) { - throw new InvalidParameterValueException("Unable to find Template id=" + templateId); + throw new InvalidParameterValueException("Unable to find Template ID=" + templateId); } _accountMgr.checkAccess(CallContext.current().getCallingAccount(), AccessType.OperateEntry, true, vmTemplate); @@ -832,9 +882,6 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, // find the size of the template to be copied TemplateDataStoreVO srcTmpltStore = _tmplStoreDao.findByStoreTemplate(srcSecStore.getId(), tmpltId); - _resourceLimitMgr.checkResourceLimit(account, ResourceType.template); - _resourceLimitMgr.checkResourceLimit(account, ResourceType.secondary_storage, new Long(srcTmpltStore.getSize()).longValue()); - // Event details String copyEventType; if (template.getFormat().equals(ImageFormat.ISO)) { @@ -897,9 +944,6 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, } } } - - return true; - } catch (Exception ex) { logger.debug("Failed to copy Template to image store:{} ,will try next one", dstSecStore, ex); } @@ -981,21 +1025,21 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, // sync template from cache store to region store if it is not there, for cases where we are going to migrate existing NFS to S3. _tmpltSvr.syncTemplateToRegionStore(template, srcSecStore); } + + AccountVO templateOwner = _accountDao.findById(template.getAccountId()); + for (Long destZoneId : destZoneIds) { DataStore dstSecStore = getImageStore(destZoneId, templateId); if (dstSecStore != null) { logger.debug("There is Template {} in secondary storage {} in zone {} , don't need to copy", template, dstSecStore, dataCenterVOs.get(destZoneId)); continue; } - if (!copy(userId, template, srcSecStore, dataCenterVOs.get(destZoneId))) { - failedZones.add(dataCenterVOs.get(destZoneId).getName()); - } - else{ - if (template.getSize() != null) { - // increase resource count - long accountId = template.getAccountId(); - _resourceLimitMgr.incrementResourceCount(accountId, ResourceType.secondary_storage, template.getSize()); + try (CheckedReservation secondaryStorageReservation = new CheckedReservation(templateOwner, ResourceType.secondary_storage, null, null, template.getSize(), reservationDao, _resourceLimitMgr)) { + if (!copy(userId, template, srcSecStore, dataCenterVOs.get(destZoneId))) { + failedZones.add(dataCenterVOs.get(destZoneId).getName()); + continue; } + _resourceLimitMgr.incrementResourceCount(templateOwner.getId(), ResourceType.secondary_storage, template.getSize()); } } } @@ -1018,9 +1062,6 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, AccountVO account = _accountDao.findById(template.getAccountId()); - - _resourceLimitMgr.checkResourceLimit(account, ResourceType.template); - try { _tmpltDao.addTemplateToZone(template, dstZoneId); return true; @@ -1839,7 +1880,7 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, // decrement resource count if (accountId != null) { _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.template); - _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.secondary_storage, new Long(volumeFinal != null ? volumeFinal.getSize() + _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.secondary_storage, (long)(volumeFinal != null ? volumeFinal.getSize() : snapshotFinal.getSize())); } } @@ -1990,107 +2031,106 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, } } - _resourceLimitMgr.checkResourceLimit(templateOwner, ResourceType.template); - _resourceLimitMgr.checkResourceLimit(templateOwner, ResourceType.secondary_storage, new Long(volume != null ? volume.getSize() : snapshot.getSize()).longValue()); + long templateSize = volume != null ? volume.getSize() : snapshot.getSize(); + try (CheckedReservation templateReservation = new CheckedReservation(templateOwner, ResourceType.template, null, null, 1L, reservationDao, _resourceLimitMgr); + CheckedReservation secondaryStorageReservation = new CheckedReservation(templateOwner, ResourceType.secondary_storage, null, null, templateSize, reservationDao, _resourceLimitMgr)) { - if (!isAdmin || featured == null) { - featured = Boolean.FALSE; - } - Long guestOSId = cmd.getOsTypeId(); - GuestOSVO guestOS = _guestOSDao.findById(guestOSId); - if (guestOS == null) { - throw new InvalidParameterValueException("GuestOS with ID: " + guestOSId + " does not exist."); - } - - Long nextTemplateId = _tmpltDao.getNextInSequence(Long.class, "id"); - String description = cmd.getDisplayText(); - boolean isExtractable = false; - Long sourceTemplateId = null; - if (volume != null) { - VMTemplateVO template = ApiDBUtils.findTemplateById(volume.getTemplateId()); - isExtractable = template != null && template.isExtractable() && template.getTemplateType() != Storage.TemplateType.SYSTEM; - if (template != null) { - arch = template.getArch(); + if (!isAdmin || featured == null) { + featured = Boolean.FALSE; } - if (volume.getIsoId() != null && volume.getIsoId() != 0) { - sourceTemplateId = volume.getIsoId(); - } else if (volume.getTemplateId() != null) { - sourceTemplateId = volume.getTemplateId(); + Long guestOSId = cmd.getOsTypeId(); + GuestOSVO guestOS = _guestOSDao.findById(guestOSId); + if (guestOS == null) { + throw new InvalidParameterValueException("GuestOS with ID: " + guestOSId + " does not exist."); } - } - String templateTag = cmd.getTemplateTag(); - if (templateTag != null) { - if (logger.isDebugEnabled()) { - logger.debug("Adding Template tag: " + templateTag); + + Long nextTemplateId = _tmpltDao.getNextInSequence(Long.class, "id"); + String description = cmd.getDisplayText(); + boolean isExtractable = false; + Long sourceTemplateId = null; + if (volume != null) { + VMTemplateVO template = ApiDBUtils.findTemplateById(volume.getTemplateId()); + isExtractable = template != null && template.isExtractable() && template.getTemplateType() != Storage.TemplateType.SYSTEM; + if (template != null) { + arch = template.getArch(); + } + if (volume.getIsoId() != null && volume.getIsoId() != 0) { + sourceTemplateId = volume.getIsoId(); + } else if (volume.getTemplateId() != null) { + sourceTemplateId = volume.getTemplateId(); + } } - } - privateTemplate = new VMTemplateVO(nextTemplateId, name, ImageFormat.RAW, isPublic, featured, isExtractable, - TemplateType.USER, null, requiresHvmValue, bitsValue, templateOwner.getId(), null, description, - passwordEnabledValue, guestOS.getId(), true, hyperType, templateTag, cmd.getDetails(), sshKeyEnabledValue, isDynamicScalingEnabled, false, false, arch, null); - - if (sourceTemplateId != null) { - if (logger.isDebugEnabled()) { - logger.debug("This Template is getting created from other Template, setting source Template ID to: " + sourceTemplateId); + String templateTag = cmd.getTemplateTag(); + if (templateTag != null) { + if (logger.isDebugEnabled()) { + logger.debug("Adding Template tag: " + templateTag); + } } - } - - - // for region wide storage, set cross zones flag - List stores = _imgStoreDao.findRegionImageStores(); - if (!CollectionUtils.isEmpty(stores)) { - privateTemplate.setCrossZones(true); - } - - privateTemplate.setSourceTemplateId(sourceTemplateId); - - VMTemplateVO template = _tmpltDao.persist(privateTemplate); - // Increment the number of templates - if (template != null) { - Map details = new HashMap(); + privateTemplate = new VMTemplateVO(nextTemplateId, name, ImageFormat.RAW, isPublic, featured, isExtractable, + TemplateType.USER, null, requiresHvmValue, bitsValue, templateOwner.getId(), null, description, + passwordEnabledValue, guestOS.getId(), true, hyperType, templateTag, cmd.getDetails(), sshKeyEnabledValue, isDynamicScalingEnabled, false, false, arch, null); if (sourceTemplateId != null) { - VMTemplateVO sourceTemplate = _tmpltDao.findById(sourceTemplateId); - if (sourceTemplate != null && sourceTemplate.getDetails() != null) { - details.putAll(sourceTemplate.getDetails()); + if (logger.isDebugEnabled()) { + logger.debug("This Template is getting created from other Template, setting source Template ID to: " + sourceTemplateId); } } - if (volume != null) { - Long vmId = volume.getInstanceId(); - if (vmId != null) { - UserVmVO userVm = _userVmDao.findById(vmId); - if (userVm != null) { - _userVmDao.loadDetails(userVm); - Map vmDetails = userVm.getDetails(); - vmDetails = vmDetails.entrySet() - .stream() - .filter(map -> map.getValue() != null) - .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); - details.putAll(vmDetails); + // for region wide storage, set cross zones flag + List stores = _imgStoreDao.findRegionImageStores(); + if (!CollectionUtils.isEmpty(stores)) { + privateTemplate.setCrossZones(true); + } + + privateTemplate.setSourceTemplateId(sourceTemplateId); + + VMTemplateVO template = _tmpltDao.persist(privateTemplate); + // Increment the number of templates + if (template != null) { + Map details = new HashMap(); + + if (sourceTemplateId != null) { + VMTemplateVO sourceTemplate = _tmpltDao.findById(sourceTemplateId); + if (sourceTemplate != null && sourceTemplate.getDetails() != null) { + details.putAll(sourceTemplate.getDetails()); } } - } - if (cmd.getDetails() != null) { - details.remove(VmDetailConstants.ENCRYPTED_PASSWORD); // new password will be generated during vm deployment from password enabled template - details.putAll(cmd.getDetails()); - } - if (!details.isEmpty()) { - privateTemplate.setDetails(details); - _tmpltDao.saveDetails(privateTemplate); + + if (volume != null) { + Long vmId = volume.getInstanceId(); + if (vmId != null) { + UserVmVO userVm = _userVmDao.findById(vmId); + if (userVm != null) { + _userVmDao.loadDetails(userVm); + Map vmDetails = userVm.getDetails(); + vmDetails = vmDetails.entrySet() + .stream() + .filter(map -> map.getValue() != null) + .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); + details.putAll(vmDetails); + } + } + } + if (cmd.getDetails() != null) { + details.remove(VmDetailConstants.ENCRYPTED_PASSWORD); // new password will be generated during vm deployment from password enabled template + details.putAll(cmd.getDetails()); + } + if (!details.isEmpty()) { + privateTemplate.setDetails(details); + _tmpltDao.saveDetails(privateTemplate); + } + + _resourceLimitMgr.incrementResourceCount(templateOwner.getId(), ResourceType.template); + _resourceLimitMgr.incrementResourceCount(templateOwner.getId(), ResourceType.secondary_storage, templateSize); } - _resourceLimitMgr.incrementResourceCount(templateOwner.getId(), ResourceType.template); - _resourceLimitMgr.incrementResourceCount(templateOwner.getId(), ResourceType.secondary_storage, - new Long(volume != null ? volume.getSize() : snapshot.getSize())); + if (template != null) { + CallContext.current().putContextParameter(VirtualMachineTemplate.class, template.getUuid()); + return template; + } else { + throw new CloudRuntimeException("Failed to create a Template"); + } } - - if (template != null) { - CallContext.current().putContextParameter(VirtualMachineTemplate.class, template.getUuid()); - return template; - } else { - throw new CloudRuntimeException("Failed to create a Template"); - } - } @Override @@ -2262,7 +2302,7 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, templateTag == null && forCks == null && arch == null && - (!cleanupDetails && details == null) //update details in every case except this one + (! cleanupDetails && details == null) // update details in every case except this one ); if (!updateNeeded) { return template; @@ -2457,7 +2497,6 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, if (MapUtils.isEmpty(details)) { return; } - String bootMode = details.get(ApiConstants.BootType.UEFI.toString()); if (bootMode == null) { return; diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index 2011d455646..74bf56cb083 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -2214,6 +2214,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M checkIfAccountManagesProjects(accountId); verifyCallerPrivilegeForUserOrAccountOperations(account); + validateNoDeleteProtectedVmsForAccount(account); CallContext.current().putContextParameter(Account.class, account.getUuid()); @@ -2253,6 +2254,23 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M return true; } + private void validateNoDeleteProtectedVmsForAccount(Account account) { + long accountId = account.getId(); + List deleteProtectedVms = _vmDao.listDeleteProtectedVmsByAccountId(accountId); + if (CollectionUtils.isEmpty(deleteProtectedVms)) { + return; + } + + if (logger.isDebugEnabled()) { + List vmUuids = deleteProtectedVms.stream().map(VMInstanceVO::getUuid).collect(Collectors.toList()); + logger.debug("Cannot delete Account {}, delete protection enabled for Instances: {}", account, vmUuids); + } + + throw new InvalidParameterValueException( + String.format("Cannot delete Account '%s'. One or more Instances have delete protection enabled.", + account.getAccountName())); + } + @Override @ActionEvent(eventType = EventTypes.EVENT_ACCOUNT_ENABLE, eventDescription = "enabling account", async = true) public AccountVO enableAccount(String accountName, Long domainId, Long accountId) { diff --git a/server/src/main/java/com/cloud/user/DomainManagerImpl.java b/server/src/main/java/com/cloud/user/DomainManagerImpl.java index 28f9bd3ab39..a590c5ad833 100644 --- a/server/src/main/java/com/cloud/user/DomainManagerImpl.java +++ b/server/src/main/java/com/cloud/user/DomainManagerImpl.java @@ -25,6 +25,7 @@ import java.util.Set; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -104,6 +105,7 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; import com.cloud.vm.ReservationContext; import com.cloud.vm.ReservationContextImpl; +import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.VMInstanceDao; import org.apache.commons.lang3.StringUtils; @@ -364,6 +366,8 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom } _accountMgr.checkAccess(caller, domain); + // Check across the domain hierarchy (current + children) for any delete-protected instances + validateNoDeleteProtectedVmsForDomain(domain); return deleteDomain(domain, cleanup); } @@ -724,6 +728,22 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom return success && deleteDomainSuccess; } + private void validateNoDeleteProtectedVmsForDomain(Domain parentDomain) { + Set allDomainIds = getDomainChildrenIds(parentDomain.getPath()); + List deleteProtectedVms = vmInstanceDao.listDeleteProtectedVmsByDomainIds(allDomainIds); + if (CollectionUtils.isEmpty(deleteProtectedVms)) { + return; + } + if (logger.isDebugEnabled()) { + List vmUuids = deleteProtectedVms.stream().map(VMInstanceVO::getUuid).collect(Collectors.toList()); + logger.debug("Cannot delete Domain {}, it has delete protection enabled for Instances: {}", parentDomain, vmUuids); + } + + throw new InvalidParameterValueException( + String.format("Cannot delete Domain '%s'. One or more Instances have delete protection enabled.", + parentDomain.getName())); + } + @Override public Pair, Integer> searchForDomains(ListDomainsCmd cmd) { Account caller = getCaller(); diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 9e325fdefcf..0242243af3a 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -60,9 +60,6 @@ import javax.naming.ConfigurationException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; -import com.cloud.serializer.GsonHelper; -import com.cloud.storage.SnapshotPolicyVO; -import com.cloud.storage.dao.SnapshotPolicyDao; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; @@ -130,6 +127,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult; +import org.apache.cloudstack.extension.ExtensionHelper; import org.apache.cloudstack.framework.async.AsyncCallFuture; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; @@ -140,6 +138,7 @@ import org.apache.cloudstack.framework.messagebus.PublishScope; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.query.QueryService; import org.apache.cloudstack.reservation.dao.ReservationDao; +import org.apache.cloudstack.resourcelimit.Reserver; import org.apache.cloudstack.snapshot.SnapshotHelper; import org.apache.cloudstack.storage.command.DeleteCommand; import org.apache.cloudstack.storage.command.DettachCommand; @@ -268,12 +267,12 @@ import com.cloud.hypervisor.kvm.dpdk.DpdkHelper; import com.cloud.kubernetes.cluster.KubernetesServiceHelper; import com.cloud.network.IpAddressManager; import com.cloud.network.Network; +import com.cloud.network.NetworkService; import com.cloud.network.Network.GuestType; import com.cloud.network.Network.IpAddresses; import com.cloud.network.Network.Provider; import com.cloud.network.Network.Service; import com.cloud.network.NetworkModel; -import com.cloud.network.NetworkService; import com.cloud.network.Networks.TrafficType; import com.cloud.network.PhysicalNetwork; import com.cloud.network.as.AutoScaleManager; @@ -315,6 +314,8 @@ import com.cloud.org.Grouping; import com.cloud.resource.ResourceManager; import com.cloud.resource.ResourceState; import com.cloud.resourcelimit.CheckedReservation; +import com.cloud.resourcelimit.ReservationHelper; +import com.cloud.serializer.GsonHelper; import com.cloud.server.ManagementService; import com.cloud.server.ResourceTag; import com.cloud.service.ServiceOfferingVO; @@ -326,6 +327,7 @@ import com.cloud.storage.GuestOSCategoryVO; import com.cloud.storage.GuestOSVO; import com.cloud.storage.ScopeType; import com.cloud.storage.Snapshot; +import com.cloud.storage.SnapshotPolicyVO; import com.cloud.storage.SnapshotVO; import com.cloud.storage.Storage; import com.cloud.storage.Storage.ImageFormat; @@ -344,6 +346,7 @@ import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.GuestOSCategoryDao; import com.cloud.storage.dao.GuestOSDao; import com.cloud.storage.dao.SnapshotDao; +import com.cloud.storage.dao.SnapshotPolicyDao; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateZoneDao; import com.cloud.storage.dao.VolumeDao; @@ -639,6 +642,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir @Inject VnfTemplateManager vnfTemplateManager; + @Inject + ExtensionHelper extensionHelper; + private ScheduledExecutorService _executor = null; private ScheduledExecutorService _vmIpFetchExecutor = null; private int _expungeInterval; @@ -1374,9 +1380,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir Account owner = _accountMgr.getActiveAccountById(vmInstance.getAccountId()); VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vmInstance.getTemplateId()); + + List reservations = new ArrayList<>(); + try { if (!VirtualMachineManager.ResourceCountRunningVMsonly.value()) { _resourceLimitMgr.checkVmResourceLimitsForServiceOfferingChange(owner, vmInstance.isDisplay(), (long) currentCpu, (long) newCpu, - (long) currentMemory, (long) newMemory, currentServiceOffering, newServiceOffering, template); + (long) currentMemory, (long) newMemory, currentServiceOffering, newServiceOffering, template, reservations); } // Check that the specified service offering ID is valid @@ -1399,6 +1408,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return _vmDao.findById(vmInstance.getId()); + } finally { + ReservationHelper.closeAll(reservations); + } } /** @@ -2140,9 +2152,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vmInstance.getTemplateId()); + List reservations = new ArrayList<>(); + try { // Check resource limits _resourceLimitMgr.checkVmResourceLimitsForServiceOfferingChange(owner, vmInstance.isDisplay(), (long) currentCpu, (long) newCpu, - (long) currentMemory, (long) newMemory, currentServiceOffering, newServiceOffering, template); + (long) currentMemory, (long) newMemory, currentServiceOffering, newServiceOffering, template, reservations); // Dynamically upgrade the running vms boolean success = false; @@ -2214,6 +2228,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } } return success; + + } finally { + ReservationHelper.closeAll(reservations); + } } protected void validateDiskOfferingChecks(ServiceOfferingVO currentServiceOffering, ServiceOfferingVO newServiceOffering) { @@ -2399,10 +2417,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId()); VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vm.getTemplateId()); + List reservations = new ArrayList<>(); + try { // First check that the maximum number of UserVMs, CPU and Memory limit for the given // accountId will not be exceeded if (!VirtualMachineManager.ResourceCountRunningVMsonly.value()) { - resourceLimitService.checkVmResourceLimit(account, vm.isDisplayVm(), serviceOffering, template); + resourceLimitService.checkVmResourceLimit(account, vm.isDisplayVm(), serviceOffering, template, reservations); } _haMgr.cancelDestroy(vm, vm.getHostId()); @@ -2427,6 +2447,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir //Update Resource Count for the given account resourceCountIncrement(account.getId(), vm.isDisplayVm(), serviceOffering, template); + + } finally { + ReservationHelper.closeAll(reservations); + } } }); @@ -2861,53 +2885,25 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir long currentCpu = currentServiceOffering.getCpu(); long currentMemory = currentServiceOffering.getRamSize(); VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vmInstance.getTemplateId()); - Long currentGpu = currentServiceOffering.getGpuCount() != null ? Long.valueOf(currentServiceOffering.getGpuCount()) : 0L; - Long newGpu = svcOffering.getGpuCount() != null ? Long.valueOf(svcOffering.getGpuCount()) : 0L; + List reservations = new ArrayList<>(); try { - checkVmLimits(owner, vmInstance, svcOffering, template, newCpu, currentCpu, newMemory, currentMemory, newGpu, currentGpu); + _resourceLimitMgr.checkVmResourceLimitsForServiceOfferingChange(owner, vmInstance.isDisplay(), currentCpu, newCpu, + currentMemory, newMemory, currentServiceOffering, svcOffering, template, reservations); + if (newCpu > currentCpu) { + _resourceLimitMgr.incrementVmCpuResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, newCpu - currentCpu); + } else if (newCpu > 0 && currentCpu > newCpu){ + _resourceLimitMgr.decrementVmCpuResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, currentCpu - newCpu); + } + if (newMemory > currentMemory) { + _resourceLimitMgr.incrementVmMemoryResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, newMemory - currentMemory); + } else if (newMemory > 0 && currentMemory > newMemory){ + _resourceLimitMgr.decrementVmMemoryResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, currentMemory - newMemory); + } } catch (ResourceAllocationException e) { logger.error(String.format("Failed to updated VM due to: %s", e.getLocalizedMessage())); throw new InvalidParameterValueException(e.getLocalizedMessage()); - } - adjustVmLimits(owner, vmInstance, svcOffering, template, newCpu, currentCpu, newMemory, currentMemory, newGpu, currentGpu); - } - - private void checkVmLimits(Account owner, UserVmVO vmInstance, ServiceOfferingVO svcOffering, - VMTemplateVO template, Long newCpu, Long currentCpu, Long newMemory, Long currentMemory, - Long newGpu, Long currentGpu - ) throws ResourceAllocationException { - if (newCpu > currentCpu) { - _resourceLimitMgr.checkVmCpuResourceLimit(owner, vmInstance.isDisplay(), svcOffering, - template, newCpu - currentCpu); - } - if (newMemory > currentMemory) { - _resourceLimitMgr.checkVmMemoryResourceLimit(owner, vmInstance.isDisplay(), svcOffering, - template, newMemory - currentMemory); - } - if (newGpu > currentGpu) { - _resourceLimitMgr.checkVmGpuResourceLimit(owner, vmInstance.isDisplay(), svcOffering, - template, newGpu - currentGpu); - } - } - - private void adjustVmLimits(Account owner, UserVmVO vmInstance, ServiceOfferingVO svcOffering, - VMTemplateVO template, Long newCpu, Long currentCpu, Long newMemory, Long currentMemory, - Long newGpu, Long currentGpu - ) { - if (newCpu > currentCpu) { - _resourceLimitMgr.incrementVmCpuResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, newCpu - currentCpu); - } else if (newCpu > 0 && currentCpu > newCpu) { - _resourceLimitMgr.decrementVmCpuResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, currentCpu - newCpu); - } - if (newMemory > currentMemory) { - _resourceLimitMgr.incrementVmMemoryResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, newMemory - currentMemory); - } else if (newMemory > 0 && currentMemory > newMemory) { - _resourceLimitMgr.decrementVmMemoryResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, currentMemory - newMemory); - } - if (newGpu > currentGpu) { - _resourceLimitMgr.incrementVmGpuResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, newGpu - currentGpu); - } else if (newGpu > 0 && currentGpu > newGpu) { - _resourceLimitMgr.decrementVmGpuResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, currentGpu - newGpu); + } finally { + ReservationHelper.closeAll(reservations); } } @@ -2973,6 +2969,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir .map(item -> (item).trim()) .collect(Collectors.toList()); userDenyListedSettings.addAll(QueryService.RootAdminOnlyVmSettings); + if (template != null && template.getExtensionId() != null) { + userDenyListedSettings.addAll(extensionHelper.getExtensionReservedResourceDetails( + template.getExtensionId())); + } + final List userReadOnlySettings = Stream.of(QueryService.UserVMReadOnlyDetails.value().split(",")) .map(item -> (item).trim()) .collect(Collectors.toList()); @@ -4427,7 +4428,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir throw new InvalidParameterValueException(String.format("Invalid disk offering %s specified for datadisk Template %s. Disk offering size should be greater than or equal to the Template size", dataDiskOffering, dataDiskTemplate)); } _templateDao.loadDetails(dataDiskTemplate); - resourceLimitService.checkVolumeResourceLimit(owner, true, dataDiskOffering.getDiskSize(), dataDiskOffering); } } @@ -5777,13 +5777,135 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return startVirtualMachine(vmId, podId, clusterId, hostId, additionalParams, deploymentPlannerToUse, true); } + private Pair> startVirtualMachineUnchecked(UserVmVO vm, VMTemplateVO template, Long podId, + Long clusterId, Long hostId, @NotNull Map additionalParams, String deploymentPlannerToUse, + boolean isExplicitHost, boolean isRootAdmin) throws ResourceUnavailableException, InsufficientCapacityException { + + // check if vm is security group enabled + if (_securityGroupMgr.isVmSecurityGroupEnabled(vm.getId()) && _securityGroupMgr.getSecurityGroupsForVm(vm.getId()).isEmpty() + && !_securityGroupMgr.isVmMappedToDefaultSecurityGroup(vm.getId()) && _networkModel.canAddDefaultSecurityGroup()) { + // if vm is not mapped to security group, create a mapping + if (logger.isDebugEnabled()) { + logger.debug("Vm " + vm + " is security group enabled, but not mapped to default security group; creating the mapping automatically"); + } + + SecurityGroup defaultSecurityGroup = _securityGroupMgr.getDefaultSecurityGroup(vm.getAccountId()); + if (defaultSecurityGroup != null) { + List groupList = new ArrayList<>(); + groupList.add(defaultSecurityGroup.getId()); + _securityGroupMgr.addInstanceToGroups(vm, groupList); + } + } + + // Choose deployment planner + // Host takes 1st preference, Cluster takes 2nd preference and Pod takes 3rd + // Default behaviour is invoked when host, cluster or pod are not specified + Pod destinationPod = getDestinationPod(podId, isRootAdmin); + Cluster destinationCluster = getDestinationCluster(clusterId, isRootAdmin); + HostVO destinationHost = getDestinationHost(hostId, isRootAdmin, isExplicitHost); + DataCenterDeployment plan = null; + boolean deployOnGivenHost = false; + if (destinationHost != null) { + logger.debug("Destination Host to deploy the VM is specified, specifying a deployment plan to deploy the VM"); + _hostDao.loadHostTags(destinationHost); + validateStrictHostTagCheck(vm, destinationHost); + + final ServiceOfferingVO offering = serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId()); + Pair cpuCapabilityAndCapacity = _capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(destinationHost, offering, false); + if (!cpuCapabilityAndCapacity.first() || !cpuCapabilityAndCapacity.second()) { + String errorMsg; + if (!cpuCapabilityAndCapacity.first()) { + errorMsg = String.format("Cannot deploy the VM to specified host %s, requested CPU and speed is more than the host capability", destinationHost); + } else { + errorMsg = String.format("Cannot deploy the VM to specified host %s, host does not have enough free CPU or RAM, please check the logs", destinationHost); + } + logger.info(errorMsg); + if (!AllowDeployVmIfGivenHostFails.value()) { + throw new InvalidParameterValueException(errorMsg); + } + } else { + plan = new DataCenterDeployment(vm.getDataCenterId(), destinationHost.getPodId(), destinationHost.getClusterId(), destinationHost.getId(), null, null); + if (!AllowDeployVmIfGivenHostFails.value()) { + deployOnGivenHost = true; + } + } + } else if (destinationCluster != null) { + logger.debug("Destination Cluster to deploy the VM is specified, specifying a deployment plan to deploy the VM"); + plan = new DataCenterDeployment(vm.getDataCenterId(), destinationCluster.getPodId(), destinationCluster.getId(), null, null, null); + if (!AllowDeployVmIfGivenHostFails.value()) { + deployOnGivenHost = true; + } + } else if (destinationPod != null) { + logger.debug("Destination Pod to deploy the VM is specified, specifying a deployment plan to deploy the VM"); + plan = new DataCenterDeployment(vm.getDataCenterId(), destinationPod.getId(), null, null, null, null); + if (!AllowDeployVmIfGivenHostFails.value()) { + deployOnGivenHost = true; + } + } + + // Set parameters + Map params = null; + if (vm.isUpdateParameters()) { + _vmDao.loadDetails(vm); + String password = getCurrentVmPasswordOrDefineNewPassword(String.valueOf(additionalParams.getOrDefault(VirtualMachineProfile.Param.VmPassword, "")), vm, template); + if (!validPassword(password)) { + throw new InvalidParameterValueException("A valid password for this virtual machine was not provided."); + } + // Check if an SSH key pair was selected for the instance and if so + // use it to encrypt & save the vm password + encryptAndStorePassword(vm, password); + params = createParameterInParameterMap(params, additionalParams, VirtualMachineProfile.Param.VmPassword, password); + } + + if (additionalParams.containsKey(VirtualMachineProfile.Param.BootIntoSetup)) { + if (!HypervisorType.VMware.equals(vm.getHypervisorType())) { + throw new InvalidParameterValueException(ApiConstants.BOOT_INTO_SETUP + " makes no sense for " + vm.getHypervisorType()); + } + Object paramValue = additionalParams.get(VirtualMachineProfile.Param.BootIntoSetup); + if (logger.isTraceEnabled()) { + logger.trace("It was specified whether to enter setup mode: " + paramValue.toString()); + } + params = createParameterInParameterMap(params, additionalParams, VirtualMachineProfile.Param.BootIntoSetup, paramValue); + } + + VirtualMachineEntity vmEntity = _orchSrvc.getVirtualMachine(vm.getUuid()); + + DeploymentPlanner planner = null; + if (deploymentPlannerToUse != null) { + // if set to null, the deployment planner would be later figured out either from global config var, or from + // the service offering + planner = _planningMgr.getDeploymentPlannerByName(deploymentPlannerToUse); + if (planner == null) { + throw new InvalidParameterValueException("Can't find a planner by name " + deploymentPlannerToUse); + } + } + vmEntity.setParamsToEntity(additionalParams); + + UserVO callerUser = _userDao.findById(CallContext.current().getCallingUserId()); + String reservationId = vmEntity.reserve(planner, plan, new ExcludeList(), Long.toString(callerUser.getId())); + vmEntity.deploy(reservationId, Long.toString(callerUser.getId()), params, deployOnGivenHost); + + Pair> vmParamPair = new Pair(vm, params); + if (vm.isUpdateParameters()) { + // this value is not being sent to the backend; need only for api + // display purposes + if (template.isEnablePassword()) { + if (vm.getDetail(VmDetailConstants.PASSWORD) != null) { + vmInstanceDetailsDao.removeDetail(vm.getId(), VmDetailConstants.PASSWORD); + } + vm.setUpdateParameters(false); + _vmDao.update(vm.getId(), vm); + } + } + return vmParamPair; + } + @Override public Pair> startVirtualMachine(long vmId, Long podId, Long clusterId, Long hostId, @NotNull Map additionalParams, String deploymentPlannerToUse, boolean isExplicitHost) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { // Input validation final Account callerAccount = CallContext.current().getCallingAccount(); - UserVO callerUser = _userDao.findById(CallContext.current().getCallingUserId()); // if account is removed, return error if (callerAccount == null || callerAccount.getRemoved() != null) { @@ -5811,133 +5933,21 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if (owner.getState() == Account.State.DISABLED) { throw new PermissionDeniedException(String.format("The owner of %s is disabled: %s", vm, owner)); } + boolean isRootAdmin = _accountService.isRootAdmin(callerAccount.getId()); + VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vm.getTemplateId()); if (VirtualMachineManager.ResourceCountRunningVMsonly.value()) { - // check if account/domain is with in resource limits to start a new vm ServiceOfferingVO offering = serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId()); - resourceLimitService.checkVmResourceLimit(owner, vm.isDisplayVm(), offering, template); + List resourceLimitHostTags = resourceLimitService.getResourceLimitHostTags(offering, template); + try (CheckedReservation vmReservation = new CheckedReservation(owner, ResourceType.user_vm, resourceLimitHostTags, 1l, reservationDao, resourceLimitService); + CheckedReservation cpuReservation = new CheckedReservation(owner, ResourceType.cpu, resourceLimitHostTags, Long.valueOf(offering.getCpu()), reservationDao, resourceLimitService); + CheckedReservation memReservation = new CheckedReservation(owner, ResourceType.memory, resourceLimitHostTags, Long.valueOf(offering.getRamSize()), reservationDao, resourceLimitService); + ) { + return startVirtualMachineUnchecked(vm, template, podId, clusterId, hostId, additionalParams, deploymentPlannerToUse, isExplicitHost, isRootAdmin); + } + } else { + return startVirtualMachineUnchecked(vm, template, podId, clusterId, hostId, additionalParams, deploymentPlannerToUse, isExplicitHost, isRootAdmin); } - // check if vm is security group enabled - if (_securityGroupMgr.isVmSecurityGroupEnabled(vmId) && _securityGroupMgr.getSecurityGroupsForVm(vmId).isEmpty() - && !_securityGroupMgr.isVmMappedToDefaultSecurityGroup(vmId) && _networkModel.canAddDefaultSecurityGroup()) { - // if vm is not mapped to security group, create a mapping - if (logger.isDebugEnabled()) { - logger.debug("Vm " + vm + " is security group enabled, but not mapped to default security group; creating the mapping automatically"); - } - - SecurityGroup defaultSecurityGroup = _securityGroupMgr.getDefaultSecurityGroup(vm.getAccountId()); - if (defaultSecurityGroup != null) { - List groupList = new ArrayList<>(); - groupList.add(defaultSecurityGroup.getId()); - _securityGroupMgr.addInstanceToGroups(vm, groupList); - } - } - // Choose deployment planner - // Host takes 1st preference, Cluster takes 2nd preference and Pod takes 3rd - // Default behaviour is invoked when host, cluster or pod are not specified - boolean isRootAdmin = _accountService.isRootAdmin(callerAccount.getId()); - Pod destinationPod = getDestinationPod(podId, isRootAdmin); - Cluster destinationCluster = getDestinationCluster(clusterId, isRootAdmin); - HostVO destinationHost = getDestinationHost(hostId, isRootAdmin, isExplicitHost); - DataCenterDeployment plan = null; - boolean deployOnGivenHost = false; - if (destinationHost != null) { - logger.debug("Destination Host to deploy the VM is specified, specifying a deployment plan to deploy the VM"); - _hostDao.loadHostTags(destinationHost); - validateStrictHostTagCheck(vm, destinationHost); - - final ServiceOfferingVO offering = serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId()); - Pair cpuCapabilityAndCapacity = _capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(destinationHost, offering, false); - if (!cpuCapabilityAndCapacity.first() || !cpuCapabilityAndCapacity.second()) { - String errorMsg; - if (!cpuCapabilityAndCapacity.first()) { - errorMsg = String.format("Cannot deploy the VM to specified host %s, requested CPU and speed is more than the host capability", destinationHost); - } else { - errorMsg = String.format("Cannot deploy the VM to specified host %s, host does not have enough free CPU or RAM, please check the logs", destinationHost); - } - logger.info(errorMsg); - if (!AllowDeployVmIfGivenHostFails.value()) { - throw new InvalidParameterValueException(errorMsg); - }; - } else { - plan = new DataCenterDeployment(vm.getDataCenterId(), destinationHost.getPodId(), destinationHost.getClusterId(), destinationHost.getId(), null, null); - if (!AllowDeployVmIfGivenHostFails.value()) { - deployOnGivenHost = true; - } - } - } else if (destinationCluster != null) { - logger.debug("Destination Cluster to deploy the VM is specified, specifying a deployment plan to deploy the VM"); - plan = new DataCenterDeployment(vm.getDataCenterId(), destinationCluster.getPodId(), destinationCluster.getId(), null, null, null); - if (!AllowDeployVmIfGivenHostFails.value()) { - deployOnGivenHost = true; - } - } else if (destinationPod != null) { - logger.debug("Destination Pod to deploy the VM is specified, specifying a deployment plan to deploy the VM"); - plan = new DataCenterDeployment(vm.getDataCenterId(), destinationPod.getId(), null, null, null, null); - if (!AllowDeployVmIfGivenHostFails.value()) { - deployOnGivenHost = true; - } - } - - // Set parameters - Map params = null; - if (vm.isUpdateParameters()) { - _vmDao.loadDetails(vm); - - String password = getCurrentVmPasswordOrDefineNewPassword(String.valueOf(additionalParams.getOrDefault(VirtualMachineProfile.Param.VmPassword, "")), vm, template); - - if (!validPassword(password)) { - throw new InvalidParameterValueException("A valid password for this virtual machine was not provided."); - } - - // Check if an SSH key pair was selected for the instance and if so - // use it to encrypt & save the vm password - encryptAndStorePassword(vm, password); - - params = createParameterInParameterMap(params, additionalParams, VirtualMachineProfile.Param.VmPassword, password); - } - - if (additionalParams.containsKey(VirtualMachineProfile.Param.BootIntoSetup)) { - if (! HypervisorType.VMware.equals(vm.getHypervisorType())) { - throw new InvalidParameterValueException(ApiConstants.BOOT_INTO_SETUP + " makes no sense for " + vm.getHypervisorType()); - } - Object paramValue = additionalParams.get(VirtualMachineProfile.Param.BootIntoSetup); - if (logger.isTraceEnabled()) { - logger.trace("It was specified whether to enter setup mode: " + paramValue.toString()); - } - params = createParameterInParameterMap(params, additionalParams, VirtualMachineProfile.Param.BootIntoSetup, paramValue); - } - - VirtualMachineEntity vmEntity = _orchSrvc.getVirtualMachine(vm.getUuid()); - - DeploymentPlanner planner = null; - if (deploymentPlannerToUse != null) { - // if set to null, the deployment planner would be later figured out either from global config var, or from - // the service offering - planner = _planningMgr.getDeploymentPlannerByName(deploymentPlannerToUse); - if (planner == null) { - throw new InvalidParameterValueException("Can't find a planner by name " + deploymentPlannerToUse); - } - } - vmEntity.setParamsToEntity(additionalParams); - - String reservationId = vmEntity.reserve(planner, plan, new ExcludeList(), Long.toString(callerUser.getId())); - vmEntity.deploy(reservationId, Long.toString(callerUser.getId()), params, deployOnGivenHost); - - Pair> vmParamPair = new Pair(vm, params); - if (vm != null && vm.isUpdateParameters()) { - // this value is not being sent to the backend; need only for api - // display purposes - if (template.isEnablePassword()) { - if (vm.getDetail(VmDetailConstants.PASSWORD) != null) { - vmInstanceDetailsDao.removeDetail(vm.getId(), VmDetailConstants.PASSWORD); - } - vm.setUpdateParameters(false); - _vmDao.update(vm.getId(), vm); - } - } - - return vmParamPair; } /** @@ -7910,38 +7920,29 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return findMigratedVm(vm.getId(), vm.getType()); } - protected void checkVolumesLimits(Account account, List volumes) throws ResourceAllocationException { - Long totalVolumes = 0L; - Long totalVolumesSize = 0L; + protected void checkVolumesLimits(Account account, List volumes, List reservations) throws ResourceAllocationException { Map> diskOfferingTagsMap = new HashMap<>(); - Map tagVolumeCountMap = new HashMap<>(); - Map tagSizeMap = new HashMap<>(); + for (VolumeVO volume : volumes) { if (!volume.isDisplay()) { continue; } - totalVolumes++; - totalVolumesSize += volume.getSize(); - if (!diskOfferingTagsMap.containsKey(volume.getDiskOfferingId())) { - diskOfferingTagsMap.put(volume.getDiskOfferingId(), _resourceLimitMgr.getResourceLimitStorageTags( - _diskOfferingDao.findById(volume.getDiskOfferingId()))); + + Long diskOfferingId = volume.getDiskOfferingId(); + if (!diskOfferingTagsMap.containsKey(diskOfferingId)) { + DiskOffering diskOffering = _diskOfferingDao.findById(diskOfferingId); + List tagsForDiskOffering = _resourceLimitMgr.getResourceLimitStorageTags(diskOffering); + diskOfferingTagsMap.put(diskOfferingId, tagsForDiskOffering); } - List tags = diskOfferingTagsMap.get(volume.getDiskOfferingId()); - for (String tag : tags) { - if (tagVolumeCountMap.containsKey(tag)) { - tagVolumeCountMap.put(tag, tagVolumeCountMap.get(tag) + 1); - tagSizeMap.put(tag, tagSizeMap.get(tag) + volume.getSize()); - } else { - tagVolumeCountMap.put(tag, 1L); - tagSizeMap.put(tag, volume.getSize()); - } - } - } - _resourceLimitMgr.checkResourceLimit(account, ResourceType.volume, totalVolumes); - _resourceLimitMgr.checkResourceLimit(account, ResourceType.primary_storage, totalVolumesSize); - for (String tag : tagVolumeCountMap.keySet()) { - resourceLimitService.checkResourceLimitWithTag(account, ResourceType.volume, tag, tagVolumeCountMap.get(tag)); - resourceLimitService.checkResourceLimitWithTag(account, ResourceType.primary_storage, tag, tagSizeMap.get(tag)); + + List tags = diskOfferingTagsMap.get(diskOfferingId); + + CheckedReservation volumeReservation = new CheckedReservation(account, ResourceType.volume, tags, 1L, reservationDao, resourceLimitService); + reservations.add(volumeReservation); + + long size = ObjectUtils.defaultIfNull(volume.getSize(), 0L); + CheckedReservation primaryStorageReservation = new CheckedReservation(account, ResourceType.primary_storage, tags, size, reservationDao, resourceLimitService); + reservations.add(primaryStorageReservation); } } @@ -7983,14 +7984,16 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir final ServiceOfferingVO offering = serviceOfferingDao.findByIdIncludingRemoved(vm.getId(), vm.getServiceOfferingId()); VirtualMachineTemplate template = _templateDao.findByIdIncludingRemoved(vm.getTemplateId()); - verifyResourceLimitsForAccountAndStorage(newAccount, vm, offering, volumes, template); - validateIfNewOwnerHasAccessToTemplate(vm, newAccount, template); DomainVO domain = _domainDao.findById(domainId); logger.trace("Verifying if the new account [{}] has access to the specified domain [{}].", newAccount, domain); _accountMgr.checkAccess(newAccount, domain); + List reservations = new ArrayList<>(); + try { + verifyResourceLimitsForAccountAndStorage(newAccount, vm, offering, volumes, template, reservations); + Network newNetwork = ensureDestinationNetwork(cmd, vm, newAccount); try { Transaction.execute(new TransactionCallbackNoReturn() { @@ -8007,6 +8010,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir throw e; } + } finally { + ReservationHelper.closeAll(reservations); + } + logger.info("VM [{}] now belongs to account [{}].", vm.getInstanceName(), newAccountName); return vm; } @@ -8077,18 +8084,18 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir * @param volumes The volumes whose total size can exceed resource limits. * @throws ResourceAllocationException */ - protected void verifyResourceLimitsForAccountAndStorage(Account account, UserVmVO vm, ServiceOfferingVO offering, List volumes, VirtualMachineTemplate template) + protected void verifyResourceLimitsForAccountAndStorage(Account account, UserVmVO vm, ServiceOfferingVO offering, List volumes, VirtualMachineTemplate template, List reservations) throws ResourceAllocationException { logger.trace("Verifying if CPU and RAM for VM [{}] do not exceed account [{}] limit.", vm, account); if (!countOnlyRunningVmsInResourceLimitation()) { - resourceLimitService.checkVmResourceLimit(account, vm.isDisplayVm(), offering, template); + resourceLimitService.checkVmResourceLimit(account, vm.isDisplayVm(), offering, template, reservations); } logger.trace("Verifying if volume size for VM [{}] does not exceed account [{}] limit.", vm, account); - checkVolumesLimits(account, volumes); + checkVolumesLimits(account, volumes, reservations); } protected boolean countOnlyRunningVmsInResourceLimitation() { @@ -8720,10 +8727,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir /** * Attempts to create a network suitable for the creation of a VM ({@link NetworkOrchestrationService#createGuestNetwork}). * If no physical network is found, throws a {@link InvalidParameterValueException}. - * @param caller The account which calls for the network creation. * @param newAccount The account to which the network will be created. * @param zone The zone where the network will be created. - * @param requiredOffering The network offering required to create the network. * @return The NetworkVO for the network created. * @throws InsufficientCapacityException * @throws ResourceAllocationException @@ -8987,12 +8992,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir VMTemplateVO template = getRestoreVirtualMachineTemplate(caller, newTemplateId, rootVols, vm); DiskOffering diskOffering = rootDiskOfferingId != null ? _diskOfferingDao.findById(rootDiskOfferingId) : null; + + List reservations = new ArrayList<>(); try { - checkRestoreVmFromTemplate(vm, template, rootVols, diskOffering, details); - } catch (ResourceAllocationException e) { - logger.error("Failed to restore VM {} due to {}", vm, e.getMessage(), e); - throw new CloudRuntimeException("Failed to restore VM " + vm.getUuid() + " due to " + e.getMessage(), e); - } + checkRestoreVmFromTemplate(vm, template, rootVols, diskOffering, details, reservations); if (needRestart) { try { @@ -9139,6 +9142,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir logger.debug("Restore VM {} done successfully", vm); return vm; + } catch (ResourceAllocationException e) { + logger.error("Failed to restore VM {} due to {}", vm, e.getMessage(), e); + throw new CloudRuntimeException("Failed to restore VM " + vm.getUuid() + " due to " + e.getMessage(), e); + } finally { + ReservationHelper.closeAll(reservations); + } } Long getRootVolumeSizeForVmRestore(Volume vol, VMTemplateVO template, UserVmVO userVm, DiskOffering diskOffering, Map details, boolean update) { @@ -9238,7 +9247,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir * @param template template * @throws InvalidParameterValueException if restore is not possible */ - private void checkRestoreVmFromTemplate(UserVmVO vm, VMTemplateVO template, List rootVolumes, DiskOffering newDiskOffering, Map details) throws ResourceAllocationException { + private void checkRestoreVmFromTemplate(UserVmVO vm, VMTemplateVO template, List rootVolumes, DiskOffering newDiskOffering, Map details, List reservations) throws ResourceAllocationException { TemplateDataStoreVO tmplStore; if (!template.isDirectDownload()) { tmplStore = _templateStoreDao.findByTemplateZoneReady(template.getId(), vm.getDataCenterId()); @@ -9256,7 +9265,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if (vm.getTemplateId() != template.getId()) { ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId()); VMTemplateVO currentTemplate = _templateDao.findByIdIncludingRemoved(vm.getTemplateId()); - _resourceLimitMgr.checkVmResourceLimitsForTemplateChange(owner, vm.isDisplay(), serviceOffering, currentTemplate, template); + _resourceLimitMgr.checkVmResourceLimitsForTemplateChange(owner, vm.isDisplay(), serviceOffering, currentTemplate, template, reservations); } for (Volume vol : rootVolumes) { @@ -9267,7 +9276,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if (newDiskOffering != null || !vol.getSize().equals(newSize)) { DiskOffering currentOffering = _diskOfferingDao.findById(vol.getDiskOfferingId()); _resourceLimitMgr.checkVolumeResourceLimitForDiskOfferingChange(owner, vol.isDisplay(), - vol.getSize(), newSize, currentOffering, newDiskOffering); + vol.getSize(), newSize, currentOffering, newDiskOffering, reservations); } } } diff --git a/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java b/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java index 5c90b5cbee6..355196fea7b 100644 --- a/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java @@ -449,7 +449,6 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme * Create, persist and return vm snapshot for userVmVo with given parameters. * Persistence and support for custom service offerings are done on the same transaction * @param userVmVo user vm - * @param vmId vm id * @param vsDescription vm description * @param vmSnapshotName vm snapshot name * @param vsDisplayName vm snapshot display name @@ -816,37 +815,43 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme } /** - * If snapshot was taken with a different service offering than actual used in vm, should change it back to it - * @param userVm vm to change service offering (if necessary) + * If snapshot was taken with a different service offering than actual used in vm, should change it back to it. + * We also call changeUserVmServiceOffering in case the service offering is dynamic in order to + * perform resource limit validation, as the amount of CPUs or memory may have been changed. * @param vmSnapshotVo vm snapshot */ protected void updateUserVmServiceOffering(UserVm userVm, VMSnapshotVO vmSnapshotVo) { if (vmSnapshotVo.getServiceOfferingId() != userVm.getServiceOfferingId()) { changeUserVmServiceOffering(userVm, vmSnapshotVo); + return; + } + ServiceOfferingVO serviceOffering = _serviceOfferingDao.findById(userVm.getServiceOfferingId()); + if (serviceOffering.isDynamic()) { + changeUserVmServiceOffering(userVm, vmSnapshotVo); } } /** * Get user vm details as a map - * @param userVm user vm + * @param vmSnapshotVo snapshot to get the details from * @return map */ - protected Map getVmMapDetails(UserVm userVm) { - List userVmDetails = _vmInstanceDetailsDao.listDetails(userVm.getId()); + protected Map getVmMapDetails(VMSnapshotVO vmSnapshotVo) { + List vmSnapshotDetails = _vmSnapshotDetailsDao.listDetails(vmSnapshotVo.getId()); Map details = new HashMap(); - for (VMInstanceDetailVO detail : userVmDetails) { + for (VMSnapshotDetailsVO detail : vmSnapshotDetails) { details.put(detail.getName(), detail.getValue()); } return details; } /** - * Update service offering on {@link userVm} to the one specified in {@link vmSnapshotVo} + * Update service offering on {code}userVm{code} to the one specified in {code}vmSnapshotVo{code} * @param userVm user vm to be updated * @param vmSnapshotVo vm snapshot */ protected void changeUserVmServiceOffering(UserVm userVm, VMSnapshotVO vmSnapshotVo) { - Map vmDetails = getVmMapDetails(userVm); + Map vmDetails = getVmMapDetails(vmSnapshotVo); boolean result = upgradeUserVmServiceOffering(userVm, vmSnapshotVo.getServiceOfferingId(), vmDetails); if (! result){ throw new CloudRuntimeException("Instance Snapshot reverting failed because the Instance service offering couldn't be changed to the one used when Snapshot was taken"); @@ -855,8 +860,8 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme } /** - * Upgrade virtual machine {@linkplain vmId} to new service offering {@linkplain serviceOfferingId} - * @param vmId vm id + * Upgrade virtual machine {code}vm{code} to new service offering {code}serviceOfferingId{code} + * @param vm vm * @param serviceOfferingId service offering id * @param details vm details * @return if operation was successful @@ -1294,7 +1299,7 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme public Pair orchestrateCreateVMSnapshot(VmWorkCreateVMSnapshot work) throws Exception { VMSnapshot snapshot = orchestrateCreateVMSnapshot(work.getVmId(), work.getVmSnapshotId(), work.isQuiesceVm()); return new Pair(JobInfo.Status.SUCCEEDED, - _jobMgr.marshallResultObject(new Long(snapshot.getId()))); + _jobMgr.marshallResultObject(Long.valueOf(snapshot.getId()))); } @ReflectionUse diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index ccacb82236c..090f702c466 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -81,6 +81,7 @@ import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.managed.context.ManagedContextTimerTask; import org.apache.cloudstack.poll.BackgroundPollManager; import org.apache.cloudstack.poll.BackgroundPollTask; +import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; @@ -89,6 +90,8 @@ import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; import com.cloud.alert.AlertManager; import com.cloud.api.ApiDispatcher; @@ -122,6 +125,7 @@ import com.cloud.network.dao.NetworkDao; import com.cloud.offering.DiskOffering; import com.cloud.offering.ServiceOffering; import com.cloud.projects.Project; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.serializer.GsonHelper; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; @@ -173,8 +177,6 @@ import com.cloud.vm.dao.VMInstanceDetailsDao; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; -import org.apache.commons.lang3.builder.ReflectionToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; public class BackupManagerImpl extends ManagerBase implements BackupManager { @@ -244,6 +246,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { private GuestOSDao _guestOSDao; @Inject private DomainHelper domainHelper; + @Inject + ReservationDao reservationDao; private AsyncJobDispatcher asyncJobDispatcher; private Timer backupTimer; @@ -940,14 +944,6 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { Long backupScheduleId = getBackupScheduleId(job); boolean isScheduledBackup = backupScheduleId != null; Account owner = accountManager.getAccount(vm.getAccountId()); - try { - resourceLimitMgr.checkResourceLimit(owner, Resource.ResourceType.backup); - } catch (ResourceAllocationException e) { - if (isScheduledBackup) { - sendExceededBackupLimitAlert(owner.getUuid(), Resource.ResourceType.backup); - } - throw e; - } Long backupSize = 0L; for (final Volume volume: volumeDao.findByInstance(vmId)) { @@ -959,42 +955,57 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { backupSize += volumeSize; } } - try { - resourceLimitMgr.checkResourceLimit(owner, Resource.ResourceType.backup_storage, backupSize); - } catch (ResourceAllocationException e) { - if (isScheduledBackup) { - sendExceededBackupLimitAlert(owner.getUuid(), Resource.ResourceType.backup_storage); - } - throw e; - } - - ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(), - EventTypes.EVENT_VM_BACKUP_CREATE, "creating backup for VM ID:" + vm.getUuid(), - vmId, ApiCommandResourceType.VirtualMachine.toString(), - true, 0); - - Pair result = backupProvider.takeBackup(vm, cmd.getQuiesceVM()); - if (!result.first()) { - throw new CloudRuntimeException("Failed to create VM backup"); - } - Backup backup = result.second(); - if (backup != null) { - BackupVO vmBackup = backupDao.findById(result.second().getId()); - vmBackup.setBackupScheduleId(backupScheduleId); - if (cmd.getName() != null) { - vmBackup.setName(cmd.getName()); - } - vmBackup.setDescription(cmd.getDescription()); - backupDao.update(vmBackup.getId(), vmBackup); - resourceLimitMgr.incrementResourceCount(vm.getAccountId(), Resource.ResourceType.backup); - resourceLimitMgr.incrementResourceCount(vm.getAccountId(), Resource.ResourceType.backup_storage, backup.getSize()); - } + createCheckedBackup(cmd, owner, isScheduledBackup, backupSize, vm, vmId, backupProvider, backupScheduleId); if (isScheduledBackup) { deleteOldestBackupFromScheduleIfRequired(vmId, backupScheduleId); } return true; } + private void createCheckedBackup(CreateBackupCmd cmd, Account owner, boolean isScheduledBackup, Long backupSize, + VMInstanceVO vm, Long vmId, BackupProvider backupProvider, Long backupScheduleId) + throws ResourceAllocationException { + try (CheckedReservation backupReservation = new CheckedReservation(owner, Resource.ResourceType.backup, + 1L, reservationDao, resourceLimitMgr); + CheckedReservation backupStorageReservation = new CheckedReservation(owner, + Resource.ResourceType.backup_storage, backupSize, reservationDao, resourceLimitMgr)) { + + ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(), + EventTypes.EVENT_VM_BACKUP_CREATE, "creating backup for VM ID:" + vm.getUuid(), + vmId, ApiCommandResourceType.VirtualMachine.toString(), + true, 0); + + Pair result = backupProvider.takeBackup(vm, cmd.getQuiesceVM()); + if (!result.first()) { + throw new CloudRuntimeException("Failed to create VM backup"); + } + Backup backup = result.second(); + if (backup != null) { + BackupVO vmBackup = backupDao.findById(result.second().getId()); + vmBackup.setBackupScheduleId(backupScheduleId); + if (cmd.getName() != null) { + vmBackup.setName(cmd.getName()); + } + vmBackup.setDescription(cmd.getDescription()); + backupDao.update(vmBackup.getId(), vmBackup); + resourceLimitMgr.incrementResourceCount(vm.getAccountId(), Resource.ResourceType.backup); + resourceLimitMgr.incrementResourceCount(vm.getAccountId(), Resource.ResourceType.backup_storage, backup.getSize()); + } + } catch (Exception e) { + if (e instanceof ResourceAllocationException) { + ResourceAllocationException rae = (ResourceAllocationException)e; + if (isScheduledBackup && (Resource.ResourceType.backup.equals(rae.getResourceType()) || + Resource.ResourceType.backup_storage.equals(rae.getResourceType()))) { + sendExceededBackupLimitAlert(owner.getUuid(), rae.getResourceType()); + } + throw rae; + } else if (e instanceof CloudRuntimeException) { + throw (CloudRuntimeException)e; + } + throw new CloudRuntimeException("Failed to create backup for VM with ID: " + vm.getUuid(), e); + } + } + /** * Sends an alert when the backup limit has been exceeded for a given account. * @@ -1121,7 +1132,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { sb.and("backupOfferingId", sb.entity().getBackupOfferingId(), SearchCriteria.Op.EQ); if (keyword != null) { - sb.or().op("keywordName", sb.entity().getName(), SearchCriteria.Op.LIKE); + sb.and().op("keywordName", sb.entity().getName(), SearchCriteria.Op.LIKE); SearchBuilder vmSearch = vmInstanceDao.createSearchBuilder(); sb.join("vmSearch", vmSearch, sb.entity().getVmId(), vmSearch.entity().getId(), JoinBuilder.JoinType.INNER); sb.or("vmSearch", "keywordVmName", vmSearch.entity().getHostName(), SearchCriteria.Op.LIKE); @@ -1688,19 +1699,35 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { throw new CloudRuntimeException(String.format("Backup offering with ID [%s] does not exist.", backup.getBackupOfferingId())); } final BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); - boolean result = backupProvider.deleteBackup(backup, forced); - if (result) { - resourceLimitMgr.decrementResourceCount(backup.getAccountId(), Resource.ResourceType.backup); - Long backupSize = backup.getSize() != null ? backup.getSize() : 0L; - resourceLimitMgr.decrementResourceCount(backup.getAccountId(), Resource.ResourceType.backup_storage, backupSize); - if (backupDao.remove(backup.getId())) { - checkAndGenerateUsageForLastBackupDeletedAfterOfferingRemove(vm, backup); - return true; - } else { - return false; + return deleteCheckedBackup(forced, backupProvider, backup, vm); + } + + private boolean deleteCheckedBackup(Boolean forced, BackupProvider backupProvider, BackupVO backup, VMInstanceVO vm) { + Account owner = accountManager.getAccount(backup.getAccountId()); + long backupSize = backup.getSize() != null ? backup.getSize() : 0L; + try (CheckedReservation backupReservation = new CheckedReservation(owner, Resource.ResourceType.backup, + backup.getId(), null, -1L, reservationDao, resourceLimitMgr); + CheckedReservation backupStorageReservation = new CheckedReservation(owner, + Resource.ResourceType.backup_storage, backup.getId(), null, -1 * backupSize, + reservationDao, resourceLimitMgr)) { + boolean result = backupProvider.deleteBackup(backup, forced); + if (result) { + resourceLimitMgr.decrementResourceCount(backup.getAccountId(), Resource.ResourceType.backup); + resourceLimitMgr.decrementResourceCount(backup.getAccountId(), Resource.ResourceType.backup_storage, backupSize); + if (backupDao.remove(backup.getId())) { + checkAndGenerateUsageForLastBackupDeletedAfterOfferingRemove(vm, backup); + return true; + } else { + return false; + } } + throw new CloudRuntimeException("Failed to delete the backup"); + } catch (Exception e) { + if (e instanceof CloudRuntimeException) { + throw (CloudRuntimeException) e; + } + throw new CloudRuntimeException("Failed to delete the backup due to: " + e.getMessage(), e); } - throw new CloudRuntimeException("Failed to delete the backup"); } private void checkForPendingBackupJobs(final BackupVO backup) { diff --git a/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java b/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java index 5a18f16fd72..59adb3a0e56 100644 --- a/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java @@ -16,6 +16,27 @@ // under the License. package org.apache.cloudstack.storage.object; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.api.command.user.bucket.CreateBucketCmd; +import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.reservation.dao.ReservationDao; +import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao; +import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO; +import org.apache.commons.lang3.ObjectUtils; +import org.jetbrains.annotations.NotNull; + import com.amazonaws.services.s3.internal.BucketNameUtils; import com.amazonaws.services.s3.model.IllegalBucketNameException; import com.cloud.agent.api.to.BucketTO; @@ -24,6 +45,7 @@ import com.cloud.event.ActionEvent; import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceAllocationException; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.resourcelimit.ResourceLimitManagerImpl; import com.cloud.storage.BucketVO; import com.cloud.storage.DataStoreRole; @@ -36,22 +58,6 @@ import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.GlobalLock; import com.cloud.utils.exception.CloudRuntimeException; -import org.apache.cloudstack.api.command.user.bucket.CreateBucketCmd; -import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; -import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.framework.config.Configurable; -import org.apache.cloudstack.managed.context.ManagedContextRunnable; -import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao; -import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO; - -import javax.inject.Inject; -import javax.naming.ConfigurationException; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; public class BucketApiServiceImpl extends ManagerBase implements BucketApiService, Configurable { @@ -68,6 +74,8 @@ public class BucketApiServiceImpl extends ManagerBase implements BucketApiServic @Inject private BucketStatisticsDao _bucketStatisticsDao; + @Inject + ReservationDao reservationDao; private ScheduledExecutorService _executor = null; @@ -136,13 +144,33 @@ public class BucketApiServiceImpl extends ManagerBase implements BucketApiServic return null; } - resourceLimitManager.checkResourceLimit(owner, Resource.ResourceType.bucket); - resourceLimitManager.checkResourceLimit(owner, Resource.ResourceType.object_storage, (cmd.getQuota() * Resource.ResourceType.bytesToGiB)); + long size = ObjectUtils.defaultIfNull(cmd.getQuota(), 0) * Resource.ResourceType.bytesToGiB; + return createCheckedBucket(cmd, owner, size, ownerId); + } - BucketVO bucket = new BucketVO(ownerId, owner.getDomainId(), cmd.getObjectStoragePoolId(), cmd.getBucketName(), cmd.getQuota(), - cmd.isVersioning(), cmd.isEncryption(), cmd.isObjectLocking(), cmd.getPolicy()); - _bucketDao.persist(bucket); - return bucket; + @NotNull + private BucketVO createCheckedBucket(CreateBucketCmd cmd, Account owner, long size, long ownerId) throws ResourceAllocationException { + try (CheckedReservation bucketReservation = new CheckedReservation(owner, Resource.ResourceType.bucket, + 1L, reservationDao, resourceLimitManager); + CheckedReservation objectStorageReservation = new CheckedReservation(owner, + Resource.ResourceType.object_storage, size, reservationDao, resourceLimitManager)) { + BucketVO bucket = new BucketVO(ownerId, owner.getDomainId(), cmd.getObjectStoragePoolId(), cmd.getBucketName(), cmd.getQuota(), + cmd.isVersioning(), cmd.isEncryption(), cmd.isObjectLocking(), cmd.getPolicy()); + _bucketDao.persist(bucket); + resourceLimitManager.incrementResourceCount(bucket.getAccountId(), Resource.ResourceType.bucket); + if (size > 0) { + resourceLimitManager.incrementResourceCount(bucket.getAccountId(), Resource.ResourceType.object_storage, + (cmd.getQuota() * Resource.ResourceType.bytesToGiB)); + } + return bucket; + } catch (Exception e) { + if (e instanceof ResourceAllocationException) { + throw (ResourceAllocationException)e; + } else if (e instanceof CloudRuntimeException) { + throw (CloudRuntimeException)e; + } + throw new CloudRuntimeException(String.format("Failed to create bucket due to: %s", e.getMessage()), e); + } } @Override @@ -160,7 +188,6 @@ public class BucketApiServiceImpl extends ManagerBase implements BucketApiServic try { bucketTO = new BucketTO(objectStore.createBucket(bucket, objectLock)); bucketCreated = true; - resourceLimitManager.incrementResourceCount(bucket.getAccountId(), Resource.ResourceType.bucket); if (cmd.isVersioning()) { objectStore.setBucketVersioning(bucketTO); @@ -172,7 +199,6 @@ public class BucketApiServiceImpl extends ManagerBase implements BucketApiServic if (cmd.getQuota() != null) { objectStore.setQuota(bucketTO, cmd.getQuota()); - resourceLimitManager.incrementResourceCount(bucket.getAccountId(), Resource.ResourceType.object_storage, (cmd.getQuota() * Resource.ResourceType.bytesToGiB)); if (objectStoreVO.getTotalSize() != null && objectStoreVO.getTotalSize() != 0 && objectStoreVO.getAllocatedSize() != null) { Long allocatedSize = objectStoreVO.getAllocatedSize() / Resource.ResourceType.bytesToGiB; Long totalSize = objectStoreVO.getTotalSize() / Resource.ResourceType.bytesToGiB; @@ -193,11 +219,16 @@ public class BucketApiServiceImpl extends ManagerBase implements BucketApiServic _objectStoreDao.updateAllocatedSize(objectStoreVO, cmd.getQuota() * Resource.ResourceType.bytesToGiB); } } catch (Exception e) { - logger.debug("Failed to create bucket with name: "+bucket.getName(), e); + logger.debug("Failed to create bucket with name: {}", bucket.getName(), e); if(bucketCreated) { objectStore.deleteBucket(bucketTO); } _bucketDao.remove(bucket.getId()); + resourceLimitManager.decrementResourceCount(bucket.getAccountId(), Resource.ResourceType.bucket); + if (bucket.getQuota() != null) { + resourceLimitManager.decrementResourceCount(bucket.getAccountId(), Resource.ResourceType.object_storage, + (bucket.getQuota() * Resource.ResourceType.bytesToGiB)); + } throw new CloudRuntimeException("Failed to create bucket with name: "+bucket.getName()+". "+e.getMessage()); } return bucket; @@ -207,22 +238,39 @@ public class BucketApiServiceImpl extends ManagerBase implements BucketApiServic @ActionEvent(eventType = EventTypes.EVENT_BUCKET_DELETE, eventDescription = "deleting bucket") public boolean deleteBucket(long bucketId, Account caller) { Bucket bucket = _bucketDao.findById(bucketId); - BucketTO bucketTO = new BucketTO(bucket); if (bucket == null) { throw new InvalidParameterValueException("Unable to find bucket with ID: " + bucketId); } _accountMgr.checkAccess(caller, null, true, bucket); ObjectStoreVO objectStoreVO = _objectStoreDao.findById(bucket.getObjectStoreId()); ObjectStoreEntity objectStore = (ObjectStoreEntity)_dataStoreMgr.getDataStore(objectStoreVO.getId(), DataStoreRole.Object); - if (objectStore.deleteBucket(bucketTO)) { - resourceLimitManager.decrementResourceCount(bucket.getAccountId(), Resource.ResourceType.bucket); - if (bucket.getQuota() != null) { - resourceLimitManager.decrementResourceCount(bucket.getAccountId(), Resource.ResourceType.object_storage, (bucket.getQuota() * Resource.ResourceType.bytesToGiB)); - _objectStoreDao.updateAllocatedSize(objectStoreVO, -(bucket.getQuota() * Resource.ResourceType.bytesToGiB)); + return deleteCheckedBucket(objectStore, bucket, objectStoreVO); + } + + private boolean deleteCheckedBucket(ObjectStoreEntity objectStore, Bucket bucket, ObjectStoreVO objectStoreVO) { + Account owner = _accountMgr.getAccount(bucket.getAccountId()); + try (CheckedReservation bucketReservation = new CheckedReservation(owner, Resource.ResourceType.bucket, + bucket.getId(), null, -1L, reservationDao, resourceLimitManager); + CheckedReservation objectStorageReservation = new CheckedReservation(owner, + Resource.ResourceType.object_storage, bucket.getId(), null, + -1*(ObjectUtils.defaultIfNull(bucket.getQuota(), 0) * Resource.ResourceType.bytesToGiB), reservationDao, resourceLimitManager)) { + BucketTO bucketTO = new BucketTO(bucket); + if (objectStore.deleteBucket(bucketTO)) { + resourceLimitManager.decrementResourceCount(bucket.getAccountId(), Resource.ResourceType.bucket); + if (bucket.getQuota() != null) { + resourceLimitManager.decrementResourceCount(bucket.getAccountId(), Resource.ResourceType.object_storage, (bucket.getQuota() * Resource.ResourceType.bytesToGiB)); + _objectStoreDao.updateAllocatedSize(objectStoreVO, -(bucket.getQuota() * Resource.ResourceType.bytesToGiB)); + } + _bucketDao.remove(bucket.getId()); + return true; } - return _bucketDao.remove(bucketId); + return false; + } catch (Exception e) { + if (e instanceof CloudRuntimeException) { + throw (CloudRuntimeException) e; + } + throw new CloudRuntimeException(String.format("Failed to delete bucket due to: %s", e.getMessage()), e); } - return false; } @Override diff --git a/server/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageManagerImpl.java b/server/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageManagerImpl.java index 62b5ac38ee2..37e4f35a9af 100644 --- a/server/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageManagerImpl.java @@ -35,6 +35,7 @@ import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; import com.cloud.offering.DiskOffering; +import com.cloud.resourcelimit.ReservationHelper; import com.cloud.storage.DataStoreRole; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.Storage; @@ -69,6 +70,7 @@ import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.resourcelimit.Reserver; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; @@ -199,10 +201,7 @@ public class VolumeImportUnmanageManagerImpl implements VolumeImportUnmanageServ logFailureAndThrowException("Volume is a reference of snapshot on primary: " + volume.getFullPath()); } - // 5. check resource limitation - checkResourceLimitForImportVolume(owner, volume); - - // 6. get disk offering + // 5. get disk offering DiskOfferingVO diskOffering = getOrCreateDiskOffering(owner, cmd.getDiskOfferingId(), pool.getDataCenterId(), pool.isLocal()); if (diskOffering.isCustomized()) { volumeApiService.validateCustomDiskOfferingSizeRange(volume.getVirtualSize() / ByteScaleUtils.GiB); @@ -211,6 +210,11 @@ public class VolumeImportUnmanageManagerImpl implements VolumeImportUnmanageServ logFailureAndThrowException(String.format("Disk offering: %s storage tags are not compatible with selected storage pool: %s", diskOffering, pool)); } + List reservations = new ArrayList<>(); + try { + // 6. check resource limitation + checkResourceLimitForImportVolume(owner, volume, diskOffering, reservations); + // 7. create records String volumeName = StringUtils.isNotBlank(cmd.getName()) ? cmd.getName().trim() : volumePath; VolumeVO volumeVO = importVolumeInternal(volume, diskOffering, owner, pool, volumeName); @@ -222,6 +226,10 @@ public class VolumeImportUnmanageManagerImpl implements VolumeImportUnmanageServ publicUsageEventForVolumeImportAndUnmanage(volumeVO, true); return responseGenerator.createVolumeResponse(ResponseObject.ResponseView.Full, volumeVO); + + } finally { + ReservationHelper.closeAll(reservations); + } } protected VolumeOnStorageTO getVolumeOnStorageAndCheck(StoragePoolVO pool, String volumePath) { @@ -457,11 +465,10 @@ public class VolumeImportUnmanageManagerImpl implements VolumeImportUnmanageServ return volumeDao.findById(diskProfile.getVolumeId()); } - protected void checkResourceLimitForImportVolume(Account owner, VolumeOnStorageTO volume) { + protected void checkResourceLimitForImportVolume(Account owner, VolumeOnStorageTO volume, DiskOfferingVO diskOffering, List reservations) { Long volumeSize = volume.getVirtualSize(); try { - resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.volume); - resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.primary_storage, volumeSize); + resourceLimitService.checkVolumeResourceLimit(owner, true, volumeSize, diskOffering, reservations); } catch (ResourceAllocationException e) { logger.error("VM resource allocation error for account: {}", owner, e); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM resource allocation error for account: %s. %s", owner.getUuid(), StringUtils.defaultString(e.getMessage()))); diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index e043791c6bf..4c9962c4bc1 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -86,6 +86,8 @@ import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.org.Cluster; import com.cloud.resource.ResourceManager; import com.cloud.resource.ResourceState; +import com.cloud.resourcelimit.CheckedReservation; +import com.cloud.resourcelimit.ReservationHelper; import com.cloud.serializer.GsonHelper; import com.cloud.server.ManagementService; import com.cloud.service.ServiceOfferingVO; @@ -164,6 +166,8 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.reservation.dao.ReservationDao; +import org.apache.cloudstack.resourcelimit.Reserver; import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; @@ -252,6 +256,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { @Inject private ResourceLimitService resourceLimitService; @Inject + private ReservationDao reservationDao; + @Inject private VMInstanceDetailsDao vmInstanceDetailsDao; @Inject private UserVmManager userVmManager; @@ -575,7 +581,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { return new Pair<>(rootDisk, dataDisks); } - private void checkUnmanagedDiskAndOfferingForImport(String instanceName, UnmanagedInstanceTO.Disk disk, DiskOffering diskOffering, ServiceOffering serviceOffering, final Account owner, final DataCenter zone, final Cluster cluster, final boolean migrateAllowed) + private void checkUnmanagedDiskAndOfferingForImport(String instanceName, UnmanagedInstanceTO.Disk disk, DiskOffering diskOffering, ServiceOffering serviceOffering, final Account owner, final DataCenter zone, final Cluster cluster, final boolean migrateAllowed, List reservations) throws ServerApiException, PermissionDeniedException, ResourceAllocationException { if (serviceOffering == null && diskOffering == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Disk offering for disk ID [%s] not found during VM [%s] import.", disk.getDiskId(), instanceName)); @@ -583,7 +589,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { if (diskOffering != null) { accountService.checkAccess(owner, diskOffering, zone); } - resourceLimitService.checkVolumeResourceLimit(owner, true, null, diskOffering); if (disk.getCapacity() == null || disk.getCapacity() == 0) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Size of disk(ID: %s) is found invalid during VM import", disk.getDiskId())); } @@ -598,9 +603,10 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { if (diskOffering != null && !migrateAllowed && !storagePoolSupportsDiskOffering(storagePool, diskOffering)) { throw new InvalidParameterValueException(String.format("Disk offering: %s is not compatible with storage pool: %s of unmanaged disk: %s", diskOffering.getUuid(), storagePool.getUuid(), disk.getDiskId())); } + resourceLimitService.checkVolumeResourceLimit(owner, true, disk.getCapacity(), diskOffering, reservations); } - private void checkUnmanagedDiskAndOfferingForImport(String intanceName, List disks, final Map diskOfferingMap, final Account owner, final DataCenter zone, final Cluster cluster, final boolean migrateAllowed) + private void checkUnmanagedDiskAndOfferingForImport(String intanceName, List disks, final Map diskOfferingMap, final Account owner, final DataCenter zone, final Cluster cluster, final boolean migrateAllowed, List reservations) throws ServerApiException, PermissionDeniedException, ResourceAllocationException { String diskController = null; for (UnmanagedInstanceTO.Disk disk : disks) { @@ -617,7 +623,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Multiple data disk controllers of different type (%s, %s) are not supported for import. Please make sure that all data disk controllers are of the same type", diskController, disk.getController())); } } - checkUnmanagedDiskAndOfferingForImport(intanceName, disk, diskOfferingDao.findById(diskOfferingMap.get(disk.getDiskId())), null, owner, zone, cluster, migrateAllowed); + checkUnmanagedDiskAndOfferingForImport(intanceName, disk, diskOfferingDao.findById(diskOfferingMap.get(disk.getDiskId())), null, owner, zone, cluster, migrateAllowed, reservations); } } @@ -1044,40 +1050,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { } } - protected void checkUnmanagedDiskLimits(Account account, UnmanagedInstanceTO.Disk rootDisk, ServiceOffering serviceOffering, - List dataDisks, Map dataDiskOfferingMap) throws ResourceAllocationException { - Long totalVolumes = 0L; - Long totalVolumesSize = 0L; - List disks = new ArrayList<>(); - disks.add(rootDisk); - disks.addAll(dataDisks); - Map diskOfferingMap = new HashMap<>(dataDiskOfferingMap); - diskOfferingMap.put(rootDisk.getDiskId(), serviceOffering.getDiskOfferingId()); - Map diskOfferingVolumeCountMap = new HashMap<>(); - Map diskOfferingSizeMap = new HashMap<>(); - for (UnmanagedInstanceTO.Disk disk : disks) { - totalVolumes++; - totalVolumesSize += disk.getCapacity(); - Long diskOfferingId = diskOfferingMap.get(disk.getDiskId()); - if (diskOfferingVolumeCountMap.containsKey(diskOfferingId)) { - diskOfferingVolumeCountMap.put(diskOfferingId, diskOfferingVolumeCountMap.get(diskOfferingId) + 1); - diskOfferingSizeMap.put(diskOfferingId, diskOfferingSizeMap.get(diskOfferingId) + disk.getCapacity()); - } else { - diskOfferingVolumeCountMap.put(diskOfferingId, 1L); - diskOfferingSizeMap.put(diskOfferingId, disk.getCapacity()); - } - } - resourceLimitService.checkResourceLimit(account, Resource.ResourceType.volume, totalVolumes); - resourceLimitService.checkResourceLimit(account, Resource.ResourceType.primary_storage, totalVolumesSize); - for (Long diskOfferingId : diskOfferingVolumeCountMap.keySet()) { - List tags = resourceLimitService.getResourceLimitStorageTags(diskOfferingDao.findById(diskOfferingId)); - for (String tag : tags) { - resourceLimitService.checkResourceLimitWithTag(account, Resource.ResourceType.volume, tag, diskOfferingVolumeCountMap.get(diskOfferingId)); - resourceLimitService.checkResourceLimitWithTag(account, Resource.ResourceType.primary_storage, tag, diskOfferingSizeMap.get(diskOfferingId)); - } - } - } - private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedInstance, final String instanceNameInternal, final DataCenter zone, final Cluster cluster, final HostVO host, final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId, final ServiceOfferingVO serviceOffering, final Map dataDiskOfferingMap, @@ -1135,99 +1107,103 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { allDetails.put(VmDetailConstants.ROOT_DISK_SIZE, String.valueOf(size)); } + List reservations = new ArrayList<>(); try { - checkUnmanagedDiskAndOfferingForImport(unmanagedInstance.getName(), rootDisk, null, validatedServiceOffering, owner, zone, cluster, migrateAllowed); + checkUnmanagedDiskAndOfferingForImport(unmanagedInstance.getName(), rootDisk, null, validatedServiceOffering, owner, zone, cluster, migrateAllowed, reservations); if (CollectionUtils.isNotEmpty(dataDisks)) { // Data disk(s) present - checkUnmanagedDiskAndOfferingForImport(unmanagedInstance.getName(), dataDisks, dataDiskOfferingMap, owner, zone, cluster, migrateAllowed); + checkUnmanagedDiskAndOfferingForImport(unmanagedInstance.getName(), dataDisks, dataDiskOfferingMap, owner, zone, cluster, migrateAllowed, reservations); allDetails.put(VmDetailConstants.DATA_DISK_CONTROLLER, dataDisks.get(0).getController()); } - checkUnmanagedDiskLimits(owner, rootDisk, serviceOffering, dataDisks, dataDiskOfferingMap); - } catch (ResourceAllocationException e) { + + // Check NICs and supplied networks + Map nicIpAddressMap = getNicIpAddresses(unmanagedInstance.getNics(), callerNicIpAddressMap); + Map allNicNetworkMap = getUnmanagedNicNetworkMap(unmanagedInstance.getName(), unmanagedInstance.getNics(), nicNetworkMap, nicIpAddressMap, zone, hostName, owner, cluster.getHypervisorType()); + if (!CollectionUtils.isEmpty(unmanagedInstance.getNics())) { + allDetails.put(VmDetailConstants.NIC_ADAPTER, unmanagedInstance.getNics().get(0).getAdapterType()); + } + + if (StringUtils.isNotEmpty(unmanagedInstance.getVncPassword())) { + allDetails.put(VmDetailConstants.KVM_VNC_PASSWORD, unmanagedInstance.getVncPassword()); + } + + addImportingVMBootTypeAndModeDetails(unmanagedInstance.getBootType(), unmanagedInstance.getBootMode(), allDetails); + + VirtualMachine.PowerState powerState = VirtualMachine.PowerState.PowerOff; + if (unmanagedInstance.getPowerState().equals(UnmanagedInstanceTO.PowerState.PowerOn)) { + powerState = VirtualMachine.PowerState.PowerOn; + } + + try { + userVm = userVmManager.importVM(zone, host, template, internalCSName, displayName, owner, + null, caller, true, null, owner.getAccountId(), userId, + validatedServiceOffering, null, guestOsId, hostName, + cluster.getHypervisorType(), allDetails, powerState, null); + } catch (InsufficientCapacityException ice) { + String errorMsg = String.format("Failed to import VM [%s] due to [%s].", displayName, ice.getMessage()); + logger.error(errorMsg, ice); + throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, errorMsg); + } + + if (userVm == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import vm name: %s", displayName)); + } + List> diskProfileStoragePoolList = new ArrayList<>(); + try { + if (rootDisk.getCapacity() == null || rootDisk.getCapacity() == 0) { + throw new InvalidParameterValueException(String.format("Root disk ID: %s size is invalid", rootDisk.getDiskId())); + } + Long minIops = null; + if (details.containsKey(MIN_IOPS)) { + minIops = Long.parseLong(details.get(MIN_IOPS)); + } + Long maxIops = null; + if (details.containsKey(MAX_IOPS)) { + maxIops = Long.parseLong(details.get(MAX_IOPS)); + } + DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); + diskProfileStoragePoolList.add(importDisk(rootDisk, userVm, cluster, diskOffering, Volume.Type.ROOT, String.format("ROOT-%d", userVm.getId()), + rootDisk.getCapacity(), minIops, maxIops, template, owner, null)); + long deviceId = 1L; + for (UnmanagedInstanceTO.Disk disk : dataDisks) { + if (disk.getCapacity() == null || disk.getCapacity() == 0) { + throw new InvalidParameterValueException(String.format("Disk ID: %s size is invalid", rootDisk.getDiskId())); + } + DiskOffering offering = diskOfferingDao.findById(dataDiskOfferingMap.get(disk.getDiskId())); + diskProfileStoragePoolList.add(importDisk(disk, userVm, cluster, offering, Volume.Type.DATADISK, String.format("DATA-%d-%s", userVm.getId(), disk.getDiskId()), + disk.getCapacity(), offering.getMinIops(), offering.getMaxIops(), + template, owner, deviceId)); + deviceId++; + } + } catch (Exception e) { + logger.error(String.format("Failed to import volumes while importing vm: %s", displayName), e); + cleanupFailedImportVM(userVm); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import volumes while importing vm: %s. %s", displayName, StringUtils.defaultString(e.getMessage()))); + } + try { + int nicIndex = 0; + for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) { + Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId())); + Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId()); + importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex == 0, forced); + nicIndex++; + } + } catch (Exception e) { + logger.error(String.format("Failed to import NICs while importing vm: %s", displayName), e); + cleanupFailedImportVM(userVm); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import NICs while importing vm: %s. %s", displayName, StringUtils.defaultString(e.getMessage()))); + } + if (migrateAllowed) { + userVm = migrateImportedVM(host, template, validatedServiceOffering, userVm, owner, diskProfileStoragePoolList); + } + publishVMUsageUpdateResourceCount(userVm, validatedServiceOffering, template); + return userVm; + + } catch (ResourceAllocationException e) { // This will be thrown by checkUnmanagedDiskAndOfferingForImport, so the VM was not imported yet logger.error("Volume resource allocation error for owner: {}", owner, e); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume resource allocation error for owner: %s. %s", owner.getUuid(), StringUtils.defaultString(e.getMessage()))); + } finally { + ReservationHelper.closeAll(reservations); } - // Check NICs and supplied networks - Map nicIpAddressMap = getNicIpAddresses(unmanagedInstance.getNics(), callerNicIpAddressMap); - Map allNicNetworkMap = getUnmanagedNicNetworkMap(unmanagedInstance.getName(), unmanagedInstance.getNics(), nicNetworkMap, nicIpAddressMap, zone, hostName, owner, cluster.getHypervisorType()); - if (!CollectionUtils.isEmpty(unmanagedInstance.getNics())) { - allDetails.put(VmDetailConstants.NIC_ADAPTER, unmanagedInstance.getNics().get(0).getAdapterType()); - } - - if (StringUtils.isNotEmpty(unmanagedInstance.getVncPassword())) { - allDetails.put(VmDetailConstants.KVM_VNC_PASSWORD, unmanagedInstance.getVncPassword()); - } - - addImportingVMBootTypeAndModeDetails(unmanagedInstance.getBootType(), unmanagedInstance.getBootMode(), allDetails); - - VirtualMachine.PowerState powerState = VirtualMachine.PowerState.PowerOff; - if (unmanagedInstance.getPowerState().equals(UnmanagedInstanceTO.PowerState.PowerOn)) { - powerState = VirtualMachine.PowerState.PowerOn; - } - - try { - userVm = userVmManager.importVM(zone, host, template, internalCSName, displayName, owner, - null, caller, true, null, owner.getAccountId(), userId, - validatedServiceOffering, null, guestOsId, hostName, - cluster.getHypervisorType(), allDetails, powerState, null); - } catch (InsufficientCapacityException ice) { - String errorMsg = String.format("Failed to import VM [%s] due to [%s].", displayName, ice.getMessage()); - logger.error(errorMsg, ice); - throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, errorMsg); - } - - if (userVm == null) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import vm name: %s", displayName)); - } - List> diskProfileStoragePoolList = new ArrayList<>(); - try { - if (rootDisk.getCapacity() == null || rootDisk.getCapacity() == 0) { - throw new InvalidParameterValueException(String.format("Root disk ID: %s size is invalid", rootDisk.getDiskId())); - } - Long minIops = null; - if (details.containsKey(MIN_IOPS)) { - minIops = Long.parseLong(details.get(MIN_IOPS)); - } - Long maxIops = null; - if (details.containsKey(MAX_IOPS)) { - maxIops = Long.parseLong(details.get(MAX_IOPS)); - } - DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); - diskProfileStoragePoolList.add(importDisk(rootDisk, userVm, cluster, diskOffering, Volume.Type.ROOT, String.format("ROOT-%d", userVm.getId()), - rootDisk.getCapacity(), minIops, maxIops, template, owner, null)); - long deviceId = 1L; - for (UnmanagedInstanceTO.Disk disk : dataDisks) { - if (disk.getCapacity() == null || disk.getCapacity() == 0) { - throw new InvalidParameterValueException(String.format("Disk ID: %s size is invalid", rootDisk.getDiskId())); - } - DiskOffering offering = diskOfferingDao.findById(dataDiskOfferingMap.get(disk.getDiskId())); - diskProfileStoragePoolList.add(importDisk(disk, userVm, cluster, offering, Volume.Type.DATADISK, String.format("DATA-%d-%s", userVm.getId(), disk.getDiskId()), - disk.getCapacity(), offering.getMinIops(), offering.getMaxIops(), - template, owner, deviceId)); - deviceId++; - } - } catch (Exception e) { - logger.error(String.format("Failed to import volumes while importing vm: %s", displayName), e); - cleanupFailedImportVM(userVm); - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import volumes while importing vm: %s. %s", displayName, StringUtils.defaultString(e.getMessage()))); - } - try { - int nicIndex = 0; - for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) { - Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId())); - Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId()); - importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex == 0, forced); - nicIndex++; - } - } catch (Exception e) { - logger.error(String.format("Failed to import NICs while importing vm: %s", displayName), e); - cleanupFailedImportVM(userVm); - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import NICs while importing vm: %s. %s", displayName, StringUtils.defaultString(e.getMessage()))); - } - if (migrateAllowed) { - userVm = migrateImportedVM(host, template, validatedServiceOffering, userVm, owner, diskProfileStoragePoolList); - } - publishVMUsageUpdateResourceCount(userVm, validatedServiceOffering, template); - return userVm; } private void addImportingVMBootTypeAndModeDetails(String bootType, String bootMode, Map allDetails) { @@ -1332,8 +1308,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { VMTemplateVO template = getTemplateForImportInstance(cmd.getTemplateId(), cluster.getHypervisorType()); ServiceOfferingVO serviceOffering = getServiceOfferingForImportInstance(cmd.getServiceOfferingId(), owner, zone); - checkResourceLimitForImportInstance(owner); - String displayName = getDisplayNameForImportInstance(cmd.getDisplayName(), instanceName); String hostName = getHostNameForImportInstance(cmd.getHostName(), cluster.getHypervisorType(), instanceName, displayName); @@ -1349,31 +1323,41 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { List managedVms = new ArrayList<>(additionalNameFilters); managedVms.addAll(getHostsManagedVms(hosts)); - ActionEventUtils.onStartedActionEvent(userId, owner.getId(), EventTypes.EVENT_VM_IMPORT, - cmd.getEventDescription(), null, null, true, 0); + List resourceLimitHostTags = resourceLimitService.getResourceLimitHostTags(serviceOffering, template); + try (CheckedReservation vmReservation = new CheckedReservation(owner, Resource.ResourceType.user_vm, resourceLimitHostTags, 1L, reservationDao, resourceLimitService); + CheckedReservation cpuReservation = new CheckedReservation(owner, Resource.ResourceType.cpu, resourceLimitHostTags, Long.valueOf(serviceOffering.getCpu()), reservationDao, resourceLimitService); + CheckedReservation memReservation = new CheckedReservation(owner, Resource.ResourceType.memory, resourceLimitHostTags, Long.valueOf(serviceOffering.getRamSize()), reservationDao, resourceLimitService)) { - if (cmd instanceof ImportVmCmd) { - ImportVmCmd importVmCmd = (ImportVmCmd) cmd; - if (StringUtils.isBlank(importVmCmd.getImportSource())) { - throw new CloudRuntimeException("Please provide an import source for importing the VM"); - } - String source = importVmCmd.getImportSource().toUpperCase(); - ImportSource importSource = Enum.valueOf(ImportSource.class, source); - if (ImportSource.VMWARE == importSource) { - userVm = importUnmanagedInstanceFromVmwareToKvm(zone, cluster, - template, instanceName, displayName, hostName, caller, owner, userId, - serviceOffering, dataDiskOfferingMap, - nicNetworkMap, nicIpAddressMap, - details, importVmCmd, forced); - } - } else { - if (List.of(Hypervisor.HypervisorType.VMware, Hypervisor.HypervisorType.KVM).contains(cluster.getHypervisorType())) { - userVm = importUnmanagedInstanceFromHypervisor(zone, cluster, hosts, additionalNameFilters, - template, instanceName, displayName, hostName, caller, owner, userId, - serviceOffering, dataDiskOfferingMap, - nicNetworkMap, nicIpAddressMap, - details, cmd.getMigrateAllowed(), managedVms, forced); + ActionEventUtils.onStartedActionEvent(userId, owner.getId(), EventTypes.EVENT_VM_IMPORT, + cmd.getEventDescription(), null, null, true, 0); + + if (cmd instanceof ImportVmCmd) { + ImportVmCmd importVmCmd = (ImportVmCmd) cmd; + if (StringUtils.isBlank(importVmCmd.getImportSource())) { + throw new CloudRuntimeException("Please provide an import source for importing the VM"); + } + String source = importVmCmd.getImportSource().toUpperCase(); + ImportSource importSource = Enum.valueOf(ImportSource.class, source); + if (ImportSource.VMWARE == importSource) { + userVm = importUnmanagedInstanceFromVmwareToKvm(zone, cluster, + template, instanceName, displayName, hostName, caller, owner, userId, + serviceOffering, dataDiskOfferingMap, + nicNetworkMap, nicIpAddressMap, + details, importVmCmd, forced); + } + } else { + if (List.of(Hypervisor.HypervisorType.VMware, Hypervisor.HypervisorType.KVM).contains(cluster.getHypervisorType())) { + userVm = importUnmanagedInstanceFromHypervisor(zone, cluster, hosts, additionalNameFilters, + template, instanceName, displayName, hostName, caller, owner, userId, + serviceOffering, dataDiskOfferingMap, + nicNetworkMap, nicIpAddressMap, + details, cmd.getMigrateAllowed(), managedVms, forced); + } } + + } catch (ResourceAllocationException e) { + logger.error(String.format("VM resource allocation error for account: %s", owner.getUuid()), e); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM resource allocation error for account: %s. %s", owner.getUuid(), StringUtils.defaultString(e.getMessage()))); } if (userVm == null) { @@ -1462,15 +1446,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { return StringUtils.isEmpty(displayName) ? instanceName : displayName; } - private void checkResourceLimitForImportInstance(Account owner) { - try { - resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.user_vm, 1); - } catch (ResourceAllocationException e) { - logger.error("VM resource allocation error for account: {}", owner, e); - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM resource allocation error for account: %s. %s", owner.getUuid(), StringUtils.defaultString(e.getMessage()))); - } - } - private ServiceOfferingVO getServiceOfferingForImportInstance(Long serviceOfferingId, Account owner, DataCenter zone) { if (serviceOfferingId == null) { throw new InvalidParameterValueException("Service offering ID cannot be null"); @@ -1705,7 +1680,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { Pair sourceInstanceDetails = getSourceVmwareUnmanagedInstance(vcenter, datacenterName, username, password, clusterName, sourceHostName, sourceVMName, serviceOffering); sourceVMwareInstance = sourceInstanceDetails.first(); isClonedInstance = sourceInstanceDetails.second(); - boolean isWindowsVm = sourceVMwareInstance.getOperatingSystem().toLowerCase().contains("windows"); if (isWindowsVm) { checkConversionSupportOnHost(convertHost, sourceVMName, true); @@ -2466,12 +2440,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { throw new InvalidParameterValueException(String.format("Service offering ID: %d cannot be found", serviceOfferingId)); } accountService.checkAccess(owner, serviceOffering, zone); - try { - resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.user_vm, 1); - } catch (ResourceAllocationException e) { - logger.error("VM resource allocation error for account: {}", owner, e); - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM resource allocation error for account: %s. %s", owner.getUuid(), StringUtils.defaultString(e.getMessage()))); - } String displayName = cmd.getDisplayName(); if (StringUtils.isEmpty(displayName)) { displayName = instanceName; @@ -2559,26 +2527,37 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { UserVm userVm = null; - if (ImportSource.EXTERNAL == importSource) { - String username = cmd.getUsername(); - String password = cmd.getPassword(); - String tmpPath = cmd.getTmpPath(); - userVm = importExternalKvmVirtualMachine(unmanagedInstanceTO, instanceName, zone, - template, displayName, hostName, caller, owner, userId, - serviceOffering, dataDiskOfferingMap, - nicNetworkMap, nicIpAddressMap, remoteUrl, username, password, tmpPath, details); - } else if (ImportSource.SHARED == importSource || ImportSource.LOCAL == importSource) { - try { - userVm = importKvmVirtualMachineFromDisk(importSource, instanceName, zone, + List resourceLimitHostTags = resourceLimitService.getResourceLimitHostTags(serviceOffering, template); + try (CheckedReservation vmReservation = new CheckedReservation(owner, Resource.ResourceType.user_vm, resourceLimitHostTags, 1L, reservationDao, resourceLimitService); + CheckedReservation cpuReservation = new CheckedReservation(owner, Resource.ResourceType.cpu, resourceLimitHostTags, Long.valueOf(serviceOffering.getCpu()), reservationDao, resourceLimitService); + CheckedReservation memReservation = new CheckedReservation(owner, Resource.ResourceType.memory, resourceLimitHostTags, Long.valueOf(serviceOffering.getRamSize()), reservationDao, resourceLimitService)) { + + if (ImportSource.EXTERNAL == importSource) { + String username = cmd.getUsername(); + String password = cmd.getPassword(); + String tmpPath = cmd.getTmpPath(); + userVm = importExternalKvmVirtualMachine(unmanagedInstanceTO, instanceName, zone, template, displayName, hostName, caller, owner, userId, - serviceOffering, dataDiskOfferingMap, networkId, hostId, poolId, diskPath, - details); - } catch (InsufficientCapacityException e) { - throw new RuntimeException(e); - } catch (ResourceAllocationException e) { - throw new RuntimeException(e); + serviceOffering, dataDiskOfferingMap, + nicNetworkMap, nicIpAddressMap, remoteUrl, username, password, tmpPath, details); + } else if (ImportSource.SHARED == importSource || ImportSource.LOCAL == importSource) { + try { + userVm = importKvmVirtualMachineFromDisk(importSource, instanceName, zone, + template, displayName, hostName, caller, owner, userId, + serviceOffering, dataDiskOfferingMap, networkId, hostId, poolId, diskPath, + details); + } catch (InsufficientCapacityException e) { + throw new RuntimeException(e); + } catch (ResourceAllocationException e) { + throw new RuntimeException(e); + } } + + } catch (ResourceAllocationException e) { + logger.error(String.format("VM resource allocation error for account: %s", owner.getUuid()), e); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM resource allocation error for account: %s. %s", owner.getUuid(), StringUtils.defaultString(e.getMessage()))); } + if (userVm == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import Vm with name: %s ", instanceName)); } @@ -2592,7 +2571,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId, final ServiceOfferingVO serviceOffering, final Map dataDiskOfferingMap, final Map nicNetworkMap, final Map callerNicIpAddressMap, - final String remoteUrl, String username, String password, String tmpPath, final Map details) { + final String remoteUrl, String username, String password, String tmpPath, final Map details) throws ResourceAllocationException { UserVm userVm = null; Map allDetails = new HashMap<>(details); @@ -2603,6 +2582,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("No attached disks found for the unmanaged VM: %s", instanceName)); } + DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); Pair> rootAndDataDisksPair = getRootAndDataDisks(unmanagedInstanceDisks, dataDiskOfferingMap); final UnmanagedInstanceTO.Disk rootDisk = rootAndDataDisksPair.first(); final List dataDisks = rootAndDataDisksPair.second(); @@ -2611,13 +2591,17 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { } allDetails.put(VmDetailConstants.ROOT_DISK_CONTROLLER, rootDisk.getController()); - // Check NICs and supplied networks - Map nicIpAddressMap = getNicIpAddresses(unmanagedInstance.getNics(), callerNicIpAddressMap); - Map allNicNetworkMap = getUnmanagedNicNetworkMap(unmanagedInstance.getName(), unmanagedInstance.getNics(), nicNetworkMap, nicIpAddressMap, zone, hostName, owner, Hypervisor.HypervisorType.KVM); - if (!CollectionUtils.isEmpty(unmanagedInstance.getNics())) { - allDetails.put(VmDetailConstants.NIC_ADAPTER, unmanagedInstance.getNics().get(0).getAdapterType()); - } - VirtualMachine.PowerState powerState = VirtualMachine.PowerState.PowerOff; + List reservations = new ArrayList<>(); + try { + checkVolumeResourceLimitsForExternalKvmVmImport(owner, rootDisk, dataDisks, diskOffering, dataDiskOfferingMap, reservations); + + // Check NICs and supplied networks + Map nicIpAddressMap = getNicIpAddresses(unmanagedInstance.getNics(), callerNicIpAddressMap); + Map allNicNetworkMap = getUnmanagedNicNetworkMap(unmanagedInstance.getName(), unmanagedInstance.getNics(), nicNetworkMap, nicIpAddressMap, zone, hostName, owner, Hypervisor.HypervisorType.KVM); + if (!CollectionUtils.isEmpty(unmanagedInstance.getNics())) { + allDetails.put(VmDetailConstants.NIC_ADAPTER, unmanagedInstance.getNics().get(0).getAdapterType()); + } + VirtualMachine.PowerState powerState = VirtualMachine.PowerState.PowerOff; try { userVm = userVmManager.importVM(zone, null, template, null, displayName, owner, @@ -2631,77 +2615,93 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { if (userVm == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import vm name: %s", instanceName)); } - DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); String rootVolumeName = String.format("ROOT-%s", userVm.getId()); - DiskProfile diskProfile = volumeManager.allocateRawVolume(Volume.Type.ROOT, rootVolumeName, diskOffering, null, null, null, userVm, template, owner, null); + DiskProfile diskProfile = volumeManager.allocateRawVolume(Volume.Type.ROOT, rootVolumeName, diskOffering, null, null, null, userVm, template, owner, null, false); DiskProfile[] dataDiskProfiles = new DiskProfile[dataDisks.size()]; int diskSeq = 0; for (UnmanagedInstanceTO.Disk disk : dataDisks) { - if (disk.getCapacity() == null || disk.getCapacity() == 0) { - throw new InvalidParameterValueException(String.format("Disk ID: %s size is invalid", disk.getDiskId())); - } DiskOffering offering = diskOfferingDao.findById(dataDiskOfferingMap.get(disk.getDiskId())); - DiskProfile dataDiskProfile = volumeManager.allocateRawVolume(Volume.Type.DATADISK, String.format("DATA-%d-%s", userVm.getId(), disk.getDiskId()), offering, null, null, null, userVm, template, owner, null); + DiskProfile dataDiskProfile = volumeManager.allocateRawVolume(Volume.Type.DATADISK, String.format("DATA-%d-%s", userVm.getId(), disk.getDiskId()), offering, null, null, null, userVm, template, owner, null, false); dataDiskProfiles[diskSeq++] = dataDiskProfile; } - final VirtualMachineProfile profile = new VirtualMachineProfileImpl(userVm, template, serviceOffering, owner, null); - ServiceOfferingVO dummyOffering = serviceOfferingDao.findById(userVm.getId(), serviceOffering.getId()); - profile.setServiceOffering(dummyOffering); - DeploymentPlanner.ExcludeList excludeList = new DeploymentPlanner.ExcludeList(); - final DataCenterDeployment plan = new DataCenterDeployment(zone.getId(), null, null, null, null, null); - DeployDestination dest = null; - try { - dest = deploymentPlanningManager.planDeployment(profile, plan, excludeList, null); - } catch (Exception e) { - logger.warn("Import failed for Vm: {} while finding deployment destination", userVm, e); - cleanupFailedImportVM(userVm); - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Import failed for Vm: %s while finding deployment destination", userVm.getInstanceName())); - } - if(dest == null) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Import failed for Vm: %s. Suitable deployment destination not found", userVm.getInstanceName())); - } - - List> diskProfileStoragePoolList = new ArrayList<>(); - try { - if (rootDisk.getCapacity() == null || rootDisk.getCapacity() == 0) { - throw new InvalidParameterValueException(String.format("Root disk ID: %s size is invalid", rootDisk.getDiskId())); + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(userVm, template, serviceOffering, owner, null); + ServiceOfferingVO dummyOffering = serviceOfferingDao.findById(userVm.getId(), serviceOffering.getId()); + profile.setServiceOffering(dummyOffering); + DeploymentPlanner.ExcludeList excludeList = new DeploymentPlanner.ExcludeList(); + final DataCenterDeployment plan = new DataCenterDeployment(zone.getId(), null, null, null, null, null); + DeployDestination dest = null; + try { + dest = deploymentPlanningManager.planDeployment(profile, plan, excludeList, null); + } catch (Exception e) { + logger.warn("Import failed for Vm: {} while finding deployment destination", userVm, e); + cleanupFailedImportVM(userVm); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Import failed for Vm: %s while finding deployment destination", userVm.getInstanceName())); + } + if(dest == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Import failed for Vm: %s. Suitable deployment destination not found", userVm.getInstanceName())); } - diskProfileStoragePoolList.add(importExternalDisk(rootDisk, userVm, dest, diskOffering, Volume.Type.ROOT, - template, null, remoteUrl, username, password, tmpPath, diskProfile)); + List> diskProfileStoragePoolList = new ArrayList<>(); + try { + diskProfileStoragePoolList.add(importExternalDisk(rootDisk, userVm, dest, diskOffering, Volume.Type.ROOT, + template, null, remoteUrl, username, password, tmpPath, diskProfile)); - long deviceId = 1L; - diskSeq = 0; - for (UnmanagedInstanceTO.Disk disk : dataDisks) { - DiskProfile dataDiskProfile = dataDiskProfiles[diskSeq++]; - DiskOffering offering = diskOfferingDao.findById(dataDiskOfferingMap.get(disk.getDiskId())); + long deviceId = 1L; + diskSeq = 0; + for (UnmanagedInstanceTO.Disk disk : dataDisks) { + DiskProfile dataDiskProfile = dataDiskProfiles[diskSeq++]; + DiskOffering offering = diskOfferingDao.findById(dataDiskOfferingMap.get(disk.getDiskId())); - diskProfileStoragePoolList.add(importExternalDisk(disk, userVm, dest, offering, Volume.Type.DATADISK, - template, deviceId, remoteUrl, username, password, tmpPath, dataDiskProfile)); - deviceId++; + diskProfileStoragePoolList.add(importExternalDisk(disk, userVm, dest, offering, Volume.Type.DATADISK, + template, deviceId, remoteUrl, username, password, tmpPath, dataDiskProfile)); + deviceId++; + } + } catch (Exception e) { + logger.error(String.format("Failed to import volumes while importing vm: %s", instanceName), e); + cleanupFailedImportVM(userVm); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import volumes while importing vm: %s. %s", instanceName, StringUtils.defaultString(e.getMessage()))); } - } catch (Exception e) { - logger.error(String.format("Failed to import volumes while importing vm: %s", instanceName), e); - cleanupFailedImportVM(userVm); - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import volumes while importing vm: %s. %s", instanceName, StringUtils.defaultString(e.getMessage()))); - } - try { - int nicIndex = 0; - for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) { - Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId())); - Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId()); - importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex==0, true); - nicIndex++; + try { + int nicIndex = 0; + for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) { + Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId())); + Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId()); + importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex==0, true); + nicIndex++; + } + } catch (Exception e) { + logger.error(String.format("Failed to import NICs while importing vm: %s", instanceName), e); + cleanupFailedImportVM(userVm); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import NICs while importing vm: %s. %s", instanceName, StringUtils.defaultString(e.getMessage()))); } - } catch (Exception e) { - logger.error(String.format("Failed to import NICs while importing vm: %s", instanceName), e); - cleanupFailedImportVM(userVm); - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import NICs while importing vm: %s. %s", instanceName, StringUtils.defaultString(e.getMessage()))); + publishVMUsageUpdateResourceCount(userVm, dummyOffering, template); + return userVm; + + } finally { + ReservationHelper.closeAll(reservations); + } + } + + protected void checkVolumeResourceLimitsForExternalKvmVmImport(Account owner, UnmanagedInstanceTO.Disk rootDisk, + List dataDisks, DiskOfferingVO rootDiskOffering, + Map dataDiskOfferingMap, List reservations) throws ResourceAllocationException { + if (rootDisk.getCapacity() == null || rootDisk.getCapacity() == 0) { + throw new InvalidParameterValueException(String.format("Root disk ID: %s size is invalid", rootDisk.getDiskId())); + } + resourceLimitService.checkVolumeResourceLimit(owner, true, rootDisk.getCapacity(), rootDiskOffering, reservations); + + if (CollectionUtils.isEmpty(dataDisks)) { + return; + } + for (UnmanagedInstanceTO.Disk disk : dataDisks) { + if (disk.getCapacity() == null || disk.getCapacity() == 0) { + throw new InvalidParameterValueException(String.format("Data disk ID: %s size is invalid", disk.getDiskId())); + } + DiskOffering offering = diskOfferingDao.findById(dataDiskOfferingMap.get(disk.getDiskId())); + resourceLimitService.checkVolumeResourceLimit(owner, true, disk.getCapacity(), offering, reservations); } - publishVMUsageUpdateResourceCount(userVm, dummyOffering, template); - return userVm; } private UserVm importKvmVirtualMachineFromDisk(final ImportSource importSource, final String instanceName, final DataCenter zone, @@ -2769,9 +2769,18 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { if (userVm == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import vm name: %s", instanceName)); } + DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); + + List reservations = new ArrayList<>(); + List resourceLimitStorageTags = resourceLimitService.getResourceLimitStorageTagsForResourceCountOperation(true, diskOffering); + try { + CheckedReservation volumeReservation = new CheckedReservation(owner, Resource.ResourceType.volume, resourceLimitStorageTags, + CollectionUtils.isNotEmpty(resourceLimitStorageTags) ? 1L : 0L, reservationDao, resourceLimitService); + reservations.add(volumeReservation); + String rootVolumeName = String.format("ROOT-%s", userVm.getId()); - DiskProfile diskProfile = volumeManager.allocateRawVolume(Volume.Type.ROOT, rootVolumeName, diskOffering, null, null, null, userVm, template, owner, null); + DiskProfile diskProfile = volumeManager.allocateRawVolume(Volume.Type.ROOT, rootVolumeName, diskOffering, null, null, null, userVm, template, owner, null, false); final VirtualMachineProfile profile = new VirtualMachineProfileImpl(userVm, template, serviceOffering, owner, null); ServiceOfferingVO dummyOffering = serviceOfferingDao.findById(userVm.getId(), serviceOffering.getId()); @@ -2815,6 +2824,10 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { } diskProfile.setSize(checkVolumeAnswer.getSize()); + CheckedReservation primaryStorageReservation = new CheckedReservation(owner, Resource.ResourceType.primary_storage, resourceLimitStorageTags, + CollectionUtils.isNotEmpty(resourceLimitStorageTags) ? diskProfile.getSize() : 0L, reservationDao, resourceLimitService); + reservations.add(primaryStorageReservation); + List> diskProfileStoragePoolList = new ArrayList<>(); try { long deviceId = 1L; @@ -2833,6 +2846,10 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { networkOrchestrationService.importNic(macAddress, 0, network, true, userVm, requestedIpPair, zone, true); publishVMUsageUpdateResourceCount(userVm, dummyOffering, template); return userVm; + + } finally { + ReservationHelper.closeAll(reservations); + } } private void checkVolume(Map volumeDetails) { diff --git a/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java b/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java index 750f4d8655b..ccc0d0c8d4c 100644 --- a/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java +++ b/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java @@ -272,7 +272,7 @@ public class QueryManagerImplTest { public void searchForEventsFailResourceIdInvalid() { ListEventsCmd cmd = setupMockListEventsCmd(); Mockito.when(cmd.getResourceId()).thenReturn("random"); - Mockito.when(cmd.getResourceType()).thenReturn(ApiCommandResourceType.VirtualMachine.toString()); + Mockito.lenient().when(cmd.getResourceType()).thenReturn(ApiCommandResourceType.VirtualMachine.toString()); queryManager.searchForEvents(cmd); } diff --git a/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java b/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java index 34e5e48cc32..e4146fd2265 100755 --- a/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java +++ b/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java @@ -27,6 +27,7 @@ import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.extension.ExtensionHelper; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -82,6 +83,9 @@ public class UserVmJoinDaoImplTest extends GenericDaoBaseWithTagInformationBaseT @Mock private VMTemplateDao vmTemplateDao; + @Mock + ExtensionHelper extensionHelper; + private UserVmJoinVO userVm = new UserVmJoinVO(); private UserVmResponse userVmResponse = new UserVmResponse(); diff --git a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java index ff34d72c218..3d3bfbdf162 100644 --- a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java +++ b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java @@ -23,7 +23,6 @@ import com.cloud.agent.api.routing.UpdateNetworkCommand; import com.cloud.agent.api.to.IpAddressTO; import com.cloud.agent.manager.Commands; import com.cloud.alert.AlertManager; -import com.cloud.configuration.Resource; import com.cloud.dc.DataCenterVO; import com.cloud.dc.VlanVO; import com.cloud.dc.dao.DataCenterDao; @@ -54,6 +53,7 @@ import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao; import com.cloud.offering.NetworkOffering; import com.cloud.offerings.NetworkOfferingServiceMapVO; import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountVO; @@ -81,6 +81,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.MockedConstruction; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnitRunner; @@ -490,7 +491,6 @@ public class VpcManagerImplTest { public void testCreateVpcDnsOfferingServiceFailure() { mockVpcDnsResources(false, false); try { - doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc); manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, ip4Dns[0], null, null, null, true, 1500, null, null, null, false); } catch (ResourceAllocationException e) { @@ -502,7 +502,6 @@ public class VpcManagerImplTest { public void testCreateVpcDnsIpv6OfferingFailure() { mockVpcDnsResources(true, false); try { - doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc); manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, ip4Dns[0], ip4Dns[1], ip6Dns[0], null, true, 1500, null, null, null, false); } catch (ResourceAllocationException e) { @@ -516,8 +515,7 @@ public class VpcManagerImplTest { VpcVO vpc = Mockito.mock(VpcVO.class); Mockito.when(vpcDao.persist(any(), anyMap())).thenReturn(vpc); Mockito.when(vpc.getUuid()).thenReturn("uuid"); - try { - doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc); + try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, ip4Dns[0], ip4Dns[1], null, null, true, 1500, null, null, null, false); } catch (ResourceAllocationException e) { @@ -533,8 +531,7 @@ public class VpcManagerImplTest { Mockito.when(vpc.getUuid()).thenReturn("uuid"); doReturn(true).when(routedIpv4Manager).isRoutedVpc(any()); doNothing().when(routedIpv4Manager).getOrCreateIpv4SubnetForVpc(any(), anyString()); - try { - doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc); + try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, ip4Dns[0], ip4Dns[1], null, null, true, 1500, null, null, null, false); } catch (ResourceAllocationException e) { @@ -556,8 +553,7 @@ public class VpcManagerImplTest { Ipv4GuestSubnetNetworkMap ipv4GuestSubnetNetworkMap = Mockito.mock(Ipv4GuestSubnetNetworkMap.class); doReturn(ipv4GuestSubnetNetworkMap).when(routedIpv4Manager).getOrCreateIpv4SubnetForVpc(any(), anyInt()); List bgpPeerIds = Arrays.asList(11L, 12L); - try { - doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc); + try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, null, vpcDomain, ip4Dns[0], ip4Dns[1], null, null, true, 1500, 24, null, bgpPeerIds, false); } catch (ResourceAllocationException e) { diff --git a/server/src/test/java/com/cloud/resourcelimit/CheckedReservationTest.java b/server/src/test/java/com/cloud/resourcelimit/CheckedReservationTest.java index 247647dd010..5e72f204493 100644 --- a/server/src/test/java/com/cloud/resourcelimit/CheckedReservationTest.java +++ b/server/src/test/java/com/cloud/resourcelimit/CheckedReservationTest.java @@ -149,23 +149,10 @@ public class CheckedReservationTest { @Test public void testMultipleReservationsWithOneFailing() { List tags = List.of("abc", "xyz"); - when(account.getAccountId()).thenReturn(1L); - when(account.getDomainId()).thenReturn(4L); Map persistedReservations = new HashMap<>(); - Mockito.when(reservationDao.persist(Mockito.any(ReservationVO.class))).thenAnswer((Answer) invocation -> { - ReservationVO reservationVO = (ReservationVO) invocation.getArguments()[0]; - Long id = (long) (persistedReservations.size() + 1); - ReflectionTestUtils.setField(reservationVO, "id", id); - persistedReservations.put(id, reservationVO); - return reservationVO; - }); - Mockito.when(reservationDao.remove(Mockito.anyLong())).thenAnswer((Answer) invocation -> { - Long id = (Long) invocation.getArguments()[0]; - persistedReservations.remove(id); - return true; - }); + try { - Mockito.doThrow(ResourceAllocationException.class).when(resourceLimitService).checkResourceLimitWithTag(account, Resource.ResourceType.cpu, "xyz", 1L); + Mockito.doThrow(ResourceAllocationException.class).when(resourceLimitService).checkResourceLimitWithTag(account, account.getDomainId(), true, Resource.ResourceType.cpu, "xyz", 1L); try (CheckedReservation vmReservation = new CheckedReservation(account, Resource.ResourceType.user_vm, tags, 1L, reservationDao, resourceLimitService); CheckedReservation cpuReservation = new CheckedReservation(account, Resource.ResourceType.cpu, tags, 1L, reservationDao, resourceLimitService); CheckedReservation memReservation = new CheckedReservation(account, Resource.ResourceType.memory, tags, 256L, reservationDao, resourceLimitService); diff --git a/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java b/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java index 84586bcdc2c..18fa16e04a2 100644 --- a/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java +++ b/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java @@ -34,9 +34,10 @@ import org.apache.cloudstack.api.response.TaggedResourceLimitAndCountResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.reservation.dao.ReservationDao; +import org.apache.cloudstack.resourcelimit.Reserver; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; + import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.After; @@ -46,6 +47,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedConstruction; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.Spy; @@ -83,6 +85,7 @@ import com.cloud.user.ResourceLimitService; import com.cloud.user.User; import com.cloud.user.dao.AccountDao; import com.cloud.utils.Pair; +import com.cloud.utils.StringUtils; import com.cloud.utils.db.EntityManager; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineManager; @@ -279,6 +282,7 @@ public class ResourceLimitManagerImplTest { @Test public void testCheckVmResourceLimit() { + List reservations = new ArrayList<>(); ServiceOffering serviceOffering = Mockito.mock(ServiceOffering.class); VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class); Mockito.when(serviceOffering.getHostTag()).thenReturn(hostTags.get(0)); @@ -286,72 +290,12 @@ public class ResourceLimitManagerImplTest { Mockito.when(serviceOffering.getRamSize()).thenReturn(256); Mockito.when(template.getTemplateTag()).thenReturn(hostTags.get(0)); Account account = Mockito.mock(Account.class); - try { - Mockito.doNothing().when(resourceLimitManager).checkResourceLimitWithTag(Mockito.any(), Mockito.any(), Mockito.any()); - Mockito.doNothing().when(resourceLimitManager).checkResourceLimitWithTag(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); - resourceLimitManager.checkVmResourceLimit(account, true, serviceOffering, template); + try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { + resourceLimitManager.checkVmResourceLimit(account, true, serviceOffering, template, reservations); List tags = new ArrayList<>(); tags.add(null); tags.add(hostTags.get(0)); - for (String tag: tags) { - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimitWithTag(account, Resource.ResourceType.user_vm, tag); - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimitWithTag(account, Resource.ResourceType.cpu, tag, 2L); - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimitWithTag(account, Resource.ResourceType.memory, tag, 256L); - } - } catch (ResourceAllocationException e) { - Assert.fail("Exception encountered: " + e.getMessage()); - } - } - - @Test - public void testCheckVmCpuResourceLimit() { - ServiceOffering serviceOffering = Mockito.mock(ServiceOffering.class); - VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class); - Mockito.when(serviceOffering.getHostTag()).thenReturn(hostTags.get(0)); - Mockito.when(template.getTemplateTag()).thenReturn(hostTags.get(0)); - Account account = Mockito.mock(Account.class); - long cpu = 2L; - try { - Mockito.doNothing().when(resourceLimitManager).checkResourceLimitWithTag(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); - resourceLimitManager.checkVmCpuResourceLimit(account, true, serviceOffering, template, cpu); - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimitWithTag(account, Resource.ResourceType.cpu, null, cpu); - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimitWithTag(account, Resource.ResourceType.cpu, hostTags.get(0), cpu); - } catch (ResourceAllocationException e) { - Assert.fail("Exception encountered: " + e.getMessage()); - } - } - - @Test - public void testCheckVmMemoryResourceLimit() { - ServiceOffering serviceOffering = Mockito.mock(ServiceOffering.class); - VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class); - Mockito.when(serviceOffering.getHostTag()).thenReturn(hostTags.get(0)); - Mockito.when(template.getTemplateTag()).thenReturn(hostTags.get(0)); - Account account = Mockito.mock(Account.class); - long delta = 256L; - try { - Mockito.doNothing().when(resourceLimitManager).checkResourceLimitWithTag(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); - resourceLimitManager.checkVmMemoryResourceLimit(account, true, serviceOffering, template, delta); - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimitWithTag(account, Resource.ResourceType.memory, null, delta); - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimitWithTag(account, Resource.ResourceType.memory, hostTags.get(0), delta); - } catch (ResourceAllocationException e) { - Assert.fail("Exception encountered: " + e.getMessage()); - } - } - - @Test - public void testCheckVmGpuResourceLimit() { - ServiceOffering serviceOffering = Mockito.mock(ServiceOffering.class); - VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class); - Mockito.when(serviceOffering.getHostTag()).thenReturn(hostTags.get(0)); - Mockito.when(template.getTemplateTag()).thenReturn(hostTags.get(0)); - Account account = Mockito.mock(Account.class); - long gpuCount = 2L; - try { - Mockito.doNothing().when(resourceLimitManager).checkResourceLimitWithTag(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); - resourceLimitManager.checkVmGpuResourceLimit(account, true, serviceOffering, template, gpuCount); - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimitWithTag(account, Resource.ResourceType.gpu, null, gpuCount); - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimitWithTag(account, Resource.ResourceType.gpu, hostTags.get(0), gpuCount); + Assert.assertEquals(4, mockCheckedReservation.constructed().size()); } catch (ResourceAllocationException e) { Assert.fail("Exception encountered: " + e.getMessage()); } @@ -359,22 +303,15 @@ public class ResourceLimitManagerImplTest { @Test public void testCheckVolumeResourceLimit() { + List reservations = new ArrayList<>(); String checkTag = storageTags.get(0); DiskOffering diskOffering = Mockito.mock(DiskOffering.class); Mockito.when(diskOffering.getTags()).thenReturn(checkTag); Mockito.when(diskOffering.getTagsArray()).thenReturn(new String[]{checkTag}); Account account = Mockito.mock(Account.class); - try { - Mockito.doNothing().when(resourceLimitManager).checkResourceLimitWithTag(Mockito.any(), Mockito.any(), Mockito.any()); - Mockito.doNothing().when(resourceLimitManager).checkResourceLimitWithTag(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); - resourceLimitManager.checkVolumeResourceLimit(account, true, 100L, diskOffering); - List tags = new ArrayList<>(); - tags.add(null); - tags.add(checkTag); - for (String tag: tags) { - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimitWithTag(account, Resource.ResourceType.volume, tag); - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimitWithTag(account, Resource.ResourceType.primary_storage, tag, 100L); - } + try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { + resourceLimitManager.checkVolumeResourceLimit(account, true, 100L, diskOffering, reservations); + Assert.assertEquals(2, reservations.size()); } catch (ResourceAllocationException e) { Assert.fail("Exception encountered: " + e.getMessage()); } @@ -627,10 +564,11 @@ public class ResourceLimitManagerImplTest { public void testCheckResourceLimitWithTagNonAdmin() throws ResourceAllocationException { AccountVO account = Mockito.mock(AccountVO.class); Mockito.when(account.getId()).thenReturn(1L); + Mockito.when(account.getDomainId()).thenReturn(1L); Mockito.when(accountManager.isRootAdmin(1L)).thenReturn(false); Mockito.doReturn(new ArrayList()).when(resourceLimitManager).lockAccountAndOwnerDomainRows(Mockito.anyLong(), Mockito.any(Resource.ResourceType.class), Mockito.anyString()); Mockito.doNothing().when(resourceLimitManager).checkAccountResourceLimit(account, null, Resource.ResourceType.cpu, hostTags.get(0), 1); - Mockito.doNothing().when(resourceLimitManager).checkDomainResourceLimit(account, null, Resource.ResourceType.cpu, hostTags.get(0), 1); + Mockito.doNothing().when(resourceLimitManager).checkDomainResourceLimit(1L, Resource.ResourceType.cpu, hostTags.get(0), 1); try { resourceLimitManager.checkResourceLimitWithTag(account, Resource.ResourceType.cpu, hostTags.get(0), 1); } catch (ResourceAllocationException e) { @@ -646,9 +584,10 @@ public class ResourceLimitManagerImplTest { Mockito.when(accountManager.isRootAdmin(1L)).thenReturn(false); ProjectVO projectVO = Mockito.mock(ProjectVO.class); Mockito.when(projectDao.findByProjectAccountId(Mockito.anyLong())).thenReturn(projectVO); + Mockito.when(projectVO.getDomainId()).thenReturn(1L); Mockito.doReturn(new ArrayList()).when(resourceLimitManager).lockAccountAndOwnerDomainRows(Mockito.anyLong(), Mockito.any(Resource.ResourceType.class), Mockito.anyString()); Mockito.doNothing().when(resourceLimitManager).checkAccountResourceLimit(account, projectVO, Resource.ResourceType.cpu, hostTags.get(0), 1); - Mockito.doNothing().when(resourceLimitManager).checkDomainResourceLimit(account, projectVO, Resource.ResourceType.cpu, hostTags.get(0), 1); + Mockito.doNothing().when(resourceLimitManager).checkDomainResourceLimit(1L, Resource.ResourceType.cpu, hostTags.get(0), 1); try { resourceLimitManager.checkResourceLimitWithTag(account, Resource.ResourceType.cpu, hostTags.get(0), 1); } catch (ResourceAllocationException e) { @@ -992,13 +931,6 @@ public class ResourceLimitManagerImplTest { Mockito.anyList(), Mockito.eq(tag)); } - private void mockCheckResourceLimitWithTag() throws ResourceAllocationException { - Mockito.doNothing().when(resourceLimitManager).checkResourceLimitWithTag( - Mockito.any(Account.class), Mockito.any(Resource.ResourceType.class), Mockito.anyString()); - Mockito.doNothing().when(resourceLimitManager).checkResourceLimitWithTag( - Mockito.any(Account.class), Mockito.any(Resource.ResourceType.class), Mockito.anyString(), Mockito.anyLong()); - } - private void mockIncrementResourceCountWithTag() { Mockito.doNothing().when(resourceLimitManager).incrementResourceCountWithTag( Mockito.anyLong(), Mockito.any(Resource.ResourceType.class), Mockito.anyString()); @@ -1015,6 +947,7 @@ public class ResourceLimitManagerImplTest { @Test public void testCheckVolumeResourceCount() throws ResourceAllocationException { + List reservations = new ArrayList<>(); Account account = Mockito.mock(Account.class); String tag = "tag"; long delta = 10L; @@ -1028,12 +961,11 @@ public class ResourceLimitManagerImplTest { Mockito.doReturn(List.of(tag)).when(resourceLimitManager) .getResourceLimitStorageTagsForResourceCountOperation(Mockito.anyBoolean(), Mockito.any(DiskOffering.class)); - mockCheckResourceLimitWithTag(); - resourceLimitManager.checkVolumeResourceLimit(account, false, delta, Mockito.mock(DiskOffering.class)); - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimitWithTag( - account, Resource.ResourceType.volume, tag); - Mockito.verify(resourceLimitManager, Mockito.times(1)) - .checkResourceLimitWithTag(account, Resource.ResourceType.primary_storage, tag, 10L); + + try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { + resourceLimitManager.checkVolumeResourceLimit(account, false, delta, Mockito.mock(DiskOffering.class), reservations); + Assert.assertEquals(2, reservations.size()); + } } @Test diff --git a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java index 0575b430ef1..77e321ab859 100644 --- a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java +++ b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java @@ -26,7 +26,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -44,6 +43,7 @@ import java.util.concurrent.ExecutionException; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; import com.cloud.host.HostVO; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.vm.snapshot.VMSnapshot; @@ -91,6 +91,7 @@ import org.junit.runner.RunWith; import org.mockito.InOrder; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedConstruction; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.Spy; @@ -99,7 +100,6 @@ import org.springframework.test.util.ReflectionTestUtils; import com.cloud.api.query.dao.ServiceOfferingJoinDao; import com.cloud.configuration.ConfigurationManager; -import com.cloud.configuration.Resource; import com.cloud.configuration.Resource.ResourceType; import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenterVO; @@ -551,7 +551,9 @@ public class VolumeApiServiceImplTest { @Test public void attachRootVolumePositive() throws NoSuchFieldException, IllegalAccessException { thrown.expect(NullPointerException.class); - volumeApiServiceImpl.attachVolumeToVM(2L, 6L, 0L, false); + try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { + volumeApiServiceImpl.attachVolumeToVM(2L, 6L, 0L, false); + } } // Negative test - attach data volume, to the vm on non-kvm hypervisor @@ -570,7 +572,9 @@ public class VolumeApiServiceImplTest { DiskOfferingVO diskOffering = Mockito.mock(DiskOfferingVO.class); when(diskOffering.getEncrypt()).thenReturn(true); when(_diskOfferingDao.findById(anyLong())).thenReturn(diskOffering); - volumeApiServiceImpl.attachVolumeToVM(4L, 10L, 1L, false); + try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { + volumeApiServiceImpl.attachVolumeToVM(4L, 10L, 1L, false); + } } // volume not Ready @@ -655,9 +659,7 @@ public class VolumeApiServiceImplTest { * The resource limit check for primary storage should not be skipped for Volume in 'Uploaded' state. */ @Test - public void testResourceLimitCheckForUploadedVolume() throws NoSuchFieldException, IllegalAccessException, ResourceAllocationException { - doThrow(new ResourceAllocationException("primary storage resource limit check failed", Resource.ResourceType.primary_storage)).when(resourceLimitServiceMock) - .checkResourceLimit(any(AccountVO.class), any(Resource.ResourceType.class), any(Long.class)); + public void testAttachVolumeToVMPerformsResourceReservation() throws NoSuchFieldException, IllegalAccessException, ResourceAllocationException { UserVmVO vm = Mockito.mock(UserVmVO.class); AccountVO acc = Mockito.mock(AccountVO.class); VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class); @@ -678,10 +680,10 @@ public class VolumeApiServiceImplTest { DataCenterVO zoneWithDisabledLocalStorage = Mockito.mock(DataCenterVO.class); when(_dcDao.findById(anyLong())).thenReturn(zoneWithDisabledLocalStorage); when(zoneWithDisabledLocalStorage.isLocalStorageEnabled()).thenReturn(true); - try { + doReturn(volumeVoMock).when(volumeApiServiceImpl).getVolumeAttachJobResult(Mockito.any(), Mockito.any(), Mockito.any()); + try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { volumeApiServiceImpl.attachVolumeToVM(2L, 9L, null, false); - } catch (InvalidParameterValueException e) { - Assert.assertEquals(e.getMessage(), ("primary storage resource limit check failed")); + Assert.assertEquals(1, mockCheckedReservation.constructed().size()); } } @@ -2208,6 +2210,36 @@ public class VolumeApiServiceImplTest { } } + @Test + public void getRequiredPrimaryStorageSizeForVolumeAttachTestTagsAreEmptyReturnsZero() { + List tags = new ArrayList<>(); + + Long result = volumeApiServiceImpl.getRequiredPrimaryStorageSizeForVolumeAttach(tags, volumeInfoMock); + + Assert.assertEquals(0L, (long) result); + } + + @Test + public void getRequiredPrimaryStorageSizeForVolumeAttachTestVolumeIsReadyReturnsZero() { + List tags = List.of("tag1", "tag2"); + Mockito.doReturn(Volume.State.Ready).when(volumeInfoMock).getState(); + + Long result = volumeApiServiceImpl.getRequiredPrimaryStorageSizeForVolumeAttach(tags, volumeInfoMock); + + Assert.assertEquals(0L, (long) result); + } + + @Test + public void getRequiredPrimaryStorageSizeForVolumeAttachTestTagsAreNotEmptyAndVolumeIsUploadedReturnsVolumeSize() { + List tags = List.of("tag1", "tag2"); + Mockito.doReturn(Volume.State.Uploaded).when(volumeInfoMock).getState(); + Mockito.doReturn(2L).when(volumeInfoMock).getSize(); + + Long result = volumeApiServiceImpl.getRequiredPrimaryStorageSizeForVolumeAttach(tags, volumeInfoMock); + + Assert.assertEquals(2L, (long) result); + } + @Test public void validateNoVmSnapshotsTestNoInstanceId() { Mockito.doReturn(null).when(volumeVoMock).getInstanceId(); diff --git a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java index 2d3cb04ab96..ff0888c184c 100644 --- a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java +++ b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java @@ -22,7 +22,6 @@ import com.cloud.dc.dao.DataCenterDao; import com.cloud.event.ActionEventUtils; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; -import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.org.Grouping; import com.cloud.storage.DataStoreRole; @@ -317,12 +316,11 @@ public class SnapshotManagerImplTest { Mockito.when(result1.isFailed()).thenReturn(false); AsyncCallFuture future1 = Mockito.mock(AsyncCallFuture.class); try { - Mockito.doNothing().when(resourceLimitService).checkResourceLimit(Mockito.any(), Mockito.any(), Mockito.anyLong()); Mockito.when(future.get()).thenReturn(result); Mockito.when(snapshotService.queryCopySnapshot(Mockito.any())).thenReturn(future); Mockito.when(future1.get()).thenReturn(result1); Mockito.when(snapshotService.copySnapshot(Mockito.any(SnapshotInfo.class), Mockito.anyString(), Mockito.any(DataStore.class))).thenReturn(future1); - } catch (ResourceAllocationException | ResourceUnavailableException | ExecutionException | InterruptedException e) { + } catch (ResourceUnavailableException | ExecutionException | InterruptedException e) { Assert.fail(e.getMessage()); } List addedZone = new ArrayList<>(); diff --git a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java index 3b5d92103e7..8eee0ed6626 100755 --- a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java +++ b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java @@ -16,6 +16,27 @@ // under the License. package com.cloud.storage.snapshot; +import org.apache.cloudstack.api.command.user.snapshot.ExtractSnapshotCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation; +import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory; +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.snapshot.SnapshotHelper; +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.storage.image.datastore.ImageStoreEntity; + import com.cloud.api.ApiDBUtils; import com.cloud.configuration.Resource.ResourceType; import com.cloud.dc.DataCenter; @@ -27,6 +48,7 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.resource.ResourceManager; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.server.ResourceTag; import com.cloud.server.TaggedResourceService; import com.cloud.storage.DataStoreRole; @@ -57,27 +79,6 @@ import com.cloud.vm.snapshot.VMSnapshot; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; -import org.apache.cloudstack.api.command.user.snapshot.ExtractSnapshotCmd; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; -import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation; -import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory; -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.snapshot.SnapshotHelper; -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.storage.image.datastore.ImageStoreEntity; - import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -87,6 +88,7 @@ import org.junit.runner.RunWith; import org.mockito.BDDMockito; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedConstruction; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @@ -237,8 +239,6 @@ public class SnapshotManagerTest { when(_storageStrategyFactory.getSnapshotStrategy(Mockito.any(SnapshotVO.class), Mockito.eq(SnapshotOperation.BACKUP))).thenReturn(snapshotStrategy); when(_storageStrategyFactory.getSnapshotStrategy(Mockito.any(SnapshotVO.class), Mockito.eq(SnapshotOperation.REVERT))).thenReturn(snapshotStrategy); - doNothing().when(_resourceLimitMgr).checkResourceLimit(any(Account.class), any(ResourceType.class)); - doNothing().when(_resourceLimitMgr).checkResourceLimit(any(Account.class), any(ResourceType.class), anyLong()); doNothing().when(_resourceLimitMgr).incrementResourceCount(anyLong(), any(ResourceType.class)); doNothing().when(_resourceLimitMgr).incrementResourceCount(anyLong(), any(ResourceType.class), anyLong()); @@ -320,7 +320,12 @@ public class SnapshotManagerTest { when(mockList2.size()).thenReturn(0); when(_vmSnapshotDao.listByInstanceId(TEST_VM_ID, VMSnapshot.State.Creating, VMSnapshot.State.Reverting, VMSnapshot.State.Expunging)).thenReturn(mockList2); when(_snapshotDao.persist(any(SnapshotVO.class))).thenReturn(snapshotMock); - _snapshotMgr.allocSnapshot(TEST_VOLUME_ID, Snapshot.MANUAL_POLICY_ID, null, null); + + try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { + _snapshotMgr.allocSnapshot(TEST_VOLUME_ID, Snapshot.MANUAL_POLICY_ID, null, null); + } catch (ResourceAllocationException e) { + Assert.fail(String.format("Failure with exception: %s", e.getMessage())); + } } @Test(expected = InvalidParameterValueException.class) @@ -468,7 +473,7 @@ public class SnapshotManagerTest { public void validateCreateTagsForSnapshotPolicyWithValidTags(){ Mockito.doReturn(null).when(taggedResourceServiceMock).createTags(any(), any(), any(), any()); - Map map = new HashMap<>(); + Map map = new HashMap<>(); map.put("test", "test"); _snapshotMgr.createTagsForSnapshotPolicy(map, snapshotPolicyVoMock); diff --git a/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java b/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java index 7bdc1e7c604..4f86f578a69 100755 --- a/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java +++ b/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java @@ -22,7 +22,6 @@ package com.cloud.template; import com.cloud.agent.AgentManager; import com.cloud.api.query.dao.SnapshotJoinDao; import com.cloud.api.query.dao.UserVmJoinDao; -import com.cloud.configuration.Resource; import com.cloud.dc.dao.DataCenterDao; import com.cloud.deployasis.dao.TemplateDeployAsIsDetailsDao; import com.cloud.domain.dao.DomainDao; @@ -34,6 +33,7 @@ import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.HypervisorGuruManager; import com.cloud.projects.ProjectManager; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.storage.DataStoreRole; import com.cloud.storage.GuestOSVO; import com.cloud.storage.Snapshot; @@ -64,12 +64,14 @@ import com.cloud.user.User; import com.cloud.user.UserData; import com.cloud.user.UserVO; import com.cloud.user.dao.AccountDao; -import com.cloud.utils.component.ComponentContext; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; + +import junit.framework.TestCase; + import org.apache.cloudstack.api.command.user.template.CreateTemplateCmd; import org.apache.cloudstack.api.command.user.template.DeleteTemplateCmd; import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd; @@ -92,6 +94,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.messagebus.MessageBus; +import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDao; import org.apache.cloudstack.secstorage.heuristics.HeuristicType; import org.apache.cloudstack.snapshot.SnapshotHelper; @@ -104,14 +107,21 @@ import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import org.apache.cloudstack.storage.heuristics.HeuristicRuleHelper; import org.apache.cloudstack.storage.template.VnfTemplateManager; + import org.apache.cloudstack.test.utils.SpringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; + +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedConstruction; import org.mockito.Mockito; +import org.mockito.Spy; import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -120,11 +130,7 @@ import org.springframework.context.annotation.FilterType; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.TypeFilter; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.AnnotationConfigContextLoader; -import javax.inject.Inject; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -137,81 +143,81 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(loader = AnnotationConfigContextLoader.class) -public class TemplateManagerImplTest { +@RunWith(MockitoJUnitRunner.class) +public class TemplateManagerImplTest extends TestCase { - @Inject - TemplateManagerImpl templateManager = new TemplateManagerImpl(); + @Spy + @InjectMocks + TemplateManagerImpl templateManager; - @Inject + @Mock DataStoreManager dataStoreManager; - @Inject + @Mock VMTemplateDao vmTemplateDao; - @Inject + @Mock VMTemplatePoolDao vmTemplatePoolDao; - @Inject + @Mock TemplateDataStoreDao templateDataStoreDao; - @Inject + @Mock StoragePoolHostDao storagePoolHostDao; - @Inject + @Mock PrimaryDataStoreDao primaryDataStoreDao; - @Inject + @Mock ResourceLimitService resourceLimitMgr; - @Inject + @Mock ImageStoreDao imgStoreDao; - @Inject + @Mock GuestOSDao guestOSDao; - @Inject - VMTemplateDao tmpltDao; - - @Inject + @Mock SnapshotDao snapshotDao; - @Inject + @Mock + VolumeDao volumeDao; + + @Mock VMTemplateDetailsDao tmpltDetailsDao; - @Inject + @Mock StorageStrategyFactory storageStrategyFactory; - @Inject + @Mock VMInstanceDao _vmInstanceDao; - @Inject - private VMTemplateDao _tmpltDao; + @Mock + ReservationDao reservationDao; - @Inject + @Mock HypervisorGuruManager _hvGuruMgr; - @Inject + @Mock AccountManager _accountMgr; - @Inject + + @Mock VnfTemplateManager vnfTemplateManager; - @Inject + @Mock SnapshotJoinDao snapshotJoinDao; - @Inject + @Mock TemplateDeployAsIsDetailsDao templateDeployAsIsDetailsDao; - @Inject + @Mock HeuristicRuleHelper heuristicRuleHelperMock; public class CustomThreadPoolExecutor extends ThreadPoolExecutor { @@ -241,7 +247,6 @@ public class TemplateManagerImplTest { @Before public void setUp() { - ComponentContext.initComponentsLifeCycle(); AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid"); UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN); CallContext.register(user, account); @@ -275,7 +280,7 @@ public class TemplateManagerImplTest { List adapters = new ArrayList(); adapters.add(templateAdapter); when(cmd.getId()).thenReturn(0L); - when(_tmpltDao.findById(cmd.getId())).thenReturn(template); + when(vmTemplateDao.findById(cmd.getId())).thenReturn(template); when(cmd.getZoneId()).thenReturn(null); when(template.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.None); @@ -296,7 +301,6 @@ public class TemplateManagerImplTest { //case 2.2: When Force delete flag is 'false' and VM instance VO list is non empty. when(cmd.isForced()).thenReturn(false); VMInstanceVO vmInstanceVO = mock(VMInstanceVO.class); - when(vmInstanceVO.getInstanceName()).thenReturn("mydDummyVM"); vmInstanceVOList.add(vmInstanceVO); when(_vmInstanceDao.listNonExpungedByTemplate(anyLong())).thenReturn(vmInstanceVOList); try { @@ -311,7 +315,6 @@ public class TemplateManagerImplTest { when(mockTemplate.getId()).thenReturn(202l); StoragePoolVO mockPool = mock(StoragePoolVO.class); - when(mockPool.getId()).thenReturn(2l); PrimaryDataStore mockPrimaryDataStore = mock(PrimaryDataStore.class); when(mockPrimaryDataStore.getId()).thenReturn(2l); @@ -319,7 +322,6 @@ public class TemplateManagerImplTest { VMTemplateStoragePoolVO mockTemplateStore = mock(VMTemplateStoragePoolVO.class); when(mockTemplateStore.getDownloadState()).thenReturn(VMTemplateStorageResourceAssoc.Status.DOWNLOADED); - when(dataStoreManager.getPrimaryDataStore(anyLong())).thenReturn(mockPrimaryDataStore); when(vmTemplateDao.findById(anyLong(), anyBoolean())).thenReturn(mockTemplate); when(vmTemplatePoolDao.findByPoolTemplate(anyLong(), anyLong(), nullable(String.class))).thenReturn(mockTemplateStore); @@ -335,13 +337,11 @@ public class TemplateManagerImplTest { when(mockTemplate.getId()).thenReturn(202l); StoragePoolVO mockPool = mock(StoragePoolVO.class); - when(mockPool.getId()).thenReturn(2l); PrimaryDataStore mockPrimaryDataStore = mock(PrimaryDataStore.class); when(mockPrimaryDataStore.getId()).thenReturn(2l); when(mockPrimaryDataStore.getDataCenterId()).thenReturn(1l); - when(dataStoreManager.getPrimaryDataStore(anyLong())).thenReturn(mockPrimaryDataStore); when(vmTemplateDao.findById(anyLong(), anyBoolean())).thenReturn(mockTemplate); when(vmTemplatePoolDao.findByPoolTemplate(anyLong(), anyLong(), nullable(String.class))).thenReturn(null); when(templateDataStoreDao.findByTemplateZoneDownloadStatus(202l, 1l, VMTemplateStorageResourceAssoc.Status.DOWNLOADED)).thenReturn(null); @@ -356,7 +356,6 @@ public class TemplateManagerImplTest { when(mockTemplate.getId()).thenReturn(202l); StoragePoolVO mockPool = mock(StoragePoolVO.class); - when(mockPool.getId()).thenReturn(2l); PrimaryDataStore mockPrimaryDataStore = mock(PrimaryDataStore.class); when(mockPrimaryDataStore.getId()).thenReturn(2l); @@ -364,7 +363,6 @@ public class TemplateManagerImplTest { TemplateDataStoreVO mockTemplateDataStore = mock(TemplateDataStoreVO.class); - when(dataStoreManager.getPrimaryDataStore(anyLong())).thenReturn(mockPrimaryDataStore); when(vmTemplateDao.findById(anyLong(), anyBoolean())).thenReturn(mockTemplate); when(vmTemplatePoolDao.findByPoolTemplate(anyLong(), anyLong(), nullable(String.class))).thenReturn(null); when(templateDataStoreDao.findByTemplateZoneDownloadStatus(202l, 1l, VMTemplateStorageResourceAssoc.Status.DOWNLOADED)).thenReturn(mockTemplateDataStore); @@ -415,20 +413,10 @@ public class TemplateManagerImplTest { PrimaryDataStore mockPrimaryDataStore = mock(PrimaryDataStore.class); VMTemplateStoragePoolVO mockTemplateStore = mock(VMTemplateStoragePoolVO.class); - when(mockPrimaryDataStore.getId()).thenReturn(2l); - when(mockPool.getId()).thenReturn(2l); when(mockPool.getStatus()).thenReturn(StoragePoolStatus.Disabled); - when(mockPool.getDataCenterId()).thenReturn(1l); - when(mockTemplate.getId()).thenReturn(202l); - when(mockTemplateStore.getDownloadState()).thenReturn(VMTemplateStorageResourceAssoc.Status.DOWNLOADED); when(vmTemplateDao.findById(anyLong())).thenReturn(mockTemplate); - when(dataStoreManager.getPrimaryDataStore(anyLong())).thenReturn(mockPrimaryDataStore); - when(vmTemplateDao.findById(anyLong(), anyBoolean())).thenReturn(mockTemplate); - when(vmTemplatePoolDao.findByPoolTemplate(anyLong(), anyLong(), nullable(String.class))).thenReturn(mockTemplateStore); when(primaryDataStoreDao.findById(anyLong())).thenReturn(mockPool); - doNothing().when(mockTemplateStore).setMarkedForGC(anyBoolean()); - ExecutorService preloadExecutor = new CustomThreadPoolExecutor(8, 8, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new NamedThreadFactory("Template-Preloader")); templateManager._preloadExecutor = preloadExecutor; @@ -446,15 +434,10 @@ public class TemplateManagerImplTest { StoragePoolVO mockPool1 = mock(StoragePoolVO.class); when(mockPool1.getId()).thenReturn(2l); - when(mockPool1.getStatus()).thenReturn(StoragePoolStatus.Up); when(mockPool1.getDataCenterId()).thenReturn(1l); StoragePoolVO mockPool2 = mock(StoragePoolVO.class); - when(mockPool2.getId()).thenReturn(3l); - when(mockPool2.getStatus()).thenReturn(StoragePoolStatus.Up); when(mockPool2.getDataCenterId()).thenReturn(1l); StoragePoolVO mockPool3 = mock(StoragePoolVO.class); - when(mockPool3.getId()).thenReturn(4l); - when(mockPool3.getStatus()).thenReturn(StoragePoolStatus.Up); when(mockPool3.getDataCenterId()).thenReturn(2l); pools.add(mockPool1); pools.add(mockPool2); @@ -467,9 +450,6 @@ public class TemplateManagerImplTest { when(dataStoreManager.getPrimaryDataStore(anyLong())).thenReturn(mockPrimaryDataStore); when(vmTemplateDao.findById(anyLong(), anyBoolean())).thenReturn(mockTemplate); when(vmTemplatePoolDao.findByPoolTemplate(anyLong(), anyLong(), nullable(String.class))).thenReturn(mockTemplateStore); - when(primaryDataStoreDao.findById(2l)).thenReturn(mockPool1); - when(primaryDataStoreDao.findById(3l)).thenReturn(mockPool2); - when(primaryDataStoreDao.findById(4l)).thenReturn(mockPool3); when(primaryDataStoreDao.listByStatus(StoragePoolStatus.Up)).thenReturn(pools); doNothing().when(mockTemplateStore).setMarkedForGC(anyBoolean()); @@ -497,7 +477,6 @@ public class TemplateManagerImplTest { when(mockCreateCmd.getVolumeId()).thenReturn(null); when(mockCreateCmd.getSnapshotId()).thenReturn(1L); when(mockCreateCmd.getOsTypeId()).thenReturn(1L); - when(mockCreateCmd.getEventDescription()).thenReturn("test"); when(mockCreateCmd.getDetails()).thenReturn(null); when(mockCreateCmd.getZoneId()).thenReturn(null); @@ -510,20 +489,17 @@ public class TemplateManagerImplTest { when(mockSnapshot.getState()).thenReturn(Snapshot.State.BackedUp); when(mockSnapshot.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.XenServer); - doNothing().when(resourceLimitMgr).checkResourceLimit(any(Account.class), eq(Resource.ResourceType.template)); - doNothing().when(resourceLimitMgr).checkResourceLimit(any(Account.class), eq(Resource.ResourceType.secondary_storage), anyLong()); - GuestOSVO mockGuestOS = mock(GuestOSVO.class); when(guestOSDao.findById(anyLong())).thenReturn(mockGuestOS); - when(tmpltDao.getNextInSequence(eq(Long.class), eq("id"))).thenReturn(1L); + when(vmTemplateDao.getNextInSequence(eq(Long.class), eq("id"))).thenReturn(1L); List mockRegionStores = new ArrayList<>(); ImageStoreVO mockRegionStore = mock(ImageStoreVO.class); mockRegionStores.add(mockRegionStore); when(imgStoreDao.findRegionImageStores()).thenReturn(mockRegionStores); - when(tmpltDao.persist(any(VMTemplateVO.class))).thenAnswer(new Answer() { + when(vmTemplateDao.persist(any(VMTemplateVO.class))).thenAnswer(new Answer() { @Override public VMTemplateVO answer(InvocationOnMock invocationOnMock) throws Throwable { Object[] args = invocationOnMock.getArguments(); @@ -531,8 +507,10 @@ public class TemplateManagerImplTest { } }); - VMTemplateVO template = templateManager.createPrivateTemplateRecord(mockCreateCmd, mockTemplateOwner); - assertTrue("Template in a region store should have cross zones set", template.isCrossZones()); + try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { + VMTemplateVO template = templateManager.createPrivateTemplateRecord(mockCreateCmd, mockTemplateOwner); + assertTrue("Template in a region store should have cross zones set", template.isCrossZones()); + } } @Test @@ -544,7 +522,7 @@ public class TemplateManagerImplTest { when(cmd.getUserdataPolicy()).thenReturn(UserData.UserDataOverridePolicy.ALLOWOVERRIDE); VMTemplateVO template = Mockito.mock(VMTemplateVO.class); - when(_tmpltDao.findById(anyLong())).thenReturn(template); + when(vmTemplateDao.findById(anyLong())).thenReturn(template); VirtualMachineTemplate resultTemplate = templateManager.linkUserDataToTemplate(cmd); @@ -560,7 +538,6 @@ public class TemplateManagerImplTest { when(cmd.getUserdataPolicy()).thenReturn(UserData.UserDataOverridePolicy.ALLOWOVERRIDE); VMTemplateVO template = Mockito.mock(VMTemplateVO.class); - when(_tmpltDao.findById(1L)).thenReturn(template); templateManager.linkUserDataToTemplate(cmd); } @@ -574,7 +551,6 @@ public class TemplateManagerImplTest { when(cmd.getUserdataPolicy()).thenReturn(UserData.UserDataOverridePolicy.ALLOWOVERRIDE); VMTemplateVO template = Mockito.mock(VMTemplateVO.class); - when(_tmpltDao.findById(1L)).thenReturn(template); templateManager.linkUserDataToTemplate(cmd); } @@ -587,7 +563,7 @@ public class TemplateManagerImplTest { when(cmd.getUserdataId()).thenReturn(2L); when(cmd.getUserdataPolicy()).thenReturn(UserData.UserDataOverridePolicy.ALLOWOVERRIDE); - when(_tmpltDao.findById(anyLong())).thenReturn(null); + when(vmTemplateDao.findById(anyLong())).thenReturn(null); templateManager.linkUserDataToTemplate(cmd); } @@ -602,7 +578,7 @@ public class TemplateManagerImplTest { VMTemplateVO template = Mockito.mock(VMTemplateVO.class); when(template.getId()).thenReturn(1L); - when(_tmpltDao.findById(1L)).thenReturn(template); + when(vmTemplateDao.findById(1L)).thenReturn(template); VirtualMachineTemplate resultTemplate = templateManager.linkUserDataToTemplate(cmd); @@ -633,7 +609,6 @@ public class TemplateManagerImplTest { DataStore dataStore = Mockito.mock(DataStore.class); VolumeVO volumeVO = Mockito.mock(VolumeVO.class); - Mockito.when(dataStoreManager.getDataStore(Mockito.anyString(), Mockito.any(DataStoreRole.class))).thenReturn(null); Mockito.when(heuristicRuleHelperMock.getImageStoreIfThereIsHeuristicRule(Mockito.anyLong(), Mockito.any(HeuristicType.class), Mockito.any(VolumeVO.class))).thenReturn(null); Mockito.when(dataStoreManager.getImageStoreWithFreeCapacity(Mockito.anyLong())).thenReturn(dataStore); @@ -646,7 +621,6 @@ public class TemplateManagerImplTest { DataStore dataStore = Mockito.mock(DataStore.class); VolumeVO volumeVO = Mockito.mock(VolumeVO.class); - Mockito.when(dataStoreManager.getDataStore(Mockito.anyString(), Mockito.any(DataStoreRole.class))).thenReturn(null); Mockito.when(heuristicRuleHelperMock.getImageStoreIfThereIsHeuristicRule(Mockito.anyLong(), Mockito.any(HeuristicType.class), Mockito.any(VolumeVO.class))).thenReturn(dataStore); templateManager.getImageStore(null, 1L, volumeVO); @@ -987,6 +961,11 @@ public class TemplateManagerImplTest { return Mockito.mock(TemplateDeployAsIsDetailsDao.class); } + @Bean + public ReservationDao reservationDao() { + return Mockito.mock(ReservationDao.class); + } + @Bean public SnapshotHelper snapshotHelper() { return Mockito.mock(SnapshotHelper.class); diff --git a/server/src/test/java/com/cloud/user/DomainManagerImplTest.java b/server/src/test/java/com/cloud/user/DomainManagerImplTest.java index 5a1dba215ae..06a8eba903b 100644 --- a/server/src/test/java/com/cloud/user/DomainManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/DomainManagerImplTest.java @@ -68,6 +68,7 @@ import com.cloud.utils.db.GlobalLock; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; +import com.cloud.vm.dao.VMInstanceDao; @RunWith(MockitoJUnitRunner.class) @@ -123,6 +124,8 @@ public class DomainManagerImplTest { Account adminAccount; @Mock GlobalLock lock; + @Mock + VMInstanceDao vmInstanceDao; List domainAccountsForCleanup; List domainNetworkIds; @@ -213,6 +216,7 @@ public class DomainManagerImplTest { @Test public void testDeleteDomainNoCleanup() { Mockito.when(_configMgr.releaseDomainSpecificVirtualRanges(Mockito.any())).thenReturn(true); + Mockito.doReturn(Collections.emptySet()).when(domainManager).getDomainChildrenIds(Mockito.any()); domainManager.deleteDomain(DOMAIN_ID, testDomainCleanup); Mockito.verify(domainManager).deleteDomain(domain, testDomainCleanup); Mockito.verify(domainManager).removeDomainWithNoAccountsForCleanupNetworksOrDedicatedResources(domain); @@ -278,6 +282,7 @@ public class DomainManagerImplTest { Mockito.when(_dedicatedDao.listByDomainId(Mockito.anyLong())).thenReturn(new ArrayList()); Mockito.when(domainDaoMock.remove(Mockito.anyLong())).thenReturn(true); Mockito.when(_configMgr.releaseDomainSpecificVirtualRanges(Mockito.any())).thenReturn(true); + Mockito.doReturn(Collections.emptySet()).when(domainManager).getDomainChildrenIds(Mockito.any()); try { Assert.assertTrue(domainManager.deleteDomain(20l, false)); @@ -309,7 +314,7 @@ public class DomainManagerImplTest { Mockito.when(_resourceCountDao.removeEntriesByOwner(Mockito.anyLong(), Mockito.eq(ResourceOwnerType.Domain))).thenReturn(1l); Mockito.when(_resourceLimitDao.removeEntriesByOwner(Mockito.anyLong(), Mockito.eq(ResourceOwnerType.Domain))).thenReturn(1l); Mockito.when(_configMgr.releaseDomainSpecificVirtualRanges(Mockito.any())).thenReturn(true); - + Mockito.when(vmInstanceDao.listDeleteProtectedVmsByDomainIds(Mockito.any())).thenReturn(Collections.emptyList()); try { Assert.assertTrue(domainManager.deleteDomain(20l, true)); } finally { diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index 1a38c1b0a06..341d6abe332 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -61,6 +61,20 @@ import java.util.Map; import java.util.TimeZone; import java.util.UUID; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.api.ApiCommandResourceType; @@ -85,6 +99,7 @@ import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.resourcelimit.Reserver; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; import org.apache.cloudstack.engine.subsystem.api.storage.Scope; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; @@ -97,18 +112,6 @@ import org.apache.cloudstack.storage.template.VnfTemplateManager; import org.apache.cloudstack.userdata.UserDataManager; import org.apache.cloudstack.vm.UnmanagedVMsManager; import org.apache.cloudstack.vm.lease.VMLeaseManager; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.Spy; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.test.util.ReflectionTestUtils; import com.cloud.api.query.dao.ServiceOfferingJoinDao; import com.cloud.api.query.vo.ServiceOfferingJoinVO; @@ -160,6 +163,7 @@ import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; import com.cloud.offerings.NetworkOfferingVO; import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.server.ManagementService; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; @@ -726,7 +730,6 @@ public class UserVmManagerImplTest { Mockito.doNothing().when(userVmManagerImpl).validateOldAndNewAccounts(Mockito.nullable(Account.class), Mockito.nullable(Account.class), Mockito.anyLong(), Mockito.nullable(String.class), Mockito.nullable(Long.class)); Mockito.doNothing().when(userVmManagerImpl).validateIfVmHasNoRules(Mockito.any(), Mockito.anyLong()); Mockito.doNothing().when(userVmManagerImpl).removeInstanceFromInstanceGroup(Mockito.anyLong()); - Mockito.doNothing().when(userVmManagerImpl).verifyResourceLimitsForAccountAndStorage(Mockito.any(), Mockito.any(), Mockito.any(), anyList(), Mockito.any()); Mockito.doNothing().when(userVmManagerImpl).validateIfNewOwnerHasAccessToTemplate(Mockito.any(), Mockito.any(), Mockito.any()); Mockito.doNothing().when(userVmManagerImpl).updateVmOwner(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); @@ -1735,23 +1738,11 @@ public class UserVmManagerImplTest { Mockito.when(vol5.isDisplay()).thenReturn(true); List volumes = List.of(vol1, undisplayedVolume, vol3, vol4, vol5); - Long size = volumes.stream().filter(VolumeVO::isDisplay).mapToLong(VolumeVO::getSize).sum(); - try { - userVmManagerImpl.checkVolumesLimits(account, volumes); - Mockito.verify(resourceLimitMgr, times(1)) - .checkResourceLimit(account, Resource.ResourceType.volume, 4); - Mockito.verify(resourceLimitMgr, times(1)) - .checkResourceLimit(account, Resource.ResourceType.primary_storage, size); - Mockito.verify(resourceLimitMgr, times(1)) - .checkResourceLimitWithTag(account, Resource.ResourceType.volume, "tag1", 2); - Mockito.verify(resourceLimitMgr, times(1)) - .checkResourceLimitWithTag(account, Resource.ResourceType.volume, "tag2", 3); - Mockito.verify(resourceLimitMgr, times(1)) - .checkResourceLimitWithTag(account, Resource.ResourceType.primary_storage, "tag1", - vol1.getSize() + vol5.getSize()); - Mockito.verify(resourceLimitMgr, times(1)) - .checkResourceLimitWithTag(account, Resource.ResourceType.primary_storage, "tag2", - vol1.getSize() + vol3.getSize() + vol5.getSize()); + List reservations = new ArrayList<>(); + + try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { + userVmManagerImpl.checkVolumesLimits(account, volumes, reservations); + Assert.assertEquals(8, reservations.size()); } catch (ResourceAllocationException e) { Assert.fail(e.getMessage()); } @@ -2042,26 +2033,26 @@ public class UserVmManagerImplTest { @Test public void verifyResourceLimitsForAccountAndStorageTestCountOnlyRunningVmsInResourceLimitationIsTrueDoesNotCallVmResourceLimitCheck() throws ResourceAllocationException { + List reservations = new ArrayList<>(); LinkedList volumeVoList = new LinkedList(); Mockito.doReturn(true).when(userVmManagerImpl).countOnlyRunningVmsInResourceLimitation(); - userVmManagerImpl.verifyResourceLimitsForAccountAndStorage(accountMock, userVmVoMock, serviceOfferingVoMock, volumeVoList, virtualMachineTemplateMock); + userVmManagerImpl.verifyResourceLimitsForAccountAndStorage(accountMock, userVmVoMock, serviceOfferingVoMock, volumeVoList, virtualMachineTemplateMock, reservations); - Mockito.verify(resourceLimitMgr, Mockito.never()).checkVmResourceLimit(Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any()); - Mockito.verify(resourceLimitMgr).checkResourceLimit(accountMock, Resource.ResourceType.volume, 0l); - Mockito.verify(resourceLimitMgr).checkResourceLimit(accountMock, Resource.ResourceType.primary_storage, 0l); + Mockito.verify(resourceLimitMgr, Mockito.never()).checkVmResourceLimit(Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.any()); + Mockito.verify(resourceLimitMgr, Mockito.never()).checkVolumeResourceLimit(Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.any()); } @Test public void verifyResourceLimitsForAccountAndStorageTestCountOnlyRunningVmsInResourceLimitationIsFalseCallsVmResourceLimitCheck() throws ResourceAllocationException { + List reservations = new ArrayList<>(); LinkedList volumeVoList = new LinkedList(); Mockito.doReturn(false).when(userVmManagerImpl).countOnlyRunningVmsInResourceLimitation(); - userVmManagerImpl.verifyResourceLimitsForAccountAndStorage(accountMock, userVmVoMock, serviceOfferingVoMock, volumeVoList, virtualMachineTemplateMock); + userVmManagerImpl.verifyResourceLimitsForAccountAndStorage(accountMock, userVmVoMock, serviceOfferingVoMock, volumeVoList, virtualMachineTemplateMock, reservations); - Mockito.verify(resourceLimitMgr).checkVmResourceLimit(Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any()); - Mockito.verify(resourceLimitMgr).checkResourceLimit(accountMock, Resource.ResourceType.volume, 0l); - Mockito.verify(resourceLimitMgr).checkResourceLimit(accountMock, Resource.ResourceType.primary_storage, 0l); + Mockito.verify(resourceLimitMgr).checkVmResourceLimit(Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.any()); + Mockito.verify(userVmManagerImpl).checkVolumesLimits(Mockito.any(), Mockito.any(), Mockito.any()); } @Test @@ -3106,7 +3097,7 @@ public class UserVmManagerImplTest { configureDoNothingForMethodsThatWeDoNotWantToTest(); doThrow(ResourceAllocationException.class).when(userVmManagerImpl).verifyResourceLimitsForAccountAndStorage(Mockito.any(), Mockito.any(), - Mockito.any(), Mockito.any(), Mockito.any()); + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); Assert.assertThrows(ResourceAllocationException.class, () -> userVmManagerImpl.moveVmToUser(assignVmCmdMock)); } diff --git a/server/src/test/java/com/cloud/vm/snapshot/VMSnapshotManagerTest.java b/server/src/test/java/com/cloud/vm/snapshot/VMSnapshotManagerTest.java index aa3cba61f62..e88d5b0a1bd 100644 --- a/server/src/test/java/com/cloud/vm/snapshot/VMSnapshotManagerTest.java +++ b/server/src/test/java/com/cloud/vm/snapshot/VMSnapshotManagerTest.java @@ -372,13 +372,13 @@ public class VMSnapshotManagerTest { _vmSnapshotMgr.updateUserVmServiceOffering(userVm, vmSnapshotVO); verify(_vmSnapshotMgr).changeUserVmServiceOffering(userVm, vmSnapshotVO); - verify(_vmSnapshotMgr).getVmMapDetails(userVm); + verify(_vmSnapshotMgr).getVmMapDetails(vmSnapshotVO); verify(_vmSnapshotMgr).upgradeUserVmServiceOffering(eq(userVm), eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture()); } @Test public void testGetVmMapDetails() { - Map result = _vmSnapshotMgr.getVmMapDetails(userVm); + Map result = _vmSnapshotMgr.getVmMapDetails(vmSnapshotVO); assert(result.containsKey(userVmDetailCpuNumber.getName())); assert(result.containsKey(userVmDetailMemory.getName())); assertEquals(userVmDetails.size(), result.size()); @@ -390,7 +390,7 @@ public class VMSnapshotManagerTest { public void testChangeUserVmServiceOffering() throws ConcurrentOperationException, ResourceUnavailableException, ManagementServerException, VirtualMachineMigrationException { when(_userVmManager.upgradeVirtualMachine(eq(TEST_VM_ID), eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture())).thenReturn(true); _vmSnapshotMgr.changeUserVmServiceOffering(userVm, vmSnapshotVO); - verify(_vmSnapshotMgr).getVmMapDetails(userVm); + verify(_vmSnapshotMgr).getVmMapDetails(vmSnapshotVO); verify(_vmSnapshotMgr).upgradeUserVmServiceOffering(eq(userVm), eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture()); } @@ -398,7 +398,7 @@ public class VMSnapshotManagerTest { public void testChangeUserVmServiceOfferingFailOnUpgradeVMServiceOffering() throws ConcurrentOperationException, ResourceUnavailableException, ManagementServerException, VirtualMachineMigrationException { when(_userVmManager.upgradeVirtualMachine(eq(TEST_VM_ID), eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture())).thenReturn(false); _vmSnapshotMgr.changeUserVmServiceOffering(userVm, vmSnapshotVO); - verify(_vmSnapshotMgr).getVmMapDetails(userVm); + verify(_vmSnapshotMgr).getVmMapDetails(vmSnapshotVO); verify(_vmSnapshotMgr).upgradeUserVmServiceOffering(eq(userVm), eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture()); } diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index de768388b44..3b120b88db4 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -952,7 +952,7 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches } @Override - public boolean resourceCountNeedsUpdate(NetworkOffering ntwkOff, ACLType aclType) { + public boolean isResourceCountUpdateNeeded(NetworkOffering ntwkOff) { return false; //To change body of implemented methods use File | Settings | File Templates. } diff --git a/server/src/test/java/com/cloud/vpc/MockResourceLimitManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockResourceLimitManagerImpl.java index 151c7ff8908..9e0997df08c 100644 --- a/server/src/test/java/com/cloud/vpc/MockResourceLimitManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockResourceLimitManagerImpl.java @@ -24,6 +24,7 @@ import javax.naming.ConfigurationException; import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.resourcelimit.Reserver; import org.springframework.stereotype.Component; import com.cloud.configuration.Resource.ResourceType; @@ -237,6 +238,11 @@ public class MockResourceLimitManagerImpl extends ManagerBase implements Resourc } + @Override + public void checkResourceLimitWithTag(Account account, Long domainId, boolean considerSystemAccount, ResourceType type, String tag, long... count) throws ResourceAllocationException { + + } + @Override public List getResourceLimitHostTags() { return null; @@ -268,19 +274,24 @@ public class MockResourceLimitManagerImpl extends ManagerBase implements Resourc } @Override - public void checkVolumeResourceLimit(Account owner, Boolean display, Long size, DiskOffering diskOffering) throws ResourceAllocationException { + public void checkVolumeResourceLimit(Account owner, Boolean display, Long size, DiskOffering diskOffering, List reservations) throws ResourceAllocationException { } + @Override + public List getResourceLimitStorageTagsForResourceCountOperation(Boolean display, DiskOffering diskOffering) { + return null; + } + @Override public void checkVolumeResourceLimitForDiskOfferingChange(Account owner, Boolean display, Long currentSize, Long newSize, - DiskOffering currentOffering, DiskOffering newOffering) throws ResourceAllocationException { + DiskOffering currentOffering, DiskOffering newOffering, List reservations) throws ResourceAllocationException { } @Override public void checkPrimaryStorageResourceLimit(Account owner, Boolean display, Long size, - DiskOffering diskOffering) { + DiskOffering diskOffering, List reservations) { } @@ -324,7 +335,7 @@ public class MockResourceLimitManagerImpl extends ManagerBase implements Resourc } @Override - public void checkVmResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template) throws ResourceAllocationException { + public void checkVmResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, List reservations) throws ResourceAllocationException { } @@ -341,19 +352,14 @@ public class MockResourceLimitManagerImpl extends ManagerBase implements Resourc @Override public void checkVmResourceLimitsForServiceOfferingChange(Account owner, Boolean display, Long currentCpu, Long newCpu, Long currentMemory, Long newMemory, ServiceOffering currentOffering, ServiceOffering newOffering, - VirtualMachineTemplate template) throws ResourceAllocationException { + VirtualMachineTemplate template, List reservations) throws ResourceAllocationException { } @Override public void checkVmResourceLimitsForTemplateChange(Account owner, Boolean display, ServiceOffering offering, VirtualMachineTemplate currentTemplate, - VirtualMachineTemplate newTemplate) throws ResourceAllocationException { - - } - - @Override - public void checkVmCpuResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long cpu) throws ResourceAllocationException { + VirtualMachineTemplate newTemplate, List reservations) throws ResourceAllocationException { } @@ -367,11 +373,6 @@ public class MockResourceLimitManagerImpl extends ManagerBase implements Resourc } - @Override - public void checkVmMemoryResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long memory) throws ResourceAllocationException { - - } - @Override public void incrementVmMemoryResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long memory) { @@ -382,11 +383,6 @@ public class MockResourceLimitManagerImpl extends ManagerBase implements Resourc } - @Override - public void checkVmGpuResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long gpu) throws ResourceAllocationException { - - } - @Override public void incrementVmGpuResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long gpu) { @@ -396,4 +392,9 @@ public class MockResourceLimitManagerImpl extends ManagerBase implements Resourc public void decrementVmGpuResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long gpu) { } + + @Override + public long recalculateDomainResourceCount(long domainId, ResourceType type, String tag) { + return 0; + } } diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java index f5ec79fbeaf..e486abc4e7a 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -16,9 +16,77 @@ // under the License. package org.apache.cloudstack.backup; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.TimeZone; +import java.util.UUID; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.springframework.test.util.ReflectionTestUtils; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.admin.backup.CloneBackupOfferingCmd; +import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd; +import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd; +import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd; +import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; +import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; +import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; +import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.backup.dao.BackupDetailsDao; +import org.apache.cloudstack.backup.dao.BackupOfferingDetailsDao; +import org.apache.cloudstack.backup.dao.BackupOfferingDao; +import org.apache.cloudstack.backup.dao.BackupScheduleDao; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; +import org.apache.cloudstack.framework.jobs.AsyncJobManager; +import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; +import org.apache.cloudstack.reservation.ReservationVO; +import org.apache.cloudstack.reservation.dao.ReservationDao; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; + +import com.cloud.alert.AlertManager; import com.cloud.api.query.dao.UserVmJoinDao; import com.cloud.api.query.vo.UserVmJoinVO; -import com.cloud.alert.AlertManager; import com.cloud.capacity.CapacityVO; import com.cloud.configuration.Resource; import com.cloud.dc.DataCenter; @@ -31,6 +99,7 @@ import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceAllocationException; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; @@ -42,7 +111,6 @@ import com.cloud.offering.DiskOffering; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; -import com.cloud.exception.ResourceAllocationException; import com.cloud.storage.Storage; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; @@ -62,6 +130,8 @@ import com.cloud.user.dao.AccountDao; import com.cloud.utils.DateUtil; import com.cloud.utils.DomainHelper; import com.cloud.utils.Pair; +import com.cloud.utils.StringUtils; +import com.cloud.utils.db.DbUtil; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.exception.CloudRuntimeException; @@ -69,72 +139,11 @@ import com.cloud.utils.fsm.NoTransitionException; import com.cloud.vm.VMInstanceDetailVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VmDiskInfo; import com.cloud.vm.VirtualMachineManager; -import com.cloud.vm.dao.VMInstanceDetailsDao; +import com.cloud.vm.VmDiskInfo; import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; import com.google.gson.Gson; -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.command.admin.backup.CloneBackupOfferingCmd; -import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd; -import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd; -import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd; -import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; -import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; -import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; -import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd; -import org.apache.cloudstack.api.response.BackupResponse; -import org.apache.cloudstack.backup.dao.BackupDao; -import org.apache.cloudstack.backup.dao.BackupDetailsDao; -import org.apache.cloudstack.backup.dao.BackupOfferingDao; -import org.apache.cloudstack.backup.dao.BackupOfferingDetailsDao; -import org.apache.cloudstack.backup.dao.BackupScheduleDao; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.apache.cloudstack.framework.jobs.AsyncJobManager; -import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; -import org.springframework.test.util.ReflectionTestUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.TimeZone; -import java.util.UUID; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.atLeastOnce; -import org.mockito.ArgumentCaptor; @RunWith(MockitoJUnitRunner.class) public class BackupManagerTest { @@ -244,6 +253,9 @@ public class BackupManagerTest { @Mock DomainDao domainDao; + @Mock + ReservationDao reservationDao; + @Mock private GuestOSDao _guestOSDao; @@ -263,6 +275,8 @@ public class BackupManagerTest { private AutoCloseable closeable; private ConfigDepotImpl configDepotImpl; private boolean updatedConfigKeyDepot = false; + private MockedStatic dbUtilMockedStatic; + private final List mockedGlobalLocks = new ArrayList<>(); @Before public void setup() throws Exception { @@ -294,6 +308,29 @@ public class BackupManagerTest { backupProvidersMap.put(backupProvider.getName().toLowerCase(), backupProvider); ReflectionTestUtils.setField(backupManager, "backupProvidersMap", backupProvidersMap); + when(reservationDao.persist(any(ReservationVO.class))) + .thenAnswer((Answer) invocation -> { + ReservationVO reservationVO = (ReservationVO)invocation.getArguments()[0]; + ReflectionTestUtils.setField(reservationVO, "id", 10L); + return reservationVO; + }); + dbUtilMockedStatic = Mockito.mockStatic(DbUtil.class); + dbUtilMockedStatic.when(() -> DbUtil.getGlobalLock(anyString(), anyInt())).thenAnswer((Answer) invocation -> { + String lockName = invocation.getArgument(0); + if (!StringUtils.isBlank(lockName) && !mockedGlobalLocks.contains(lockName)) { + mockedGlobalLocks.add(lockName); + return true; + } + return false; + }); + dbUtilMockedStatic.when(() -> DbUtil.releaseGlobalLock(anyString())).thenAnswer((Answer) invocation -> { + String lockName = invocation.getArgument(0); + if (!StringUtils.isBlank(lockName)) { + mockedGlobalLocks.remove(lockName); + } + return true; + }); + Account account = mock(Account.class); User user = mock(User.class); CallContext.register(user, account); @@ -301,6 +338,7 @@ public class BackupManagerTest { @After public void tearDown() throws Exception { + dbUtilMockedStatic.close(); closeable.close(); if (updatedConfigKeyDepot) { ReflectionTestUtils.setField(BackupManager.BackupFrameworkEnabled, "s_depot", configDepotImpl); @@ -638,6 +676,7 @@ public class BackupManagerTest { Long oldestBackupId = 7L; Long newBackupSize = 1000000000L; Long oldBackupSize = 400000000L; + long domainId = 101L; when(vmInstanceDao.findById(vmId)).thenReturn(vmInstanceVOMock); when(vmInstanceVOMock.getDataCenterId()).thenReturn(zoneId); @@ -652,6 +691,7 @@ public class BackupManagerTest { Mockito.doReturn(scheduleId).when(backupManager).getBackupScheduleId(asyncJobVOMock); when(accountManager.getAccount(accountId)).thenReturn(accountVOMock); + when(accountVOMock.getDomainId()).thenReturn(domainId); BackupScheduleVO schedule = mock(BackupScheduleVO.class); when(backupScheduleDao.findById(scheduleId)).thenReturn(schedule); @@ -693,8 +733,10 @@ public class BackupManagerTest { assertTrue(backupManager.createBackup(cmd, asyncJobVOMock)); - Mockito.verify(resourceLimitMgr, times(1)).checkResourceLimit(accountVOMock, Resource.ResourceType.backup); - Mockito.verify(resourceLimitMgr, times(1)).checkResourceLimit(accountVOMock, Resource.ResourceType.backup_storage, newBackupSize); + Mockito.verify(resourceLimitMgr, times(1)) + .checkResourceLimitWithTag(accountVOMock, domainId, true, Resource.ResourceType.backup, null, 1L); + Mockito.verify(resourceLimitMgr, times(1)) + .checkResourceLimitWithTag(accountVOMock, domainId, true, Resource.ResourceType.backup_storage, null, newBackupSize); Mockito.verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup); Mockito.verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup_storage, newBackupSize); @@ -710,6 +752,7 @@ public class BackupManagerTest { Long scheduleId = 3L; Long backupOfferingId = 4L; Long accountId = 5L; + long domainId = 101L; when(vmInstanceDao.findById(vmId)).thenReturn(vmInstanceVOMock); when(vmInstanceVOMock.getDataCenterId()).thenReturn(zoneId); @@ -724,7 +767,9 @@ public class BackupManagerTest { Account account = Mockito.mock(Account.class); when(accountManager.getAccount(accountId)).thenReturn(account); - Mockito.doThrow(new ResourceAllocationException("", Resource.ResourceType.backup)).when(resourceLimitMgr).checkResourceLimit(account, Resource.ResourceType.backup); + when(account.getDomainId()).thenReturn(domainId); + Mockito.doThrow(new ResourceAllocationException("", Resource.ResourceType.backup)).when(resourceLimitMgr) + .checkResourceLimitWithTag(account, domainId, true, Resource.ResourceType.backup, null, 1L); CreateBackupCmd cmd = Mockito.mock(CreateBackupCmd.class); when(cmd.getVmId()).thenReturn(vmId); @@ -748,6 +793,8 @@ public class BackupManagerTest { Long scheduleId = 3L; Long backupOfferingId = 4L; Long accountId = 5L; + long domainId = 101L; + long size = 10000L; VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); when(vmInstanceDao.findById(vmId)).thenReturn(vm); @@ -755,6 +802,11 @@ public class BackupManagerTest { when(vm.getBackupOfferingId()).thenReturn(backupOfferingId); when(vm.getAccountId()).thenReturn(accountId); + VolumeVO volume = mock(VolumeVO.class); + when(volumeDao.findByInstance(vmId)).thenReturn(List.of(volume)); + when(volume.getState()).thenReturn(Volume.State.Ready); + when(volumeApiService.getVolumePhysicalSize(null, null, null)).thenReturn(size); + overrideBackupFrameworkConfigValue(); BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class); when(backupOfferingDao.findById(backupOfferingId)).thenReturn(offering); @@ -765,7 +817,10 @@ public class BackupManagerTest { Account account = Mockito.mock(Account.class); when(accountManager.getAccount(accountId)).thenReturn(account); - Mockito.doThrow(new ResourceAllocationException("", Resource.ResourceType.backup_storage)).when(resourceLimitMgr).checkResourceLimit(account, Resource.ResourceType.backup_storage, 0L); + when(account.getDomainId()).thenReturn(domainId); + Mockito.doThrow(new ResourceAllocationException("", Resource.ResourceType.backup_storage)) + .when(resourceLimitMgr) + .checkResourceLimitWithTag(account, domainId, true, Resource.ResourceType.backup_storage, null, size); CreateBackupCmd cmd = Mockito.mock(CreateBackupCmd.class); when(cmd.getVmId()).thenReturn(vmId); @@ -1507,6 +1562,7 @@ public class BackupManagerTest { when(backup.getVmId()).thenReturn(vmId); when(backup.getZoneId()).thenReturn(zoneId); when(backup.getAccountId()).thenReturn(accountId); + when(accountManager.getAccount(accountId)).thenReturn(accountVOMock); when(backup.getBackupOfferingId()).thenReturn(backupOfferingId); when(backup.getSize()).thenReturn(100L); when(backup.getUuid()).thenReturn("backup-uuid"); diff --git a/server/src/test/java/org/apache/cloudstack/storage/object/BucketApiServiceImplTest.java b/server/src/test/java/org/apache/cloudstack/storage/object/BucketApiServiceImplTest.java index b630ddc69a7..6dbdd45ce4c 100644 --- a/server/src/test/java/org/apache/cloudstack/storage/object/BucketApiServiceImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/storage/object/BucketApiServiceImplTest.java @@ -16,19 +16,36 @@ // under the License. package org.apache.cloudstack.storage.object; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + import org.apache.cloudstack.api.command.user.bucket.CreateBucketCmd; import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.reservation.ReservationVO; +import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao; import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO; +import org.apache.commons.lang3.StringUtils; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; import org.springframework.test.util.ReflectionTestUtils; import com.cloud.agent.api.to.BucketTO; @@ -40,6 +57,9 @@ import com.cloud.storage.DataStoreRole; import com.cloud.storage.dao.BucketDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.User; +import com.cloud.utils.db.DbUtil; @RunWith(MockitoJUnitRunner.class) public class BucketApiServiceImplTest { @@ -62,33 +82,90 @@ public class BucketApiServiceImplTest { @Mock private BucketDao bucketDao; + @Mock + ReservationDao reservationDao; + + @Mock + private AccountVO mockAccountVO; + + private MockedStatic dbUtilMockedStatic; + private final List mockedGlobalLocks = new ArrayList<>(); + private static final long ACCOUNT_ID = 1001L; + private static final long DOMAIN_ID = 10L; + + @Before + public void setup() { + when(accountManager.getActiveAccountById(ACCOUNT_ID)).thenReturn(mockAccountVO); + when(mockAccountVO.getDomainId()).thenReturn(DOMAIN_ID); + when(reservationDao.persist(any(ReservationVO.class))) + .thenAnswer((Answer) invocation -> { + ReservationVO reservationVO = (ReservationVO)invocation.getArguments()[0]; + ReflectionTestUtils.setField(reservationVO, "id", 10L); + return reservationVO; + }); + dbUtilMockedStatic = Mockito.mockStatic(DbUtil.class); + dbUtilMockedStatic.when(() -> DbUtil.getGlobalLock(anyString(), anyInt())) + .thenAnswer((Answer) invocation -> { + String lockName = invocation.getArgument(0); + if (!StringUtils.isBlank(lockName) && !mockedGlobalLocks.contains(lockName)) { + mockedGlobalLocks.add(lockName); + return true; + } + return false; + }); + dbUtilMockedStatic.when(() -> DbUtil.releaseGlobalLock(anyString())) + .thenAnswer((Answer) invocation -> { + String lockName = invocation.getArgument(0); + if (!StringUtils.isBlank(lockName)) { + mockedGlobalLocks.remove(lockName); + } + return true; + }); + + Account account = mock(Account.class); + User user = mock(User.class); + CallContext.register(user, account); + } + + @After + public void tearDown() throws Exception { + dbUtilMockedStatic.close(); + CallContext.unregister(); + } + @Test public void testAllocBucket() throws ResourceAllocationException { String bucketName = "bucket1"; - Long accountId = 1L; Long poolId = 2L; Long objectStoreId = 3L; + int quota = 1; CreateBucketCmd cmd = Mockito.mock(CreateBucketCmd.class); Mockito.when(cmd.getBucketName()).thenReturn(bucketName); - Mockito.when(cmd.getEntityOwnerId()).thenReturn(accountId); + Mockito.when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); Mockito.when(cmd.getObjectStoragePoolId()).thenReturn(poolId); - Mockito.when(cmd.getQuota()).thenReturn(1); - - Account account = Mockito.mock(Account.class); - Mockito.when(accountManager.getActiveAccountById(accountId)).thenReturn(account); + Mockito.when(cmd.getQuota()).thenReturn(quota); ObjectStoreVO objectStoreVO = Mockito.mock(ObjectStoreVO.class); Mockito.when(objectStoreVO.getId()).thenReturn(objectStoreId); Mockito.when(objectStoreDao.findById(poolId)).thenReturn(objectStoreVO); ObjectStoreEntity objectStore = Mockito.mock(ObjectStoreEntity.class); Mockito.when(dataStoreMgr.getDataStore(objectStoreId, DataStoreRole.Object)).thenReturn(objectStore); - Mockito.when(objectStore.createUser(accountId)).thenReturn(true); + Mockito.when(objectStore.createUser(ACCOUNT_ID)).thenReturn(true); bucketApiService.allocBucket(cmd); - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimit(account, Resource.ResourceType.bucket); - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimit(account, Resource.ResourceType.object_storage, 1 * Resource.ResourceType.bytesToGiB); + long size = quota * Resource.ResourceType.bytesToGiB; + Mockito.verify(resourceLimitManager, Mockito.times(1)) + .checkResourceLimitWithTag(mockAccountVO, DOMAIN_ID, true, + Resource.ResourceType.bucket, null, 1L); + Mockito.verify(resourceLimitManager, Mockito.times(1)) + .checkResourceLimitWithTag(mockAccountVO, DOMAIN_ID, true, + Resource.ResourceType.object_storage, null, size); + Mockito.verify(resourceLimitManager, Mockito.times(1)) + .incrementResourceCount(ACCOUNT_ID, Resource.ResourceType.bucket); + Mockito.verify(resourceLimitManager, Mockito.times(1)) + .incrementResourceCount(ACCOUNT_ID, Resource.ResourceType.object_storage, size); } @Test @@ -96,21 +173,21 @@ public class BucketApiServiceImplTest { Long objectStoreId = 1L; Long poolId = 2L; Long bucketId = 3L; - Long accountId = 4L; String bucketName = "bucket1"; + int quota = 3; CreateBucketCmd cmd = Mockito.mock(CreateBucketCmd.class); Mockito.when(cmd.getObjectStoragePoolId()).thenReturn(poolId); Mockito.when(cmd.getEntityId()).thenReturn(bucketId); - Mockito.when(cmd.getQuota()).thenReturn(1); + Mockito.when(cmd.getQuota()).thenReturn(quota); BucketVO bucket = new BucketVO(bucketName); Mockito.when(bucketDao.findById(bucketId)).thenReturn(bucket); - ReflectionTestUtils.setField(bucket, "accountId", accountId); + ReflectionTestUtils.setField(bucket, "accountId", ACCOUNT_ID); ObjectStoreVO objectStoreVO = Mockito.mock(ObjectStoreVO.class); Mockito.when(objectStoreVO.getId()).thenReturn(objectStoreId); - Mockito.when(objectStoreVO.getTotalSize()).thenReturn(2000000000L); + Mockito.when(objectStoreVO.getTotalSize()).thenReturn(10 * Resource.ResourceType.bytesToGiB); Mockito.when(objectStoreDao.findById(poolId)).thenReturn(objectStoreVO); ObjectStoreEntity objectStore = Mockito.mock(ObjectStoreEntity.class); Mockito.when(dataStoreMgr.getDataStore(objectStoreId, DataStoreRole.Object)).thenReturn(objectStore); @@ -118,23 +195,23 @@ public class BucketApiServiceImplTest { bucketApiService.createBucket(cmd); - Mockito.verify(resourceLimitManager, Mockito.times(1)).incrementResourceCount(accountId, Resource.ResourceType.bucket); - Mockito.verify(resourceLimitManager, Mockito.times(1)).incrementResourceCount(accountId, Resource.ResourceType.object_storage, 1 * Resource.ResourceType.bytesToGiB); - Assert.assertEquals(bucket.getState(), Bucket.State.Created); + Assert.assertEquals(Bucket.State.Created, bucket.getState()); } @Test public void testDeleteBucket() { Long bucketId = 1L; - Long accountId = 2L; Long objectStoreId = 3L; String bucketName = "bucket1"; + int quota = 2; - BucketVO bucket = new BucketVO(bucketName); + BucketVO bucket = mock(BucketVO.class); + when(bucket.getName()).thenReturn(bucketName); + when(bucket.getObjectStoreId()).thenReturn(objectStoreId); + when(bucket.getQuota()).thenReturn(quota); + when(bucket.getAccountId()).thenReturn(ACCOUNT_ID); + when(accountManager.getAccount(ACCOUNT_ID)).thenReturn(mock(AccountVO.class)); Mockito.when(bucketDao.findById(bucketId)).thenReturn(bucket); - ReflectionTestUtils.setField(bucket, "objectStoreId", objectStoreId); - ReflectionTestUtils.setField(bucket, "quota", 1); - ReflectionTestUtils.setField(bucket, "accountId", accountId); ObjectStoreVO objectStoreVO = Mockito.mock(ObjectStoreVO.class); Mockito.when(objectStoreVO.getId()).thenReturn(objectStoreId); @@ -145,15 +222,17 @@ public class BucketApiServiceImplTest { bucketApiService.deleteBucket(bucketId, null); - Mockito.verify(resourceLimitManager, Mockito.times(1)).decrementResourceCount(accountId, Resource.ResourceType.bucket); - Mockito.verify(resourceLimitManager, Mockito.times(1)).decrementResourceCount(accountId, Resource.ResourceType.object_storage, 1 * Resource.ResourceType.bytesToGiB); + Mockito.verify(resourceLimitManager, Mockito.times(1)) + .decrementResourceCount(ACCOUNT_ID, Resource.ResourceType.bucket); + Mockito.verify(resourceLimitManager, Mockito.times(1)) + .decrementResourceCount(ACCOUNT_ID, Resource.ResourceType.object_storage, + quota * Resource.ResourceType.bytesToGiB); } @Test public void testUpdateBucket() throws ResourceAllocationException { Long bucketId = 1L; Long objectStoreId = 2L; - Long accountId = 3L; Integer bucketQuota = 2; Integer cmdQuota = 1; String bucketName = "bucket1"; @@ -164,12 +243,10 @@ public class BucketApiServiceImplTest { BucketVO bucket = new BucketVO(bucketName); ReflectionTestUtils.setField(bucket, "quota", bucketQuota); - ReflectionTestUtils.setField(bucket, "accountId", accountId); + ReflectionTestUtils.setField(bucket, "accountId", ACCOUNT_ID); ReflectionTestUtils.setField(bucket, "objectStoreId", objectStoreId); Mockito.when(bucketDao.findById(bucketId)).thenReturn(bucket); - Account account = Mockito.mock(Account.class); - ObjectStoreVO objectStoreVO = Mockito.mock(ObjectStoreVO.class); Mockito.when(objectStoreVO.getId()).thenReturn(objectStoreId); Mockito.when(objectStoreDao.findById(objectStoreId)).thenReturn(objectStoreVO); @@ -178,6 +255,8 @@ public class BucketApiServiceImplTest { bucketApiService.updateBucket(cmd, null); - Mockito.verify(resourceLimitManager, Mockito.times(1)).decrementResourceCount(accountId, Resource.ResourceType.object_storage, (bucketQuota - cmdQuota) * Resource.ResourceType.bytesToGiB); + Mockito.verify(resourceLimitManager, Mockito.times(1)) + .decrementResourceCount(ACCOUNT_ID, Resource.ResourceType.object_storage, + (bucketQuota - cmdQuota) * Resource.ResourceType.bytesToGiB); } } diff --git a/server/src/test/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageManagerImplTest.java index 9ee34d32a17..f3ed13c3d6b 100644 --- a/server/src/test/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageManagerImplTest.java @@ -268,9 +268,6 @@ public class VolumeImportUnmanageManagerImplTest { doNothing().when(volumeImportUnmanageManager).checkIfVolumeIsEncrypted(volumeOnStorageTO); doNothing().when(volumeImportUnmanageManager).checkIfVolumeHasBackingFile(volumeOnStorageTO); - doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.volume); - doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.primary_storage, virtualSize); - DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); when(diskOffering.isCustomized()).thenReturn(true); doReturn(diskOffering).when(volumeImportUnmanageManager).getOrCreateDiskOffering(account, diskOfferingId, zoneId, isLocal); diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index 98e6388f3d6..dfc1829ab5c 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -39,11 +39,22 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import com.cloud.offering.DiskOffering; -import com.cloud.storage.Snapshot; -import com.cloud.storage.SnapshotVO; -import com.cloud.storage.dao.SnapshotDao; -import com.cloud.vm.ImportVMTaskVO; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.BDDMockito; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.ResponseObject; @@ -66,20 +77,6 @@ import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.jetbrains.annotations.NotNull; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.BDDMockito; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; -import org.mockito.junit.MockitoJUnitRunner; import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; @@ -98,7 +95,6 @@ import com.cloud.agent.api.GetUnmanagedInstancesCommand; import com.cloud.agent.api.ImportConvertedInstanceAnswer; import com.cloud.agent.api.ImportConvertedInstanceCommand; import com.cloud.agent.api.to.DataStoreTO; -import com.cloud.configuration.Resource; import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; @@ -115,7 +111,6 @@ import com.cloud.exception.InsufficientServerCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.PermissionDeniedException; -import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.UnsupportedServiceException; import com.cloud.host.Host; import com.cloud.host.HostVO; @@ -128,16 +123,20 @@ import com.cloud.network.Network; import com.cloud.network.NetworkModel; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; +import com.cloud.offering.DiskOffering; import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; import com.cloud.org.Grouping; import com.cloud.resource.ResourceManager; import com.cloud.resource.ResourceState; +import com.cloud.resourcelimit.CheckedReservation; 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.ScopeType; +import com.cloud.storage.Snapshot; +import com.cloud.storage.SnapshotVO; import com.cloud.storage.Storage; import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePoolHostVO; @@ -147,6 +146,7 @@ import com.cloud.storage.Volume; import com.cloud.storage.VolumeApiService; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.SnapshotDao; import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplatePoolDao; @@ -164,6 +164,7 @@ import com.cloud.utils.Pair; import com.cloud.utils.db.EntityManager; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.DiskProfile; +import com.cloud.vm.ImportVMTaskVO; import com.cloud.vm.NicProfile; import com.cloud.vm.UserVmManager; import com.cloud.vm.UserVmVO; @@ -308,7 +309,6 @@ public class UnmanagedVMsManagerImplTest { clusterVO.setHypervisorType(Hypervisor.HypervisorType.VMware.toString()); when(clusterDao.findById(anyLong())).thenReturn(clusterVO); when(configurationDao.getValue(Mockito.anyString())).thenReturn(null); - doNothing().when(resourceLimitService).checkResourceLimit(any(Account.class), any(Resource.ResourceType.class), anyLong()); List hosts = new ArrayList<>(); HostVO hostVO = Mockito.mock(HostVO.class); when(hostVO.isInMaintenanceStates()).thenReturn(false); @@ -448,7 +448,8 @@ public class UnmanagedVMsManagerImplTest { when(importUnmanageInstanceCmd.getName()).thenReturn("TestInstance"); when(importUnmanageInstanceCmd.getDomainId()).thenReturn(null); when(volumeApiService.doesStoragePoolSupportDiskOffering(any(StoragePool.class), any())).thenReturn(true); - try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class)) { + try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class); + MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { unmanagedVMsManager.importUnmanagedInstance(importUnmanageInstanceCmd); } } @@ -650,7 +651,7 @@ public class UnmanagedVMsManagerImplTest { DeployDestination mockDest = Mockito.mock(DeployDestination.class); when(deploymentPlanningManager.planDeployment(any(), any(), any(), any())).thenReturn(mockDest); DiskProfile diskProfile = Mockito.mock(DiskProfile.class); - when(volumeManager.allocateRawVolume(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) + when(volumeManager.allocateRawVolume(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), anyBoolean())) .thenReturn(diskProfile); Map storage = new HashMap<>(); VolumeVO volume = Mockito.mock(VolumeVO.class); @@ -662,7 +663,8 @@ public class UnmanagedVMsManagerImplTest { CopyRemoteVolumeAnswer copyAnswer = Mockito.mock(CopyRemoteVolumeAnswer.class); when(copyAnswer.getResult()).thenReturn(true); when(agentManager.easySend(anyLong(), any(CopyRemoteVolumeCommand.class))).thenReturn(copyAnswer); - try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class)) { + try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class); + MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { unmanagedVMsManager.importVm(cmd); } } @@ -850,7 +852,8 @@ public class UnmanagedVMsManagerImplTest { Mockito.lenient().when(agentManager.send(Mockito.eq(convertHostId), Mockito.any(ImportConvertedInstanceCommand.class))).thenReturn(convertImportedInstanceAnswer); } - try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class)) { + try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class); + MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { unmanagedVMsManager.importVm(importVmCmd); verify(vmwareGuru).getHypervisorVMOutOfBandAndCloneIfRequired(Mockito.eq(host), Mockito.eq(vmName), anyMap()); verify(vmwareGuru).createVMTemplateOutOfBand(Mockito.eq(host), Mockito.eq(vmName), anyMap(), any(DataStoreTO.class), anyInt()); @@ -884,7 +887,7 @@ public class UnmanagedVMsManagerImplTest { DeployDestination mockDest = Mockito.mock(DeployDestination.class); when(deploymentPlanningManager.planDeployment(any(), any(), any(), any())).thenReturn(mockDest); DiskProfile diskProfile = Mockito.mock(DiskProfile.class); - when(volumeManager.allocateRawVolume(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) + when(volumeManager.allocateRawVolume(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), anyBoolean())) .thenReturn(diskProfile); Map storage = new HashMap<>(); VolumeVO volume = Mockito.mock(VolumeVO.class); @@ -903,7 +906,8 @@ public class UnmanagedVMsManagerImplTest { when(volumeApiService.doesStoragePoolSupportDiskOffering(any(StoragePool.class), any())).thenReturn(true); StoragePoolHostVO storagePoolHost = Mockito.mock(StoragePoolHostVO.class); when(storagePoolHostDao.findByPoolHost(anyLong(), anyLong())).thenReturn(storagePoolHost); - try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class)) { + try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class); + MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { unmanagedVMsManager.importVm(cmd); } } @@ -1251,42 +1255,6 @@ public class UnmanagedVMsManagerImplTest { unmanagedVMsManager.selectKVMHostForConversionInCluster(cluster, hostId); } - @Test - public void testCheckUnmanagedDiskLimits() { - Account owner = Mockito.mock(Account.class); - UnmanagedInstanceTO.Disk disk = Mockito.mock(UnmanagedInstanceTO.Disk.class); - Mockito.when(disk.getDiskId()).thenReturn("disk1"); - Mockito.when(disk.getCapacity()).thenReturn(100L); - ServiceOffering serviceOffering = Mockito.mock(ServiceOffering.class); - Mockito.when(serviceOffering.getDiskOfferingId()).thenReturn(1L); - UnmanagedInstanceTO.Disk dataDisk = Mockito.mock(UnmanagedInstanceTO.Disk.class); - Mockito.when(dataDisk.getDiskId()).thenReturn("disk2"); - Mockito.when(dataDisk.getCapacity()).thenReturn(1000L); - Map dataDiskMap = new HashMap<>(); - dataDiskMap.put("disk2", 2L); - DiskOfferingVO offering1 = Mockito.mock(DiskOfferingVO.class); - Mockito.when(diskOfferingDao.findById(1L)).thenReturn(offering1); - String tag1 = "tag1"; - Mockito.when(resourceLimitService.getResourceLimitStorageTags(offering1)).thenReturn(List.of(tag1)); - DiskOfferingVO offering2 = Mockito.mock(DiskOfferingVO.class); - Mockito.when(diskOfferingDao.findById(2L)).thenReturn(offering2); - String tag2 = "tag2"; - Mockito.when(resourceLimitService.getResourceLimitStorageTags(offering2)).thenReturn(List.of(tag2)); - try { - Mockito.doNothing().when(resourceLimitService).checkResourceLimit(any(), any(), any()); - Mockito.doNothing().when(resourceLimitService).checkResourceLimitWithTag(any(), any(), any(), any()); - unmanagedVMsManager.checkUnmanagedDiskLimits(owner, disk, serviceOffering, List.of(dataDisk), dataDiskMap); - Mockito.verify(resourceLimitService, Mockito.times(1)).checkResourceLimit(owner, Resource.ResourceType.volume, 2); - Mockito.verify(resourceLimitService, Mockito.times(1)).checkResourceLimit(owner, Resource.ResourceType.primary_storage, 1100L); - Mockito.verify(resourceLimitService, Mockito.times(1)).checkResourceLimitWithTag(owner, Resource.ResourceType.volume, tag1,1); - Mockito.verify(resourceLimitService, Mockito.times(1)).checkResourceLimitWithTag(owner, Resource.ResourceType.volume, tag2,1); - Mockito.verify(resourceLimitService, Mockito.times(1)).checkResourceLimitWithTag(owner, Resource.ResourceType.primary_storage, tag1,100L); - Mockito.verify(resourceLimitService, Mockito.times(1)).checkResourceLimitWithTag(owner, Resource.ResourceType.primary_storage, tag2,1000L); - } catch (ResourceAllocationException e) { - Assert.fail("Exception encountered: " + e.getMessage()); - } - } - @Test public void testCheckConversionStoragePoolSecondaryStorageStaging() { unmanagedVMsManager.checkConversionStoragePool(null, false); diff --git a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/HttpUploadServerHandler.java b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/HttpUploadServerHandler.java index a580105d52a..605749649bb 100644 --- a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/HttpUploadServerHandler.java +++ b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/HttpUploadServerHandler.java @@ -130,6 +130,7 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler uploadEntityStateMap = new HashMap<>(); + private final Map uploadEntityStateMap = new ConcurrentHashMap<>(); + private final Map uploadChannelMap = new ConcurrentHashMap<>(); private String _ssvmPSK = null; private long processTimeout; @@ -2395,6 +2397,20 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S String entityUuid = cmd.getEntityUuid(); if (uploadEntityStateMap.containsKey(entityUuid)) { UploadEntity uploadEntity = uploadEntityStateMap.get(entityUuid); + if (Boolean.TRUE.equals(cmd.getAbort())) { + updateStateMapWithError(entityUuid, "Upload Entity aborted"); + String errorMsg = uploadEntity.getErrorMessage(); + if (errorMsg == null) { + errorMsg = "Upload aborted by management server"; + } + Channel channel = uploadChannelMap.remove(entityUuid); + if (channel != null && channel.isActive()) { + logger.info("Closing upload channel for entity {}", entityUuid); + channel.close(); + } + uploadEntityStateMap.remove(entityUuid); + return new UploadStatusAnswer(cmd, UploadStatus.ERROR, errorMsg); + } if (uploadEntity.getUploadState() == UploadEntity.Status.ERROR) { uploadEntityStateMap.remove(entityUuid); return new UploadStatusAnswer(cmd, UploadStatus.ERROR, uploadEntity.getErrorMessage()); @@ -2413,6 +2429,7 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S UploadStatusAnswer answer = new UploadStatusAnswer(cmd, UploadStatus.IN_PROGRESS); long downloadedSize = FileUtils.sizeOfDirectory(new File(uploadEntity.getInstallPathPrefix())); int downloadPercent = (int)(100 * downloadedSize / uploadEntity.getContentLength()); + answer.setPhysicalSize(downloadedSize); answer.setDownloadPercent(Math.min(downloadPercent, 100)); return answer; } @@ -3442,6 +3459,10 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S public String postUpload(String uuid, String filename, long processTimeout) { UploadEntity uploadEntity = uploadEntityStateMap.get(uuid); + if (uploadEntity == null) { + logger.warn("Upload entity not found for uuid: {}. Upload may have been aborted.", uuid); + return "Upload entity not found. Upload may have been aborted."; + } int installTimeoutPerGig = 180 * 60 * 1000; String resourcePath = uploadEntity.getInstallPathPrefix(); @@ -3592,6 +3613,16 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S return _ssvmPSK; } + public void registerUploadChannel(String uuid, Channel channel) { + uploadChannelMap.put(uuid, channel); + } + + public void deregisterUploadChannel(String uuid) { + if (uuid != null) { + uploadChannelMap.remove(uuid); + } + } + public void updateStateMapWithError(String uuid, String errorMessage) { UploadEntity uploadEntity = null; if (uploadEntityStateMap.get(uuid) != null) { diff --git a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManagerImpl.java b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManagerImpl.java index 23ad6767b20..f7cdf1ab51a 100644 --- a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManagerImpl.java +++ b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManagerImpl.java @@ -999,7 +999,7 @@ public class DownloadManagerImpl extends ManagerBase implements DownloadManager break; // TODO } return new DownloadAnswer(jobId, getDownloadPct(jobId), getDownloadError(jobId), getDownloadStatus2(jobId), getDownloadLocalPath(jobId), getInstallPath(jobId), - getDownloadTemplateSize(jobId), getDownloadTemplatePhysicalSize(jobId), getDownloadCheckSum(jobId)); + getDownloadTemplateSize(jobId), td.getDownloadedBytes(), getDownloadCheckSum(jobId)); } private String getInstallPath(String jobId) { diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 6f3a623b809..2a6b7316b70 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -699,6 +699,7 @@ "label.create.sharedfs": "Create Shared FileSystem", "label.create.network": "Create new Network", "label.create.nfs.secondary.staging.storage": "Create NFS secondary staging storage", +"label.create.on.storage": "Create on Storage", "label.create.project": "Create Project", "label.create.project.role": "Create Project Role", "label.create.routing.policy": "Create Routing Policy", @@ -715,6 +716,7 @@ "label.create.tier.networkofferingid.description": "The Network offering for the Network Tier.", "label.create.tungsten.routing.policy": "Create Tungsten-Fabric routing policy", "label.create.user": "Create User", +"label.create.volume.on.primary.storage": "Create Volume on the specified Primary Storage", "label.create.vm": "Create Instance", "label.create.vm.and.stay": "Create Instance & stay on this page", "label.create.vpn.connection": "Create VPN connection", @@ -2133,6 +2135,7 @@ "label.requireshvm": "HVM", "label.requiresupgrade": "Requires upgrade", "label.reserved": "Reserved", +"label.reservedresourcedetails": "Reserved resource details", "label.reserved.system.gateway": "Reserved system gateway", "label.reserved.system.ip": "Reserved system IP", "label.reserved.system.netmask": "Reserved system netmask", diff --git a/ui/src/config/section/extension.js b/ui/src/config/section/extension.js index 4c6d9ebf076..5904abae30b 100644 --- a/ui/src/config/section/extension.js +++ b/ui/src/config/section/extension.js @@ -44,7 +44,7 @@ export default { }, 'created'] return fields }, - details: ['name', 'description', 'id', 'type', 'details', 'path', 'pathready', 'isuserdefined', 'orchestratorrequirespreparevm', 'created'], + details: ['name', 'description', 'id', 'type', 'details', 'path', 'pathready', 'isuserdefined', 'orchestratorrequirespreparevm', 'reservedresourcedetails', 'created'], filters: ['orchestrator'], tabs: [{ name: 'details', diff --git a/ui/src/views/extension/CreateExtension.vue b/ui/src/views/extension/CreateExtension.vue index 6311e6dea95..3baae81174e 100644 --- a/ui/src/views/extension/CreateExtension.vue +++ b/ui/src/views/extension/CreateExtension.vue @@ -89,6 +89,14 @@ + + + +