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) {

Reply via email to