This is an automated email from the ASF dual-hosted git repository.
DaanHoogland pushed a commit to branch 4.20
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.20 by this push:
new 9f96c9d5eb9 Flexibilize public IP selection (#11076)
9f96c9d5eb9 is described below
commit 9f96c9d5eb97f1836103918cf520d72cc3395c51
Author: Erik Böck <[email protected]>
AuthorDate: Tue Apr 21 07:53:11 2026 -0300
Flexibilize public IP selection (#11076)
---
.../cloud/network/dao/PublicIpQuarantineDao.java | 12 +++
.../network/dao/PublicIpQuarantineDaoImpl.java | 22 ++++++
.../com/cloud/network/IpAddressManagerImpl.java | 91 +++++++++++++---------
.../com/cloud/network/IpAddressManagerTest.java | 1 +
test/integration/smoke/test_quarantined_ips.py | 8 +-
5 files changed, 92 insertions(+), 42 deletions(-)
diff --git
a/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDao.java
b/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDao.java
index ccba6bb1889..606bdaaaa7a 100644
---
a/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDao.java
+++
b/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDao.java
@@ -19,9 +19,21 @@ package com.cloud.network.dao;
import com.cloud.network.vo.PublicIpQuarantineVO;
import com.cloud.utils.db.GenericDao;
+import java.util.Date;
+import java.util.List;
+
public interface PublicIpQuarantineDao extends
GenericDao<PublicIpQuarantineVO, Long> {
PublicIpQuarantineVO findByPublicIpAddressId(long publicIpAddressId);
PublicIpQuarantineVO findByIpAddress(String publicIpAddress);
+
+ /**
+ * Returns a list of public IP addresses that are actively quarantined at
the specified date and the previous owner differs from the specified user.
+ *
+ * @param userId used to check against the IP's previous owner;
+ * @param date used to check if the quarantine is active;
+ * @return a list of PublicIpQuarantineVOs.
+ */
+ List<PublicIpQuarantineVO> listQuarantinedIpAddressesToUser(Long userId,
Date date);
}
diff --git
a/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDaoImpl.java
b/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDaoImpl.java
index a1b789b8a46..0c47a0d36e3 100644
---
a/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDaoImpl.java
+++
b/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDaoImpl.java
@@ -26,6 +26,8 @@ import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
+import java.util.Date;
+import java.util.List;
@Component
public class PublicIpQuarantineDaoImpl extends
GenericDaoBase<PublicIpQuarantineVO, Long> implements PublicIpQuarantineDao {
@@ -33,6 +35,8 @@ public class PublicIpQuarantineDaoImpl extends
GenericDaoBase<PublicIpQuarantine
private SearchBuilder<IPAddressVO> ipAddressSearchBuilder;
+ private SearchBuilder<PublicIpQuarantineVO> quarantinedIpAddressesSearch;
+
@Inject
IPAddressDao ipAddressDao;
@@ -47,8 +51,16 @@ public class PublicIpQuarantineDaoImpl extends
GenericDaoBase<PublicIpQuarantine
publicIpAddressByIdSearch.join("quarantineJoin",
ipAddressSearchBuilder, ipAddressSearchBuilder.entity().getId(),
publicIpAddressByIdSearch.entity().getPublicIpAddressId(),
JoinBuilder.JoinType.INNER);
+ quarantinedIpAddressesSearch = createSearchBuilder();
+ quarantinedIpAddressesSearch.and("previousOwnerId",
quarantinedIpAddressesSearch.entity().getPreviousOwnerId(),
SearchCriteria.Op.NEQ);
+ quarantinedIpAddressesSearch.and();
+ quarantinedIpAddressesSearch.op("removedIsNull",
quarantinedIpAddressesSearch.entity().getRemoved(), SearchCriteria.Op.NULL);
+ quarantinedIpAddressesSearch.and("endDate",
quarantinedIpAddressesSearch.entity().getEndDate(), SearchCriteria.Op.GT);
+ quarantinedIpAddressesSearch.cp();
+
ipAddressSearchBuilder.done();
publicIpAddressByIdSearch.done();
+ quarantinedIpAddressesSearch.done();
}
@Override
@@ -68,4 +80,14 @@ public class PublicIpQuarantineDaoImpl extends
GenericDaoBase<PublicIpQuarantine
return findOneBy(sc, filter);
}
+
+ @Override
+ public List<PublicIpQuarantineVO> listQuarantinedIpAddressesToUser(Long
userId, Date date) {
+ SearchCriteria<PublicIpQuarantineVO> sc =
quarantinedIpAddressesSearch.create();
+
+ sc.setParameters("previousOwnerId", userId);
+ sc.setParameters("endDate", date);
+
+ return searchIncludingRemoved(sc, null, false, false);
+ }
}
diff --git a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java
b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java
index fe555af9d50..c5ca392a437 100644
--- a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java
@@ -514,6 +514,9 @@ public class IpAddressManagerImpl extends ManagerBase
implements IpAddressManage
AssignIpAddressSearch.and("allocated",
AssignIpAddressSearch.entity().getAllocatedTime(), Op.NULL);
AssignIpAddressSearch.and("vlanId",
AssignIpAddressSearch.entity().getVlanId(), Op.IN);
AssignIpAddressSearch.and("forSystemVms",
AssignIpAddressSearch.entity().isForSystemVms(), Op.EQ);
+ AssignIpAddressSearch.and("id",
AssignIpAddressSearch.entity().getId(), Op.NIN);
+ AssignIpAddressSearch.and("requestedAddress",
AssignIpAddressSearch.entity().getAddress(), Op.EQ);
+ AssignIpAddressSearch.and("routerAddress",
AssignIpAddressSearch.entity().getAddress(), Op.NEQ);
SearchBuilder<VlanVO> vlanSearch = _vlanDao.createSearchBuilder();
vlanSearch.and("type", vlanSearch.entity().getVlanType(), Op.EQ);
@@ -883,10 +886,23 @@ public class IpAddressManagerImpl extends ManagerBase
implements IpAddressManage
if (podId != null) {
sc = AssignIpAddressFromPodVlanSearch.create();
sc.setJoinParameters("podVlanMapSB", "podId", podId);
- errorMessage.append(" pod id=" + podId);
+ errorMessage.append(" pod id=").append(podId);
} else {
sc = AssignIpAddressSearch.create();
- errorMessage.append(" zone id=" + dcId);
+ errorMessage.append(" zone id=").append(dcId);
+ }
+
+ if (lockOneRow) {
+ logger.debug("Listing quarantined public IPs to ignore on search
for public IP for system VM. The IPs ignored will be the ones that: were not
associated to account [{}]; were not removed yet; and with quarantine end dates
after [{}].", owner.getUuid(), new Date());
+
+ List<PublicIpQuarantineVO> quarantinedAddresses =
publicIpQuarantineDao.listQuarantinedIpAddressesToUser(owner.getId(), new
Date());
+ List<Long> quarantinedAddressesIDs =
quarantinedAddresses.stream().map(PublicIpQuarantineVO::getPublicIpAddressId).collect(Collectors.toList());
+
+ logger.debug("Found addresses with the following IDs: [{}] that
will be ignored when searching for available public IPs.",
quarantinedAddressesIDs);
+
+ if (CollectionUtils.isNotEmpty(quarantinedAddressesIDs)) {
+ sc.setParameters("id", quarantinedAddressesIDs.toArray());
+ }
}
sc.setParameters("dc", dcId);
@@ -894,11 +910,11 @@ public class IpAddressManagerImpl extends ManagerBase
implements IpAddressManage
// for direct network take ip addresses only from the vlans belonging
to the network
if (vlanUse == VlanType.DirectAttached) {
sc.setJoinParameters("vlan", "networkId", guestNetworkId);
- errorMessage.append(", network id=" + guestNetworkId);
+ errorMessage.append(", network id=").append(guestNetworkId);
}
if (requestedGateway != null) {
sc.setJoinParameters("vlan", "vlanGateway", requestedGateway);
- errorMessage.append(", requested gateway=" + requestedGateway);
+ errorMessage.append(", requested
gateway=").append(requestedGateway);
}
sc.setJoinParameters("vlan", "type", vlanUse);
@@ -908,38 +924,39 @@ public class IpAddressManagerImpl extends ManagerBase
implements IpAddressManage
NetworkDetailVO routerIpDetail =
_networkDetailsDao.findDetail(network.getId(), ApiConstants.ROUTER_IP);
routerIpAddress = routerIpDetail != null ?
routerIpDetail.getValue() : null;
}
+
if (requestedIp != null) {
- sc.addAnd("address", SearchCriteria.Op.EQ, requestedIp);
- errorMessage.append(": requested ip " + requestedIp + " is not
available");
+ sc.setParameters("requestedAddress", requestedIp);
+ errorMessage.append(": requested ip
").append(requestedIp).append(" is not available");
} else if (routerIpAddress != null) {
- sc.addAnd("address", Op.NEQ, routerIpAddress);
+ sc.setParameters("routerAddress", routerIpAddress);
}
boolean ascOrder = ! forSystemVms;
- Filter filter = new Filter(IPAddressVO.class, "forSystemVms",
ascOrder, 0l, 1l);
+ Filter filter = new Filter(IPAddressVO.class, "forSystemVms",
ascOrder, 0L, 1L);
filter.addOrderBy(IPAddressVO.class,"vlanId", true);
- List<IPAddressVO> addrs = new ArrayList<>();
+ List<IPAddressVO> addresses = new ArrayList<>();
if (forSystemVms) {
// Get Public IPs for system vms in dedicated ranges
sc.setParameters("forSystemVms", true);
if (lockOneRow) {
- addrs = _ipAddressDao.lockRows(sc, filter, true);
+ addresses = _ipAddressDao.lockRows(sc, filter, true);
} else {
- addrs = new ArrayList<>(_ipAddressDao.search(sc, null));
+ addresses = new ArrayList<>(_ipAddressDao.search(sc, null));
}
}
- if ((!lockOneRow || (lockOneRow && CollectionUtils.isEmpty(addrs))) &&
+ if ((!lockOneRow || (lockOneRow &&
CollectionUtils.isEmpty(addresses))) &&
!(forSystemVms &&
SystemVmPublicIpReservationModeStrictness.value())) {
sc.setParameters("forSystemVms", false);
// If owner has dedicated Public IP ranges, fetch IP from the
dedicated range
// Otherwise fetch IP from the system pool
// Checking if network is null in the case of system VM's. At the
time of allocation of IP address to systemVm, no network is present.
if (network == null || !(network.getGuestType() ==
GuestType.Shared && zone.getNetworkType() == NetworkType.Advanced)) {
- List<AccountVlanMapVO> maps =
_accountVlanMapDao.listAccountVlanMapsByAccount(owner.getId());
- for (AccountVlanMapVO map : maps) {
+ List<AccountVlanMapVO> accountVlanMaps =
_accountVlanMapDao.listAccountVlanMapsByAccount(owner.getId());
+ for (AccountVlanMapVO map : accountVlanMaps) {
if (vlanDbIds == null ||
vlanDbIds.contains(map.getVlanDbId()))
dedicatedVlanDbIds.add(map.getVlanDbId());
}
@@ -958,10 +975,10 @@ public class IpAddressManagerImpl extends ManagerBase
implements IpAddressManage
if (!dedicatedVlanDbIds.isEmpty()) {
fetchFromDedicatedRange = true;
sc.setParameters("vlanId", dedicatedVlanDbIds.toArray());
- errorMessage.append(", vlanId id=" +
Arrays.toString(dedicatedVlanDbIds.toArray()));
+ errorMessage.append(", vlanId
id=").append(Arrays.toString(dedicatedVlanDbIds.toArray()));
} else if (!nonDedicatedVlanDbIds.isEmpty()) {
sc.setParameters("vlanId",
nonDedicatedVlanDbIds.toArray());
- errorMessage.append(", vlanId id=" +
Arrays.toString(nonDedicatedVlanDbIds.toArray()));
+ errorMessage.append(", vlanId
id=").append(Arrays.toString(nonDedicatedVlanDbIds.toArray()));
} else {
if (podId != null) {
InsufficientAddressCapacityException ex = new
InsufficientAddressCapacityException("Insufficient address capacity",
Pod.class, podId);
@@ -975,13 +992,13 @@ public class IpAddressManagerImpl extends ManagerBase
implements IpAddressManage
}
}
if (lockOneRow) {
- addrs = _ipAddressDao.lockRows(sc, filter, true);
+ addresses = _ipAddressDao.lockRows(sc, filter, true);
} else {
- addrs = new ArrayList<>(_ipAddressDao.search(sc, null));
+ addresses = new ArrayList<>(_ipAddressDao.search(sc, null));
}
// If all the dedicated IPs of the owner are in use fetch an IP
from the system pool
- if ((!lockOneRow || (lockOneRow && addrs.size() == 0)) &&
fetchFromDedicatedRange && vlanUse == VlanType.VirtualNetwork) {
+ if ((!lockOneRow || (lockOneRow && addresses.isEmpty())) &&
fetchFromDedicatedRange && vlanUse == VlanType.VirtualNetwork) {
// Verify if account is allowed to acquire IPs from the system
boolean useSystemIps =
UseSystemPublicIps.valueIn(owner.getId());
if (useSystemIps && !nonDedicatedVlanDbIds.isEmpty()) {
@@ -989,15 +1006,15 @@ public class IpAddressManagerImpl extends ManagerBase
implements IpAddressManage
sc.setParameters("vlanId",
nonDedicatedVlanDbIds.toArray());
errorMessage.append(", vlanId id=" +
Arrays.toString(nonDedicatedVlanDbIds.toArray()));
if (lockOneRow) {
- addrs = _ipAddressDao.lockRows(sc, filter, true);
+ addresses = _ipAddressDao.lockRows(sc, filter, true);
} else {
- addrs.addAll(_ipAddressDao.search(sc, null));
+ addresses.addAll(_ipAddressDao.search(sc, null));
}
}
}
}
- if (lockOneRow && addrs.size() == 0) {
+ if (lockOneRow && addresses.isEmpty()) {
if (podId != null) {
InsufficientAddressCapacityException ex = new
InsufficientAddressCapacityException("Insufficient address capacity",
Pod.class, podId);
// for now, we hardcode the table names, but we should ideally
do a lookup for the tablename from the VO object.
@@ -1011,13 +1028,12 @@ public class IpAddressManagerImpl extends ManagerBase
implements IpAddressManage
}
if (lockOneRow) {
- assert (addrs.size() == 1) : "Return size is incorrect: " +
addrs.size();
- IpAddress ipAddress = addrs.get(0);
- boolean ipCanBeAllocated =
canPublicIpAddressBeAllocated(ipAddress, owner);
+ IPAddressVO allocatableIp = addresses.get(0);
+
+ boolean isPublicIpAllocatable =
canPublicIpAddressBeAllocated(allocatableIp, owner);
- if (!ipCanBeAllocated) {
- throw new
InsufficientAddressCapacityException(String.format("Failed to allocate public
IP address [%s] as it is in quarantine.", ipAddress.getAddress()),
- DataCenter.class, dcId);
+ if (!isPublicIpAllocatable) {
+ throw new
InsufficientAddressCapacityException(String.format("Failed to allocate public
IP [%s] as it is in quarantine.", allocatableIp.getAddress()),
DataCenter.class, dcId);
}
}
@@ -1026,12 +1042,12 @@ public class IpAddressManagerImpl extends ManagerBase
implements IpAddressManage
try {
_resourceLimitMgr.checkResourceLimit(owner,
ResourceType.public_ip);
} catch (ResourceAllocationException ex) {
- logger.warn("Failed to allocate resource of type " +
ex.getResourceType() + " for account " + owner);
+ logger.warn("Failed to allocate resource of type {} for
account {}", ex.getResourceType(), owner);
throw new AccountLimitException("Maximum number of public IP
addresses for account: " + owner.getAccountName() + " has been exceeded.");
}
}
- return addrs;
+ return addresses;
}
@DB
@@ -2458,26 +2474,27 @@ public class IpAddressManagerImpl extends ManagerBase
implements IpAddressManage
PublicIpQuarantineVO publicIpQuarantineVO =
publicIpQuarantineDao.findByPublicIpAddressId(ip.getId());
if (publicIpQuarantineVO == null) {
- logger.debug(String.format("Public IP address [%s] is not in
quarantine; therefore, it is allowed to be allocated.", ip));
+ logger.debug("Public IP address [{}] is not in quarantine;
therefore, it is allowed to be allocated.", ip);
return true;
}
if (!isPublicIpAddressStillInQuarantine(publicIpQuarantineVO, new
Date())) {
- logger.debug(String.format("Public IP address [%s] is no longer in
quarantine; therefore, it is allowed to be allocated.", ip));
+ logger.debug("Public IP address [{}] is no longer in quarantine;
therefore, it is allowed to be allocated.", ip);
+ removePublicIpAddressFromQuarantine(publicIpQuarantineVO.getId(),
"IP was removed from quarantine because it was no longer in quarantine.");
return true;
}
Account previousOwner =
_accountMgr.getAccount(publicIpQuarantineVO.getPreviousOwnerId());
if (Objects.equals(previousOwner.getUuid(), newOwner.getUuid())) {
- logger.debug(String.format("Public IP address [%s] is in
quarantine; however, the Public IP previous owner [%s] is the same as the new
owner [%s]; therefore the IP" +
- " can be allocated. The public IP address will be removed
from quarantine.", ip, previousOwner, newOwner));
+ logger.debug("Public IP address [{}] is in quarantine; however,
the Public IP previous owner [{}] is the same as the new owner [{}]; therefore
the IP" +
+ " can be allocated. The public IP address will be removed
from quarantine.", ip, previousOwner, newOwner);
removePublicIpAddressFromQuarantine(publicIpQuarantineVO.getId(),
"IP was removed from quarantine because it has been allocated by the previous
owner");
return true;
}
- logger.error(String.format("Public IP address [%s] is in quarantine
and the previous owner [%s] is different than the new owner [%s]; therefore,
the IP cannot be " +
- "allocated.", ip, previousOwner, newOwner));
+ logger.error("Public IP address [{}] is in quarantine and the previous
owner [{}] is different than the new owner [{}]; therefore, the IP cannot be " +
+ "allocated.", ip, previousOwner, newOwner);
return false;
}
@@ -2528,7 +2545,7 @@ public class IpAddressManagerImpl extends ManagerBase
implements IpAddressManage
publicIpQuarantineVO.setRemovalReason(removalReason);
publicIpQuarantineVO.setRemoverAccountId(removerAccountId);
- logger.debug(String.format("Removing public IP Address [%s] from
quarantine by updating the removed date to [%s].", ipAddress, removedDate));
+ logger.debug("Removing public IP Address [{}] from quarantine by
updating the removed date to [{}].", ipAddress, removedDate);
publicIpQuarantineDao.persist(publicIpQuarantineVO);
}
diff --git a/server/src/test/java/com/cloud/network/IpAddressManagerTest.java
b/server/src/test/java/com/cloud/network/IpAddressManagerTest.java
index 824d4ee4701..cf3a886ce99 100644
--- a/server/src/test/java/com/cloud/network/IpAddressManagerTest.java
+++ b/server/src/test/java/com/cloud/network/IpAddressManagerTest.java
@@ -356,6 +356,7 @@ public class IpAddressManagerTest {
Mockito.when(ipAddressMock.getId()).thenReturn(dummyID);
Mockito.when(publicIpQuarantineDaoMock.findByPublicIpAddressId(Mockito.anyLong())).thenReturn(publicIpQuarantineVOMock);
Mockito.doReturn(false).when(ipAddressManager).isPublicIpAddressStillInQuarantine(Mockito.any(PublicIpQuarantineVO.class),
Mockito.any(Date.class));
+
Mockito.doNothing().when(ipAddressManager).removePublicIpAddressFromQuarantine(Mockito.anyLong(),
Mockito.anyString());
boolean result =
ipAddressManager.canPublicIpAddressBeAllocated(ipAddressMock, newOwnerMock);
diff --git a/test/integration/smoke/test_quarantined_ips.py
b/test/integration/smoke/test_quarantined_ips.py
index 42349fd2a53..2469760da13 100644
--- a/test/integration/smoke/test_quarantined_ips.py
+++ b/test/integration/smoke/test_quarantined_ips.py
@@ -85,7 +85,7 @@ class TestQuarantineIPs(cloudstackTestCase):
self.services["root_admin"]["roletype"])
"""
- Set public.ip.address.quarantine.duration to 60 minutes
+ Set public.ip.address.quarantine.duration to 1 minute
"""
update_configuration_cmd = updateConfiguration.updateConfigurationCmd()
update_configuration_cmd.name = "public.ip.address.quarantine.duration"
@@ -168,8 +168,7 @@ class TestQuarantineIPs(cloudstackTestCase):
zoneid=self.zone.id,
vpcid=root_vpc.id,
ipaddress=ip_address)
- self.assertIn(f"Failed to allocate public IP address [{ip_address}] as
it is in quarantine.",
- exception.exception.errorMsg)
+ self.assertIn("errorCode: 533", exception.exception.errorMsg)
# Owner should be able to allocate its IP in quarantine
public_ip = PublicIPAddress.create(self.domain_admin_apiclient,
@@ -267,8 +266,7 @@ class TestQuarantineIPs(cloudstackTestCase):
zoneid=self.zone.id,
networkid=root_network.id,
ipaddress=ip_address)
- self.assertIn(f"Failed to allocate public IP address [{ip_address}] as
it is in quarantine.",
- exception.exception.errorMsg)
+ self.assertIn("errorCode: 533", exception.exception.errorMsg)
# Owner should be able to allocate its IP in quarantine
public_ip = PublicIPAddress.create(self.domain_admin_apiclient,