From 9db630932e0c711043c409cce4328d63bc4e7a3a Mon Sep 17 00:00:00 2001 From: Fabricio Duarte Date: Tue, 10 Mar 2026 18:33:00 -0300 Subject: [PATCH] Address public IP limit validations --- .../com/cloud/dc/dao/AccountVlanMapDao.java | 2 +- .../cloud/dc/dao/AccountVlanMapDaoImpl.java | 4 +- .../com/cloud/dc/dao/DomainVlanMapDao.java | 2 +- .../cloud/dc/dao/DomainVlanMapDaoImpl.java | 4 +- .../main/java/com/cloud/api/ApiDBUtils.java | 4 + .../ConfigurationManagerImpl.java | 184 ++++++++++-------- .../com/cloud/network/NetworkServiceImpl.java | 24 ++- .../resourcelimit/CheckedReservation.java | 6 + 8 files changed, 135 insertions(+), 95 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 { public List 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 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 { public List listDomainVlanMapsByDomain(long domainId); public List 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 } @Override - public DomainVlanMapVO findDomainVlanMap(long domainId, long vlanDbId) { + public DomainVlanMapVO findDomainVlanMap(Long domainId, long vlanDbId) { SearchCriteria 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 f7ffb039801..2d359b87d2e 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -2291,6 +2291,10 @@ public class ApiDBUtils { return s_accountService.isAdmin(account.getId()); } + public static Account getSystemAccount() { + return s_accountService.getSystemAccount(); + } + public static List 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 81970b0f8a7..cc062baa4bd 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -49,10 +49,6 @@ import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.consoleproxy.ConsoleProxyManager; -import com.cloud.network.router.VirtualNetworkApplianceManager; -import com.cloud.storage.secondary.SecondaryStorageVmManager; -import com.cloud.vm.VirtualMachineManager; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.affinity.AffinityGroup; @@ -126,6 +122,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; @@ -154,6 +151,7 @@ import com.cloud.api.query.vo.NetworkOfferingJoinVO; import com.cloud.capacity.CapacityManager; import com.cloud.capacity.dao.CapacityDao; import com.cloud.configuration.Resource.ResourceType; +import com.cloud.consoleproxy.ConsoleProxyManager; import com.cloud.dc.AccountVlanMapVO; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; @@ -247,6 +245,7 @@ import com.cloud.network.dao.UserIpv6AddressDao; import com.cloud.network.element.NetrisProviderVO; import com.cloud.network.element.NsxProviderVO; import com.cloud.network.netris.NetrisService; +import com.cloud.network.router.VirtualNetworkApplianceManager; import com.cloud.network.rules.LoadBalancerContainer.Scheme; import com.cloud.network.vpc.VpcManager; import com.cloud.offering.DiskOffering; @@ -264,6 +263,7 @@ import com.cloud.org.Grouping; import com.cloud.org.Grouping.AllocationState; import com.cloud.projects.Project; import com.cloud.projects.ProjectManager; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.server.ManagementService; import com.cloud.service.ServiceOfferingDetailsVO; import com.cloud.service.ServiceOfferingVO; @@ -281,6 +281,7 @@ import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.StoragePoolTagsDao; import com.cloud.storage.dao.VMTemplateZoneDao; import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.secondary.SecondaryStorageVmManager; import com.cloud.test.IPRangeConfig; import com.cloud.user.Account; import com.cloud.user.AccountDetailVO; @@ -314,6 +315,7 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; import com.cloud.vm.NicIpAlias; import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.NicIpAliasDao; import com.cloud.vm.dao.NicIpAliasVO; @@ -406,6 +408,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Inject ResourceLimitService _resourceLimitMgr; @Inject + ReservationDao reservationDao; + @Inject ProjectManager _projectMgr; @Inject NetworkOfferingServiceMapDao _ntwkOffServiceMapDao; @@ -537,7 +541,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati public static final ConfigKey DELETE_QUERY_BATCH_SIZE = new ConfigKey<>("Advanced", Long.class, "delete.query.batch.size", "0", "Indicates the limit applied while deleting entries in bulk. With this, the delete query will apply the limit as many times as necessary," + " to delete all the entries. This is advised when retaining several days of records, which can lead to slowness. <= 0 means that no limit will " + - "be applied. Default value is 0. For now, this is used for deletion of vm & volume stats only.", true); + "be applied. Default value is 0. For now, this is used for deletion of VM stats, volume stats, and usage records.", true); private static final String IOPS_READ_RATE = "IOPS Read"; private static final String IOPS_WRITE_RATE = "IOPS Write"; @@ -641,7 +645,6 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati protected void populateConfigKeysAllowedOnlyForDefaultAdmin() { configKeysAllowedOnlyForDefaultAdmin.add(AccountManagerImpl.listOfRoleTypesAllowedForOperationsOfSameRoleType.key()); configKeysAllowedOnlyForDefaultAdmin.add(AccountManagerImpl.allowOperationsOnUsersInSameAccount.key()); - configKeysAllowedOnlyForDefaultAdmin.add(VirtualMachineManager.SystemVmEnableUserData.key()); configKeysAllowedOnlyForDefaultAdmin.add(ConsoleProxyManager.ConsoleProxyVmUserData.key()); configKeysAllowedOnlyForDefaultAdmin.add(SecondaryStorageVmManager.SecondaryStorageVmUserData.key()); @@ -828,7 +831,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati case Account: final AccountVO account = _accountDao.findById(resourceId); if (account == null) { - throw new InvalidParameterValueException("unable to find account by id " + resourceId); + throw new InvalidParameterValueException("Unable to find Account by id " + resourceId); } resourceType = ApiCommandResourceType.Account; AccountDetailVO accountDetailVO = _accountDetailsDao.findDetail(resourceId, name); @@ -984,7 +987,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati String hypervisors = _configDao.getValue(hypervisorListConfigName); if (Arrays.asList(hypervisors.split(",")).contains(previousValue)) { hypervisors = hypervisors.replace(previousValue, newValue); - logger.info("Updating the hypervisor list configuration '{}}' to match the new custom hypervisor display name", + logger.info("Updating the hypervisor list configuration '{}' to match the new custom hypervisor display name", hypervisorListConfigName); _configDao.update(hypervisorListConfigName, hypervisors); } @@ -1753,7 +1756,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati // Check if there are any non-removed vms in the pod. if (!_vmInstanceDao.listByPodId(podId).isEmpty()) { - throw new CloudRuntimeException(errorMsg + "there are virtual machines in this pod."); + throw new CloudRuntimeException(errorMsg + "there are Instances in this pod."); } // Check if there are any non-removed clusters in the pod. @@ -1797,7 +1800,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati // Don't allow gateway to overlap with start/endIp if (!skipGatewayOverlapCheck) { if (NetUtils.ipRangesOverlap(startIp, endIp, gateway, gateway)) { - throw new InvalidParameterValueException("The gateway shouldn't overlap start/end ip addresses"); + throw new InvalidParameterValueException("The gateway shouldn't overlap start/end IP addresses"); } } @@ -1851,7 +1854,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final List privateIps = _privateIpAddressDao.listByPodIdDcId(podId, pod.getDataCenterId()); if (!privateIps.isEmpty()) { if (!_privateIpAddressDao.deleteIpAddressByPod(podId)) { - throw new CloudRuntimeException(String.format("Failed to cleanup private ip addresses for pod %s", pod)); + throw new CloudRuntimeException(String.format("Failed to cleanup private IP addresses for pod %s", pod)); } } @@ -1859,7 +1862,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final List localIps = _linkLocalIpAllocDao.listByPodIdDcId(podId, pod.getDataCenterId()); if (!localIps.isEmpty()) { if (!_linkLocalIpAllocDao.deleteIpAddressByPod(podId)) { - throw new CloudRuntimeException(String.format("Failed to cleanup private ip addresses for pod %s", pod)); + throw new CloudRuntimeException(String.format("Failed to cleanup private IP addresses for pod %s", pod)); } } @@ -1921,7 +1924,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final Account account = CallContext.current().getCallingAccount(); if(!_accountMgr.isRootAdmin(account.getId())) { - throw new PermissionDeniedException(String.format("Cannot perform this operation, Calling account is not root admin: %s", account)); + throw new PermissionDeniedException(String.format("Cannot perform this operation, Calling Account is not root admin: %s", account)); } final long podId = cmd.getPodId(); @@ -1988,7 +1991,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } if (NetUtils.ipRangesOverlap(startIp, endIp, gateway, gateway)) { - throw new InvalidParameterValueException("The gateway shouldn't overlap start/end ip addresses"); + throw new InvalidParameterValueException("The gateway shouldn't overlap start/end IP addresses"); } final String[] existingPodIpRanges = pod.getDescription().split(","); @@ -2032,7 +2035,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati lock = _podDao.acquireInLockTable(podId); if (lock == null) { - String msg = String.format("Unable to acquire lock on table to update the ip range of POD: %s, Creation failed.", pod); + String msg = String.format("Unable to acquire lock on table to update the IP range of POD: %s, Creation failed.", pod); logger.warn(msg); throw new CloudRuntimeException(msg); } @@ -2145,7 +2148,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati lock = _podDao.acquireInLockTable(podId); if (lock == null) { - String msg = String.format("Unable to acquire lock on table to update the ip range of POD: %s, Deletion failed.", pod); + String msg = String.format("Unable to acquire lock on table to update the IP range of POD: %s, Deletion failed.", pod); logger.warn(msg); throw new CloudRuntimeException(msg); } @@ -2159,7 +2162,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati for(long ipAddr = NetUtils.ip2Long(startIp); ipAddr <= NetUtils.ip2Long(endIp); ipAddr++) { if (!_privateIpAddressDao.deleteIpAddressByPodDc(NetUtils.long2Ip(ipAddr), podId, pod.getDataCenterId())) { - throw new CloudRuntimeException(String.format("Failed to cleanup private ip address: %s of Pod: %s DC: %s", NetUtils.long2Ip(ipAddr), pod, _zoneDao.findById(pod.getDataCenterId()))); + throw new CloudRuntimeException(String.format("Failed to cleanup private IP address: %s of Pod: %s DC: %s", NetUtils.long2Ip(ipAddr), pod, _zoneDao.findById(pod.getDataCenterId()))); } } } @@ -2266,7 +2269,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati try { lock = _podDao.acquireInLockTable(podId); if (lock == null) { - String msg = String.format("Unable to acquire lock on table to update the ip range of POD: %s, Update failed.", pod); + String msg = String.format("Unable to acquire lock on table to update the IP range of POD: %s, Update failed.", pod); logger.warn(msg); throw new CloudRuntimeException(msg); } @@ -2281,7 +2284,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati if (currentIpRange.size() > 0) { for (Long startIP: currentIpRange) { if (!_privateIpAddressDao.deleteIpAddressByPodDc(NetUtils.long2Ip(startIP),podId,zoneId)) { - throw new CloudRuntimeException(String.format("Failed to remove private ip address: %s of Pod: %s DC: %s", NetUtils.long2Ip(startIP), pod, _zoneDao.findById(pod.getDataCenterId()))); + throw new CloudRuntimeException(String.format("Failed to remove private IP address: %s of Pod: %s DC: %s", NetUtils.long2Ip(startIP), pod, _zoneDao.findById(pod.getDataCenterId()))); } } } @@ -2499,7 +2502,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } if (NetUtils.ipRangesOverlap(existingPodIpRange[0], existingPodIpRange[1], gateway, gateway)) { - throw new InvalidParameterValueException("The gateway shouldn't overlap some start/end ip addresses"); + throw new InvalidParameterValueException("The gateway shouldn't overlap some start/end IP addresses"); } } } @@ -3419,7 +3422,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati isCustomizedIops = false; if (cmd.getHypervisorSnapshotReserve() != null) { - throw new InvalidParameterValueException("Hypervisor snapshot reserve is not a valid parameter for a system VM."); + throw new InvalidParameterValueException("Hypervisor Snapshot reserve is not a valid parameter for a system VM."); } } else { allowNetworkRate = true; @@ -4794,7 +4797,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Override @DB - @ActionEvent(eventType = EventTypes.EVENT_VLAN_IP_RANGE_CREATE, eventDescription = "creating vlan ip range", async = false) + @ActionEvent(eventType = EventTypes.EVENT_VLAN_IP_RANGE_CREATE, eventDescription = "Creating VLAN IP range", async = false) public Vlan createVlanAndPublicIpRange(final CreateVlanIpRangeCmd cmd) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, ResourceAllocationException { Long zoneId = cmd.getZoneId(); @@ -4957,7 +4960,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } if (zone.isSecurityGroupEnabled() && zone.getNetworkType() != DataCenter.NetworkType.Basic && forVirtualNetwork) { - throw new InvalidParameterValueException("Can't add virtual ip range into a zone with security group enabled"); + throw new InvalidParameterValueException("Can't add virtual IP range into a zone with security group enabled"); } // If networkId is not specified, and vlan is Virtual or Direct @@ -4993,13 +4996,13 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati Pair> sameSubnet = null; // Can add vlan range only to the network which allows it if (!network.getSpecifyIpRanges()) { - throw new InvalidParameterValueException("Network " + network + " doesn't support adding ip ranges"); + throw new InvalidParameterValueException("Network " + network + " doesn't support adding IP ranges"); } if (zone.getNetworkType() == DataCenter.NetworkType.Advanced) { if (network.getTrafficType() == TrafficType.Guest) { if (network.getGuestType() != GuestType.Shared) { - throw new InvalidParameterValueException(String.format("Can execute createVLANIpRanges on shared guest network, but type of this guest network %s is %s", network, network.getGuestType())); + throw new InvalidParameterValueException(String.format("Can execute createVLANIpRanges on shared guest Network, but type of this guest Network %s is %s", network, network.getGuestType())); } final List vlans = _vlanDao.listVlansByNetworkId(network.getId()); @@ -5026,22 +5029,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.getProvider()); + 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.getProvider()); + } } private Network getNetwork(Long networkId) { @@ -5137,10 +5138,10 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati "either both netmask and gateway should be passed or both should me omited."); } else { if (!NetUtils.sameSubnet(newStartIP, newVlanGateway, newVlanNetmask)) { - throw new InvalidParameterValueException("The start ip and gateway do not belong to the same subnet"); + throw new InvalidParameterValueException("The start IP and gateway do not belong to the same subnet"); } if (!NetUtils.sameSubnet(newEndIP, newVlanGateway, newVlanNetmask)) { - throw new InvalidParameterValueException("The end ip and gateway do not belong to the same subnet"); + throw new InvalidParameterValueException("The end IP and gateway do not belong to the same subnet"); } } final String cidrnew = NetUtils.getCidrFromGatewayAndNetmask(newVlanGateway, newVlanNetmask); @@ -5169,7 +5170,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati ipv6, ip6Gateway, ip6Cidr, startIPv6, endIPv6, network); } if (newVlanGateway == null && newVlanNetmask == null && !sameSubnet) { - throw new InvalidParameterValueException("The ip range dose not belong to any of the existing subnets, Provide the netmask and gateway if you want to add new subnet"); + throw new InvalidParameterValueException("The IP range dose not belong to any of the existing subnets, Provide the netmask and gateway if you want to add new subnet"); } Pair vlanDetails = null; @@ -5577,7 +5578,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati ip.isSourceNat(), vlan.getVlanType().toString(), ip.getSystem(), usageHidden, ip.getClass().getName(), ip.getUuid()); } // increment resource count for dedicated public ip's - _resourceLimitMgr.incrementResourceCount(vlanOwner.getId(), ResourceType.public_ip, new Long(ips.size())); + _resourceLimitMgr.incrementResourceCount(vlanOwner.getId(), ResourceType.public_ip, (long)ips.size()); } else if (domain != null && !forSystemVms) { // This VLAN is domain-wide, so create a DomainVlanMapVO entry final DomainVlanMapVO domainVlanMapVO = new DomainVlanMapVO(domain.getId(), vlan.getId()); @@ -5621,7 +5622,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) { @@ -5641,24 +5642,49 @@ 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 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 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) { - updateVlanAndIpv4Range(id, vlanRange, startIp, endIp, gateway, netmask, isRangeForSystemVM, forSystemVms); + 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); @@ -5738,10 +5764,10 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final List listAllocatedIPs = _ipv6Dao.listByVlanIdAndState(id, IpAddress.State.Allocated); if (ip6Gateway != null && !ip6Gateway.equals(vlanRange.getIp6Gateway()) && (CollectionUtils.isNotEmpty(listAllocatedIPs) || CollectionUtils.isNotEmpty(ipv6Service.getAllocatedIpv6FromVlanRange(vlanRange)))) { - throw new InvalidParameterValueException(String.format("Unable to change ipv6 gateway to %s because some IPs are in use", ip6Gateway)); + throw new InvalidParameterValueException(String.format("Unable to change IPv6 gateway to %s because some IPs are in use", ip6Gateway)); } if (ip6Cidr != null && !ip6Cidr.equals(vlanRange.getIp6Cidr()) && (CollectionUtils.isNotEmpty(listAllocatedIPs) || CollectionUtils.isNotEmpty(ipv6Service.getAllocatedIpv6FromVlanRange(vlanRange)))) { - throw new InvalidParameterValueException(String.format("Unable to change ipv6 cidr to %s because some IPs are in use", ip6Cidr)); + throw new InvalidParameterValueException(String.format("Unable to change IPv6 CIDR to %s because some IPs are in use", ip6Cidr)); } ip6Gateway = MoreObjects.firstNonNull(ip6Gateway, vlanRange.getIp6Gateway()); ip6Cidr = MoreObjects.firstNonNull(ip6Cidr, vlanRange.getIp6Cidr()); @@ -5937,7 +5963,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } finally { _vlanDao.releaseFromLockTable(vlanDbId); if (resourceCountToBeDecrement > 0) { //Making sure to decrement the count of only success operations above. For any reaason if disassociation fails then this number will vary from original range length. - _resourceLimitMgr.decrementResourceCount(acctVln.get(0).getAccountId(), ResourceType.public_ip, new Long(resourceCountToBeDecrement)); + _resourceLimitMgr.decrementResourceCount(acctVln.get(0).getAccountId(), ResourceType.public_ip, (long)resourceCountToBeDecrement); } } } else { // !isAccountSpecific @@ -6045,12 +6071,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 ips = _publicIpAddressDao.listByVlanId(vlanDbId); @@ -6071,29 +6091,35 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } - if (vlanOwner != null) { - // Create an AccountVlanMapVO entry - final AccountVlanMapVO accountVlanMapVO = new AccountVlanMapVO(vlanOwner.getId(), vlan.getId()); - _accountVlanMapDao.persist(accountVlanMapVO); + // 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)) { - // generate usage event for dedication of every ip address in the range - for (final IPAddressVO ip : ips) { - final boolean usageHidden = _ipAddrMgr.isUsageHidden(ip); - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NET_IP_ASSIGN, vlanOwner.getId(), ip.getDataCenterId(), ip.getId(), ip.getAddress().toString(), ip.isSourceNat(), - vlan.getVlanType().toString(), ip.getSystem(), usageHidden, ip.getClass().getName(), ip.getUuid()); + if (vlanOwner != null) { + // Create an AccountVlanMapVO entry + final AccountVlanMapVO accountVlanMapVO = new AccountVlanMapVO(vlanOwner.getId(), vlan.getId()); + _accountVlanMapDao.persist(accountVlanMapVO); + + // generate usage event for dedication of every ip address in the range + for (final IPAddressVO ip : ips) { + final boolean usageHidden = _ipAddrMgr.isUsageHidden(ip); + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NET_IP_ASSIGN, vlanOwner.getId(), ip.getDataCenterId(), ip.getId(), ip.getAddress().toString(), ip.isSourceNat(), + vlan.getVlanType().toString(), ip.getSystem(), usageHidden, ip.getClass().getName(), ip.getUuid()); + } + } else if (domain != null) { + // Create an DomainVlanMapVO entry + DomainVlanMapVO domainVlanMapVO = new DomainVlanMapVO(domain.getId(), vlan.getId()); + _domainVlanMapDao.persist(domainVlanMapVO); } - } else if (domain != null) { - // Create an DomainVlanMapVO entry - DomainVlanMapVO domainVlanMapVO = new DomainVlanMapVO(domain.getId(), vlan.getId()); - _domainVlanMapDao.persist(domainVlanMapVO); - } - // increment resource count for dedicated public ip's - if (vlanOwner != null) { - _resourceLimitMgr.incrementResourceCount(vlanOwner.getId(), ResourceType.public_ip, new Long(ips.size())); - } + // increment resource count for dedicated public ip's + if (vlanOwner != null) { + _resourceLimitMgr.incrementResourceCount(vlanOwner.getId(), ResourceType.public_ip, (long)ips.size()); + } - return vlan; + return vlan; + + } } @Override @@ -6182,7 +6208,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } // decrement resource count for dedicated public ip's - _resourceLimitMgr.decrementResourceCount(acctVln.get(0).getAccountId(), ResourceType.public_ip, new Long(ips.size())); + _resourceLimitMgr.decrementResourceCount(acctVln.get(0).getAccountId(), ResourceType.public_ip, (long)ips.size()); success = true; } else if (isDomainSpecific && _domainVlanMapDao.remove(domainVlan.get(0).getId())) { logger.debug("Remove the vlan from domain_vlan_map successfully."); @@ -6351,7 +6377,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final List newCidrPair = new ArrayList<>(); newCidrPair.add(0, getCidrAddress(cidr)); newCidrPair.add(1, (long)getCidrSize(cidr)); - currentPodCidrSubnets.put(new Long(-1), newCidrPair); + currentPodCidrSubnets.put(-1L, newCidrPair); final DataCenterVO dcVo = _zoneDao.findById(dcId); final String guestNetworkCidr = dcVo.getGuestNetworkCidr(); @@ -6451,7 +6477,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } private String getPodName(final long podId) { - return _podDao.findById(new Long(podId)).getName(); + return _podDao.findById(podId).getName(); } private boolean validZone(final String zoneName) { @@ -6463,7 +6489,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } private String getZoneName(final long zoneId) { - final DataCenterVO zone = _zoneDao.findById(new Long(zoneId)); + final DataCenterVO zone = _zoneDao.findById(zoneId); if (zone != null) { return zone.getName(); } else { diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index 8b5e2c5a5ef..d872317fa28 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -41,6 +41,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; @@ -76,6 +77,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; @@ -335,6 +337,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C @Inject ResourceLimitService _resourceLimitMgr; @Inject + ReservationDao reservationDao; + @Inject DomainManager _domainMgr; @Inject ProjectManager _projectMgr; @@ -1152,15 +1156,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 maps = _accountVlanMapDao.listAccountVlanMapsByVlan(ipVO.getVlanId()); ipVO.setAllocatedTime(new Date()); ipVO.setAllocatedToAccountId(account.getAccountId()); @@ -1170,10 +1169,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) {