This is an automated email from the ASF dual-hosted git repository. dahn pushed a commit to branch 4.20 in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit dc7068a13517717edb37431966527d3bc1c6374e Author: Fabricio Duarte <[email protected]> AuthorDate: Tue Mar 10 18:33:00 2026 -0300 Address public IP limit validations --- .../java/com/cloud/dc/dao/AccountVlanMapDao.java | 2 +- .../com/cloud/dc/dao/AccountVlanMapDaoImpl.java | 4 +- .../java/com/cloud/dc/dao/DomainVlanMapDao.java | 2 +- .../com/cloud/dc/dao/DomainVlanMapDaoImpl.java | 4 +- server/src/main/java/com/cloud/api/ApiDBUtils.java | 4 ++ .../configuration/ConfigurationManagerImpl.java | 74 +++++++++++++++------- .../java/com/cloud/network/NetworkServiceImpl.java | 24 ++++--- .../cloud/resourcelimit/CheckedReservation.java | 6 ++ 8 files changed, 81 insertions(+), 39 deletions(-) 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<AccountVlanMapVO, Long> { public List<AccountVlanMapVO> 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<AccountVlanMapVO, Long } @Override - public AccountVlanMapVO findAccountVlanMap(long accountId, long vlanDbId) { + public AccountVlanMapVO findAccountVlanMap(Long accountId, long vlanDbId) { SearchCriteria<AccountVlanMapVO> 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<DomainVlanMapVO, Long> { public List<DomainVlanMapVO> listDomainVlanMapsByDomain(long domainId); public List<DomainVlanMapVO> 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<DomainVlanMapVO, Long> } @Override - public DomainVlanMapVO findDomainVlanMap(long domainId, long vlanDbId) { + public DomainVlanMapVO findDomainVlanMap(Long domainId, long vlanDbId) { SearchCriteria<DomainVlanMapVO> sc = DomainVlanSearch.create(); - sc.setParameters("domainId", domainId); + sc.setParametersIfNotNull("domainId", domainId); sc.setParameters("vlanDbId", vlanDbId); return findOneIncludingRemovedBy(sc); } diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index 4ef1b28b9c0..4ff25472efe 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -2244,6 +2244,10 @@ public class ApiDBUtils { return s_accountService.isAdmin(account.getId()); } + public static Account getSystemAccount() { + return s_accountService.getSystemAccount(); + } + public static List<ResourceTagJoinVO> listResourceTagViewByResourceUUID(String resourceUUID, ResourceObjectType resourceType) { return s_tagJoinDao.listBy(resourceUUID, resourceType); } diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index eb138bb10b0..8ba8234c1ba 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -52,6 +52,7 @@ import javax.naming.ConfigurationException; import com.cloud.exception.UnsupportedServiceException; import com.cloud.network.as.AutoScaleManager; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.user.AccountManagerImpl; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker; @@ -128,6 +129,7 @@ import org.apache.cloudstack.region.PortableIpVO; import org.apache.cloudstack.region.Region; import org.apache.cloudstack.region.RegionVO; import org.apache.cloudstack.region.dao.RegionDao; +import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO; import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; @@ -395,6 +397,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Inject ResourceLimitService _resourceLimitMgr; @Inject + ReservationDao reservationDao; + @Inject ProjectManager _projectMgr; @Inject DataStoreManager _dataStoreMgr; @@ -4833,22 +4837,20 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati throw new InvalidParameterValueException("Gateway, netmask and zoneId have to be passed in for virtual and direct untagged networks"); } - if (forVirtualNetwork) { - if (vlanOwner != null) { - - final long accountIpRange = NetUtils.ip2Long(endIP) - NetUtils.ip2Long(startIP) + 1; - - // check resource limits - _resourceLimitMgr.checkResourceLimit(vlanOwner, ResourceType.public_ip, accountIpRange); - } - } // Check if the IP range overlaps with the private ip if (ipv4) { checkOverlapPrivateIpRange(zoneId, startIP, endIP); } - return commitVlan(zoneId, podId, startIP, endIP, newVlanGateway, newVlanNetmask, vlanId, forVirtualNetwork, forSystemVms, networkId, physicalNetworkId, startIPv6, endIPv6, ip6Gateway, - ip6Cidr, domain, vlanOwner, network, sameSubnet, cmd.isForNsx()); + long reservedIpAddressesAmount = 0L; + if (forVirtualNetwork && vlanOwner != null) { + reservedIpAddressesAmount = NetUtils.ip2Long(endIP) - NetUtils.ip2Long(startIP) + 1; + } + + try (CheckedReservation publicIpReservation = new CheckedReservation(vlanOwner, ResourceType.public_ip, null, null, null, reservedIpAddressesAmount, null, reservationDao, _resourceLimitMgr)) { + return commitVlan(zoneId, podId, startIP, endIP, newVlanGateway, newVlanNetmask, vlanId, forVirtualNetwork, forSystemVms, networkId, physicalNetworkId, startIPv6, endIPv6, ip6Gateway, + ip6Cidr, domain, vlanOwner, network, sameSubnet, cmd.isForNsx()); + } } private Network getNetwork(Long networkId) { @@ -5377,7 +5379,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati String endIpv6, String ip6Gateway, String ip6Cidr, - Boolean forSystemVms) throws ConcurrentOperationException { + Boolean forSystemVms) throws ConcurrentOperationException, ResourceAllocationException { VlanVO vlanRange = _vlanDao.findById(id); if (vlanRange == null) { @@ -5397,24 +5399,50 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } + AccountVlanMapVO accountMap = _accountVlanMapDao.findAccountVlanMap(null, id); + Account account = accountMap != null ? _accountDao.findById(accountMap.getAccountId()) : null; + + DomainVlanMapVO domainMap = _domainVlanMapDao.findDomainVlanMap(null, id); + Long domainId = domainMap != null ? domainMap.getDomainId() : null; + final Boolean isRangeForSystemVM = checkIfVlanRangeIsForSystemVM(id); if (forSystemVms != null && isRangeForSystemVM != forSystemVms) { if (VlanType.DirectAttached.equals(vlanRange.getVlanType())) { throw new InvalidParameterValueException("forSystemVms is not available for this IP range with vlan type: " + VlanType.DirectAttached); } // Check if range has already been dedicated - final List<AccountVlanMapVO> 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<DomainVlanMapVO> 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) { + 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); @@ -5801,12 +5829,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<IPAddressVO> ips = _publicIpAddressDao.listByVlanId(vlanDbId); @@ -5827,6 +5849,10 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } + // 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)) { + if (vlanOwner != null) { // Create an AccountVlanMapVO entry final AccountVlanMapVO accountVlanMapVO = new AccountVlanMapVO(vlanOwner.getId(), vlan.getId()); @@ -5850,6 +5876,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } return vlan; + + } } @Override diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index 54c1d133943..c75fe4efbcc 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -40,6 +40,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; @@ -75,6 +76,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; @@ -328,6 +330,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C @Inject ResourceLimitService _resourceLimitMgr; @Inject + ReservationDao reservationDao; + @Inject DomainManager _domainMgr; @Inject ProjectManager _projectMgr; @@ -1143,15 +1147,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<AccountVlanMapVO> maps = _accountVlanMapDao.listAccountVlanMapsByVlan(ipVO.getVlanId()); ipVO.setAllocatedTime(new Date()); ipVO.setAllocatedToAccountId(account.getAccountId()); @@ -1161,10 +1160,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 diff --git a/server/src/main/java/com/cloud/resourcelimit/CheckedReservation.java b/server/src/main/java/com/cloud/resourcelimit/CheckedReservation.java index 5f9913e2ee5..cab77ccf16a 100644 --- a/server/src/main/java/com/cloud/resourcelimit/CheckedReservation.java +++ b/server/src/main/java/com/cloud/resourcelimit/CheckedReservation.java @@ -23,6 +23,7 @@ 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; @@ -146,6 +147,11 @@ public class CheckedReservation implements Reserver { this.reservationDao = reservationDao; this.resourceLimitService = resourceLimitService; + + // 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) {
