Greg Padgett has uploaded a new change for review.

Change subject: [WIP] core: introduce RemoveSnapshotSingleDiskLive BLL command
......................................................................

[WIP] core: introduce RemoveSnapshotSingleDiskLive BLL command

Backend command to merge snapshots while a VM is running.

Change-Id: Ic47eb91a0ea1fe150e3b2152e2c9d5f1f2eb3678
Bug-Url: https://bugzilla.redhat.com/??????
Signed-off-by: Greg Padgett <gpadg...@redhat.com>
---
M 
backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotCommand.java
M 
backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotSingleDiskCommand.java
A 
backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotSingleDiskCommandBase.java
A 
backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotSingleDiskLiveCommand.java
M 
backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/validator/VmValidator.java
M 
backend/manager/modules/bll/src/test/java/org/ovirt/engine/core/bll/RemoveSnapshotCommandTest.java
M 
backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/VdcActionType.java
M 
backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VM.java
M 
backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VMStatus.java
M 
backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/errors/VdcBllErrors.java
M backend/manager/modules/dal/src/main/resources/bundles/VdsmErrors.properties
11 files changed, 303 insertions(+), 91 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/09/26909/1

diff --git 
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotCommand.java
 
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotCommand.java
index da927b9..0d00468 100644
--- 
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotCommand.java
+++ 
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotCommand.java
@@ -29,7 +29,6 @@
 import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotStatus;
 import org.ovirt.engine.core.common.businessentities.StorageDomain;
 import org.ovirt.engine.core.common.businessentities.VM;
-import org.ovirt.engine.core.common.businessentities.VMStatus;
 import org.ovirt.engine.core.common.errors.VdcBLLException;
 import org.ovirt.engine.core.common.errors.VdcBllErrors;
 import org.ovirt.engine.core.common.errors.VdcBllMessages;
@@ -41,11 +40,13 @@
 import org.ovirt.engine.core.utils.transaction.TransactionMethod;
 import org.ovirt.engine.core.utils.transaction.TransactionSupport;
 
+/**
+ * Merges snapshots either live or non-live based on VM status
+ */
 @DisableInPrepareMode
 @LockIdNameAttribute
 public class RemoveSnapshotCommand<T extends RemoveSnapshotParameters> extends 
