Skip removal of offerings if in use during domain removal (#11780)

This PR fixes #11502

    - Prevent service offering update to specific domains if any instance for the offering are outside of those
    - Removal of offerings is skipped if it is in use by any Instance.
This commit is contained in:
Manoj Kumar 2026-01-07 09:25:11 +05:30 committed by GitHub
parent a29de0ed06
commit 57331aca2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 84 additions and 5 deletions

View File

@ -162,4 +162,6 @@ public interface VolumeDao extends GenericDao<VolumeVO, Long>, StateDao<Volume.S
List<VolumeVO> searchRemovedByVms(List<Long> vmIds, Long batchSize);
VolumeVO findOneByIScsiName(String iScsiName);
int getVolumeCountByOfferingId(long diskOfferingId);
}

View File

@ -77,6 +77,7 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
protected GenericSearchBuilder<VolumeVO, SumCount> primaryStorageSearch2;
protected GenericSearchBuilder<VolumeVO, SumCount> secondaryStorageSearch;
private final SearchBuilder<VolumeVO> poolAndPathSearch;
final GenericSearchBuilder<VolumeVO, Integer> CountByOfferingId;
@Inject
ReservationDao reservationDao;
@ -504,6 +505,11 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
poolAndPathSearch.and("poolId", poolAndPathSearch.entity().getPoolId(), Op.EQ);
poolAndPathSearch.and("path", poolAndPathSearch.entity().getPath(), Op.EQ);
poolAndPathSearch.done();
CountByOfferingId = createSearchBuilder(Integer.class);
CountByOfferingId.select(null, Func.COUNT, CountByOfferingId.entity().getId());
CountByOfferingId.and("diskOfferingId", CountByOfferingId.entity().getDiskOfferingId(), Op.EQ);
CountByOfferingId.done();
}
@Override
@ -909,4 +915,12 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
sc.setParameters("iScsiName", iScsiName);
return findOneIncludingRemovedBy(sc);
}
@Override
public int getVolumeCountByOfferingId(long diskOfferingId) {
SearchCriteria<Integer> sc = CountByOfferingId.create();
sc.setParameters("diskOfferingId", diskOfferingId);
List<Integer> results = customSearch(sc, null);
return results.get(0);
}
}

View File

@ -187,4 +187,7 @@ public interface VMInstanceDao extends GenericDao<VMInstanceVO, Long>, StateDao<
Map<String, Long> getNameIdMapForVmIds(Collection<Long> ids);
int getVmCountByOfferingId(Long serviceOfferingId);
int getVmCountByOfferingNotInDomain(Long serviceOfferingId, List<Long> domainIds);
}

View File

@ -104,6 +104,8 @@ public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implem
protected SearchBuilder<VMInstanceVO> LastHostAndStatesSearch;
protected SearchBuilder<VMInstanceVO> VmsNotInClusterUsingPool;
protected SearchBuilder<VMInstanceVO> IdsPowerStateSelectSearch;
GenericSearchBuilder<VMInstanceVO, Integer> CountByOfferingId;
GenericSearchBuilder<VMInstanceVO, Integer> CountUserVmNotInDomain;
@Inject
ResourceTagDao tagsDao;
@ -344,6 +346,18 @@ public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implem
IdsPowerStateSelectSearch.entity().getPowerStateUpdateCount(),
IdsPowerStateSelectSearch.entity().getPowerStateUpdateTime());
IdsPowerStateSelectSearch.done();
CountByOfferingId = createSearchBuilder(Integer.class);
CountByOfferingId.select(null, Func.COUNT, CountByOfferingId.entity().getId());
CountByOfferingId.and("serviceOfferingId", CountByOfferingId.entity().getServiceOfferingId(), Op.EQ);
CountByOfferingId.done();
CountUserVmNotInDomain = createSearchBuilder(Integer.class);
CountUserVmNotInDomain.select(null, Func.COUNT, CountUserVmNotInDomain.entity().getId());
CountUserVmNotInDomain.and("serviceOfferingId", CountUserVmNotInDomain.entity().getServiceOfferingId(), Op.EQ);
CountUserVmNotInDomain.and("domainIdsNotIn", CountUserVmNotInDomain.entity().getDomainId(), Op.NIN);
CountUserVmNotInDomain.done();
}
@Override
@ -1224,4 +1238,27 @@ public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implem
return vms.stream()
.collect(Collectors.toMap(VMInstanceVO::getInstanceName, VMInstanceVO::getId));
}
@Override
public int getVmCountByOfferingId(Long serviceOfferingId) {
if (serviceOfferingId == null) {
return 0;
}
SearchCriteria<Integer> sc = CountByOfferingId.create();
sc.setParameters("serviceOfferingId", serviceOfferingId);
List<Integer> count = customSearch(sc, null);
return count.get(0);
}
@Override
public int getVmCountByOfferingNotInDomain(Long serviceOfferingId, List<Long> domainIds) {
if (serviceOfferingId == null || CollectionUtils.isEmpty(domainIds)) {
return 0;
}
SearchCriteria<Integer> sc = CountUserVmNotInDomain.create();
sc.setParameters("serviceOfferingId", serviceOfferingId);
sc.setParameters("domainIdsNotIn", domainIds.toArray());
List<Integer> count = customSearch(sc, null);
return count.get(0);
}
}

