Address public IP limit validations

This commit is contained in:
Fabricio Duarte 2026-03-10 18:33:00 -03:00 committed by Daan Hoogland
parent e8f8aca694
commit dc7068a135
8 changed files with 81 additions and 39 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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

View File

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