From cfdb33a052289ac4ff0410e6d8772942b0892bbd Mon Sep 17 00:00:00 2001 From: Vishesh Date: Thu, 25 Apr 2024 16:29:35 +0530 Subject: [PATCH] Fixup resource limit checks (#8935) --- .../com/cloud/user/ResourceLimitService.java | 22 ++ .../main/java/com/cloud/vm/UserVmService.java | 4 +- .../api/command/user/vm/RebootVMCmd.java | 3 +- .../com/cloud/vm/VirtualMachineManager.java | 3 +- .../com/cloud/api/query/QueryManagerImpl.java | 65 ++--- .../ResourceLimitManagerImpl.java | 232 ++++++++++++++++++ .../cloud/storage/VolumeApiServiceImpl.java | 94 ++----- .../java/com/cloud/vm/UserVmManagerImpl.java | 113 +++++---- .../com/cloud/vm/UserVmManagerImplTest.java | 16 +- .../vpc/MockResourceLimitManagerImpl.java | 39 +++ 10 files changed, 417 insertions(+), 174 deletions(-) diff --git a/api/src/main/java/com/cloud/user/ResourceLimitService.java b/api/src/main/java/com/cloud/user/ResourceLimitService.java index 04560df428f..d0aa9f69f84 100644 --- a/api/src/main/java/com/cloud/user/ResourceLimitService.java +++ b/api/src/main/java/com/cloud/user/ResourceLimitService.java @@ -239,13 +239,35 @@ public interface ResourceLimitService { 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 checkVolumeResourceLimitForDiskOfferingChange(Account owner, Boolean display, Long currentSize, Long newSize, + DiskOffering currentOffering, DiskOffering newOffering) throws ResourceAllocationException; + void incrementVolumeResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering); void decrementVolumeResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering); + + void updateVmResourceCountForTemplateChange(long accountId, Boolean display, ServiceOffering offering, VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate); + + void updateVmResourceCountForServiceOfferingChange(long accountId, Boolean display, Long currentCpu, Long newCpu, Long currentMemory, + Long newMemory, + ServiceOffering currentOffering, ServiceOffering newOffering, + VirtualMachineTemplate template); + + void updateVolumeResourceCountForDiskOfferingChange(long accountId, Boolean display, Long currentSize, Long newSize, + DiskOffering currentDiskOffering, DiskOffering newDiskOffering); + 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 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; + + void checkVmResourceLimitsForTemplateChange(Account owner, Boolean display, ServiceOffering offering, + VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate) 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); diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java b/api/src/main/java/com/cloud/vm/UserVmService.java index 787ed7bde37..9d8b196a4ff 100644 --- a/api/src/main/java/com/cloud/vm/UserVmService.java +++ b/api/src/main/java/com/cloud/vm/UserVmService.java @@ -110,7 +110,7 @@ public interface UserVmService { UserVm startVirtualMachine(StartVMCmd cmd) throws StorageUnavailableException, ExecutionException, ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException; - UserVm rebootVirtualMachine(RebootVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException; + UserVm rebootVirtualMachine(RebootVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ResourceAllocationException; UserVm updateVirtualMachine(UpdateVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException; @@ -490,7 +490,7 @@ public interface UserVmService { VirtualMachine vmStorageMigration(Long vmId, Map volumeToPool); - UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException; + UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ResourceAllocationException; UserVm restoreVirtualMachine(Account caller, long vmId, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map details) throws InsufficientCapacityException, ResourceUnavailableException; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RebootVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RebootVMCmd.java index 10900f61b22..153f5ea6563 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RebootVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RebootVMCmd.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.api.command.user.vm; +import com.cloud.exception.ResourceAllocationException; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.APICommand; @@ -113,7 +114,7 @@ public class RebootVMCmd extends BaseAsyncCmd implements UserCmd { } @Override - public void execute() throws ResourceUnavailableException, InsufficientCapacityException { + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); UserVm result; result = _userVmService.rebootVirtualMachine(this); diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java index cbdd803c5b5..04ba9a483e1 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -22,6 +22,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import com.cloud.exception.ResourceAllocationException; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.ConfigKey; @@ -254,7 +255,7 @@ public interface VirtualMachineManager extends Manager { */ boolean unmanage(String vmUuid); - UserVm restoreVirtualMachine(long vmId, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map details) throws ResourceUnavailableException, InsufficientCapacityException; + UserVm restoreVirtualMachine(long vmId, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map details) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException; boolean checkIfVmHasClusterWideVolumes(Long vmId); diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index a03b35e8576..64cb5a30f31 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -2978,7 +2978,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q ListResponse response = new ListResponse<>(); List poolResponses = ViewResponseHelper.createStoragePoolResponse(storagePools.first().toArray(new StoragePoolJoinVO[storagePools.first().size()])); - Map poolUuidToIdMap = storagePools.first().stream().collect(Collectors.toMap(StoragePoolJoinVO::getUuid, StoragePoolJoinVO::getId)); + Map poolUuidToIdMap = storagePools.first().stream().collect(Collectors.toMap(StoragePoolJoinVO::getUuid, StoragePoolJoinVO::getId, (a, b) -> a)); for (StoragePoolResponse poolResponse : poolResponses) { DataStore store = dataStoreManager.getPrimaryDataStore(poolResponse.getId()); if (store != null) { @@ -3821,9 +3821,8 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q List storageTags = com.cloud.utils.StringUtils.csvTagsToList(diskOffering.getTags()); if (!storageTags.isEmpty() && VolumeApiServiceImpl.MatchStoragePoolTagsWithDiskOffering.value()) { for (String tag : storageTags) { - diskOfferingSearch.and(tag, diskOfferingSearch.entity().getTags(), Op.EQ); + diskOfferingSearch.and("storageTag" + tag, diskOfferingSearch.entity().getTags(), Op.FIND_IN_SET); } - diskOfferingSearch.done(); } } @@ -3935,18 +3934,24 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q srvOffrDomainDetailSearch.entity().getName(), serviceOfferingSearch.entity().setString(ApiConstants.DOMAIN_ID)); } + List hostTags = getHostTagsFromTemplateForServiceOfferingsListing(caller, templateId); if (currentVmOffering != null) { - List hostTags = com.cloud.utils.StringUtils.csvTagsToList(currentVmOffering.getHostTag()); - if (!hostTags.isEmpty()) { + hostTags.addAll(com.cloud.utils.StringUtils.csvTagsToList(currentVmOffering.getHostTag())); + } - serviceOfferingSearch.and().op("hostTag", serviceOfferingSearch.entity().getHostTag(), Op.NULL); - serviceOfferingSearch.or().op(); - - for(String tag : hostTags) { - serviceOfferingSearch.and(tag, serviceOfferingSearch.entity().getHostTag(), Op.EQ); + if (!hostTags.isEmpty()) { + serviceOfferingSearch.and().op("hostTag", serviceOfferingSearch.entity().getHostTag(), Op.NULL); + serviceOfferingSearch.or(); + boolean flag = true; + for(String tag : hostTags) { + if (flag) { + flag = false; + serviceOfferingSearch.op("hostTag" + tag, serviceOfferingSearch.entity().getHostTag(), Op.FIND_IN_SET); + } else { + serviceOfferingSearch.and("hostTag" + tag, serviceOfferingSearch.entity().getHostTag(), Op.FIND_IN_SET); } - serviceOfferingSearch.cp().cp().done(); } + serviceOfferingSearch.cp().cp(); } SearchCriteria sc = serviceOfferingSearch.create(); @@ -4063,41 +4068,19 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q sc.setJoinParameters("domainDetailSearchNormalUser", "domainIdIN", domainIds.toArray()); } - List hostTags = getHostTagsFromTemplateForServiceOfferingsListing(caller, templateId); - - if (currentVmOffering != null) { - - if (diskOffering != null) { - List storageTags = com.cloud.utils.StringUtils.csvTagsToList(diskOffering.getTags()); - if (!storageTags.isEmpty() && VolumeApiServiceImpl.MatchStoragePoolTagsWithDiskOffering.value()) { - for(String tag : storageTags) { - sc.setJoinParameters("diskOfferingSearch", tag, tag); - } + if (diskOffering != null) { + List storageTags = com.cloud.utils.StringUtils.csvTagsToList(diskOffering.getTags()); + if (!storageTags.isEmpty() && VolumeApiServiceImpl.MatchStoragePoolTagsWithDiskOffering.value()) { + for (String tag : storageTags) { + sc.setJoinParameters("diskOfferingSearch", "storageTag" + tag, tag); } } - - List offeringHostTags = com.cloud.utils.StringUtils.csvTagsToList(currentVmOffering.getHostTag()); - if (!offeringHostTags.isEmpty()) { - hostTags.addAll(offeringHostTags); - } } + if (CollectionUtils.isNotEmpty(hostTags)) { - SearchBuilder hostTagsSearchBuilder = _srvOfferingJoinDao.createSearchBuilder(); - for(String tag : hostTags) { - hostTagsSearchBuilder.and(tag, hostTagsSearchBuilder.entity().getHostTag(), Op.FIND_IN_SET); + for (String tag : hostTags) { + sc.setParameters("hostTag" + tag, tag); } - hostTagsSearchBuilder.done(); - - SearchCriteria hostTagsSearchCriteria = hostTagsSearchBuilder.create(); - for(String tag : hostTags) { - hostTagsSearchCriteria.setParameters(tag, tag); - } - - SearchCriteria finalHostTagsSearchCriteria = _srvOfferingJoinDao.createSearchCriteria(); - finalHostTagsSearchCriteria.addOr("hostTag", Op.NULL); - finalHostTagsSearchCriteria.addOr("hostTag", Op.SC, hostTagsSearchCriteria); - - sc.addAnd("hostTagsConstraint", SearchCriteria.Op.SC, finalHostTagsSearchCriteria); } Pair, Integer> uniquePair = _srvOfferingDao.searchAndCount(sc, searchFilter); diff --git a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java index 7962b380adc..f040b526a6e 100644 --- a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java +++ b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java @@ -35,6 +35,7 @@ import java.util.stream.Stream; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.utils.Ternary; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.DomainResponse; @@ -1112,6 +1113,13 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim } protected boolean updateResourceCountForAccount(final long accountId, final ResourceType type, String tag, final boolean increment, final long delta) { + if (delta == 0) { + return true; + } else if (delta < 0) { + logger.warn("Resource count delta is negative, delta = {} for Account = {} Type = {} tag = {}", + delta, accountId, type, tag); + return true; + } if (logger.isDebugEnabled()) { String convertedDelta = String.valueOf(delta); if (type == ResourceType.secondary_storage || type == ResourceType.primary_storage){ @@ -1231,6 +1239,8 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim accountRC.setCount((newCount == null) ? 0 : newCount); _resourceCountDao.update(accountRC.getId(), accountRC); } + } else if (newCount != null) { + _resourceCountDao.persist(new ResourceCountVO(type, newCount, accountId, ResourceOwnerType.Account, tag)); } // No need to log message for primary and secondary storage because both are recalculating the @@ -1631,6 +1641,30 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim } } + @Override + public void checkVolumeResourceLimitForDiskOfferingChange(Account owner, Boolean display, Long currentSize, Long newSize, + DiskOffering currentOffering, DiskOffering newOffering + ) 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); + } + } + + for (String tag : newTags) { + checkResourceLimitWithTag(owner, ResourceType.volume, tag, 1L); + checkResourceLimitWithTag(owner, ResourceType.primary_storage, tag, newSize); + } + } + @DB @Override public void incrementVolumeResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering) { @@ -1671,6 +1705,144 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim }); } + @Override + public void updateVmResourceCountForTemplateChange(long accountId, Boolean display, ServiceOffering offering, + VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate + ) { + updateVmResourceCountForServiceOfferingAndTemplateChange(accountId, display, null, null, null, null, + offering, offering, currentTemplate, newTemplate); + } + + @Override + public void updateVmResourceCountForServiceOfferingChange(long accountId, Boolean display, Long currentCpu, Long newCpu,Long currentMemory, Long newMemory, + ServiceOffering currentOffering, ServiceOffering newOffering, VirtualMachineTemplate template + ) { + updateVmResourceCountForServiceOfferingAndTemplateChange(accountId, display, currentCpu, newCpu, currentMemory, newMemory, currentOffering, + newOffering != null ? newOffering : currentOffering, template, template); + } + + private Ternary, Set, Set> getResourceLimitHostTagsForVmServiceOfferingAndTemplateChange( + Boolean display, ServiceOffering currentOffering, ServiceOffering newOffering, + VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate + ) { + Set currentOfferingTags = new HashSet<>(getResourceLimitHostTagsForResourceCountOperation(display, currentOffering, currentTemplate)); + if (currentOffering.getId() == newOffering.getId() && currentTemplate.getId() == newTemplate.getId()) { + return new Ternary<>(currentOfferingTags, new HashSet<>(), new HashSet<>()); + } + Set newOfferingTags = new HashSet<>(getResourceLimitHostTagsForResourceCountOperation(display, newOffering, newTemplate)); + + if (currentOfferingTags.isEmpty() && newOfferingTags.isEmpty()) { + return null; + } + Set sameTags = currentOfferingTags.stream().filter(newOfferingTags::contains).collect(Collectors.toSet());; + Set newTags = newOfferingTags.stream().filter(tag -> !currentOfferingTags.contains(tag)).collect(Collectors.toSet()); + Set removedTags = currentOfferingTags.stream().filter(tag -> !newOfferingTags.contains(tag)).collect(Collectors.toSet()); + return new Ternary<>(sameTags, newTags, removedTags); + } + + private void updateVmResourceCountForServiceOfferingAndTemplateChange(long accountId, Boolean display, Long currentCpu, + Long newCpu, Long currentMemory, Long newMemory, ServiceOffering currentOffering, ServiceOffering newOffering, + VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate + ) { + Ternary, Set, Set> updatedResourceLimitHostTags = getResourceLimitHostTagsForVmServiceOfferingAndTemplateChange(display, currentOffering, newOffering, currentTemplate, newTemplate); + if (updatedResourceLimitHostTags == null) { + return; + } + if (currentCpu == null) { + currentCpu = currentOffering.getCpu() != null ? Long.valueOf(currentOffering.getCpu()) : 0L; + } + if (newCpu == null) { + newCpu = newOffering.getCpu() != null ? Long.valueOf(newOffering.getCpu()) : 0L; + } + if (currentMemory == null) { + currentMemory = currentOffering.getRamSize() != null ? Long.valueOf(currentOffering.getRamSize()) : 0L; + } + if (newMemory == null) { + newMemory = newOffering.getRamSize() != null ? Long.valueOf(newOffering.getRamSize()) : 0L; + } + + Set sameTags = updatedResourceLimitHostTags.first(); + Set newTags = updatedResourceLimitHostTags.second(); + Set removedTags = updatedResourceLimitHostTags.third(); + + if (!newCpu.equals(currentCpu) || !newMemory.equals(currentMemory)) { + for (String tag : sameTags) { + if (newCpu - currentCpu > 0) { + incrementResourceCountWithTag(accountId, ResourceType.cpu, tag, newCpu - currentCpu); + } else if (newCpu - currentCpu < 0) { + decrementResourceCountWithTag(accountId, ResourceType.cpu, tag, currentCpu - newCpu); + } + + if (newMemory - currentMemory > 0) { + incrementResourceCountWithTag(accountId, ResourceType.memory, tag, newMemory - currentMemory); + } else if (newMemory - currentMemory < 0) { + decrementResourceCountWithTag(accountId, ResourceType.memory, tag, currentMemory - newMemory); + } + } + } + + for (String tag : removedTags) { + decrementResourceCountWithTag(accountId, ResourceType.user_vm, tag, 1L); + decrementResourceCountWithTag(accountId, ResourceType.cpu, tag, currentCpu); + decrementResourceCountWithTag(accountId, ResourceType.memory, tag, currentMemory); + } + + for (String tag : newTags) { + incrementResourceCountWithTag(accountId, ResourceType.user_vm, tag, 1L); + incrementResourceCountWithTag(accountId, ResourceType.cpu, tag, newCpu); + incrementResourceCountWithTag(accountId, ResourceType.memory, tag, newMemory); + } + } + + private Ternary, Set, Set> getResourceLimitStorageTagsForDiskOfferingChange( + Boolean display, DiskOffering currentOffering, DiskOffering newOffering + ) { + Set currentOfferingTags = new HashSet<>(getResourceLimitStorageTagsForResourceCountOperation(display, currentOffering)); + if (newOffering == null || currentOffering.getId() == newOffering.getId()) { + return new Ternary<>(currentOfferingTags, new HashSet<>(), new HashSet<>()); + } + Set newOfferingTags = new HashSet<>(getResourceLimitStorageTagsForResourceCountOperation(display, newOffering)); + if (currentOfferingTags.isEmpty() && newOfferingTags.isEmpty()) { + return null; + } + Set sameTags = currentOfferingTags.stream().filter(newOfferingTags::contains).collect(Collectors.toSet());; + Set newTags = newOfferingTags.stream().filter(tag -> !currentOfferingTags.contains(tag)).collect(Collectors.toSet()); + Set removedTags = currentOfferingTags.stream().filter(tag -> !newOfferingTags.contains(tag)).collect(Collectors.toSet()); + return new Ternary<>(sameTags, newTags, removedTags); + } + + @Override + public void updateVolumeResourceCountForDiskOfferingChange(long accountId, Boolean display, Long currentSize, Long newSize, + DiskOffering currentOffering, DiskOffering newOffering + ) { + Ternary, Set, Set> updatedResourceLimitStorageTags = getResourceLimitStorageTagsForDiskOfferingChange(display, currentOffering, newOffering); + if (updatedResourceLimitStorageTags == null) { + return; + } + Set sameTags = updatedResourceLimitStorageTags.first(); + Set newTags = updatedResourceLimitStorageTags.second(); + Set removedTags = updatedResourceLimitStorageTags.third(); + + if (!newSize.equals(currentSize)) { + for (String tag : sameTags) { + if (newSize - currentSize > 0) { + incrementResourceCountWithTag(accountId, ResourceType.primary_storage, tag, newSize - currentSize); + } else if (newSize - currentSize < 0) { + decrementResourceCountWithTag(accountId, ResourceType.primary_storage, tag, currentSize - newSize); + } + } + } + for (String tag : removedTags) { + decrementResourceCountWithTag(accountId, ResourceType.volume, tag, 1L); + decrementResourceCountWithTag(accountId, ResourceType.primary_storage, tag, currentSize); + } + + for (String tag : newTags) { + incrementResourceCountWithTag(accountId, ResourceType.volume, tag, 1L); + incrementResourceCountWithTag(accountId, ResourceType.primary_storage, tag, newSize); + } + } + @Override public void incrementVolumePrimaryStorageResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering) { if (size == null) { @@ -1768,6 +1940,66 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim }); } + @Override + public void checkVmResourceLimitsForTemplateChange(Account owner, Boolean display, ServiceOffering offering, + VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate) throws ResourceAllocationException { + checkVmResourceLimitsForServiceOfferingAndTemplateChange(owner, display, null, null, + null, null, offering, offering, currentTemplate, newTemplate); + } + + @Override + public void checkVmResourceLimitsForServiceOfferingChange(Account owner, Boolean display, Long currentCpu, Long newCpu, + Long currentMemory, Long newMemory, + ServiceOffering currentOffering, ServiceOffering newOffering, VirtualMachineTemplate template + ) throws ResourceAllocationException { + checkVmResourceLimitsForServiceOfferingAndTemplateChange(owner, display, currentCpu, newCpu, currentMemory, newMemory, currentOffering, + newOffering != null ? newOffering : currentOffering, template, template); + } + + private void checkVmResourceLimitsForServiceOfferingAndTemplateChange(Account owner, Boolean display, Long currentCpu, Long newCpu, + Long currentMemory, Long newMemory, ServiceOffering currentOffering, ServiceOffering newOffering, + VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate + ) throws ResourceAllocationException { + Ternary, Set, Set> updatedResourceLimitHostTags = getResourceLimitHostTagsForVmServiceOfferingAndTemplateChange(display, currentOffering, newOffering, currentTemplate, newTemplate); + if (updatedResourceLimitHostTags == null) { + return; + } + + if (currentCpu == null) { + currentCpu = currentOffering.getCpu() != null ? Long.valueOf(currentOffering.getCpu()) : 0L; + } + if (newCpu == null) { + newCpu = newOffering.getCpu() != null ? Long.valueOf(newOffering.getCpu()) : 0L; + } + if (currentMemory == null) { + currentMemory = currentOffering.getRamSize() != null ? Long.valueOf(currentOffering.getRamSize()) : 0L; + } + if (newMemory == null) { + newMemory = newOffering.getRamSize() != null ? Long.valueOf(newOffering.getRamSize()) : 0L; + } + + Set sameTags = updatedResourceLimitHostTags.first(); + Set newTags = updatedResourceLimitHostTags.second(); + + if (newCpu - currentCpu > 0 || newMemory - currentMemory > 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); + } + } + } + + for (String tag : newTags) { + checkResourceLimitWithTag(owner, ResourceType.user_vm, tag, 1L); + checkResourceLimitWithTag(owner, ResourceType.cpu, tag, newCpu); + checkResourceLimitWithTag(owner, ResourceType.memory, tag, newMemory); + } + } + @Override public void checkVmCpuResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long cpu) throws ResourceAllocationException { List tags = getResourceLimitHostTagsForResourceCountOperation(display, serviceOffering, template); diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index e8093b8a3c3..e79e9b9941d 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -1240,57 +1240,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } long currentSize = volume.getSize(); - - // if the caller is looking to change the size of the volume - if (currentSize != newSize) { - if (volume.getInstanceId() != null) { - // Check that VM to which this volume is attached does not have VM snapshots - if (_vmSnapshotDao.findByVm(volume.getInstanceId()).size() > 0) { - throw new InvalidParameterValueException("A volume that is attached to a VM with any VM snapshots cannot be resized."); - } - } - - if (!validateVolumeSizeInBytes(newSize)) { - throw new InvalidParameterValueException("Requested size out of range"); - } - - Long storagePoolId = volume.getPoolId(); - - if (storagePoolId != null) { - StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePoolId); - - if (storagePoolVO.isManaged() && !List.of( - Storage.StoragePoolType.PowerFlex, - Storage.StoragePoolType.FiberChannel).contains(storagePoolVO.getPoolType())) { - Long instanceId = volume.getInstanceId(); - - if (instanceId != null) { - VMInstanceVO vmInstanceVO = _vmInstanceDao.findById(instanceId); - - if (vmInstanceVO.getHypervisorType() == HypervisorType.KVM && vmInstanceVO.getState() != State.Stopped) { - throw new CloudRuntimeException("This kind of KVM disk cannot be resized while it is connected to a VM that's not in the Stopped state."); - } - } - } - } - - /* - * Let's make certain they (think they) know what they're doing if they - * want to shrink by forcing them to provide the shrinkok parameter. - * This will be checked again at the hypervisor level where we can see - * the actual disk size. - */ - if (currentSize > newSize && !shrinkOk) { - throw new InvalidParameterValueException("Going from existing size of " + currentSize + " to size of " + newSize + " would shrink the volume." - + "Need to sign off by supplying the shrinkok parameter with value of true."); - } - - if (newSize > currentSize) { - /* Check resource limit for this account on primary storage resource */ - _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(volume.getAccountId()), ResourceType.primary_storage, volume.isDisplayVolume(), - new Long(newSize - currentSize).longValue()); - } - } + 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 @@ -1310,6 +1260,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } _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; @@ -1420,6 +1372,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic final VolumeVO volume = _volsDao.findById(volumeId); UserVmVO userVm = _userVmDao.findById(volume.getInstanceId()); StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId()); + Long currentDiskOfferingId = volume.getDiskOfferingId(); boolean isManaged = storagePool.isManaged(); if (!storageMgr.storagePoolHasEnoughSpaceForResize(storagePool, currentSize, newSize)) { @@ -1546,11 +1499,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic /* Update resource count for the account on primary storage resource */ DiskOffering diskOffering = _diskOfferingDao.findByIdIncludingRemoved(volume.getDiskOfferingId()); - if (!shrinkOk) { - _resourceLimitMgr.incrementVolumePrimaryStorageResourceCount(volume.getAccountId(), volume.isDisplayVolume(), newSize - currentSize, diskOffering); - } else { - _resourceLimitMgr.decrementVolumePrimaryStorageResourceCount(volume.getAccountId(), volume.isDisplayVolume(), currentSize - newSize, diskOffering); - } + DiskOffering currentDiskOffering = _diskOfferingDao.findByIdIncludingRemoved(currentDiskOfferingId); + _resourceLimitMgr.updateVolumeResourceCountForDiskOfferingChange(volume.getAccountId(), volume.isDisplayVolume(), currentSize, newSize, currentDiskOffering, diskOffering); 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()); @@ -2032,7 +1982,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic newMaxIops = updateNewMaxIops[0]; newHypervisorSnapshotReserve = updateNewHypervisorSnapshotReserve[0]; long currentSize = volume.getSize(); - validateVolumeResizeWithSize(volume, currentSize, newSize, shrinkOk); + validateVolumeResizeWithSize(volume, currentSize, newSize, shrinkOk, existingDiskOffering, newDiskOffering); /* 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 @@ -2050,6 +2000,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } _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()); @@ -2303,10 +2256,11 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } } - private void validateVolumeResizeWithSize(VolumeVO volume, long currentSize, Long newSize, boolean shrinkOk) throws ResourceAllocationException { + private void validateVolumeResizeWithSize(VolumeVO volume, long currentSize, Long newSize, boolean shrinkOk, + DiskOfferingVO existingDiskOffering, DiskOfferingVO newDiskOffering) throws ResourceAllocationException { // if the caller is looking to change the size of the volume - if (currentSize != newSize) { + if (newSize != null && currentSize != newSize) { if (volume.getInstanceId() != null) { // Check that VM to which this volume is attached does not have VM snapshots if (_vmSnapshotDao.findByVm(volume.getInstanceId()).size() > 0) { @@ -2323,7 +2277,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic if (storagePoolId != null) { StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePoolId); - if (storagePoolVO.isManaged()) { + if (storagePoolVO.isManaged() && !List.of( + Storage.StoragePoolType.PowerFlex, + Storage.StoragePoolType.FiberChannel).contains(storagePoolVO.getPoolType())) { Long instanceId = volume.getInstanceId(); if (instanceId != null) { @@ -2348,18 +2304,16 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic logger.warn(message); throw new InvalidParameterValueException(message); } - } - if (currentSize > newSize && !shrinkOk) { - throw new InvalidParameterValueException("Going from existing size of " + currentSize + " to size of " + newSize + " would shrink the volume." - + "Need to sign off by supplying the shrinkok parameter with value of true."); - } - - if (newSize > currentSize) { - /* Check resource limit for this account on primary storage resource */ - _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(volume.getAccountId()), ResourceType.primary_storage, volume.isDisplayVolume(), - new Long(newSize - currentSize).longValue()); + if (!shrinkOk) { + throw new InvalidParameterValueException("Going from existing size of " + currentSize + " to size of " + newSize + " would shrink the volume." + + "Need to sign off by supplying the shrinkok parameter with value of true."); + } } } + /* Check resource limit for this account */ + _resourceLimitMgr.checkVolumeResourceLimitForDiskOfferingChange(_accountMgr.getAccount(volume.getAccountId()), + volume.isDisplayVolume(), currentSize, newSize != null ? newSize : currentSize, + existingDiskOffering, newDiskOffering); } @Override diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 9123d3ccc9d..a1e0e770ee6 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -17,6 +17,7 @@ package com.cloud.vm; import static com.cloud.configuration.ConfigurationManagerImpl.VM_USERDATA_MAX_LENGTH; +import static com.cloud.storage.Volume.IOPS_LIMIT; import static com.cloud.utils.NumbersUtil.toHumanReadableSize; import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS; import static org.apache.cloudstack.api.ApiConstants.MIN_IOPS; @@ -56,6 +57,7 @@ import javax.xml.parsers.ParserConfigurationException; import com.cloud.kubernetes.cluster.KubernetesClusterHelper; import com.cloud.network.dao.NsxProviderDao; import com.cloud.network.element.NsxProviderVO; +import com.cloud.user.AccountVO; import com.cloud.utils.exception.ExceptionProxyObject; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; @@ -1349,13 +1351,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir Account owner = _accountMgr.getActiveAccountById(vmInstance.getAccountId()); VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vmInstance.getTemplateId()); - if (! VirtualMachineManager.ResourceCountRunningVMsonly.value()) { - if (newCpu > currentCpu) { - _resourceLimitMgr.checkVmCpuResourceLimit(owner, vmInstance.isDisplay(), newServiceOffering, template, (long)(newCpu - currentCpu)); - } - if (newMemory > currentMemory) { - _resourceLimitMgr.checkVmMemoryResourceLimit(owner, vmInstance.isDisplay(), newServiceOffering, template, (long)(newMemory - currentMemory)); - } + if (!VirtualMachineManager.ResourceCountRunningVMsonly.value()) { + _resourceLimitMgr.checkVmResourceLimitsForServiceOfferingChange(owner, vmInstance.isDisplay(), (long) currentCpu, (long) newCpu, + (long) currentMemory, (long) newMemory, currentServiceOffering, newServiceOffering, template); } // Check that the specified service offering ID is valid @@ -1371,17 +1369,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir _itMgr.upgradeVmDb(vmId, newServiceOffering, currentServiceOffering); // Increment or decrement CPU and Memory count accordingly. - if (! VirtualMachineManager.ResourceCountRunningVMsonly.value()) { - if (newCpu > currentCpu) { - _resourceLimitMgr.incrementVmCpuResourceCount(owner.getAccountId(), vmInstance.isDisplay(), newServiceOffering, template, (long)(newCpu - currentCpu)); - } else if (currentCpu > newCpu) { - _resourceLimitMgr.decrementVmMemoryResourceCount(owner.getAccountId(), vmInstance.isDisplay(), newServiceOffering, template, (long)(currentCpu - newCpu)); - } - if (newMemory > currentMemory) { - _resourceLimitMgr.incrementVmMemoryResourceCount(owner.getAccountId(), vmInstance.isDisplay(), newServiceOffering, template, (long)(newMemory - currentMemory)); - } else if (currentMemory > newMemory) { - _resourceLimitMgr.decrementVmMemoryResourceCount(owner.getAccountId(), vmInstance.isDisplay(), newServiceOffering, template, (long)(currentMemory - newMemory)); - } + if (!VirtualMachineManager.ResourceCountRunningVMsonly.value()) { + _resourceLimitMgr.updateVmResourceCountForServiceOfferingChange(owner.getAccountId(), vmInstance.isDisplay(), (long) currentCpu, (long) newCpu, + (long) currentMemory, (long) newMemory, currentServiceOffering, newServiceOffering, template); } return _vmDao.findById(vmInstance.getId()); @@ -2063,13 +2053,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vmInstance.getTemplateId()); // Check resource limits - if (newCpu > currentCpu) { - _resourceLimitMgr.checkVmCpuResourceLimit(owner, vmInstance.isDisplay(), newServiceOffering, template, (long)(newCpu - currentCpu)); - } - - if (newMemory > currentMemory) { - _resourceLimitMgr.checkVmMemoryResourceLimit(caller, vmInstance.isDisplay(), newServiceOffering, template, (long)memoryDiff); - } + _resourceLimitMgr.checkVmResourceLimitsForServiceOfferingChange(owner, vmInstance.isDisplay(), (long) currentCpu, (long) newCpu, + (long) currentMemory, (long) newMemory, currentServiceOffering, newServiceOffering, template); // Dynamically upgrade the running vms boolean success = false; @@ -2099,14 +2084,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir boolean existingHostHasCapacity = false; // Increment CPU and Memory count accordingly. - if (newCpu > currentCpu) { - _resourceLimitMgr.incrementVmCpuResourceCount(caller.getAccountId(), vmInstance.isDisplay(), newServiceOffering, template, (long)(newCpu - currentCpu)); - } - - if (memoryDiff > 0) { - _resourceLimitMgr.incrementVmMemoryResourceCount(caller.getAccountId(), vmInstance.isDisplay(), newServiceOffering, template, (long)memoryDiff); - } - + _resourceLimitMgr.updateVmResourceCountForServiceOfferingChange(caller.getAccountId(), vmInstance.isDisplay(), + (long) currentCpu, (long) newCpu, (long) currentMemory, (long) newMemory, + currentServiceOffering, newServiceOffering, template); // #1 Check existing host has capacity if (!excludes.shouldAvoid(ApiDBUtils.findHostById(vmInstance.getHostId()))) { existingHostHasCapacity = _capacityMgr.checkIfHostHasCpuCapability(vmInstance.getHostId(), newCpu, newSpeed) @@ -2135,13 +2115,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } finally { if (!success) { // Decrement CPU and Memory count accordingly. - if (newCpu > currentCpu) { - _resourceLimitMgr.decrementVmCpuResourceCount(caller.getAccountId(), vmInstance.isDisplay(), newServiceOffering, template, (long)(newCpu - currentCpu)); - } - - if (memoryDiff > 0) { - _resourceLimitMgr.decrementVmMemoryResourceCount(caller.getAccountId(), vmInstance.isDisplay(), newServiceOffering, template, (long)memoryDiff); - } + _resourceLimitMgr.updateVmResourceCountForServiceOfferingChange(caller.getAccountId(), vmInstance.isDisplay(), + (long) newCpu, (long) currentCpu, (long) newMemory, (long) currentMemory, + newServiceOffering, currentServiceOffering, template); } } } @@ -2201,8 +2177,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if (customParameters.containsKey(MIN_IOPS)) { minIopsInNewDiskOffering = Long.parseLong(customParameters.get(MIN_IOPS)); } - if (customParameters.containsKey(MAX_IOPS)) { - minIopsInNewDiskOffering = Long.parseLong(customParameters.get(MAX_IOPS)); + + if (customParameters.containsKey(IOPS_LIMIT)) { + maxIopsInNewDiskOffering = Long.parseLong(customParameters.get(IOPS_LIMIT)); + } else if (customParameters.containsKey(MAX_IOPS)) { + maxIopsInNewDiskOffering = Long.parseLong(customParameters.get(MAX_IOPS)); } if (customParameters.containsKey(ApiConstants.AUTO_MIGRATE)) { autoMigrate = Boolean.parseBoolean(customParameters.get(ApiConstants.AUTO_MIGRATE)); @@ -2333,7 +2312,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // First check that the maximum number of UserVMs, CPU and Memory limit for the given // accountId will not be exceeded - if (! VirtualMachineManager.ResourceCountRunningVMsonly.value()) { + if (!VirtualMachineManager.ResourceCountRunningVMsonly.value()) { resourceLimitService.checkVmResourceLimit(account, vm.isDisplayVm(), serviceOffering, template); } @@ -3281,7 +3260,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir @Override @ActionEvent(eventType = EventTypes.EVENT_VM_REBOOT, eventDescription = "rebooting Vm", async = true) - public UserVm rebootVirtualMachine(RebootVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException { + public UserVm rebootVirtualMachine(RebootVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); Long vmId = cmd.getId(); @@ -7830,7 +7809,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } @Override - public UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException { + public UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ResourceAllocationException { // Input validation Account caller = CallContext.current().getCallingAccount(); @@ -7884,12 +7863,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return restoreVMInternal(caller, vm, newTemplateId, rootDiskOfferingId, expunge, details); } - public UserVm restoreVMInternal(Account caller, UserVmVO vm, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map details) throws InsufficientCapacityException, ResourceUnavailableException { + public UserVm restoreVMInternal(Account caller, UserVmVO vm, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map details) throws InsufficientCapacityException, ResourceUnavailableException, ResourceAllocationException { return _itMgr.restoreVirtualMachine(vm.getId(), newTemplateId, rootDiskOfferingId, expunge, details); } - public UserVm restoreVMInternal(Account caller, UserVmVO vm) throws InsufficientCapacityException, ResourceUnavailableException { + public UserVm restoreVMInternal(Account caller, UserVmVO vm) throws InsufficientCapacityException, ResourceUnavailableException, ResourceAllocationException { return restoreVMInternal(caller, vm, null, null, false, null); } @@ -7981,7 +7960,13 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } VMTemplateVO template = getRestoreVirtualMachineTemplate(caller, newTemplateId, rootVols, vm); - checkRestoreVmFromTemplate(vm, template); + DiskOffering diskOffering = rootDiskOfferingId != null ? _diskOfferingDao.findById(rootDiskOfferingId) : null; + try { + checkRestoreVmFromTemplate(vm, template, rootVols, diskOffering, details); + } catch (ResourceAllocationException e) { + logger.error("Failed to restore VM " + vm.getUuid() + " due to " + e.getMessage(), e); + throw new CloudRuntimeException("Failed to restore VM " + vm.getUuid() + " due to " + e.getMessage(), e); + } if (needRestart) { try { @@ -7994,7 +7979,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } } - DiskOffering diskOffering = rootDiskOfferingId != null ? _diskOfferingDao.findById(rootDiskOfferingId) : null; for (VolumeVO root : rootVols) { if ( !Volume.State.Allocated.equals(root.getState()) || newTemplateId != null ) { _volumeService.validateDestroyVolume(root, caller, expunge, false); @@ -8034,7 +8018,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // 1. Save usage event and update resource count for user vm volumes try { - _resourceLimitMgr.incrementVolumeResourceCount(newVol.getAccountId(), newVol.isDisplay(), newVol.getSize(), _diskOfferingDao.findById(newVol.getDiskOfferingId())); + _resourceLimitMgr.incrementVolumeResourceCount(userVm.getAccountId(), newVol.isDisplay(), + newVol.getSize(), diskOffering != null ? diskOffering : _diskOfferingDao.findById(newVol.getDiskOfferingId())); } catch (final CloudRuntimeException e) { throw e; } catch (final Exception e) { @@ -8061,6 +8046,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir _volsDao.detachVolume(root.getId()); _volumeService.destroyVolume(root.getId(), caller, expunge, false); + if (currentTemplate.getId() != template.getId() && VirtualMachine.Type.User.equals(vm.type) && !VirtualMachineManager.ResourceCountRunningVMsonly.value()) { + ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId()); + _resourceLimitMgr.updateVmResourceCountForTemplateChange(vm.getAccountId(), vm.isDisplay(), serviceOffering, currentTemplate, template); + } + // For VMware hypervisor since the old root volume is replaced by the new root volume, force expunge old root volume if it has been created in storage if (vm.getHypervisorType() == HypervisorType.VMware) { VolumeInfo volumeInStorage = volFactory.getVolume(root.getId()); @@ -8179,7 +8169,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) { + private void checkRestoreVmFromTemplate(UserVmVO vm, VMTemplateVO template, List volumes, DiskOffering newDiskOffering, Map details) throws ResourceAllocationException { TemplateDataStoreVO tmplStore; if (!template.isDirectDownload()) { tmplStore = _templateStoreDao.findByTemplateZoneReady(template.getId(), vm.getDataCenterId()); @@ -8192,6 +8182,27 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir throw new InvalidParameterValueException("Cannot restore the vm as the bypassed template " + template.getUuid() + " isn't available in the zone"); } } + + AccountVO owner = _accountDao.findByIdIncludingRemoved(vm.getAccountId()); + 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); + } + + Long newSize = newDiskOffering != null ? newDiskOffering.getDiskSize() : null; + if (MapUtils.isNotEmpty(details) && StringUtils.isNumeric(details.get(VmDetailConstants.ROOT_DISK_SIZE))) { + newSize = Long.parseLong(details.get(VmDetailConstants.ROOT_DISK_SIZE)) * GiB_TO_BYTES; + } + if (newDiskOffering != null || newSize != null) { + for (Volume vol : volumes) { + if (newDiskOffering != null || !vol.getSize().equals(newSize)) { + DiskOffering currentOffering = _diskOfferingDao.findById(vol.getDiskOfferingId()); + _resourceLimitMgr.checkVolumeResourceLimitForDiskOfferingChange(owner, vol.isDisplay(), + vol.getSize(), newSize, currentOffering, newDiskOffering); + } + } + } } private void handleManagedStorage(UserVmVO vm, VolumeVO root) { diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index 1292b9e230c..c464af6385b 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -1296,7 +1296,7 @@ public class UserVmManagerImplTest { } @Test(expected = InvalidParameterValueException.class) - public void testRestoreVMNoVM() throws ResourceUnavailableException, InsufficientCapacityException { + public void testRestoreVMNoVM() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { CallContext callContextMock = Mockito.mock(CallContext.class); Mockito.lenient().doReturn(accountMock).when(callContextMock).getCallingAccount(); @@ -1309,7 +1309,7 @@ public class UserVmManagerImplTest { } @Test(expected = CloudRuntimeException.class) - public void testRestoreVMWithVolumeSnapshots() throws ResourceUnavailableException, InsufficientCapacityException { + public void testRestoreVMWithVolumeSnapshots() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { CallContext callContextMock = Mockito.mock(CallContext.class); Mockito.lenient().doReturn(accountMock).when(callContextMock).getCallingAccount(); Mockito.lenient().doNothing().when(accountManager).checkAccess(accountMock, null, true, userVmVoMock); @@ -1323,7 +1323,7 @@ public class UserVmManagerImplTest { } @Test(expected = InvalidParameterValueException.class) - public void testRestoreVirtualMachineNoOwner() throws ResourceUnavailableException, InsufficientCapacityException { + public void testRestoreVirtualMachineNoOwner() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { long userId = 1l; long accountId = 2l; long newTemplateId = 2l; @@ -1336,7 +1336,7 @@ public class UserVmManagerImplTest { } @Test(expected = PermissionDeniedException.class) - public void testRestoreVirtualMachineOwnerDisabled() throws ResourceUnavailableException, InsufficientCapacityException { + public void testRestoreVirtualMachineOwnerDisabled() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { long userId = 1l; long accountId = 2l; long newTemplateId = 2l; @@ -1350,7 +1350,7 @@ public class UserVmManagerImplTest { } @Test(expected = CloudRuntimeException.class) - public void testRestoreVirtualMachineNotInRightState() throws ResourceUnavailableException, InsufficientCapacityException { + public void testRestoreVirtualMachineNotInRightState() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { long userId = 1l; long accountId = 2l; long newTemplateId = 2l; @@ -1365,7 +1365,7 @@ public class UserVmManagerImplTest { } @Test(expected = InvalidParameterValueException.class) - public void testRestoreVirtualMachineNoRootVolume() throws ResourceUnavailableException, InsufficientCapacityException { + public void testRestoreVirtualMachineNoRootVolume() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { long userId = 1l; long accountId = 2l; long currentTemplateId = 1l; @@ -1386,7 +1386,7 @@ public class UserVmManagerImplTest { } @Test(expected = InvalidParameterValueException.class) - public void testRestoreVirtualMachineMoreThanOneRootVolume() throws ResourceUnavailableException, InsufficientCapacityException { + public void testRestoreVirtualMachineMoreThanOneRootVolume() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { long userId = 1l; long accountId = 2l; long currentTemplateId = 1l; @@ -1413,7 +1413,7 @@ public class UserVmManagerImplTest { } @Test(expected = InvalidParameterValueException.class) - public void testRestoreVirtualMachineWithVMSnapshots() throws ResourceUnavailableException, InsufficientCapacityException { + public void testRestoreVirtualMachineWithVMSnapshots() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { long userId = 1l; long accountId = 2l; long currentTemplateId = 1l; diff --git a/server/src/test/java/com/cloud/vpc/MockResourceLimitManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockResourceLimitManagerImpl.java index e633816a1e2..74e2a7e6545 100644 --- a/server/src/test/java/com/cloud/vpc/MockResourceLimitManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockResourceLimitManagerImpl.java @@ -272,6 +272,12 @@ public class MockResourceLimitManagerImpl extends ManagerBase implements Resourc } + @Override + public void checkVolumeResourceLimitForDiskOfferingChange(Account owner, Boolean display, Long currentSize, Long newSize, + DiskOffering currentOffering, DiskOffering newOffering) throws ResourceAllocationException { + + } + @Override public void incrementVolumeResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering) { @@ -282,6 +288,25 @@ public class MockResourceLimitManagerImpl extends ManagerBase implements Resourc } + @Override + public void updateVmResourceCountForTemplateChange(long accountId, Boolean display, ServiceOffering offering, + VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate) { + + } + + @Override + public void updateVmResourceCountForServiceOfferingChange(long accountId, Boolean display, Long currentCpu, Long newCpu, + Long currentMemory, Long newMemory, ServiceOffering currentOffering, ServiceOffering newOffering, + VirtualMachineTemplate template) { + + } + + @Override + public void updateVolumeResourceCountForDiskOfferingChange(long accountId, Boolean display, Long currentSize, Long newSize, + DiskOffering currentDiskOffering, DiskOffering newDiskOffering) { + + } + @Override public void incrementVolumePrimaryStorageResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering) { @@ -307,6 +332,20 @@ 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 { + + } + + @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 {