Compare commits

...

7 Commits

Author SHA1 Message Date
Suresh Kumar Anaparti 2b295bf3e9
Merge 77073a36b8 into cd5bb09d0d 2026-01-22 09:59:55 +00:00
Abhisar Sinha cd5bb09d0d
Fix potential leaks in executePipedCommands (#12478) 2026-01-22 10:59:41 +01:00
Wei Zhou b5e9178078
UI: fix issues when deploy VNF applicance on network with SG (#12436) 2026-01-22 10:56:03 +01:00
Suresh Kumar Anaparti 77073a36b8
review 2026-01-21 15:16:00 +05:30
Suresh Kumar Anaparti 5e0024f678
review 2026-01-21 13:33:20 +05:30
Suresh Kumar Anaparti 0659e8d973
Update snapshot physical size 2026-01-21 12:37:06 +05:30
Suresh Kumar Anaparti 71884a1501
Update snapshot size for the primary storage resource after snapshot creation and during resource count recalculation 2026-01-20 18:27:39 +05:30
11 changed files with 102 additions and 27 deletions

View File

@ -244,8 +244,7 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
}
private Snapshot.LocationType getLocationType() {
if (Snapshot.LocationType.values() == null || Snapshot.LocationType.values().length == 0 || locationType == null) {
if (locationType == null) {
return null;
}

View File

@ -110,4 +110,18 @@ StateDao<ObjectInDataStoreStateMachine.State, ObjectInDataStoreStateMachine.Even
void updateDisplayForSnapshotStoreRole(long snapshotId, long storeId, DataStoreRole role, boolean display);
int expungeBySnapshotList(List<Long> snapshotIds, Long batchSize);
/**
* Returns the total physical size, in bytes, of all snapshots stored on primary
* storage for the specified account that have not yet been backed up to
* secondary storage.
*
* <p>If no such snapshots are found, this method returns {@code 0}.</p>
*
* @param accountId the ID of the account whose snapshots on primary storage
* should be considered
* @return the total physical size in bytes of matching snapshots on primary
* storage, or {@code 0} if none are found
*/
long getSnapshotsPhysicalSizeOnPrimaryStorageByAccountId(long accountId);
}

View File

@ -78,6 +78,15 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
" order by created %s " +
" limit 1";
private static final String GET_PHYSICAL_SIZE_OF_SNAPSHOTS_ON_PRIMARY_BY_ACCOUNT = "SELECT SUM(s.physical_size) " +
"FROM cloud.snapshot_store_ref s " +
"LEFT JOIN cloud.snapshots ON s.snapshot_id = snapshots.id " +
"WHERE snapshots.account_id = ? " +
"AND snapshots.removed IS NULL " +
"AND s.state = 'Ready' " +
"AND s.store_role = 'Primary' " +
"AND NOT EXISTS (SELECT 1 FROM cloud.snapshot_store_ref i WHERE i.snapshot_id = s.snapshot_id AND i.store_role = 'Image')";
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
super.configure(name, params);
@ -118,7 +127,6 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
stateSearch.and(STATE, stateSearch.entity().getState(), SearchCriteria.Op.IN);
stateSearch.done();
idStateNeqSearch = createSearchBuilder();
idStateNeqSearch.and(SNAPSHOT_ID, idStateNeqSearch.entity().getSnapshotId(), SearchCriteria.Op.EQ);
idStateNeqSearch.and(STATE, idStateNeqSearch.entity().getState(), SearchCriteria.Op.NEQ);
@ -578,4 +586,23 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
sc.setParameters("snapshotIds", snapshotIds.toArray());
return batchExpunge(sc, batchSize);
}
@Override
public long getSnapshotsPhysicalSizeOnPrimaryStorageByAccountId(long accountId) {
long snapshotsPhysicalSize = 0;
try (TransactionLegacy transactionLegacy = TransactionLegacy.currentTxn()) {
try (PreparedStatement preparedStatement = transactionLegacy.prepareStatement(GET_PHYSICAL_SIZE_OF_SNAPSHOTS_ON_PRIMARY_BY_ACCOUNT)) {
preparedStatement.setLong(1, accountId);
try (ResultSet resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
snapshotsPhysicalSize = resultSet.getLong(1);
}
}
}
} catch (SQLException e) {
logger.warn("Failed to get the snapshots physical size for the account [{}] due to [{}].", accountId, e.getMessage(), e);
}
return snapshotsPhysicalSize;
}
}

View File

