diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java index 8b7565d66ed..09a3a33d915 100644 --- a/api/src/main/java/com/cloud/storage/VolumeApiService.java +++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java @@ -164,6 +164,8 @@ public interface VolumeApiService { Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge); + void destroyVolume(long volumeId); + Volume recoverVolume(long volumeId); void validateCustomDiskOfferingSizeRange(Long sizeInGB); diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java b/api/src/main/java/com/cloud/vm/UserVmService.java index 3ec365f48e4..d58b75b0dca 100644 --- a/api/src/main/java/com/cloud/vm/UserVmService.java +++ b/api/src/main/java/com/cloud/vm/UserVmService.java @@ -429,6 +429,14 @@ public interface UserVmService { UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, StorageUnavailableException, ResourceAllocationException; + /** + * This API is mostly to trigger VM.CREATE event for deployVirtualMachine with startvm=false, because there is no code in "execute" part of VM creation. + * However, it can be used for additional VM customization in the future. + * @param vmId - Virtual Machine Id + * @return - Virtual Machine + */ + UserVm finalizeCreateVirtualMachine(long vmId); + UserVm getUserVm(long vmId); VirtualMachine getVm(long vmId); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index cd7de7e6ed7..bd050b154d2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -769,7 +769,10 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG @Override public String getEventDescription() { - return "starting Vm. Vm Id: " + getEntityUuid(); + if(getStartVm()) { + return "starting Vm. Vm Id: " + getEntityUuid(); + } + return "deploying Vm. Vm Id: " + getEntityUuid(); } @Override @@ -781,28 +784,33 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG public void execute() { UserVm result; - try { - CallContext.current().setEventDetails("Vm Id: " + getEntityUuid()); - result = _userVmService.startVirtualMachine(this); - } catch (ResourceUnavailableException ex) { - s_logger.warn("Exception: ", ex); - throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage()); - } catch (ResourceAllocationException ex) { - s_logger.warn("Exception: ", ex); - throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage()); - } catch (ConcurrentOperationException ex) { - s_logger.warn("Exception: ", ex); - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); - } catch (InsufficientCapacityException ex) { - StringBuilder message = new StringBuilder(ex.getMessage()); - if (ex instanceof InsufficientServerCapacityException) { - if (((InsufficientServerCapacityException)ex).isAffinityApplied()) { - message.append(", Please check the affinity groups provided, there may not be sufficient capacity to follow them"); + CallContext.current().setEventDetails("Vm Id: " + getEntityUuid()); + if (getStartVm()) { + try { + result = _userVmService.startVirtualMachine(this); + } catch (ResourceUnavailableException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage()); + } catch (ResourceAllocationException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage()); + } catch (ConcurrentOperationException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } catch (InsufficientCapacityException ex) { + StringBuilder message = new StringBuilder(ex.getMessage()); + if (ex instanceof InsufficientServerCapacityException) { + if (((InsufficientServerCapacityException)ex).isAffinityApplied()) { + message.append(", Please check the affinity groups provided, there may not be sufficient capacity to follow them"); + } } + s_logger.info(String.format("%s: %s", message.toString(), ex.getLocalizedMessage())); + s_logger.debug(message.toString(), ex); + throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, message.toString()); } - s_logger.info(ex); - s_logger.info(message.toString(), ex); - throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, message.toString()); + } else { + s_logger.info("VM " + getEntityUuid() + " already created, load UserVm from DB"); + result = _userVmService.finalizeCreateVirtualMachine(getEntityId()); } if (result != null) { diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index d3ba95061b6..cf188cbf58d 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -500,22 +500,29 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac allocateRootVolume(persistedVm, template, rootDiskOfferingInfo, owner, rootDiskSizeFinal); - if (dataDiskOfferings != null) { - for (final DiskOfferingInfo dataDiskOfferingInfo : dataDiskOfferings) { - volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId(), dataDiskOfferingInfo.getDiskOffering(), dataDiskOfferingInfo.getSize(), - dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), persistedVm, template, owner, null); + // Create new Volume context and inject event resource type, id and details to generate VOLUME.CREATE event for the ROOT disk. + CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume); + try { + if (dataDiskOfferings != null) { + for (final DiskOfferingInfo dataDiskOfferingInfo : dataDiskOfferings) { + volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId(), dataDiskOfferingInfo.getDiskOffering(), dataDiskOfferingInfo.getSize(), + dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), persistedVm, template, owner, null); + } } - } - if (datadiskTemplateToDiskOfferingMap != null && !datadiskTemplateToDiskOfferingMap.isEmpty()) { - int diskNumber = 1; - for (Entry dataDiskTemplateToDiskOfferingMap : datadiskTemplateToDiskOfferingMap.entrySet()) { - DiskOffering diskOffering = dataDiskTemplateToDiskOfferingMap.getValue(); - long diskOfferingSize = diskOffering.getDiskSize() / (1024 * 1024 * 1024); - VMTemplateVO dataDiskTemplate = _templateDao.findById(dataDiskTemplateToDiskOfferingMap.getKey()); - volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId() + "-" + String.valueOf(diskNumber), diskOffering, diskOfferingSize, null, null, - persistedVm, dataDiskTemplate, owner, Long.valueOf(diskNumber)); - diskNumber++; + if (datadiskTemplateToDiskOfferingMap != null && !datadiskTemplateToDiskOfferingMap.isEmpty()) { + int diskNumber = 1; + for (Entry dataDiskTemplateToDiskOfferingMap : datadiskTemplateToDiskOfferingMap.entrySet()) { + DiskOffering diskOffering = dataDiskTemplateToDiskOfferingMap.getValue(); + long diskOfferingSize = diskOffering.getDiskSize() / (1024 * 1024 * 1024); + VMTemplateVO dataDiskTemplate = _templateDao.findById(dataDiskTemplateToDiskOfferingMap.getKey()); + volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId() + "-" + String.valueOf(diskNumber), diskOffering, diskOfferingSize, null, null, + persistedVm, dataDiskTemplate, owner, Long.valueOf(diskNumber)); + diskNumber++; + } } + } finally { + // Remove volumeContext and pop vmContext back + CallContext.unregister(); } if (s_logger.isDebugEnabled()) { diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index 2aa997e13aa..f7c8c9c70bf 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -819,7 +819,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati vol.getTemplateId()); } - @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating ROOT volume", create = true) + @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating volume", create = true) @Override public DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template, Account owner, Long deviceId) { @@ -1035,13 +1035,13 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati private void updateRootDiskVolumeEventDetails(Type type, VirtualMachine vm, List diskProfiles) { CallContext callContext = CallContext.current(); // Update only for volume type ROOT and API command resource type Volume - if (type == Type.ROOT && callContext != null && callContext.getEventResourceType() == ApiCommandResourceType.Volume) { + if ((type == Type.ROOT || type == Type.DATADISK) && callContext != null && callContext.getEventResourceType() == ApiCommandResourceType.Volume) { List volumeIds = diskProfiles.stream().map(DiskProfile::getVolumeId).filter(volumeId -> volumeId != null).collect(Collectors.toList()); if (!volumeIds.isEmpty()) { callContext.setEventResourceId(volumeIds.get(0)); } String volumeUuids = volumeIds.stream().map(volumeId -> this._uuidMgr.getUuid(Volume.class, volumeId)).collect(Collectors.joining(", ")); - callContext.setEventDetails("Volume Id: " + volumeUuids + " Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, vm.getId())); + callContext.setEventDetails("Volume Type: " + type + "Volume Id: " + volumeUuids + " Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, vm.getId())); } } @@ -1245,7 +1245,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati // Destroy volume if not already destroyed boolean volumeAlreadyDestroyed = (vol.getState() == Volume.State.Destroy || vol.getState() == Volume.State.Expunged || vol.getState() == Volume.State.Expunging); if (!volumeAlreadyDestroyed) { - volService.destroyVolume(vol.getId()); + destroyVolumeInContext(vol); } else { s_logger.debug(String.format("Skipping destroy for the volume [%s] as it is in [%s] state.", volumeToString, vol.getState().toString())); } @@ -1277,6 +1277,21 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati } } + private void destroyVolumeInContext(Volume volume) { + // Create new context and inject correct event resource type, id and details, + // otherwise VOLUME.DESTROY event will be associated with VirtualMachine and contain VM id and other information. + CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume); + volumeContext.setEventDetails("Volume Type: " + volume.getVolumeType() + " Volume Id: " + volume.getUuid() + " Vm Id: " + _uuidMgr.getUuid(VirtualMachine.class, volume.getInstanceId())); + volumeContext.setEventResourceType(ApiCommandResourceType.Volume); + volumeContext.setEventResourceId(volume.getId()); + try { + _volumeApiService.destroyVolume(volume.getId()); + } finally { + // Remove volumeContext and pop vmContext back + CallContext.unregister(); + } + } + @Override public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) { DataStoreDriver dataStoreDriver = dataStore != null ? dataStore.getDriver() : null; @@ -2080,7 +2095,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.volume, volume.isDisplay()); _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, volume.isDisplay(), new Long(volume.getSize())); } else { - volService.destroyVolume(volume.getId()); + destroyVolumeInContext(volume); } // FIXME - All this is boiler plate code and should be done as part of state transition. This shouldn't be part of orchestrator. // publish usage event for the volume diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41800to41810.sql b/engine/schema/src/main/resources/META-INF/db/schema-41800to41810.sql index 4f27d0408d7..07ac5c8f166 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41800to41810.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41800to41810.sql @@ -31,3 +31,8 @@ CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2022 (64-bit)', 'VM CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2022 (64-bit)', 'VMware', '8.0', 'windows2019srvNext_64Guest'); CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2022 (64-bit)', 'VMware', '8.0.0.1', 'windows2019srvNext_64Guest'); CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2022 (64-bit)', 'Xenserver', '8.2.0', 'Windows Server 2022 (64-bit)'); + +-- Don't enable CPU cap for default system offerings, fixes regression from https://github.com/apache/cloudstack/pull/6420 +UPDATE `cloud`.`service_offering` so +SET so.limit_cpu_use = 0 +WHERE so.default_use = 1 AND so.vm_type IN ('domainrouter', 'secondarystoragevm', 'consoleproxy', 'internalloadbalancervm', 'elasticloadbalancervm'); diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 8fd05a2cd77..dc90b26c8c0 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -727,6 +727,10 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third(); Filter searchFilter = new Filter(EventJoinVO.class, "createDate", false, cmd.getStartIndex(), cmd.getPageSizeVal()); + // additional order by since createdDate does not have milliseconds + // and two events, created within one second can be incorrectly ordered (for example VM.CREATE Completed before Scheduled) + searchFilter.addOrderBy(EventJoinVO.class, "id", false); + SearchBuilder sb = _eventJoinDao.createSearchBuilder(); _accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); @@ -1317,7 +1321,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q vmIds[i++] = v.getId(); } List vms = _userVmJoinDao.searchByIds(vmIds); - return new Pair, Integer>(vms, count); + return new Pair<>(vms, count); } @Override diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index bc9a4215fc4..d0eaebb7d71 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -1728,6 +1728,12 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic return volume; } + @Override + @ActionEvent(eventType = EventTypes.EVENT_VOLUME_DESTROY, eventDescription = "destroying a volume") + public void destroyVolume(long volumeId) { + volService.destroyVolume(volumeId); + } + @Override @ActionEvent(eventType = EventTypes.EVENT_VOLUME_RECOVER, eventDescription = "recovering a volume in Destroy state") public Volume recoverVolume(long volumeId) { diff --git a/server/src/main/java/com/cloud/user/DomainManagerImpl.java b/server/src/main/java/com/cloud/user/DomainManagerImpl.java index da796d2e48f..ad056655717 100644 --- a/server/src/main/java/com/cloud/user/DomainManagerImpl.java +++ b/server/src/main/java/com/cloud/user/DomainManagerImpl.java @@ -24,7 +24,15 @@ import java.util.UUID; import javax.inject.Inject; +import com.cloud.api.query.dao.NetworkOfferingJoinDao; +import com.cloud.api.query.dao.VpcOfferingJoinDao; +import com.cloud.api.query.vo.NetworkOfferingJoinVO; +import com.cloud.api.query.vo.VpcOfferingJoinVO; import com.cloud.domain.dao.DomainDetailsDao; +import com.cloud.network.vpc.dao.VpcOfferingDao; +import com.cloud.network.vpc.dao.VpcOfferingDetailsDao; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.offerings.dao.NetworkOfferingDetailsDao; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; @@ -106,12 +114,24 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom @Inject private DiskOfferingDetailsDao diskOfferingDetailsDao; @Inject + private NetworkOfferingDao networkOfferingDao; + @Inject + private NetworkOfferingJoinDao networkOfferingJoinDao; + @Inject + private NetworkOfferingDetailsDao networkOfferingDetailsDao; + @Inject private ServiceOfferingJoinDao serviceOfferingJoinDao; @Inject private ServiceOfferingDao serviceOfferingDao; @Inject private ServiceOfferingDetailsDao serviceOfferingDetailsDao; @Inject + private VpcOfferingDao vpcOfferingDao; + @Inject + private VpcOfferingJoinDao vpcOfferingJoinDao; + @Inject + private VpcOfferingDetailsDao vpcOfferingDetailsDao; + @Inject private ProjectDao _projectDao; @Inject private ProjectManager _projectMgr; @@ -483,18 +503,48 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom } String domainIdString = String.valueOf(domainId); - List diskOfferingsDetailsToRemove = new ArrayList<>(); - List serviceOfferingsDetailsToRemove = new ArrayList<>(); - // delete the service and disk offerings associated with this domain - List diskOfferingsForThisDomain = diskOfferingJoinDao.findByDomainId(domainId); - for (DiskOfferingJoinVO diskOffering : diskOfferingsForThisDomain) { - if (domainIdString.equals(diskOffering.getDomainId())) { - diskOfferingDao.remove(diskOffering.getId()); + removeDiskOfferings(domainId, domainIdString); + + removeServiceOfferings(domainId, domainIdString); + + removeNetworkOfferings(domainId, domainIdString); + + removeVpcOfferings(domainId, domainIdString); + } + + private void removeVpcOfferings(Long domainId, String domainIdString) { + List vpcOfferingsDetailsToRemove = new ArrayList<>(); + List vpcOfferingsForThisDomain = vpcOfferingJoinDao.findByDomainId(domainId); + for (VpcOfferingJoinVO vpcOffering : vpcOfferingsForThisDomain) { + if (domainIdString.equals(vpcOffering.getDomainId())) { + vpcOfferingDao.remove(vpcOffering.getId()); } else { - diskOfferingsDetailsToRemove.add(diskOffering.getId()); + vpcOfferingsDetailsToRemove.add(vpcOffering.getId()); } } + for (final Long vpcOfferingId : vpcOfferingsDetailsToRemove) { + vpcOfferingDetailsDao.removeDetail(vpcOfferingId, ApiConstants.DOMAIN_ID, domainIdString); + } + } + + private void removeNetworkOfferings(Long domainId, String domainIdString) { + List networkOfferingsDetailsToRemove = new ArrayList<>(); + List networkOfferingsForThisDomain = networkOfferingJoinDao.findByDomainId(domainId, false); + for (NetworkOfferingJoinVO networkOffering : networkOfferingsForThisDomain) { + if (domainIdString.equals(networkOffering.getDomainId())) { + networkOfferingDao.remove(networkOffering.getId()); + } else { + networkOfferingsDetailsToRemove.add(networkOffering.getId()); + } + } + for (final Long networkOfferingId : networkOfferingsDetailsToRemove) { + networkOfferingDetailsDao.removeDetail(networkOfferingId, ApiConstants.DOMAIN_ID, domainIdString); + } + } + + private void removeServiceOfferings(Long domainId, String domainIdString) { + List serviceOfferingsDetailsToRemove = new ArrayList<>(); List serviceOfferingsForThisDomain = serviceOfferingJoinDao.findByDomainId(domainId); for (ServiceOfferingJoinVO serviceOffering : serviceOfferingsForThisDomain) { if (domainIdString.equals(serviceOffering.getDomainId())) { @@ -503,14 +553,25 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom serviceOfferingsDetailsToRemove.add(serviceOffering.getId()); } } + for (final Long serviceOfferingId : serviceOfferingsDetailsToRemove) { + serviceOfferingDetailsDao.removeDetail(serviceOfferingId, ApiConstants.DOMAIN_ID, domainIdString); + } + } + private void removeDiskOfferings(Long domainId, String domainIdString) { + List diskOfferingsDetailsToRemove = new ArrayList<>(); + List diskOfferingsForThisDomain = diskOfferingJoinDao.findByDomainId(domainId); + for (DiskOfferingJoinVO diskOffering : diskOfferingsForThisDomain) { + if (domainIdString.equals(diskOffering.getDomainId())) { + diskOfferingDao.remove(diskOffering.getId()); + } else { + diskOfferingsDetailsToRemove.add(diskOffering.getId()); + } + } // Remove domain IDs for offerings which may be multi-domain for (final Long diskOfferingId : diskOfferingsDetailsToRemove) { diskOfferingDetailsDao.removeDetail(diskOfferingId, ApiConstants.DOMAIN_ID, domainIdString); } - for (final Long serviceOfferingId : serviceOfferingsDetailsToRemove) { - serviceOfferingDetailsDao.removeDetail(serviceOfferingId, ApiConstants.DOMAIN_ID, domainIdString); - } } protected boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOperationException, ResourceUnavailableException { diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 159c230b876..1dd6d6541e9 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -3274,9 +3274,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir autoScaleManager.removeVmFromVmGroup(vmId); - vmScheduleManager.removeScheduleByVmId(vmId, expunge); - - deleteVolumesFromVm(volumesToBeDeleted, expunge); + deleteVolumesFromVm(vm, volumesToBeDeleted, expunge); if (getDestroyRootVolumeOnVmDestruction(vm.getDomainId())) { VolumeVO rootVolume = _volsDao.getInstanceRootVolume(vm.getId()); @@ -3720,6 +3718,19 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir dataDiskTemplateToDiskOfferingMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, overrideDiskOfferingId); } + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = "deploying Vm") + public UserVm finalizeCreateVirtualMachine(long vmId) { + s_logger.info("Loading UserVm " + vmId + " from DB"); + UserVm userVm = getUserVm(vmId); + if (userVm == null) { + s_logger.info("Loaded UserVm " + vmId + " (" + userVm.getUuid() + ") from DB"); + } else { + s_logger.warn("UserVm " + vmId + " does not exist in DB"); + } + return userVm; + } + private NetworkVO getNetworkToAddToNetworkList(VirtualMachineTemplate template, Account owner, HypervisorType hypervisor, List vpcSupportedHTypes, Long networkId) { NetworkVO network = _networkDao.findById(networkId); @@ -7974,7 +7985,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // Create new context and inject correct event resource type, id and details, // otherwise VOLUME.DETACH event will be associated with VirtualMachine and contain VM id and other information. CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume); - volumeContext.setEventDetails("Volume Id: " + this._uuidMgr.getUuid(Volume.class, volume.getId()) + " Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, volume.getInstanceId())); + volumeContext.setEventDetails("Volume Type: " + volume.getVolumeType() + " Volume Id: " + this._uuidMgr.getUuid(Volume.class, volume.getId()) + " Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, volume.getInstanceId())); volumeContext.setEventResourceType(ApiCommandResourceType.Volume); volumeContext.setEventResourceId(volume.getId()); @@ -7992,15 +8003,29 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } } - private void deleteVolumesFromVm(List volumes, boolean expunge) { + private void deleteVolumesFromVm(UserVmVO vm, List volumes, boolean expunge) { for (VolumeVO volume : volumes) { + destroyVolumeInContext(vm, expunge, volume); + } + } + private void destroyVolumeInContext(UserVmVO vm, boolean expunge, VolumeVO volume) { + // Create new context and inject correct event resource type, id and details, + // otherwise VOLUME.DESTROY event will be associated with VirtualMachine and contain VM id and other information. + CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume); + volumeContext.setEventDetails("Volume Type: " + volume.getVolumeType() + " Volume Id: " + this._uuidMgr.getUuid(Volume.class, volume.getId()) + " Vm Id: " + vm.getUuid()); + volumeContext.setEventResourceType(ApiCommandResourceType.Volume); + volumeContext.setEventResourceId(volume.getId()); + try { Volume result = _volumeService.destroyVolume(volume.getId(), CallContext.current().getCallingAccount(), expunge, false); if (result == null) { - s_logger.error("DestroyVM remove volume - failed to delete volume " + volume.getInstanceId() + " from instance " + volume.getId()); + s_logger.error(String.format("DestroyVM remove volume - failed to delete volume %s from instance %s", volume.getId(), volume.getInstanceId())); } + } finally { + // Remove volumeContext and pop vmContext back + CallContext.unregister(); } } diff --git a/server/src/test/java/com/cloud/user/DomainManagerImplTest.java b/server/src/test/java/com/cloud/user/DomainManagerImplTest.java index 0290c060667..3b270f30e84 100644 --- a/server/src/test/java/com/cloud/user/DomainManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/DomainManagerImplTest.java @@ -22,6 +22,8 @@ import java.util.Collections; import java.util.List; import java.util.UUID; +import com.cloud.api.query.dao.NetworkOfferingJoinDao; +import com.cloud.api.query.dao.VpcOfferingJoinDao; import com.cloud.configuration.ResourceLimit; import com.cloud.domain.dao.DomainDetailsDao; import com.cloud.utils.UuidUtils; @@ -82,7 +84,11 @@ public class DomainManagerImplTest { @Mock DiskOfferingJoinDao _diskOfferingDao; @Mock - ServiceOfferingJoinDao _offeringsDao; + NetworkOfferingJoinDao networkOfferingJoinDao; + @Mock + ServiceOfferingJoinDao serviceOfferingJoinDao; + @Mock + VpcOfferingJoinDao vpcOfferingJoinDao; @Mock ProjectDao _projectDao; @Mock @@ -142,6 +148,11 @@ public class DomainManagerImplTest { Mockito.when(_accountDao.findCleanupsForRemovedAccounts(DOMAIN_ID)).thenReturn(domainAccountsForCleanup); Mockito.when(_networkDomainDao.listNetworkIdsByDomain(DOMAIN_ID)).thenReturn(domainNetworkIds); Mockito.when(_dedicatedDao.listByDomainId(DOMAIN_ID)).thenReturn(domainDedicatedResources); + + Mockito.when(_diskOfferingDao.findByDomainId(Mockito.anyLong())).thenReturn(Collections.emptyList()); + Mockito.when(networkOfferingJoinDao.findByDomainId(Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(Collections.emptyList()); + Mockito.when(serviceOfferingJoinDao.findByDomainId(Mockito.anyLong())).thenReturn(Collections.emptyList()); + Mockito.when(vpcOfferingJoinDao.findByDomainId(Mockito.anyLong())).thenReturn(Collections.emptyList()); } @Test @@ -266,8 +277,6 @@ public class DomainManagerImplTest { Mockito.when(_dedicatedDao.listByDomainId(Mockito.anyLong())).thenReturn(new ArrayList()); Mockito.when(domainDaoMock.remove(Mockito.anyLong())).thenReturn(true); Mockito.when(_configMgr.releaseDomainSpecificVirtualRanges(Mockito.anyLong())).thenReturn(true); - Mockito.when(_diskOfferingDao.findByDomainId(Mockito.anyLong())).thenReturn(Collections.emptyList()); - Mockito.when(_offeringsDao.findByDomainId(Mockito.anyLong())).thenReturn(Collections.emptyList()); try { Assert.assertTrue(domainManager.deleteDomain(20l, false)); @@ -299,8 +308,6 @@ public class DomainManagerImplTest { Mockito.when(_resourceCountDao.removeEntriesByOwner(Mockito.anyLong(), Mockito.eq(ResourceOwnerType.Domain))).thenReturn(1l); Mockito.when(_resourceLimitDao.removeEntriesByOwner(Mockito.anyLong(), Mockito.eq(ResourceOwnerType.Domain))).thenReturn(1l); Mockito.when(_configMgr.releaseDomainSpecificVirtualRanges(Mockito.anyLong())).thenReturn(true); - Mockito.when(_diskOfferingDao.findByDomainId(Mockito.anyLong())).thenReturn(Collections.emptyList()); - Mockito.when(_offeringsDao.findByDomainId(Mockito.anyLong())).thenReturn(Collections.emptyList()); try { Assert.assertTrue(domainManager.deleteDomain(20l, true)); diff --git a/test/integration/smoke/test_events_resource.py b/test/integration/smoke/test_events_resource.py index 4d6872af57e..660cbd37bce 100644 --- a/test/integration/smoke/test_events_resource.py +++ b/test/integration/smoke/test_events_resource.py @@ -16,6 +16,7 @@ # under the License. """ BVT tests for Events Resource """ +import json import os import tempfile import time @@ -184,7 +185,7 @@ class TestEventsResource(cloudstackTestCase): for event in events: if event.type.startswith("VM.") or (event.type.startswith("NETWORK.") and not event.type.startswith("NETWORK.ELEMENT")) or event.type.startswith("VOLUME.") or event.type.startswith("ACCOUNT.") or event.type.startswith("DOMAIN.") or event.type.startswith("TEMPLATE."): if event.resourceid is None or event.resourcetype is None: - self.debug("Failed event:: %" % event) + self.debug("Failed event:: %s" % json.dumps(event, indent=2)) self.fail("resourceid or resourcetype for the event not found!") else: self.debug("Event %s at %s:: Resource Type: %s, Resource ID: %s" % (event.type, event.created, event.resourcetype, event.resourceid))