VmCommand<T>
         implements QuotaStorageDependent {
-
     private List<DiskImage> _sourceImages = null;
 
     public RemoveSnapshotCommand(T parameters) {
@@ -85,9 +86,9 @@
 
     @Override
     protected void executeCommand() {
-        if (getVm().getStatus() != VMStatus.Down) {
-            log.error("Cannot remove VM snapshot. Vm is not Down");
-            throw new VdcBLLException(VdcBllErrors.IRS_IMAGE_STATUS_ILLEGAL);
+        if (!getVm().isQualifiedForSnapshotMerge()) {
+            log.error("Cannot remove VM snapshot. Vm is not Down, Up or 
Paused");
+            throw new 
VdcBLLException(VdcBllErrors.VM_NOT_QUALIFIED_FOR_SNAPSHOT_MERGE);
         }
 
         final Snapshot snapshot = 
getSnapshotDao().get(getParameters().getSnapshotId());
@@ -143,7 +144,7 @@
             DiskImage dest = 
getDiskImageDao().getAllSnapshotsForParent(source.getImageId()).get(0);
 
             VdcReturnValueBase vdcReturnValue = getBackend().runInternalAction(
-                    VdcActionType.RemoveSnapshotSingleDisk,
+                    getSnapshotActionType(),
                     buildRemoveSnapshotSingleDiskParameters(source, dest),
                     
ExecutionHandler.createDefaultContexForTasks(getExecutionContext()));
 
@@ -180,6 +181,8 @@
         parameters.setEntityInfo(getParameters().getEntityInfo());
         parameters.setParentParameters(getParameters());
         parameters.setParentCommand(getActionType());
+        parameters.setCommandType(getSnapshotActionType());
+        parameters.setVdsId(getVm().getRunOnVds());
         return parameters;
     }
 
@@ -221,7 +224,7 @@
                 !validateVmNotDuringSnapshot() ||
                 !validateVmNotInPreview() ||
                 !validateSnapshotExists() ||
-                !validate(vmValidator.vmDown()) ||
+                !validate(vmValidator.vmQualifiedForSnapshotMerge()) ||
                 
!validate(vmValidator.vmNotHavingDeviceSnapshotsAttachedToOtherVms(false))) {
             return false;
         }
@@ -338,10 +341,10 @@
     protected SnapshotsValidator createSnapshotValidator() {
         return new SnapshotsValidator();
     }
+
     protected VmValidator createVmValidator(VM vm) {
         return new VmValidator(vm);
     }
-
 
     @Override
     public AuditLogType getAuditLogTypeValue() {
@@ -360,7 +363,12 @@
 
     @Override
     protected VdcActionType getChildActionType() {
-        return VdcActionType.RemoveSnapshotSingleDisk;
+        // TODO GP verify this isn't called, then remove it
+        return VdcActionType.Unknown;
+    }
+
+    private VdcActionType getSnapshotActionType() {
+        return getVm().isDown() ? VdcActionType.RemoveSnapshotSingleDisk : 
VdcActionType.RemoveSnapshotSingleDiskLive;
     }
 
     @Override
diff --git 
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotSingleDiskCommand.java
 
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotSingleDiskCommand.java
index d9b3da7..aa0f214 100644
--- 
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotSingleDiskCommand.java
+++ 
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotSingleDiskCommand.java
@@ -1,118 +1,45 @@
 package org.ovirt.engine.core.bll;
 
-import java.util.Collections;
-import java.util.Map;
-
 import org.ovirt.engine.core.common.VdcObjectType;
 import org.ovirt.engine.core.common.action.ImagesContainterParametersBase;
 import org.ovirt.engine.core.common.action.VdcActionType;
 import org.ovirt.engine.core.common.asynctasks.AsyncTaskType;
-import org.ovirt.engine.core.common.businessentities.DiskImage;
 import org.ovirt.engine.core.common.job.StepEnum;
-import 
org.ovirt.engine.core.common.vdscommands.GetImageInfoVDSCommandParameters;
 import 
org.ovirt.engine.core.common.vdscommands.MergeSnapshotsVDSCommandParameters;
 import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
 import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
 import org.ovirt.engine.core.compat.Guid;
 import org.ovirt.engine.core.dal.job.ExecutionMessageDirector;
-import org.springframework.util.CollectionUtils;
 
 @InternalCommandAttribute
-public class RemoveSnapshotSingleDiskCommand<T extends 
ImagesContainterParametersBase> extends BaseImagesCommand<T> {
+public class RemoveSnapshotSingleDiskCommand<T extends 
ImagesContainterParametersBase> extends RemoveSnapshotSingleDiskCommandBase {
     public RemoveSnapshotSingleDiskCommand(T parameters) {
         super(parameters);
     }
 
     @Override
-    protected void executeCommand() {
-        Guid storagePoolId = (getDiskImage().getStoragePoolId() != null) ?
-                getDiskImage().getStoragePoolId() : Guid.Empty;
-
-        Guid storageDomainId = 
!CollectionUtils.isEmpty(getDiskImage().getStorageIds()) ?
-                getDiskImage().getStorageIds().get(0) : Guid.Empty;
-
-        Guid taskId = 
persistAsyncTaskPlaceHolder(VdcActionType.RemoveSnapshot);
-        VDSReturnValue vdsReturnValue = mergeSnapshots(storagePoolId, 
storageDomainId);
-        if (vdsReturnValue != null && vdsReturnValue.getCreationInfo() != 
null) {
-            
getReturnValue().getInternalVdsmTaskIdList().add(createTask(taskId, 
vdsReturnValue, storageDomainId));
-            setSucceeded(vdsReturnValue.getSucceeded());
-        } else {
-            setSucceeded(false);
-        }
+    protected VdcActionType getParentActionType() {
+        return VdcActionType.RemoveSnapshot;
     }
 
-    private VDSReturnValue mergeSnapshots(Guid storagePoolId, Guid 
storageDomainId) {
+    @Override
+    protected VDSReturnValue mergeSnapshots(Guid storagePoolId, Guid 
storageDomainId) {
         MergeSnapshotsVDSCommandParameters params = new 
MergeSnapshotsVDSCommandParameters(storagePoolId,
                 storageDomainId, getVmId(), getDiskImage().getId(), 
getDiskImage().getImageId(),
                 getDestinationDiskImage().getImageId(), 
getDiskImage().isWipeAfterDelete());
-
         return runVdsCommand(VDSCommandType.MergeSnapshots, params);
     }
 
-    private Guid createTask(Guid taskId, VDSReturnValue vdsReturnValue, Guid 
storageDomainId) {
+    @Override
+    protected Guid createTask(Guid taskId, VDSReturnValue vdsReturnValue, Guid 
storageDomainId) {
         String message = 
ExecutionMessageDirector.resolveStepMessage(StepEnum.MERGE_SNAPSHOTS,
                 getJobMessageProperties());
-
         return super.createTask(taskId, vdsReturnValue.getCreationInfo(), 
VdcActionType.RemoveSnapshot,
                 message, VdcObjectType.Storage, storageDomainId);
     }
 
     @Override
-    public Map<String, String> getJobMessageProperties() {
-        return 
Collections.singletonMap(VdcObjectType.Disk.name().toLowerCase(), 
getDiskImage().getDiskAlias());
-    }
-
-    @Override
     protected AsyncTaskType getTaskType() {
         return AsyncTaskType.mergeSnapshots;
-    }
-
-    @Override
-    protected void endSuccessfully() {
-        // NOTE: The removal of the images from DB is done here
-        // assuming that there might be situation (related to
-        // tasks failures) in which we will want to preserve the
-        // original state (before the merge-attempt).
-        if (getDestinationDiskImage() != null) {
-            DiskImage curr = getDestinationDiskImage();
-            while (!curr.getParentId().equals(getDiskImage().getParentId())) {
-                curr = getDiskImageDao().getSnapshotById(curr.getParentId());
-                getImageDao().remove(curr.getImageId());
-            }
-            getDestinationDiskImage().setvolumeFormat(curr.getVolumeFormat());
-            getDestinationDiskImage().setVolumeType(curr.getVolumeType());
-            
getDestinationDiskImage().setParentId(getDiskImage().getParentId());
-            getBaseDiskDao().update(curr);
-            getImageDao().update(getDestinationDiskImage().getImage());
-            updateDiskImageDynamic();
-        }
-
-        setSucceeded(true);
-    }
-
-    private void updateDiskImageDynamic() {
-        VDSReturnValue ret = runVdsCommand(
-                VDSCommandType.GetImageInfo,
-                new 
GetImageInfoVDSCommandParameters(getDestinationDiskImage().getStoragePoolId(),
-                        getDestinationDiskImage().getStorageIds().get(0),
-                        getDestinationDiskImage().getId(),
-                        getDestinationDiskImage().getImageId()));
-
-        // Update image's actual size in DB
-        DiskImage imageFromIRS = (DiskImage) ret.getReturnValue();
-        if (imageFromIRS != null) {
-            completeImageData(imageFromIRS);
-        } else {
-            log.warnFormat("Could not update DiskImage's size with ID {0}",
-                    getDestinationDiskImage().getImageId());
-        }
-    }
-
-    @Override
-    protected void endWithFailure() {
-        // TODO: FILL! We should determine what to do in case of
-        // failure (is everything rolled-backed? rolled-forward?
-        // some and some?).
-        setSucceeded(true);
     }
 }
diff --git 
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotSingleDiskCommandBase.java
 
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotSingleDiskCommandBase.java
new file mode 100644
index 0000000..213a505
--- /dev/null
+++ 
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotSingleDiskCommandBase.java
@@ -0,0 +1,99 @@
+package org.ovirt.engine.core.bll;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.ovirt.engine.core.common.VdcObjectType;
+import org.ovirt.engine.core.common.action.ImagesContainterParametersBase;
+import org.ovirt.engine.core.common.action.VdcActionType;
+import org.ovirt.engine.core.common.businessentities.DiskImage;
+import 
org.ovirt.engine.core.common.vdscommands.GetImageInfoVDSCommandParameters;
+import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
+import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
+import org.ovirt.engine.core.compat.Guid;
+import org.springframework.util.CollectionUtils;
+
+public abstract class RemoveSnapshotSingleDiskCommandBase<T extends 
ImagesContainterParametersBase> extends BaseImagesCommand<T> {
+    public RemoveSnapshotSingleDiskCommandBase(T parameters) {
+        super(parameters);
+    }
+
+    @Override
+    protected void executeCommand() {
+        Guid storagePoolId = (getDiskImage().getStoragePoolId() != null) ?
+                getDiskImage().getStoragePoolId() : Guid.Empty;
+
+        Guid storageDomainId = 
!CollectionUtils.isEmpty(getDiskImage().getStorageIds()) ?
+                getDiskImage().getStorageIds().get(0) : Guid.Empty;
+
+        Guid taskId = persistAsyncTaskPlaceHolder(getParentActionType());
+        VDSReturnValue vdsReturnValue = mergeSnapshots(storagePoolId, 
storageDomainId);
+        if (vdsReturnValue != null && vdsReturnValue.getCreationInfo() != 
null) {
+            
getReturnValue().getInternalVdsmTaskIdList().add(createTask(taskId, 
vdsReturnValue, storageDomainId));
+            setSucceeded(vdsReturnValue.getSucceeded());
+        } else {
+            setSucceeded(false);
+        }
+    }
+
+    protected abstract VdcActionType getParentActionType();
+
+    protected abstract VDSReturnValue mergeSnapshots(Guid storagePoolId, Guid 
storageDomainId);
+
+    protected abstract Guid createTask(Guid taskId, VDSReturnValue 
vdsReturnValue, Guid storageDomainId);
+
+    @Override
+    public Map<String, String> getJobMessageProperties() {
+        return 
Collections.singletonMap(VdcObjectType.Disk.name().toLowerCase(), 
getDiskImage().getDiskAlias());
+    }
+
+    @Override
+    protected void endSuccessfully() {
+        // NOTE: The removal of the images from DB is done here
+        // assuming that there might be situation (related to
+        // tasks failures) in which we will want to preserve the
+        // original state (before the merge-attempt).
+        if (getDestinationDiskImage() != null) {
+            DiskImage curr = getDestinationDiskImage();
+            while (!curr.getParentId().equals(getDiskImage().getParentId())) {
+                curr = getDiskImageDao().getSnapshotById(curr.getParentId());
+                getImageDao().remove(curr.getImageId());
+            }
+            getDestinationDiskImage().setvolumeFormat(curr.getVolumeFormat());
+            getDestinationDiskImage().setVolumeType(curr.getVolumeType());
+            
getDestinationDiskImage().setParentId(getDiskImage().getParentId());
+            getBaseDiskDao().update(curr);
+            getImageDao().update(getDestinationDiskImage().getImage());
+            updateDiskImageDynamic();
+        }
+
+        setSucceeded(true);
+    }
+
+    private void updateDiskImageDynamic() {
+        VDSReturnValue ret = runVdsCommand(
+                VDSCommandType.GetImageInfo,
+                new 
GetImageInfoVDSCommandParameters(getDestinationDiskImage().getStoragePoolId(),
+                        getDestinationDiskImage().getStorageIds().get(0),
+                        getDestinationDiskImage().getId(),
+                        getDestinationDiskImage().getImageId()));
+
+        // Update image's actual size in DB
+        DiskImage imageFromIRS = (DiskImage) ret.getReturnValue();
+        if (imageFromIRS != null) {
+            completeImageData(imageFromIRS);
+        } else {
+            log.warnFormat("Could not update DiskImage's size with ID {0}",
+                    getDestinationDiskImage().getImageId());
+        }
+    }
+
+    @Override
+    protected void endWithFailure() {
+        // TODO: FILL! We should determine what to do in case of
+        // failure (is everything rolled-backed? rolled-forward?
+        // some and some?).
+        // TODO GP need to unlock image?
+        setSucceeded(true);
+    }
+}
diff --git 
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotSingleDiskLiveCommand.java
 
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotSingleDiskLiveCommand.java
new file mode 100644
index 0000000..7627fc4
--- /dev/null
+++ 
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RemoveSnapshotSingleDiskLiveCommand.java
@@ -0,0 +1,135 @@
+package org.ovirt.engine.core.bll;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.ovirt.engine.core.common.VdcObjectType;
+import org.ovirt.engine.core.common.action.ImagesContainterParametersBase;
+import org.ovirt.engine.core.common.action.VdcActionType;
+import org.ovirt.engine.core.common.asynctasks.AsyncTaskType;
+import org.ovirt.engine.core.common.businessentities.AsyncTaskResultEnum;
+import org.ovirt.engine.core.common.businessentities.DiskImage;
+import org.ovirt.engine.core.common.businessentities.Snapshot;
+import org.ovirt.engine.core.common.businessentities.VmJob;
+import org.ovirt.engine.core.common.job.StepEnum;
+import 
org.ovirt.engine.core.common.vdscommands.GetVolumeChainVDSCommandParameters;
+import org.ovirt.engine.core.common.vdscommands.MergeVDSCommandParameters;
+import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
+import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
+import org.ovirt.engine.core.compat.Guid;
+import org.ovirt.engine.core.dal.dbbroker.DbFacade;
+import org.ovirt.engine.core.dal.job.ExecutionMessageDirector;
+
+@InternalCommandAttribute
+public class RemoveSnapshotSingleDiskLiveCommand<T extends 
ImagesContainterParametersBase>
+        extends RemoveSnapshotSingleDiskCommandBase
+        implements CommandCallback {
+
+    public RemoveSnapshotSingleDiskLiveCommand(T parameters) {
+        super(parameters);
+    }
+
+    @Override
+    protected VdcActionType getParentActionType() {
+        return VdcActionType.RemoveSnapshot;
+    }
+
+    @Override
+    protected VDSReturnValue mergeSnapshots(Guid storagePoolId, Guid 
storageDomainId) {
+        Guid snapshotId = getSnapshotDao().getId(getVmId(), 
Snapshot.SnapshotType.ACTIVE);
+        DiskImage activeImage = 
getDiskImageDao().getDiskSnapshotForVmSnapshot(getDiskImage().getId(), 
snapshotId);
+
+        MergeVDSCommandParameters params = new 
MergeVDSCommandParameters(getVdsId(), getVmId(),
+                storagePoolId,
+                storageDomainId,
+                activeImage.getImageId(),
+                getDestinationDiskImage().getImageId(),
+                getDiskImage().getImageId(),
+                getDestinationDiskImage().getImageId(),
+                0);
+        return runVdsCommand(VDSCommandType.Merge, params);
+    }
+
+    @Override
+    protected Guid createTask(Guid taskId, VDSReturnValue vdsReturnValue, Guid 
storageDomainId) {
+        String message = 
ExecutionMessageDirector.resolveStepMessage(StepEnum.MERGE_SNAPSHOTS,
+                getJobMessageProperties());
+
+        return super.createTask(taskId, vdsReturnValue.getCreationInfo(), 
VdcActionType.RemoveSnapshot,
+                message, VdcObjectType.Storage, storageDomainId);
+    }
+
+    @Override
+    protected AsyncTaskType getTaskType() {
+        return AsyncTaskType.liveMerge;
+    }
+
+    @Override
+    public boolean isActive(Guid taskId) {
+        // TODO GP check generation id, assume true if stats not yet collected
+        List<VmJob> vmJobs = 
DbFacade.getInstance().getVmJobDao().getAllForVmDisk(getVmId(), 
getImageGroupId());
+        for (VmJob vmJob : vmJobs) {
+            if (vmJob.getId().equals(taskId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public AsyncTaskResultEnum getFinalStatus(Guid taskId) {
+        VDSReturnValue vdsReturnValue = 
runVdsCommand(VDSCommandType.GetVolumeChain,
+                new GetVolumeChainVDSCommandParameters(getVdsId(), getVmId(),
+                        getDiskImage().getStoragePoolId(),
+                        getDiskImage().getStorageIds().get(0),
+                        getDiskImage().getId(),
+                        getDiskImage().getImageId()));
+        // TODO GP which image do we look for?  If active layer was merged, we 
need the full
+        // TODO GP chain based on the base image id, else we have to assume 
failure to retrieve
+        // TODO GP the chain is success, which isn't safe.
+
+        if (vdsReturnValue == null) {
+            return AsyncTaskResultEnum.failure;
+        }
+
+        Set<Guid> images = new HashSet<>((Set<Guid>) 
vdsReturnValue.getReturnValue());
+        images.retainAll(getImagesToRemove());
+        if (!images.isEmpty()) {
+            // TODO GP test msg, or try:  Arrays.toString(images.toArray())
+            log.errorFormat("Failed to live merge, snapshots still in volume 
chain: {0}", images.toString());
+            return AsyncTaskResultEnum.failure;
+        } else {
+            return AsyncTaskResultEnum.success;
+        }
+    }
+
+    private Set<Guid> getImagesToRemove() {
+        Set<Guid> removedImages = new HashSet<>();
+        DiskImage curr = getDestinationDiskImage();
+        removedImages.add(curr.getImageId());
+        while (!curr.getParentId().equals(getDiskImage().getParentId())) {
+            curr = getDiskImageDao().getSnapshotById(curr.getParentId());
+            removedImages.add(curr.getImageId());
+        }
+        return removedImages;
+    }
+
+    @Override
+    public void stopTask(Guid taskId) {
+        // TODO GP constant
+        throw new UnsupportedOperationException("Can't stop live merge"); 
//$NON-NLS-1$
+    }
+
+    @Override
+    protected void endSuccessfully() {
+        // TODO GP finish: delete the now-unused volume, sync the volume 
chain, unlock image (done in VmCommand?)
+        super.endSuccessfully();
+    }
+
+    @Override
+    protected void endWithFailure() {
+        // TODO GP finish: unlock image (done in VmCommand?)
+        super.endWithFailure();
+    }
+}
diff --git 
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/validator/VmValidator.java
 
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/validator/VmValidator.java
index 52e51ec..1c3ef6f 100644
--- 
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/validator/VmValidator.java
+++ 
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/validator/VmValidator.java
@@ -50,6 +50,17 @@
         return ValidationResult.VALID;
     }
 
+    /** @return Validation result that indicates if the VM is qualified to 
have its snapshots merged. */
+    public ValidationResult vmQualifiedForSnapshotMerge() {
+        for (VM vm : vms) {
+            if (!vm.isQualifiedForSnapshotMerge()) {
+                return new 
ValidationResult(VdcBllMessages.ACTION_TYPE_FAILED_VM_IS_NOT_DOWN_OR_UP);
+            }
+        }
+
+        return ValidationResult.VALID;
+    }
+
     public ValidationResult vmNotLocked() {
         for (VM vm : vms) {
             if (vm.getStatus() == VMStatus.ImageLocked) {
diff --git 
a/backend/manager/modules/bll/src/test/java/org/ovirt/engine/core/bll/RemoveSnapshotCommandTest.java
 
b/backend/manager/modules/bll/src/test/java/org/ovirt/engine/core/bll/RemoveSnapshotCommandTest.java
index 9ba25ff..3140f08 100644
--- 
a/backend/manager/modules/bll/src/test/java/org/ovirt/engine/core/bll/RemoveSnapshotCommandTest.java
+++ 
b/backend/manager/modules/bll/src/test/java/org/ovirt/engine/core/bll/RemoveSnapshotCommandTest.java
@@ -233,10 +233,24 @@
     public void testCanDoActionVmUp() {
         prepareForVmValidatorTests();
         cmd.getVm().setStatus(VMStatus.Up);
-        CanDoActionTestUtils.runAndAssertCanDoActionFailure(cmd, 
VdcBllMessages.ACTION_TYPE_FAILED_VM_IS_NOT_DOWN);
+        CanDoActionTestUtils.runAndAssertCanDoActionSuccess(cmd);
     }
 
     @Test
+    public void testCanDoActionVmDown() {
+        prepareForVmValidatorTests();
+        cmd.getVm().setStatus(VMStatus.Down);
+        CanDoActionTestUtils.runAndAssertCanDoActionSuccess(cmd);
+    }
+
+    @Test
+    public void testCanDoActionVmMigrating() {
+        prepareForVmValidatorTests();
+        cmd.getVm().setStatus(VMStatus.MigratingTo);
+        CanDoActionTestUtils.runAndAssertCanDoActionFailure(cmd,
+                VdcBllMessages.ACTION_TYPE_FAILED_VM_IS_NOT_DOWN_OR_UP);
+    }
+    @Test
     public void vmHasPluggedDdeviceSnapshotsAttachedToOtherVms() {
         prepareForVmValidatorTests();
         doReturn(new 
ValidationResult(VdcBllMessages.ACTION_TYPE_FAILED_VM_DISK_SNAPSHOT_IS_ATTACHED_TO_ANOTHER_VM)).when(vmValidator)
diff --git 
a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/VdcActionType.java
 
b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/VdcActionType.java
index 872b6c5..8d94bea 100644
--- 
a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/VdcActionType.java
+++ 
b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/VdcActionType.java
@@ -139,6 +139,7 @@
     GetDiskAlignment(232, QuotaDependency.NONE),
     RemoveVmHibernationVolumes(233, QuotaDependency.NONE),
     RemoveMemoryVolumes(234, QuotaDependency.NONE),
+    RemoveSnapshotSingleDiskLive(235, QuotaDependency.STORAGE),
     // VmPoolCommands
     AddVmPool(301, QuotaDependency.NONE),
     AddVmPoolWithVms(304, ActionGroup.CREATE_VM_POOL, QuotaDependency.BOTH),
diff --git 
a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VM.java
 
b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VM.java
index 8f529ac..06ed95b 100644
--- 
a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VM.java
+++ 
b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VM.java
@@ -1595,6 +1595,10 @@
         return getStatus().isQualifyToMigrate();
     }
 
+    public boolean isQualifiedForSnapshotMerge() {
+        return getStatus().isQualifiedForSnapshotMerge();
+    }
+
     public boolean isRunningAndQualifyForDisksMigration() {
         return getStatus().isUpOrPaused() && getRunOnVds() != null && 
!getRunOnVds().equals(Guid.Empty);
     }
diff --git 
a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VMStatus.java
 
b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VMStatus.java
index c608f84..dd19052 100644
--- 
a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VMStatus.java
+++ 
b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VMStatus.java
@@ -66,6 +66,17 @@
     }
 
     /**
+     * This method reflects whether the VM is qualified to have its snapshots 
merged.  For
+     * this to be true, the VM must up with qemu in a non-transient state, or 
down.
+     *
+     * @return true if this status indicates that the VM status indicates that 
snapshot merge
+     * may be possible, otherwise false
+     */
+    public boolean isQualifiedForSnapshotMerge() {
+        return this == Up || this == PoweringUp || this == Paused || this == 
RebootInProgress || this == Down;
+    }
+
+    /**
      * This method reflects whether the VM is surely running or paused in this 
status
      *
      * @see #isRunning()
diff --git 
a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/errors/VdcBllErrors.java
 
b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/errors/VdcBllErrors.java
index c64ab25..bd5cd51 100644
--- 
a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/errors/VdcBllErrors.java
+++ 
b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/errors/VdcBllErrors.java
@@ -428,6 +428,7 @@
     PROVIDER_FAILURE(5050),
     PROVIDER_IMPORT_CERTIFICATE_CHAIN_ERROR(5051),
     PROVIDER_SSL_FAILURE(5052),
+    VM_NOT_QUALIFIED_FOR_SNAPSHOT_MERGE(5053),
 
     // Network Labels
     LABELED_NETWORK_INTERFACE_NOT_FOUND(5200),
diff --git 
a/backend/manager/modules/dal/src/main/resources/bundles/VdsmErrors.properties 
b/backend/manager/modules/dal/src/main/resources/bundles/VdsmErrors.properties
index eb15c33..68024bc 100644
--- 
a/backend/manager/modules/dal/src/main/resources/bundles/VdsmErrors.properties
+++ 
b/backend/manager/modules/dal/src/main/resources/bundles/VdsmErrors.properties
@@ -385,6 +385,7 @@
 PROVIDER_FAILURE=Failed to communicate with the external provider.
 PROVIDER_IMPORT_CERTIFICATE_CHAIN_ERROR=Failed to import provider certificate 
chain.
 PROVIDER_SSL_FAILURE=SSL problem while trying to connect to the external 
provider.
+VM_NOT_QUALIFIED_FOR_SNAPSHOT_MERGE=To merge snapshots, a VM must be Down, Up 
or Paused.
 MIGRATION_DEST_INVALID_HOSTNAME=Migration destination has an invalid hostname
 MIGRATION_CANCEL_ERROR=Migration not in progress
 MIGRATION_CANCEL_ERROR_NO_VM=Cancel migration has failed. Please try again in 
a few moments and track the VM's event log for details.
@@ -406,4 +407,4 @@
 VolumeResizeValueError=Incorrect size value for volume resize
 ResizeErr=Wrong resize disk parameter
 UpdateDevice=Failed to update device
-SETUP_NETWORKS_ROLLBACK=Reverting back to last known saved configuration.
\ No newline at end of file
+SETUP_NETWORKS_ROLLBACK=Reverting back to last known saved configuration.


-- 
To view, visit http://gerrit.ovirt.org/26909
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic47eb91a0ea1fe150e3b2152e2c9d5f1f2eb3678
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-engine
Gerrit-Branch: master
Gerrit-Owner: Greg Padgett <gpadg...@redhat.com>
_______________________________________________
Engine-patches mailing list
Engine-patches@ovirt.org
http://lists.ovirt.org/mailman/listinfo/engine-patches

Reply via email to