@ -1171,7 +1171,6 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim
}
return Transaction.execute((TransactionCallback<Long>) status -> {
long newResourceCount = 0L;
List<Long> domainIdList = childDomains.stream().map(DomainVO::getId).collect(Collectors.toList());
domainIdList.add(domainId);
List<Long> accountIdList = accounts.stream().map(AccountVO::getId).collect(Collectors.toList());
@ -1189,6 +1188,7 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim
List<ResourceCountVO> resourceCounts = _resourceCountDao.lockRows(rowIdsToLock);
long oldResourceCount = 0L;
long newResourceCount = 0L;
ResourceCountVO domainRC = null;
// calculate project count here
@ -1210,7 +1210,7 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim
if (oldResourceCount != newResourceCount) {
domainRC.setCount(newResourceCount);
_resourceCountDao.update(domainRC.getId(), domainRC);
logger.warn("Discrepency in the resource count has been detected (original count = {} correct count = {}) for Type = {} for Domain ID = {} is fixed during resource count recalculation.",
logger.warn("Discrepancy in the resource count has been detected (original count = {} correct count = {}) for Type = {} for Domain ID = {} is fixed during resource count recalculation.",
oldResourceCount, newResourceCount, type, domainId);
}
return newResourceCount;
@ -1436,16 +1436,17 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim
}
protected long calculatePrimaryStorageForAccount(long accountId, String tag) {
long snapshotsPhysicalSizeOnPrimaryStorage = _snapshotDataStoreDao.getSnapshotsPhysicalSizeOnPrimaryStorageByAccountId(accountId);
if (StringUtils.isEmpty(tag)) {
List<Long> virtualRouters = _vmDao.findIdsOfAllocatedVirtualRoutersForAccount(accountId);
return _volumeDao.primaryStorageUsedForAccount(accountId, virtualRouters);
return snapshotsPhysicalSizeOnPrimaryStorage + _volumeDao.primaryStorageUsedForAccount(accountId, virtualRouters);
}
long storage = 0;
List<VolumeVO> volumes = getVolumesWithAccountAndTag(accountId, tag);
for (VolumeVO volume : volumes) {
storage += volume.getSize() == null ? 0L : volume.getSize();
}
return storage;
return snapshotsPhysicalSizeOnPrimaryStorage + storage;
}
@Override
@ -2143,7 +2144,6 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim
protected class ResourceCountCheckTask extends ManagedContextRunnable {
public ResourceCountCheckTask() {
}
@Override

View File

@ -276,6 +276,15 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
return !DataCenter.Type.Edge.equals(zone.getType());
}
private ResourceType getStoreResourceType(long dataCenterId, Snapshot.LocationType locationType) {
ResourceType storeResourceType = ResourceType.secondary_storage;
if (!isBackupSnapshotToSecondaryForZone(dataCenterId) ||
Snapshot.LocationType.PRIMARY.equals(locationType)) {
storeResourceType = ResourceType.primary_storage;
}
return storeResourceType;
}
@Override
public String getConfigComponentName() {
return SnapshotManager.class.getSimpleName();
@ -614,7 +623,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
_snapshotDao.update(snapshot.getId(), snapshot);
snapshotInfo = this.snapshotFactory.getSnapshot(snapshotId, store);
Long snapshotOwnerId = vm.getAccountId();
long snapshotOwnerId = vm.getAccountId();
try {
SnapshotStrategy snapshotStrategy = _storageStrategyFactory.getSnapshotStrategy(snapshot, SnapshotOperation.BACKUP);
@ -622,7 +631,6 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
throw new CloudRuntimeException(String.format("Unable to find Snapshot strategy to handle Snapshot [%s]", snapshot));
}
snapshotInfo = snapshotStrategy.backupSnapshot(snapshotInfo);
} catch (Exception e) {
logger.debug("Failed to backup Snapshot from Instance Snapshot", e);
_resourceLimitMgr.decrementResourceCount(snapshotOwnerId, ResourceType.snapshot);
@ -771,12 +779,11 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
_accountMgr.checkAccess(caller, null, true, snapshotCheck);
SnapshotStrategy snapshotStrategy = _storageStrategyFactory.getSnapshotStrategy(snapshotCheck, zoneId, SnapshotOperation.DELETE);
if (snapshotStrategy == null) {
logger.error("Unable to find snapshot strategy to handle snapshot [{}]", snapshotCheck);
return false;
}
Pair<List<SnapshotDataStoreVO>, List<Long>> storeRefAndZones = getStoreRefsAndZonesForSnapshotDelete(snapshotId, zoneId);
List<SnapshotDataStoreVO> snapshotStoreRefs = storeRefAndZones.first();
List<Long> zoneIds = storeRefAndZones.second();
@ -1472,8 +1479,9 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_CREATE, snapshot.getAccountId(), snapshot.getDataCenterId(), snapshotId, snapshot.getName(), null, null,
snapshotStoreRef.getPhysicalSize(), volume.getSize(), snapshot.getClass().getName(), snapshot.getUuid());
ResourceType storeResourceType = dataStoreRole == DataStoreRole.Image ? ResourceType.secondary_storage : ResourceType.primary_storage;
// Correct the resource count of snapshot in case of delta snapshots.
_resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), ResourceType.secondary_storage, new Long(volume.getSize() - snapshotStoreRef.getPhysicalSize()));
_resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), storeResourceType, new Long(volume.getSize() - snapshotStoreRef.getPhysicalSize()));
if (!payload.getAsyncBackup() && backupSnapToSecondary) {
copyNewSnapshotToZones(snapshotId, snapshot.getDataCenterId(), payload.getZoneIds());
@ -1485,15 +1493,17 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
if (logger.isDebugEnabled()) {
logger.debug("Failed to create snapshot" + cre.getLocalizedMessage());
}
ResourceType storeResourceType = getStoreResourceType(volume.getDataCenterId(), payload.getLocationType());
_resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), ResourceType.snapshot);
_resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), ResourceType.secondary_storage, new Long(volume.getSize()));
_resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), storeResourceType, new Long(volume.getSize()));
throw cre;
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to create snapshot", e);
}
ResourceType storeResourceType = getStoreResourceType(volume.getDataCenterId(), payload.getLocationType());
_resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), ResourceType.snapshot);
_resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), ResourceType.secondary_storage, new Long(volume.getSize()));
_resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), storeResourceType, new Long(volume.getSize()));
throw new CloudRuntimeException("Failed to create snapshot", e);
}
return snapshot;
@ -1695,11 +1705,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
Type snapshotType = getSnapshotType(policyId);
Account owner = _accountMgr.getAccount(volume.getAccountId());
ResourceType storeResourceType = ResourceType.secondary_storage;
if (!isBackupSnapshotToSecondaryForZone(volume.getDataCenterId()) ||
Snapshot.LocationType.PRIMARY.equals(locationType)) {
storeResourceType = ResourceType.primary_storage;
}
ResourceType storeResourceType = getStoreResourceType(volume.getDataCenterId(), locationType);
try {
_resourceLimitMgr.checkResourceLimit(owner, ResourceType.snapshot);
_resourceLimitMgr.checkResourceLimit(owner, storeResourceType, volume.getSize());

View File

@ -28,6 +28,7 @@ import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.TaggedResourceLimitAndCountResponse;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.reservation.dao.ReservationDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
@ -118,6 +119,8 @@ public class ResourceLimitManagerImplTest extends TestCase {
VolumeDao volumeDao;
@Mock
UserVmDao userVmDao;
@Mock
SnapshotDataStoreDao snapshotDataStoreDao;
private List<String> hostTags = List.of("htag1", "htag2", "htag3");
private List<String> storageTags = List.of("stag1", "stag2");
@ -840,12 +843,13 @@ public class ResourceLimitManagerImplTest extends TestCase {
String tag = null;
Mockito.when(vmDao.findIdsOfAllocatedVirtualRoutersForAccount(accountId))
.thenReturn(List.of(1L));
Mockito.when(snapshotDataStoreDao.getSnapshotsPhysicalSizeOnPrimaryStorageByAccountId(accountId)).thenReturn(100L);
Mockito.when(volumeDao.primaryStorageUsedForAccount(Mockito.eq(accountId), Mockito.anyList())).thenReturn(100L);
Assert.assertEquals(100L, resourceLimitManager.calculatePrimaryStorageForAccount(accountId, tag));
Assert.assertEquals(200L, resourceLimitManager.calculatePrimaryStorageForAccount(accountId, tag));
tag = "";
Mockito.when(volumeDao.primaryStorageUsedForAccount(Mockito.eq(accountId), Mockito.anyList())).thenReturn(200L);
Assert.assertEquals(200L, resourceLimitManager.calculatePrimaryStorageForAccount(accountId, tag));
Assert.assertEquals(300L, resourceLimitManager.calculatePrimaryStorageForAccount(accountId, tag));
tag = "tag";
VolumeVO vol = Mockito.mock(VolumeVO.class);
@ -853,7 +857,7 @@ public class ResourceLimitManagerImplTest extends TestCase {
Mockito.when(vol.getSize()).thenReturn(size);
List<VolumeVO> vols = List.of(vol, vol);
Mockito.doReturn(vols).when(resourceLimitManager).getVolumesWithAccountAndTag(accountId, tag);
Assert.assertEquals(vols.size() * size, resourceLimitManager.calculatePrimaryStorageForAccount(accountId, tag));
Assert.assertEquals((vols.size() * size) + 100L, resourceLimitManager.calculatePrimaryStorageForAccount(accountId, tag));
}
@Test

View File

@ -2527,7 +2527,7 @@
"label.vnf.app.action.reinstall": "Reinstall VNF Appliance",
"label.vnf.cidr.list": "CIDR from which access to the VNF appliance's Management interface should be allowed from",
"label.vnf.cidr.list.tooltip": "the CIDR list to forward traffic from to the VNF management interface. Multiple entries must be separated by a single comma character (,). The default value is 0.0.0.0/0.",
"label.vnf.configure.management": "Configure Firewall and Port Forwarding rules for VNF's management interfaces",
"label.vnf.configure.management": "Configure network rules for VNF's management interfaces",
"label.vnf.configure.management.tooltip": "True by default, security group or network rules (source nat and firewall rules) will be configured for VNF management interfaces. False otherwise. Learn what rules are configured at http://docs.cloudstack.apache.org/en/latest/adminguide/networking/vnf_templates_appliances.html#deploying-vnf-appliances",
"label.vnf.detail.add": "Add VNF detail",
"label.vnf.detail.remove": "Remove VNF detail",

View File

@ -356,7 +356,10 @@ export default {
permission: ['listVnfAppliances'],
resourceType: 'UserVm',
params: () => {
return { details: 'servoff,tmpl,nics', isvnf: true }
return {
details: 'group,nics,secgrp,tmpl,servoff,diskoff,iso,volume,affgrp,backoff,vnfnics',
isvnf: true
}
},
columns: () => {
const fields = ['name', 'state', 'ipaddress']

View File

@ -1305,7 +1305,7 @@ export default {
for (const deviceId of managementDeviceIds) {
if (this.vnfNicNetworks && this.vnfNicNetworks[deviceId] &&
((this.vnfNicNetworks[deviceId].type === 'Isolated' && this.vnfNicNetworks[deviceId].vpcid === undefined) ||
(this.vnfNicNetworks[deviceId].type === 'Shared' && this.zone.securitygroupsenabled))) {
(this.vnfNicNetworks[deviceId].type === 'Shared' && this.vnfNicNetworks[deviceId].service.filter(svc => svc.name === 'SecurityGroupProvider')))) {
return true
}
}

View File

@ -120,7 +120,7 @@ export default {
methods: {
fetchData () {
var params = {
details: 'servoff,tmpl,nics',
details: 'group,nics,secgrp,tmpl,servoff,diskoff,iso,volume,affgrp,backoff,vnfnics',
isVnf: true,
listAll: true
}

View File

@ -40,9 +40,11 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.cloudstack.utils.security.KeyStoreUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -708,13 +710,31 @@ public class Script implements Callable<String> {
return executeCommandForExitValue(0, command);
}
private static void cleanupProcesses(AtomicReference<List<Process>> processesRef) {
List<Process> processes = processesRef.get();
if (CollectionUtils.isNotEmpty(processes)) {
for (Process process : processes) {
if (process == null) {
continue;
}
LOGGER.trace(String.format("Cleaning up process [%s] from piped commands.", process.pid()));
IOUtils.closeQuietly(process.getErrorStream());
IOUtils.closeQuietly(process.getOutputStream());
IOUtils.closeQuietly(process.getInputStream());
process.destroyForcibly();
}
}
}
public static Pair<Integer, String> executePipedCommands(List<String[]> commands, long timeout) {
if (timeout <= 0) {
timeout = DEFAULT_TIMEOUT;
}
final AtomicReference<List<Process>> processesRef = new AtomicReference<>();
Callable<Pair<Integer, String>> commandRunner = () -> {
List<ProcessBuilder> builders = commands.stream().map(ProcessBuilder::new).collect(Collectors.toList());
List<Process> processes = ProcessBuilder.startPipeline(builders);
processesRef.set(processes);
Process last = processes.get(processes.size()-1);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(last.getInputStream()))) {
String line;
@ -741,6 +761,8 @@ public class Script implements Callable<String> {
result.second(ERR_TIMEOUT);
} catch (InterruptedException | ExecutionException e) {
LOGGER.error("Error executing piped commands", e);
} finally {
cleanupProcesses(processesRef);
}
return result;
}