VM.CREATE/VOLUME.DELETE/VOLUME.DESTROY not being emitted (#7760)

VM.CREATE/VOLUME.DELETE/VOLUME.DESTROY not being emitted

* Update server/src/main/java/com/cloud/vm/UserVmManagerImpl.java

Co-authored-by: dahn <daan.hoogland@gmail.com>

* Update api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java

Co-authored-by: dahn <daan.hoogland@gmail.com>

---------

Co-authored-by: Maxim Prokopchuk <mprokopchuk@apple.com>
Co-authored-by: dahn <daan.hoogland@gmail.com>
This commit is contained in:
mprokopchuk 2023-08-06 21:48:17 -07:00 committed by GitHub
parent dc5e4f3ec6
commit ab0297ea9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 123 additions and 45 deletions

View File

@ -163,6 +163,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);

View File

@ -432,6 +432,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);

View File

@ -753,7 +753,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
@ -765,28 +768,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) {

View File

@ -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<Long, DiskOffering> 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<Long, DiskOffering> 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()) {

View File

@ -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<DiskProfile> 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<Long> 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

View File

@ -721,6 +721,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<EventJoinVO> sb = _eventJoinDao.createSearchBuilder();
_accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);

View File

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

View File

@ -3272,7 +3272,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
autoScaleManager.removeVmFromVmGroup(vmId);
deleteVolumesFromVm(volumesToBeDeleted, expunge);
deleteVolumesFromVm(vm, volumesToBeDeleted, expunge);
if (getDestroyRootVolumeOnVmDestruction(vm.getDomainId())) {
VolumeVO rootVolume = _volsDao.getInstanceRootVolume(vm.getId());
@ -3716,6 +3716,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<HypervisorType> vpcSupportedHTypes, Long networkId) {
NetworkVO network = _networkDao.findById(networkId);
@ -8002,7 +8015,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());
@ -8020,15 +8033,29 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
}
private void deleteVolumesFromVm(List<VolumeVO> volumes, boolean expunge) {
private void deleteVolumesFromVm(UserVmVO vm, List<VolumeVO> 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();
}
}

View File

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