[20.3] resource allocation

This commit is contained in:
dahn 2026-02-20 16:20:14 +01:00 committed by Daan Hoogland
parent 56dc11980f
commit 89df318164
10 changed files with 101 additions and 57 deletions

View File

@ -185,6 +185,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
@ -284,4 +285,5 @@ public interface ResourceLimitService {
void incrementVmMemoryResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long memory);
void decrementVmMemoryResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long memory);
long recalculateDomainResourceCount(final long domainId, final ResourceType type, String tag);
}

View File

@ -302,7 +302,7 @@ public interface NetworkOrchestrationService {
void removeDhcpServiceInSubnet(Nic nic);
boolean resourceCountNeedsUpdate(NetworkOffering ntwkOff, ACLType aclType);
boolean isResourceCountUpdateNeeded(NetworkOffering networkOffering);
void prepareAllNicsForMigration(VirtualMachineProfile vm, DeployDestination dest);

View File

@ -39,12 +39,14 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.configuration.Resource;
import com.cloud.dc.ASNumberVO;
import com.cloud.bgp.BGPService;
import com.cloud.dc.VlanDetailsVO;
import com.cloud.dc.dao.ASNumberDao;
import com.cloud.dc.dao.VlanDetailsDao;
import com.cloud.network.dao.NsxProviderDao;
import com.cloud.resourcelimit.CheckedReservation;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
@ -62,6 +64,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;
@ -441,6 +444,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
ClusterDao clusterDao;
@Inject
RoutedIpv4Manager routedIpv4Manager;
@Inject
private ReservationDao reservationDao;
protected StateMachine2<Network.State, Network.Event, Network> _stateMachine;
ScheduledExecutorService _executor;
@ -2721,12 +2726,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
@ -2745,6 +2744,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;
}
@ -3084,8 +3085,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);
@ -3096,6 +3097,10 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
CallContext.current().setEventDetails("Network Id: " + network.getId());
CallContext.current().putContextParameter(Network.class, network.getUuid());
return network;
} catch (Exception e) {
logger.error(e);
throw new RuntimeException(e);
}
}
@Override
@ -3460,9 +3465,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();
@ -3485,6 +3489,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<VlanVO> deletedVlanRangeToPublish) {
if (CollectionUtils.isNotEmpty(deletedVlanRangeToPublish)) {
for (VlanVO vlan : deletedVlanRangeToPublish) {
@ -3494,10 +3515,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<Boolean, List<VlanVO>> deleteVlansInNetwork(final NetworkVO network, final long userId, final Account callerAccount) {

View File

@ -3168,7 +3168,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);
}

View File

@ -49,6 +49,7 @@ public class CheckedReservation implements AutoCloseable {
ResourceLimitService resourceLimitService;
private final Account account;
private Long domainId;
private final ResourceType resourceType;
private Long amount;
private List<ResourceReservation> reservations;
@ -73,12 +74,12 @@ public class CheckedReservation implements AutoCloseable {
this.reservations = null;
}
protected void checkLimitAndPersistReservations(Account account, ResourceType resourceType, Long resourceId, List<String> resourceLimitTags, Long amount) throws ResourceAllocationException {
protected void checkLimitAndPersistReservations(Account account, Long domainId, ResourceType resourceType, Long resourceId, List<String> 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 +88,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);
}
@ -114,9 +115,20 @@ public class CheckedReservation implements AutoCloseable {
*/
public CheckedReservation(Account account, ResourceType resourceType, Long resourceId, List<String> resourceLimitTags, Long amount,
ReservationDao reservationDao, ResourceLimitService resourceLimitService) throws ResourceAllocationException {
this(account, account.getDomainId(), resourceType, resourceId, resourceLimitTags, amount, reservationDao, resourceLimitService);
}
public CheckedReservation(Account account, Long domainId, ResourceType resourceType, Long resourceId, List<String> resourceLimitTags, Long amount,
ReservationDao reservationDao, ResourceLimitService resourceLimitService) throws ResourceAllocationException {
this.reservationDao = reservationDao;
this.resourceLimitService = resourceLimitService;
this.account = account;
this.domainId = domainId;
if (domainId == null) {
this.domainId = account.getDomainId();
}
this.resourceType = resourceType;
this.amount = amount;
this.reservations = new ArrayList<>();
@ -127,7 +139,7 @@ public class CheckedReservation implements AutoCloseable {
setGlobalLock();
if (quotaLimitLock.lock(TRY_TO_GET_LOCK_TIME)) {
try {
checkLimitAndPersistReservations(account, resourceType, resourceId, resourceLimitTags, amount);
checkLimitAndPersistReservations(account, this.domainId, resourceType, resourceId, resourceLimitTags, amount);
CallContext.current().putContextParameter(getContextParameterKey(), getIds());
} catch (NullPointerException npe) {
throw new CloudRuntimeException("not enough means to check limits", npe);
@ -138,11 +150,11 @@ 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, resourceLimitTags, amount);
}
} 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());
}
}
@ -153,7 +165,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));
}