View File

@ -50,7 +50,7 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.exception.UnsupportedServiceException;
import com.cloud.network.as.AutoScaleManager;
import com.cloud.user.AccountManagerImpl;
import org.apache.cloudstack.acl.RoleType;
@ -3722,6 +3722,12 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
List<Long> filteredDomainIds = filterChildSubDomains(domainIds);
Collections.sort(filteredDomainIds);
// avoid domain update of service offering if any instance is associated to it
int instanceCount = _vmInstanceDao.getVmCountByOfferingNotInDomain(offeringHandle.getId(), filteredDomainIds);
if (instanceCount > 0) {
throw new UnsupportedServiceException("There are Instances associated to this service offering outside of the specified domains.");
}
List<Long> filteredZoneIds = new ArrayList<>();
if (CollectionUtils.isNotEmpty(zoneIds)) {
filteredZoneIds.addAll(zoneIds);

View File

@ -34,6 +34,8 @@ import com.cloud.api.query.vo.NetworkOfferingJoinVO;
import com.cloud.api.query.vo.VpcOfferingJoinVO;
import com.cloud.configuration.Resource;
import com.cloud.domain.dao.DomainDetailsDao;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.vpc.dao.VpcDao;
import com.cloud.network.vpc.dao.VpcOfferingDao;
import com.cloud.network.vpc.dao.VpcOfferingDetailsDao;
import com.cloud.offerings.dao.NetworkOfferingDao;
@ -85,6 +87,7 @@ import com.cloud.projects.dao.ProjectDao;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.service.dao.ServiceOfferingDetailsDao;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.dao.AccountDao;
import com.cloud.utils.Pair;
import com.cloud.utils.component.ManagerBase;
@ -101,6 +104,8 @@ import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils;
import com.cloud.vm.ReservationContext;
import com.cloud.vm.ReservationContextImpl;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.commons.lang3.StringUtils;
@Component
@ -141,6 +146,14 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom
@Inject
private ProjectDao _projectDao;
@Inject
private VMInstanceDao vmInstanceDao;
@Inject
private NetworkDao networkDao;
@Inject
private VolumeDao volumeDao;
@Inject
private VpcDao vpcDao;
@Inject
private ProjectManager _projectMgr;
@Inject
private RegionManager _regionMgr;
@ -543,7 +556,8 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom
List<Long> vpcOfferingsDetailsToRemove = new ArrayList<>();
List<VpcOfferingJoinVO> vpcOfferingsForThisDomain = vpcOfferingJoinDao.findByDomainId(domainId);
for (VpcOfferingJoinVO vpcOffering : vpcOfferingsForThisDomain) {
if (domainIdString.equals(vpcOffering.getDomainId())) {
int vpcCount = vpcDao.getVpcCountByOfferingId(vpcOffering.getId());
if (vpcCount == 0) {
vpcOfferingDao.remove(vpcOffering.getId());
} else {
vpcOfferingsDetailsToRemove.add(vpcOffering.getId());
@ -558,7 +572,8 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom
List<Long> networkOfferingsDetailsToRemove = new ArrayList<>();
List<NetworkOfferingJoinVO> networkOfferingsForThisDomain = networkOfferingJoinDao.findByDomainId(domainId, false);
for (NetworkOfferingJoinVO networkOffering : networkOfferingsForThisDomain) {
if (domainIdString.equals(networkOffering.getDomainId())) {
int networkCount = networkDao.getNetworkCountByNetworkOffId(networkOffering.getId());
if (networkCount == 0) {
networkOfferingDao.remove(networkOffering.getId());
} else {
networkOfferingsDetailsToRemove.add(networkOffering.getId());
@ -573,7 +588,8 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom
List<Long> serviceOfferingsDetailsToRemove = new ArrayList<>();
List<ServiceOfferingJoinVO> serviceOfferingsForThisDomain = serviceOfferingJoinDao.findByDomainId(domainId);
for (ServiceOfferingJoinVO serviceOffering : serviceOfferingsForThisDomain) {
if (domainIdString.equals(serviceOffering.getDomainId())) {
int vmCount = vmInstanceDao.getVmCountByOfferingId(serviceOffering.getId());
if (vmCount == 0) {
serviceOfferingDao.remove(serviceOffering.getId());
} else {
serviceOfferingsDetailsToRemove.add(serviceOffering.getId());
@ -588,7 +604,8 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom
List<Long> diskOfferingsDetailsToRemove = new ArrayList<>();
List<DiskOfferingJoinVO> diskOfferingsForThisDomain = diskOfferingJoinDao.findByDomainId(domainId);
for (DiskOfferingJoinVO diskOffering : diskOfferingsForThisDomain) {
if (domainIdString.equals(diskOffering.getDomainId())) {
int volumeCount = volumeDao.getVolumeCountByOfferingId(diskOffering.getId());
if (volumeCount == 0) {
diskOfferingDao.remove(diskOffering.getId());
} else {
diskOfferingsDetailsToRemove.add(diskOffering.getId());