Merge release branch 4.20 to 4.22

* 4.20:
  Fix/flasharray delete rename destroy patch conflict (#13049)
  Fix VPC network offerings listing in isolated network creation form (#12645)
  Update mysql java connector version to 8.4.0 (matching version for MySQL 8.4) (#12640)
  adaptive: honor user-provided capacityBytes when provider stats are unavailable (#13059)
  Flexibilize public IP selection (#11076)
This commit is contained in:
Fabricio Duarte 2026-05-22 08:31:35 -03:00
commit 21b2025c50
22 changed files with 193 additions and 96 deletions

View File

@ -282,7 +282,7 @@ jobs:
# https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2004-Readme.md#mysql
sudo apt-get install -y mysql-server
sudo systemctl start mysql
sudo mysql -uroot -proot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''; FLUSH PRIVILEGES;"
sudo mysql -uroot -proot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY ''; FLUSH PRIVILEGES;"
sudo systemctl restart mysql
sudo mysql -uroot -e "SELECT VERSION();"

View File

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

View File

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

View File

@ -57,8 +57,8 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -53,8 +53,8 @@
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>

View File

@ -17,7 +17,7 @@
JAVA_OPTS="-Djava.security.properties=/etc/cloudstack/management/java.security.ciphers -Djava.awt.headless=true -Xmx2G -XX:+UseParallelGC -XX:MaxGCPauseMillis=500 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:ErrorFile=/var/log/cloudstack/management/cloudstack-management.err --add-opens=java.base/java.lang=ALL-UNNAMED --add-exports=java.base/sun.security.x509=ALL-UNNAMED"
CLASSPATH="/usr/share/cloudstack-management/lib/*:/etc/cloudstack/management:/usr/share/cloudstack-common:/usr/share/cloudstack-management/setup:/usr/share/cloudstack-management:/usr/share/java/mysql-connector-java.jar:/usr/share/cloudstack-mysql-ha/lib/*"
CLASSPATH="/usr/share/cloudstack-management/lib/*:/etc/cloudstack/management:/usr/share/cloudstack-common:/usr/share/cloudstack-management/setup:/usr/share/cloudstack-management:/usr/share/cloudstack-mysql-ha/lib/*"
BOOTSTRAP_CLASS=org.apache.cloudstack.ServerDaemon

View File

@ -17,7 +17,7 @@
JAVA_OPTS="-Xms256m -Xmx2048m --add-opens=java.base/java.lang=ALL-UNNAMED"
CLASSPATH="/usr/share/cloudstack-usage/*:/usr/share/cloudstack-usage/lib/*:/usr/share/cloudstack-mysql-ha/lib/*:/etc/cloudstack/usage:/usr/share/java/mysql-connector-java.jar"
CLASSPATH="/usr/share/cloudstack-usage/*:/usr/share/cloudstack-usage/lib/*:/usr/share/cloudstack-mysql-ha/lib/*:/etc/cloudstack/usage"
JAVA_CLASS=com.cloud.usage.UsageServer

View File

@ -33,8 +33,8 @@
<artifactId>globodns-client</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -41,8 +41,8 @@
<artifactId>reload4j</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -217,13 +217,14 @@ public class AdaptiveDataStoreLifeCycleImpl extends BasePrimaryDataStoreLifeCycl
// validate the provided details are correct/valid for the provider
api.validate();
// if we have user-provided capacity bytes, validate they do not exceed the manaaged storage capacity bytes
// User-provided capacityBytes always wins; validate against storage stats only when
// the provider could actually report them. If the provider cannot (empty pod with no
// footprint, no quota set, transient probe failure), fall through and use what the
// user supplied rather than failing the whole registration.
ProviderVolumeStorageStats stats = api.getManagedStorageStats();
if (capacityBytes != null && capacityBytes != 0 && stats != null) {
if (stats.getCapacityInBytes() > 0) {
if (stats.getCapacityInBytes() < capacityBytes) {
throw new InvalidParameterValueException("Capacity bytes provided exceeds the capacity of the storage endpoint: provided by user: " + capacityBytes + ", storage capacity from storage provider: " + stats.getCapacityInBytes());
}
if (capacityBytes != null && capacityBytes > 0) {
if (stats != null && stats.getCapacityInBytes() > 0 && stats.getCapacityInBytes() < capacityBytes) {
throw new InvalidParameterValueException("Provided capacity bytes exceed the capacity of the storage endpoint: provided by user: " + capacityBytes + ", storage capacity from storage provider: " + stats.getCapacityInBytes());
}
parameters.setCapacityBytes(capacityBytes);
}

View File

@ -23,7 +23,8 @@ import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@ -88,6 +89,9 @@ public class FlashArrayAdapter implements ProviderAdapter {
static final ObjectMapper mapper = new ObjectMapper();
public String pod = null;
public String hostgroup = null;
private static final DateTimeFormatter DELETION_TIMESTAMP_FORMAT =
DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneOffset.UTC);
private String username;
private String password;
private String accessToken;
@ -200,28 +204,63 @@ public class FlashArrayAdapter implements ProviderAdapter {
@Override
public void delete(ProviderAdapterContext context, ProviderAdapterDataObject dataObject) {
// first make sure we are disconnected
removeVlunsAll(context, pod, dataObject.getExternalName());
String fullName = normalizeName(pod, dataObject.getExternalName());
FlashArrayVolume volume = new FlashArrayVolume();
// Snapshots live under /volume-snapshots and already use the array's
// reserved form <volume>.<suffix>, which legitimately contains ".".
// The stricter [A-Za-z0-9_-] naming rule applies to regular volume
// names and free-form rename targets, not to these reserved snapshot
// names. Since FlashArray snapshot names are system-defined rather
// than arbitrary rename targets, we skip the usual timestamped rename
// and only mark snapshots destroyed; the array's own ".N" suffix
// already disambiguates them in the recycle bin.
if (dataObject.getType() == ProviderAdapterDataObject.Type.SNAPSHOT) {
try {
FlashArrayVolume destroy = new FlashArrayVolume();
destroy.setDestroyed(true);
PATCH("/volume-snapshots?names=" + fullName, destroy, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
});
} catch (CloudRuntimeException e) {
String msg = e.getMessage();
if (msg != null && (msg.contains("No such volume or snapshot")
|| msg.contains("Volume does not exist"))) {
return;
}
throw e;
}
return;
}
// rename as we delete so it doesn't conflict if the template or volume is ever recreated
// pure keeps the volume(s) around in a Destroyed bucket for a period of time post delete
String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new java.util.Date());
volume.setExternalName(fullName + "-" + timestamp);
// first make sure we are disconnected
removeVlunsAll(context, pod, dataObject.getExternalName());
// Rename then destroy: FlashArray keeps destroyed volumes in a recycle
// bin (default 24h) from which they can be recovered. Renaming with a
// deletion timestamp gives operators a forensic trail when browsing the
// array - they can see when each destroyed copy was deleted on the
// CloudStack side. FlashArray rejects a single PATCH that combines
// {name, destroyed}, so the rename and the destroy must be issued as
// two separate requests each carrying only its own field.
// Use UTC so the rename suffix is stable regardless of the management
// server's local timezone or DST changes - operators correlating the
// CloudStack delete event with the array's audit log get a consistent
// wall-clock value.
String timestamp = DELETION_TIMESTAMP_FORMAT.format(java.time.Instant.now());
String renamedName = fullName + "-" + timestamp;
try {
PATCH("/volumes?names=" + fullName, volume, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
FlashArrayVolume rename = new FlashArrayVolume();
rename.setExternalName(renamedName);
PATCH("/volumes?names=" + fullName, rename, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
});
// now delete it with new name
volume.setDestroyed(true);
PATCH("/volumes?names=" + fullName + "-" + timestamp, volume, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
FlashArrayVolume destroy = new FlashArrayVolume();
destroy.setDestroyed(true);
PATCH("/volumes?names=" + renamedName, destroy, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
});
} catch (CloudRuntimeException e) {
if (e.toString().contains("Volume does not exist")) {
String msg = e.getMessage();
if (msg != null && msg.contains("Volume does not exist")) {
return;
} else {
throw e;

12
pom.xml
View File

@ -168,7 +168,7 @@
<cs.libvirt-java.version>0.5.3</cs.libvirt-java.version>
<cs.mail.version>1.5.0-b01</cs.mail.version>
<cs.mustache.version>0.9.14</cs.mustache.version>
<cs.mysql.version>8.0.33</cs.mysql.version>
<cs.mysql.version>8.4.0</cs.mysql.version>
<cs.neethi.version>2.0.4</cs.neethi.version>
<cs.nitro.version>10.1</cs.nitro.version>
<cs.opensaml.version>2.6.6</cs.opensaml.version>
@ -468,8 +468,8 @@
<version>${cs.reload4j.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${cs.mysql.version}</version>
<scope>test</scope>
</dependency>
@ -484,12 +484,6 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${cs.mysql.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>

View File

@ -539,6 +539,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);
@ -945,10 +948,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);
@ -956,11 +972,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);
@ -970,38 +986,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());
}
@ -1020,10 +1037,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);
@ -1037,13 +1054,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()) {
@ -1051,15 +1068,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.
@ -1073,13 +1090,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);
if (!ipCanBeAllocated) {
throw new InsufficientAddressCapacityException(String.format("Failed to allocate public IP address [%s] as it is in quarantine.", ipAddress.getAddress()),
DataCenter.class, dcId);
boolean isPublicIpAllocatable = canPublicIpAddressBeAllocated(allocatableIp, owner);
if (!isPublicIpAllocatable) {
throw new InsufficientAddressCapacityException(String.format("Failed to allocate public IP [%s] as it is in quarantine.", allocatableIp.getAddress()), DataCenter.class, dcId);
}
}
@ -1088,12 +1104,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
@ -2546,26 +2562,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;
}
@ -2616,7 +2633,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);
}

View File

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

View File

@ -104,9 +104,10 @@ CP=./
CP=${CP}$PATHSEP$CATALINA_HOME/conf
# Add mysql jar from mysql-connector-java package to CP
# Add mysql jar from mysql-connector-j package to CP
# for Jenkins
CP=${CP}${PATHSEP}/usr/share/java/mysql-connector-java.jar
MYSQL_CONNECTOR_VERSION = '8.4.0'
CP=${CP}${PATHSEP}/usr/share/java/mysql-connector-j-${MYSQL_CONNECTOR_VERSION}.jar
for file in $CATALINA_HOME/webapps/client/WEB-INF/lib/*.jar
do

View File

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

View File

@ -58,7 +58,7 @@ RUN mvn -Pdeveloper -Dsimulator -DskipTests clean install
RUN find /var/lib/mysql -type f -exec touch {} \; && \
(/usr/bin/mysqld_safe &) && \
sleep 5; \
mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password by ''" --connect-expired-password; \
mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password by ''" --connect-expired-password; \
mvn -Pdeveloper -pl developer -Ddeploydb; \
mvn -Pdeveloper -pl developer -Ddeploydb-simulator; \
MARVIN_FILE=`find /root/tools/marvin/dist/ -name "Marvin*.tar.gz"`; \

View File

@ -46,7 +46,7 @@ setup(name="Marvin",
"marvin.sandbox.basic"],
license="LICENSE.txt",
install_requires=[
"mysql-connector-python <= 8.0.30",
"mysql-connector-python <= 8.4.0",
"requests >= 2.2.1",
"paramiko >= 1.13.0",
"nose >= 1.3.3",

View File

@ -4092,6 +4092,7 @@
"message.warn.importing.instance.without.nic": "WARNING: This Instance is being imported without NICs and many Network resources will not be available. Consider creating a NIC via vCenter before importing or as soon as the Instance is imported. For KVM host, allocate a NIC to Instance after import.",
"message.warn.select.template": "Please select a Template for Registration.",
"message.warn.zone.mtu.update": "Please note that this limit won't affect pre-existing Network's MTU settings",
"message.warn.vpc.offerings": "VPC offerings will only be shown if the selected account has at least one VPC.",
"message.webhook.deliveries.time.filter": "Webhook deliveries list can be filtered based on date-time. Select 'Custom' for specifying start and end date range.",
"message.zone.creation.complete": "Zone creation complete.",
"message.zone.detail.description": "Populate Zone details.",

View File

@ -2524,6 +2524,7 @@
"message.vr.alert.upon.network.offering.creation.others": "Como nenhum dos servi\u00e7os obrigat\u00f3rios para cria\u00e7\u00e3o do VR (VPN, DHCP, DNS, Firewall, LB, UserData, SourceNat, StaticNat, PortForwarding) foram habilitados, o VR n\u00e3o ser\u00e1 criado e a oferta de computa\u00e7\u00e3o n\u00e3o ser\u00e1 usada.",
"message.warn.filetype": "jpg, jpeg, png, bmp e svg s\u00e3o os \u00fanicos formatos de imagem suportados",
"message.warn.importing.instance.without.nic": "AVISO: essa inst\u00e2ncia est\u00e1 sendo importada sem NICs e muitos recursos de rede n\u00e3o estar\u00e3o dispon\u00edveis. Considere criar uma NIC antes de importar via VCenter ou assim que a inst\u00e2ncia for importada.",
"message.warn.vpc.offerings": "Ofertas de VPC somente ser\u00c3o exibidas caso a conta selecionada possua ao menos uma VPC.",
"message.zone.creation.complete": "Cria\u00e7\u00e3o de zona completa",
"message.zone.detail.description": "Preencha os detalhes da zona",
"message.zone.detail.hint": "Uma zona \u00e9 a maior unidade organizacional no CloudStack, e normalmente corresponde a um \u00fanico datacenter. As zonas proporcionam isolamento f\u00edsico e redund\u00e2ncia. Uma zona consiste em um ou mais pods (cada um contendo hosts e servidores de armazenamento prim\u00e1rio) e um servidor de armazenamento secund\u00e1rio que \u00e9 compartilhado por todos os pods da zona.",

View File

@ -96,6 +96,11 @@
{{ opt.displaytext || opt.name || opt.description }}
</a-select-option>
</a-select>
<a-alert type="warning" v-if="!this.hasVPC">
<template #message>
<span v-html="$t('message.warn.vpc.offerings')"/>
</template>
</a-alert>
</a-form-item>
<a-form-item ref="asnumber" name="asnumber" v-if="isASNumberRequired()">
<template #label>
@ -369,7 +374,8 @@ export default {
setMTU: false,
asNumberLoading: false,
selectedAsNumber: 0,
asNumbersZone: []
asNumbersZone: [],
hasVPC: true
}
},
watch: {
@ -515,13 +521,17 @@ export default {
if (this.vpc !== null) { // from VPC section
this.fetchNetworkOfferingData(true)
} else { // from guest network section
var params = {}
const params = {
account: this.owner.account,
projectid: this.owner.projectid,
domainid: this.owner.domainid
}
this.networkOfferingLoading = true
if ('listVPCs' in this.$store.getters.apis) {
getAPI('listVPCs', params).then(json => {
const listVPCs = json.listvpcsresponse.vpc
var vpcAvailable = this.arrayHasItems(listVPCs)
if (vpcAvailable === false) {
this.hasVPC = this.arrayHasItems(listVPCs)
if (!this.hasVPC) {
this.fetchNetworkOfferingData(false)
} else {
this.fetchNetworkOfferingData()
@ -534,7 +544,7 @@ export default {
},
fetchNetworkOfferingData (forVpc) {
this.networkOfferingLoading = true
var params = {
const params = {
zoneid: this.selectedZone.id,
guestiptype: 'Isolated',
state: 'Enabled'
@ -577,7 +587,7 @@ export default {
},
fetchVpcData () {
this.vpcLoading = true
var params = {
const params = {
listAll: true,
details: 'min'
}
@ -600,14 +610,14 @@ export default {
const formRaw = toRaw(this.form)
const values = this.handleRemoveFields(formRaw)
this.actionLoading = true
var params = {
const params = {
zoneId: this.selectedZone.id,
name: values.name,
displayText: values.displaytext,
networkOfferingId: this.selectedNetworkOffering.id
}
var usefulFields = ['gateway', 'netmask', 'cidrsize', 'startip', 'startipv4', 'endip', 'endipv4', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'sourcenatipaddress', 'externalid', 'vpcid', 'vlan', 'networkdomain']
for (var field of usefulFields) {
const usefulFields = ['gateway', 'netmask', 'cidrsize', 'startip', 'startipv4', 'endip', 'endipv4', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'sourcenatipaddress', 'externalid', 'vpcid', 'vlan', 'networkdomain']
for (const field of usefulFields) {
if (this.isValidTextValueForKey(values, field)) {
params[field] = values[field]
}

View File

@ -289,7 +289,7 @@ public class UsageSanityChecker {
}
/**
* usage something like: /usr/bin/java -Xmx2G -cp /usr/share/cloudstack-usage/*:/usr/share/cloudstack-usage/lib/*:/usr/share/cloudstack-mysql-ha/lib/*:/etc/cloudstack/usage:/usr/share/java/mysql-connector-java.jar:/usr/share/cloudstack-common com.cloud.usage.UsageSanityChecker
* usage something like: /usr/bin/java -Xmx2G -cp /usr/share/cloudstack-usage/*:/usr/share/cloudstack-usage/lib/*:/usr/share/cloudstack-mysql-ha/lib/*:/etc/cloudstack/usage:/usr/share/cloudstack-common com.cloud.usage.UsageSanityChecker
* @param args none
*/
public static void main(String[] args) {