View File

@ -36,6 +36,7 @@ import java.util.stream.Stream;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.network.dao.NetworkDomainDao;
import com.cloud.utils.Ternary;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.response.AccountResponse;
@ -192,6 +193,8 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim
ServiceOfferingDao serviceOfferingDao;
@Inject
DiskOfferingDao diskOfferingDao;
@Inject
private NetworkDomainDao networkDomainDao;
protected GenericSearchBuilder<TemplateDataStoreVO, SumCount> templateSizeSearch;
protected GenericSearchBuilder<SnapshotDataStoreVO, SumCount> snapshotSizeSearch;
@ -488,15 +491,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
@ -618,11 +613,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;
}
@ -630,6 +630,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<ResourceAllocationException>() {
@Override
@ -639,7 +647,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);
}
});
}
@ -1155,7 +1163,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<AccountVO> accounts = _accountDao.findActiveAccountsForDomain(domainId);
List<DomainVO> childDomains = _domainDao.findImmediateChildrenForParent(domainId);
@ -1196,6 +1204,10 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim
newResourceCount += _projectDao.countProjectsForDomain(domainId);
}
if (type == ResourceType.network) {
newResourceCount += networkDomainDao.listDomainNetworkMapByDomain(domainId).size();
}
// TODO make sure that the resource counts are not null
for (ResourceCountVO resourceCount : resourceCounts) {
if (resourceCount.getResourceOwnerType() == ResourceOwnerType.Domain && resourceCount.getDomainId() == domainId) {

View File

@ -149,23 +149,10 @@ public class CheckedReservationTest {
@Test
public void testMultipleReservationsWithOneFailing() {
List<String> tags = List.of("abc", "xyz");
when(account.getAccountId()).thenReturn(1L);
when(account.getDomainId()).thenReturn(4L);
Map<Long, ReservationVO> persistedReservations = new HashMap<>();
Mockito.when(reservationDao.persist(Mockito.any(ReservationVO.class))).thenAnswer((Answer<ReservationVO>) 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<Boolean>) 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);

View File

@ -587,10 +587,11 @@ public class ResourceLimitManagerImplTest extends TestCase {
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<ResourceLimitVO>()).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) {
@ -606,9 +607,10 @@ public class ResourceLimitManagerImplTest extends TestCase {
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<ResourceLimitVO>()).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) {

View File

@ -951,7 +951,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.
}

View File

@ -237,6 +237,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<String> getResourceLimitHostTags() {
return null;
@ -381,4 +386,9 @@ public class MockResourceLimitManagerImpl extends ManagerBase implements Resourc
public void decrementVmMemoryResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long memory) {
}
@Override
public long recalculateDomainResourceCount(long domainId, ResourceType type, String tag) {
return 0;
}
}