When VM is created and ROOT volume is created it should emit a VOLUME.CREATE event (#209)

* When VM is created and ROOT volume is created it should emit a VOLUME.CREATE event

* added space between methods

* Updated volume context comments.

Co-authored-by: Maxim Prokopchuk <mprokopchuk@apple.com>
diff --git a/api/src/main/java/org/apache/cloudstack/context/CallContext.java b/api/src/main/java/org/apache/cloudstack/context/CallContext.java
index d63ccef..50049b3 100644
--- a/api/src/main/java/org/apache/cloudstack/context/CallContext.java
+++ b/api/src/main/java/org/apache/cloudstack/context/CallContext.java
@@ -223,6 +223,20 @@
         return register(user, account);
     }
 
+    /**
+     * Register child CallContext.
+     * @param parent - parent CallContext
+     * @param eventResourceType - command resource type
+     * @return Call context
+     * @throws CloudAuthenticationException
+     */
+    public static CallContext register(CallContext parent, ApiCommandResourceType eventResourceType) throws CloudAuthenticationException {
+        CallContext callContext = register(parent.getCallingUserId(), parent.getCallingAccountId());
+        callContext.setStartEventId(parent.getStartEventId());
+        callContext.setEventResourceType(eventResourceType);
+        return callContext;
+    }
+
     public static CallContext register(long callingUserId, long callingAccountId) throws CloudAuthenticationException {
         Account account = s_entityMgr.findById(Account.class, callingAccountId);
         if (account == 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 6c15ad0..1e87657 100755
--- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
+++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
@@ -48,6 +48,7 @@
 import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
+import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd;
 import org.apache.cloudstack.api.command.admin.volume.MigrateVolumeCmdByAdmin;
@@ -485,15 +486,22 @@
                     s_logger.debug("Allocating disks for " + vmFinal);
                 }
 
-                String rootVolumeName = String.format("ROOT-%s", vmFinal.getId());
-                if (template.getFormat() == ImageFormat.ISO) {
-                    volumeMgr.allocateRawVolume(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskOfferingInfo.getSize(),
-                            rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vmFinal, template, owner, null);
-                } else if (template.getFormat() == ImageFormat.BAREMETAL) {
-                    s_logger.debug(String.format("%s has format [%s]. Skipping ROOT volume [%s] allocation.", template.toString(), ImageFormat.BAREMETAL, rootVolumeName));
-                } else {
-                    volumeMgr.allocateTemplatedVolumes(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskSizeFinal,
-                            rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), template, vmFinal, owner);
+                // 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 {
+                    String rootVolumeName = String.format("ROOT-%s", vmFinal.getId());
+                    if (template.getFormat() == ImageFormat.ISO) {
+                        volumeMgr.allocateRawVolume(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskOfferingInfo.getSize(),
+                                rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vmFinal, template, owner, null);
+                    } else if (template.getFormat() == ImageFormat.BAREMETAL) {
+                        s_logger.debug(String.format("%s has format [%s]. Skipping ROOT volume [%s] allocation.", template.toString(), ImageFormat.BAREMETAL, rootVolumeName));
+                    } else {
+                        volumeMgr.allocateTemplatedVolumes(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskSizeFinal,
+                                rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), template, vmFinal, owner);
+                    }
+                } finally {
+                    // Remove volumeContext and pop vmContext back
+                    CallContext.unregister();
                 }
 
                 if (dataDiskOfferings != null) {
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 e427202..73047c4 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
@@ -37,6 +37,16 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.event.ActionEvent;
+import com.cloud.utils.db.DB;
+import com.cloud.utils.db.EntityManager;
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.db.TransactionCallback;
+import com.cloud.utils.db.TransactionCallbackNoReturn;
+import com.cloud.utils.db.TransactionStatus;
+import com.cloud.utils.db.UUIDManager;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.secret.dao.PassphraseDao;
 import org.apache.cloudstack.secret.PassphraseVO;
 import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd;
@@ -138,12 +148,6 @@
 import com.cloud.utils.Pair;
 import com.cloud.utils.StringUtils;
 import com.cloud.utils.component.ManagerBase;
-import com.cloud.utils.db.DB;
-import com.cloud.utils.db.EntityManager;
-import com.cloud.utils.db.Transaction;
-import com.cloud.utils.db.TransactionCallback;
-import com.cloud.utils.db.TransactionCallbackNoReturn;
-import com.cloud.utils.db.TransactionStatus;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.fsm.NoTransitionException;
 import com.cloud.utils.fsm.StateMachine2;
@@ -177,6 +181,10 @@
 
     @Inject
     EntityManager _entityMgr;
+
+    @Inject
+    private UUIDManager _uuidMgr;
+
     @Inject
     protected TemplateManager _tmpltMgr;
     @Inject
@@ -788,6 +796,7 @@
                 vol.getTemplateId());
     }
 
+    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating ROOT 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) {
@@ -847,7 +856,15 @@
             _resourceLimitMgr.incrementResourceCount(vm.getAccountId(), ResourceType.volume, vol.isDisplayVolume());
             _resourceLimitMgr.incrementResourceCount(vm.getAccountId(), ResourceType.primary_storage, vol.isDisplayVolume(), new Long(vol.getSize()));
         }
-        return toDiskProfile(vol, offering);
+        DiskProfile diskProfile = toDiskProfile(vol, offering);
+        // Set context information for VOLUME.CREATE event for ROOT disk.
+        CallContext volumeContext = CallContext.current();
+        if (type == Type.ROOT && volumeContext != null && volumeContext.getEventResourceType() == ApiCommandResourceType.Volume) {
+            volumeContext.setEventDetails("Volume Id: " + this._uuidMgr.getUuid(Volume.class, diskProfile.getVolumeId()) + " Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, vm.getId()));
+            volumeContext.setEventResourceId(diskProfile.getVolumeId());
+        }
+
+        return diskProfile;
     }
 
     private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm,
@@ -928,6 +945,7 @@
         return toDiskProfile(vol, offering);
     }
 
+    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating ROOT volume", create = true)
     @Override
     public List<DiskProfile> allocateTemplatedVolumes(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm,
                                                       Account owner) {
@@ -978,6 +996,13 @@
                     template, vm, owner, deviceId, configurationId);
             profiles.add(diskProfile);
         }
+        // Set context information for VOLUME.CREATE event for ROOT disk.
+        CallContext volumeContext = CallContext.current();
+        if (type == Type.ROOT && volumeContext != null && volumeContext.getEventResourceType() == ApiCommandResourceType.Volume) {
+            String volumeIds = profiles.stream().map(diskProfile -> this._uuidMgr.getUuid(Volume.class, diskProfile.getVolumeId())).collect(Collectors.joining(", "));
+            volumeContext.setEventDetails("Volume Id: " + volumeIds + " Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, vm.getId()));
+            volumeContext.setEventResourceId(profiles.stream().findFirst().map(DiskProfile::getVolumeId).orElse(null));
+        }
 
         handleRootDiskControllerTpeForDeployAsIs(templateAsIsDisks, vm);
         return profiles;
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index 3bc93b3..f4bd018 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -7985,14 +7985,12 @@
     private void detachVolumesFromVm(List<VolumeVO> volumes) {
 
         for (VolumeVO volume : volumes) {
-            CallContext vmContext = CallContext.current();
             // 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(vmContext.getCallingUserId(), vmContext.getCallingAccountId());
+            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.setEventResourceType(ApiCommandResourceType.Volume);
             volumeContext.setEventResourceId(volume.getId());
-            volumeContext.setStartEventId(vmContext.getStartEventId());
 
             Volume detachResult = null;
             try {