This is an automated email from the ASF dual-hosted git repository.

gabriel pushed a commit to branch kvm-filesystem-datamotion-strategy
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 2e5de075d5155e8754234f92cd7965d44f76a71b
Author: GabrielBrascher <gabr...@pcextreme.nl>
AuthorDate: Thu Nov 8 02:48:42 2018 -0200

    Allow KVM VM live migration with ROOT volume on file
    - Add JUnit tests
---
 .../KvmNonManagedStorageDataMotionStrategy.java    |  27 ++-
 .../motion/StorageSystemDataMotionStrategy.java    |  21 +-
 .../KvmNonManagedStorageSystemDataMotionTest.java  | 256 +++++++++++++++++++++
 .../StorageSystemDataMotionStrategyTest.java       | 151 ++++++++++--
 .../wrapper/LibvirtMigrateCommandWrapper.java      |  27 ++-
 .../wrapper/LibvirtMigrateCommandWrapperTest.java  | 132 ++++++++++-
 6 files changed, 568 insertions(+), 46 deletions(-)

diff --git 
a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java
 
b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java
index 037a89d..9260fb6 100644
--- 
a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java
+++ 
b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java
@@ -30,7 +30,6 @@ import 
org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
 import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
-import org.apache.commons.lang.StringUtils;
 import org.springframework.stereotype.Component;
 
 import com.cloud.agent.api.Answer;
@@ -56,10 +55,10 @@ import com.cloud.vm.DiskProfile;
 public class KvmNonManagedStorageDataMotionStrategy extends 
StorageSystemDataMotionStrategy {
 
     @Inject
-    private TemplateDataFactory tmplFactory;
+    private TemplateDataFactory templateDataFactory;
 
     /**
-     * Uses the canHandle from the Super class {@link 
StorageSystemDataMotionStrategy}. If the storage pool is of file and the 
internalCanHandle from {@link StorageSystemDataMotionStrategy} CANT_HANDLE, 
returns the HIGHEST strategy priority. otherwise returns CANT_HANDLE.
+     * Uses the canHandle from the Super class {@link 
StorageSystemDataMotionStrategy}. If the storage pool is of file and the 
internalCanHandle from {@link StorageSystemDataMotionStrategy} CANT_HANDLE, 
returns the StrategyPriority.HYPERVISOR strategy priority. otherwise returns 
CANT_HANDLE.
      * Note that the super implementation (override) is called by {@link 
#canHandle(Map, Host, Host)} which ensures that {@link #internalCanHandle(Map)} 
will be executed only if the source host is KVM.
      */
     @Override
@@ -69,7 +68,7 @@ public class KvmNonManagedStorageDataMotionStrategy extends 
StorageSystemDataMot
 
             for (VolumeInfo volumeInfo : volumeInfoSet) {
                 StoragePoolVO storagePoolVO = 
_storagePoolDao.findById(volumeInfo.getPoolId());
-                if (storagePoolVO.getPoolType() != StoragePoolType.Filesystem) 
{
+                if (storagePoolVO.getPoolType() != StoragePoolType.Filesystem 
&& storagePoolVO.getPoolType() != StoragePoolType.NetworkFilesystem) {
                     return StrategyPriority.CANT_HANDLE;
                 }
             }
@@ -108,21 +107,22 @@ public class KvmNonManagedStorageDataMotionStrategy 
extends StorageSystemDataMot
             throw new CloudRuntimeException(String.format("Unable to modify 
target volume on the host [host id:%s, name:%s]", destHost.getId(), 
destHost.getName()));
         }
 
-        String libvirtDestImgsPath = StringUtils.EMPTY;
+        String libvirtDestImgsPath = null;
         if (rootImageProvisioningAnswer instanceof CreateAnswer) {
-            libvirtDestImgsPath = 
((CreateAnswer)rootImageProvisioningAnswer).getVolume().getName() + 
File.separator;
+            libvirtDestImgsPath = 
((CreateAnswer)rootImageProvisioningAnswer).getVolume().getName();
         }
-        return libvirtDestImgsPath + destVolumeInfo.getUuid();
+        // File.getAbsolutePath is used to keep the file separator as it 
should be and eliminate a verification to check if exists a file separator in 
the last character of libvirtDestImgsPath.
+        return new File(libvirtDestImgsPath, 
destVolumeInfo.getUuid()).getAbsolutePath();
     }
 
     /**
-     * Returns the template UUID of the given {@link VolumeVO}.
+     * Returns the template UUID with the given id. If the template ID is 
null, it returns null.
      */
     protected String getTemplateUuid(Long templateId) {
         if (templateId == null) {
             return null;
         }
-        TemplateInfo templateImage = tmplFactory.getTemplate(templateId, 
DataStoreRole.Image);
+        TemplateInfo templateImage = 
templateDataFactory.getTemplate(templateId, DataStoreRole.Image);
         return templateImage.getUuid();
     }
 
@@ -133,4 +133,13 @@ public class KvmNonManagedStorageDataMotionStrategy 
extends StorageSystemDataMot
     protected void setVolumePath(VolumeVO volume) {
         volume.setPath(volume.getUuid());
     }
+
+    /**
+     * Return true if the volume should be migrated. Currently only supports 
migrating volumes on storage pool of the type StoragePoolType.Filesystem.
+     * This ensures that volumes on shared storage are not migrated and those 
on local storage pools are migrated.
+     */
+    @Override
+    protected boolean shouldMigrateVolume(StoragePoolVO sourceStoragePool, 
Host destHost, StoragePoolVO destStoragePool) {
+        return sourceStoragePool.getPoolType() == StoragePoolType.Filesystem;
+    }
 }
diff --git 
a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
 
b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
index 9ecf3f1..948c21c 100644
--- 
a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
+++ 
b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
@@ -1687,10 +1687,12 @@ public class StorageSystemDataMotionStrategy implements 
DataMotionStrategy {
 
     /**
      * For each disk to migrate:
-     *   Create a volume on the target storage system.
-     *   Make the newly created volume accessible to the target KVM host.
-     *   Send a command to the target KVM host to connect to the newly created 
volume.
-     * Send a command to the source KVM host to migrate the VM and its storage.
+     * <ul>
+     *  <li>Create a volume on the target storage system.</li>
+     *  <li>Make the newly created volume accessible to the target KVM 
host.</li>
+     *  <li>Send a command to the target KVM host to connect to the newly 
created volume.</li>
+     *  <li>Send a command to the source KVM host to migrate the VM and its 
storage.</li>
+     * </ul>
      */
     @Override
     public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, 
VirtualMachineTO vmTO, Host srcHost, Host destHost, 
AsyncCompletionCallback<CopyCommandResult> callback) {
@@ -1718,6 +1720,10 @@ public class StorageSystemDataMotionStrategy implements 
DataMotionStrategy {
                 StoragePoolVO destStoragePool = 
_storagePoolDao.findById(destDataStore.getId());
                 StoragePoolVO sourceStoragePool = 
_storagePoolDao.findById(srcVolumeInfo.getPoolId());
 
+                if (!shouldMigrateVolume(sourceStoragePool, destHost, 
destStoragePool)) {
+                    continue;
+                }
+
                 VolumeVO destVolume = 
duplicateVolumeOnAnotherStorage(srcVolume, destStoragePool);
                 VolumeInfo destVolumeInfo = 
_volumeDataFactory.getVolume(destVolume.getId(), destDataStore);
 
@@ -1818,6 +1824,13 @@ public class StorageSystemDataMotionStrategy implements 
DataMotionStrategy {
     }
 
     /**
+     * Returns true. This method was implemented considering the classes that 
extend this {@link StorageSystemDataMotionStrategy} and cannot migrate volumes 
from certain types of source storage pools and/or to a different kind of 
destiny storage pool.
+     */
+    protected boolean shouldMigrateVolume(StoragePoolVO sourceStoragePool, 
Host destHost, StoragePoolVO destStoragePool) {
+        return true;
+    }
+
+    /**
      * Returns true if the storage pool type is {@link 
StoragePoolType.Filesystem}.
      */
     protected boolean isStoragePoolTypeOfFile(StoragePoolVO sourceStoragePool) 
{
diff --git 
a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageSystemDataMotionTest.java
 
b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageSystemDataMotionTest.java
new file mode 100644
index 0000000..320d213
--- /dev/null
+++ 
b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageSystemDataMotionTest.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cloudstack.storage.motion;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.storage.datastore.PrimaryDataStoreImpl;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import com.cloud.agent.AgentManager;
+import com.cloud.agent.api.MigrateCommand;
+import com.cloud.agent.api.storage.CreateAnswer;
+import com.cloud.agent.api.storage.CreateCommand;
+import com.cloud.agent.api.to.VirtualMachineTO;
+import com.cloud.agent.api.to.VolumeTO;
+import com.cloud.host.HostVO;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.DiskOfferingVO;
+import com.cloud.storage.Storage;
+import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.storage.Volume;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.DiskOfferingDao;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.DiskProfile;
+
+@RunWith(MockitoJUnitRunner.class)
+public class KvmNonManagedStorageSystemDataMotionTest {
+
+    @Mock
+    private PrimaryDataStoreDao primaryDataStoreDao;
+
+    @Mock
+    private TemplateDataFactory templateFactory;
+
+    @Mock
+    private AgentManager agentMgr;
+
+    @Mock
+    private DiskOfferingDao diskOfferingDao;
+
+    @Spy
+    @InjectMocks
+    private KvmNonManagedStorageDataMotionStrategy 
kvmStorageDataMotionStrategy;
+
+    @Test
+    public void canHandleTestExpectHypervisorStrategyForKvm() {
+        canHandleExpectCannotHandle(HypervisorType.KVM, 1, 
StrategyPriority.HYPERVISOR);
+    }
+
+    @Test
+    public void canHandleTestExpectCannotHandle() {
+        HypervisorType[] hypervisorTypeArray = HypervisorType.values();
+        for (int i = 0; i < hypervisorTypeArray.length; i++) {
+            HypervisorType ht = hypervisorTypeArray[i];
+            if (ht.equals(HypervisorType.KVM)) {
+                continue;
+            }
+            canHandleExpectCannotHandle(ht, 0, StrategyPriority.CANT_HANDLE);
+        }
+    }
+
+    private void canHandleExpectCannotHandle(HypervisorType hypervisorType, 
int times, StrategyPriority expectedStrategyPriority) {
+        HostVO srcHost = new HostVO("sourceHostUuid");
+        srcHost.setHypervisorType(hypervisorType);
+        
Mockito.doReturn(StrategyPriority.HYPERVISOR).when(kvmStorageDataMotionStrategy).internalCanHandle(new
 HashMap<>());
+
+        StrategyPriority strategyPriority = 
kvmStorageDataMotionStrategy.canHandle(new HashMap<>(), srcHost, new 
HostVO("destHostUuid"));
+
+        Mockito.verify(kvmStorageDataMotionStrategy, 
Mockito.times(times)).internalCanHandle(new HashMap<>());
+        Assert.assertEquals(expectedStrategyPriority, strategyPriority);
+    }
+
+    @Test
+    public void internalCanHandleTestNonManaged() {
+        StoragePoolType[] storagePoolTypeArray = StoragePoolType.values();
+        for (int i = 0; i < storagePoolTypeArray.length; i++) {
+            Map<VolumeInfo, DataStore> volumeMap = 
configureTestInternalCanHandle(false, storagePoolTypeArray[i]);
+            StrategyPriority strategyPriority = 
kvmStorageDataMotionStrategy.internalCanHandle(volumeMap);
+            if (storagePoolTypeArray[i] == StoragePoolType.Filesystem || 
storagePoolTypeArray[i] == StoragePoolType.NetworkFilesystem) {
+                Assert.assertEquals(StrategyPriority.HYPERVISOR, 
strategyPriority);
+            } else {
+                Assert.assertEquals(StrategyPriority.CANT_HANDLE, 
strategyPriority);
+            }
+        }
+    }
+
+    @Test
+    public void internalCanHandleTestIsManaged() {
+        StoragePoolType[] storagePoolTypeArray = StoragePoolType.values();
+        for (int i = 0; i < storagePoolTypeArray.length; i++) {
+            Map<VolumeInfo, DataStore> volumeMap = 
configureTestInternalCanHandle(true, storagePoolTypeArray[i]);
+            StrategyPriority strategyPriority = 
kvmStorageDataMotionStrategy.internalCanHandle(volumeMap);
+            Assert.assertEquals(StrategyPriority.CANT_HANDLE, 
strategyPriority);
+        }
+    }
+
+    private Map<VolumeInfo, DataStore> configureTestInternalCanHandle(boolean 
isManagedStorage, StoragePoolType storagePoolType) {
+        VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
+        Mockito.doReturn(0l).when(volumeInfo).getPoolId();
+        DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
+        Mockito.doReturn(0l).when(ds).getId();
+
+        Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
+        volumeMap.put(volumeInfo, ds);
+
+        StoragePoolVO storagePool = Mockito.spy(new StoragePoolVO());
+        Mockito.doReturn(storagePoolType).when(storagePool).getPoolType();
+
+        Mockito.doReturn(storagePool).when(primaryDataStoreDao).findById(0l);
+        Mockito.doReturn(isManagedStorage).when(storagePool).isManaged();
+        return volumeMap;
+    }
+
+    @Test
+    public void getTemplateUuidTestTemplateIdNotNull() {
+        String expectedTemplateUuid = prepareTestGetTemplateUuid();
+        String templateUuid = kvmStorageDataMotionStrategy.getTemplateUuid(0l);
+        Assert.assertEquals(expectedTemplateUuid, templateUuid);
+    }
+
+    @Test
+    public void getTemplateUuidTestTemplateIdNull() {
+        prepareTestGetTemplateUuid();
+        String templateUuid = 
kvmStorageDataMotionStrategy.getTemplateUuid(null);
+        Assert.assertEquals(null, templateUuid);
+    }
+
+    private String prepareTestGetTemplateUuid() {
+        TemplateInfo templateImage = Mockito.mock(TemplateInfo.class);
+        String expectedTemplateUuid = "template uuid";
+        Mockito.when(templateImage.getUuid()).thenReturn(expectedTemplateUuid);
+        Mockito.doReturn(templateImage).when(templateFactory).getTemplate(0l, 
DataStoreRole.Image);
+        return expectedTemplateUuid;
+    }
+
+    @Test
+    public void configureMigrateDiskInfoTest() {
+        VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
+        Mockito.doReturn("volume path").when(srcVolumeInfo).getPath();
+        MigrateCommand.MigrateDiskInfo migrateDiskInfo = 
kvmStorageDataMotionStrategy.configureMigrateDiskInfo(srcVolumeInfo, 
"destPath");
+        Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.FILE, 
migrateDiskInfo.getDiskType());
+        Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.QCOW2, 
migrateDiskInfo.getDriverType());
+        Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.FILE, 
migrateDiskInfo.getSource());
+        Assert.assertEquals("destPath", migrateDiskInfo.getSourceText());
+        Assert.assertEquals("volume path", migrateDiskInfo.getSerialNumber());
+    }
+
+    @Test
+    public void generateDestPathTest() {
+        configureAndVerifygenerateDestPathTest(true, false);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void generateDestPathTestExpectCloudRuntimeException() {
+        configureAndVerifygenerateDestPathTest(false, false);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void generateDestPathTestExpectCloudRuntimeException2() {
+        configureAndVerifygenerateDestPathTest(false, true);
+    }
+
+    private void configureAndVerifygenerateDestPathTest(boolean answerResult, 
boolean answerIsNull) {
+        String uuid = "f3d49ecc-870c-475a-89fa-fd0124420a9b";
+        String destPath = "/var/lib/libvirt/images/";
+
+        VirtualMachineTO vmTO = Mockito.mock(VirtualMachineTO.class);
+        Mockito.when(vmTO.getName()).thenReturn("vmName");
+
+        VolumeVO srcVolume = Mockito.spy(new VolumeVO("name", 0l, 0l, 0l, 0l, 
0l, "folder", "path", Storage.ProvisioningType.THIN, 0l, Volume.Type.ROOT));
+        StoragePoolVO destStoragePool = Mockito.spy(new StoragePoolVO());
+
+        VolumeInfo destVolumeInfo = Mockito.spy(new VolumeObject());
+        Mockito.doReturn(0l).when(destVolumeInfo).getTemplateId();
+        Mockito.doReturn(0l).when(destVolumeInfo).getId();
+        
Mockito.doReturn(Volume.Type.ROOT).when(destVolumeInfo).getVolumeType();
+        Mockito.doReturn("name").when(destVolumeInfo).getName();
+        Mockito.doReturn(0l).when(destVolumeInfo).getSize();
+        Mockito.doReturn(uuid).when(destVolumeInfo).getUuid();
+
+        DiskOfferingVO diskOffering = Mockito.spy(new DiskOfferingVO());
+        Mockito.doReturn(0l).when(diskOffering).getId();
+        Mockito.doReturn(diskOffering).when(diskOfferingDao).findById(0l);
+        DiskProfile diskProfile = Mockito.spy(new DiskProfile(destVolumeInfo, 
diskOffering, HypervisorType.KVM));
+
+        String templateUuid = 
Mockito.doReturn("templateUuid").when(kvmStorageDataMotionStrategy).getTemplateUuid(0l);
+        CreateCommand rootImageProvisioningCommand = new 
CreateCommand(diskProfile, templateUuid, destStoragePool, true);
+        CreateAnswer createAnswer = Mockito.spy(new 
CreateAnswer(rootImageProvisioningCommand, "details"));
+        Mockito.doReturn(answerResult).when(createAnswer).getResult();
+
+        VolumeTO volumeTo = Mockito.mock(VolumeTO.class);
+        Mockito.doReturn(destPath).when(volumeTo).getName();
+        Mockito.doReturn(volumeTo).when(createAnswer).getVolume();
+
+        if (answerIsNull) {
+            Mockito.doReturn(null).when(agentMgr).easySend(0l, 
rootImageProvisioningCommand);
+        } else {
+            Mockito.doReturn(createAnswer).when(agentMgr).easySend(0l, 
rootImageProvisioningCommand);
+        }
+
+        String generatedDestPath = 
kvmStorageDataMotionStrategy.generateDestPath(vmTO, srcVolume, new 
HostVO("sourceHostUuid"), destStoragePool, destVolumeInfo);
+
+        Assert.assertEquals(destPath + uuid, generatedDestPath);
+    }
+
+    @Test
+    public void shouldMigrateVolumeTest() {
+        StoragePoolVO sourceStoragePool = Mockito.spy(new StoragePoolVO());
+        HostVO destHost = new HostVO("guid");
+        StoragePoolVO destStoragePool = new StoragePoolVO();
+        StoragePoolType[] storagePoolTypes = StoragePoolType.values();
+        for (int i = 0; i < storagePoolTypes.length; i++) {
+            
Mockito.doReturn(storagePoolTypes[i]).when(sourceStoragePool).getPoolType();
+            boolean result = 
kvmStorageDataMotionStrategy.shouldMigrateVolume(sourceStoragePool, destHost, 
destStoragePool);
+            if (storagePoolTypes[i] == StoragePoolType.Filesystem) {
+                Assert.assertTrue(result);
+            } else {
+                Assert.assertFalse(result);
+            }
+        }
+    }
+}
diff --git 
a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategyTest.java
 
b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategyTest.java
index ec85f7d..7e189c8 100644
--- 
a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategyTest.java
+++ 
b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategyTest.java
@@ -18,29 +18,43 @@
  */
 package org.apache.cloudstack.storage.motion;
 
-import com.cloud.storage.DataStoreRole;
-import com.cloud.storage.ImageStore;
-import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import java.util.HashMap;
+import java.util.Map;
+
 import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
 import org.apache.cloudstack.storage.datastore.PrimaryDataStoreImpl;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.cloudstack.storage.image.store.ImageStoreImpl;
 import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.MockitoAnnotations.initMocks;
+import com.cloud.agent.api.MigrateCommand;
+import com.cloud.agent.api.to.VirtualMachineTO;
+import com.cloud.host.HostVO;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.ImageStore;
+import com.cloud.storage.Storage;
+import com.cloud.storage.Volume;
+import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.storage.VolumeVO;
 
 @RunWith(MockitoJUnitRunner.class)
 public class StorageSystemDataMotionStrategyTest {
@@ -54,10 +68,12 @@ public class StorageSystemDataMotionStrategyTest {
     @Mock
     ImageStore destinationStore;
 
+    @Spy
     @InjectMocks
-    DataMotionStrategy strategy = new StorageSystemDataMotionStrategy();
+    StorageSystemDataMotionStrategy storageSystemDataMotionStrategy;
+
     @Mock
-    PrimaryDataStoreDao _storagePoolDao;
+    PrimaryDataStoreDao storagePoolDao;
 
     @Before public void setUp() throws Exception {
         sourceStore = mock(PrimaryDataStoreImpl.class);
@@ -65,7 +81,7 @@ public class StorageSystemDataMotionStrategyTest {
         source = mock(VolumeObject.class);
         destination = mock(VolumeObject.class);
 
-                initMocks(strategy);
+                initMocks(storageSystemDataMotionStrategy);
     }
 
     @Test
@@ -77,8 +93,117 @@ public class StorageSystemDataMotionStrategyTest {
         doReturn(sourceStore).when(source).getDataStore();
         doReturn(destinationStore).when(destination).getDataStore();
         StoragePoolVO storeVO = new StoragePoolVO();
-        doReturn(storeVO).when(_storagePoolDao).findById(0l);
+        doReturn(storeVO).when(storagePoolDao).findById(0l);
+
+        
assertTrue(storageSystemDataMotionStrategy.canHandle(source,destination) == 
StrategyPriority.CANT_HANDLE);
+    }
+
+    @Test
+    public void internalCanHandleTestAllStoragePoolsAreManaged() {
+        configureAndTestInternalCanHandle(true, true, 
StrategyPriority.HIGHEST);
+    }
+
+    @Test
+    public void internalCanHandleTestFirstStoragePoolsIsManaged() {
+        configureAndTestInternalCanHandle(false, true, 
StrategyPriority.HIGHEST);
+    }
+
+    @Test
+    public void internalCanHandleTestSecondStoragePoolsIsManaged() {
+        configureAndTestInternalCanHandle(true, false, 
StrategyPriority.HIGHEST);
+    }
+
+    @Test
+    public void internalCanHandleTestNoStoragePoolsIsManaged() {
+        configureAndTestInternalCanHandle(false, false, 
StrategyPriority.CANT_HANDLE);
+    }
+
+    private void configureAndTestInternalCanHandle(boolean sPool0IsManaged, 
boolean sPool1IsManaged, StrategyPriority expectedStrategyPriority) {
+        VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
+        Mockito.doReturn(0l).when(volumeInfo).getPoolId();
+
+        DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
+        Mockito.doReturn(1l).when(ds).getId();
+
+        Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
+        volumeMap.put(volumeInfo, ds);
+
+        StoragePoolVO storagePool0 = Mockito.spy(new StoragePoolVO());
+        Mockito.doReturn(sPool0IsManaged).when(storagePool0).isManaged();
+        StoragePoolVO storagePool1 = Mockito.spy(new StoragePoolVO());
+        Mockito.doReturn(sPool1IsManaged).when(storagePool1).isManaged();
+
+        Mockito.doReturn(storagePool0).when(storagePoolDao).findById(0l);
+        Mockito.doReturn(storagePool1).when(storagePoolDao).findById(1l);
 
-        assertTrue(strategy.canHandle(source,destination) == 
StrategyPriority.CANT_HANDLE);
+        StrategyPriority strategyPriority = 
storageSystemDataMotionStrategy.internalCanHandle(volumeMap);
+
+        Assert.assertEquals(expectedStrategyPriority, strategyPriority);
+    }
+
+    @Test
+    public void isStoragePoolTypeOfFileTest() {
+        StoragePoolVO sourceStoragePool = Mockito.spy(new StoragePoolVO());
+        StoragePoolType[] storagePoolTypeArray = StoragePoolType.values();
+        for (int i = 0; i < storagePoolTypeArray.length; i++) {
+            
Mockito.doReturn(storagePoolTypeArray[i]).when(sourceStoragePool).getPoolType();
+            boolean result = 
storageSystemDataMotionStrategy.isStoragePoolTypeOfFile(sourceStoragePool);
+            if (sourceStoragePool.getPoolType() == StoragePoolType.Filesystem) 
{
+                Assert.assertTrue(result);
+            } else {
+                Assert.assertFalse(result);
+            }
+        }
+    }
+
+    @Test
+    public void generateDestPathTest() {
+        VolumeObject destVolumeInfo = Mockito.spy(new VolumeObject());
+        HostVO destHost = new HostVO("guid");
+        Mockito.doReturn("iScsiName").when(destVolumeInfo).get_iScsiName();
+        Mockito.doReturn(0l).when(destVolumeInfo).getPoolId();
+        
Mockito.doReturn("expected").when(storageSystemDataMotionStrategy).connectHostToVolume(destHost,
 0l, "iScsiName");
+
+        String expected = 
storageSystemDataMotionStrategy.generateDestPath(Mockito.mock(VirtualMachineTO.class),
 Mockito.mock(VolumeVO.class), destHost, Mockito.mock(StoragePoolVO.class),
+                destVolumeInfo);
+
+        Assert.assertEquals(expected, "expected");
+        
Mockito.verify(storageSystemDataMotionStrategy).connectHostToVolume(destHost, 
0l, "iScsiName");
+    }
+
+    @Test
+    public void configureMigrateDiskInfoTest() {
+        VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
+        Mockito.doReturn("volume path").when(srcVolumeInfo).getPath();
+        MigrateCommand.MigrateDiskInfo migrateDiskInfo = 
storageSystemDataMotionStrategy.configureMigrateDiskInfo(srcVolumeInfo, 
"destPath");
+        Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, 
migrateDiskInfo.getDiskType());
+        Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.RAW, 
migrateDiskInfo.getDriverType());
+        Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.DEV, 
migrateDiskInfo.getSource());
+        Assert.assertEquals("destPath", migrateDiskInfo.getSourceText());
+        Assert.assertEquals("volume path", migrateDiskInfo.getSerialNumber());
+    }
+
+    @Test
+    public void setVolumePathTest() {
+        VolumeVO volume = new VolumeVO("name", 0l, 0l, 0l, 0l, 0l, "folder", 
"path", Storage.ProvisioningType.THIN, 0l, Volume.Type.ROOT);
+        String volumePath = "iScsiName";
+        volume.set_iScsiName(volumePath);
+
+        storageSystemDataMotionStrategy.setVolumePath(volume);
+
+        Assert.assertEquals(volumePath, volume.getPath());
+    }
+
+    @Test
+    public void shouldMigrateVolumeTest() {
+        StoragePoolVO sourceStoragePool = Mockito.spy(new StoragePoolVO());
+        HostVO destHost = new HostVO("guid");
+        StoragePoolVO destStoragePool = new StoragePoolVO();
+        StoragePoolType[] storagePoolTypes = StoragePoolType.values();
+        for (int i = 0; i < storagePoolTypes.length; i++) {
+            
Mockito.doReturn(storagePoolTypes[i]).when(sourceStoragePool).getPoolType();
+            boolean result = 
storageSystemDataMotionStrategy.shouldMigrateVolume(sourceStoragePool, 
destHost, destStoragePool);
+            Assert.assertTrue(result);
+        }
     }
-}
\ No newline at end of file
+}
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java
index 174c836..2e3bd20 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java
@@ -207,14 +207,7 @@ public final class LibvirtMigrateCommandWrapper extends 
CommandWrapper<MigrateCo
             destDomain = migrateThread.get(10, TimeUnit.SECONDS);
 
             if (destDomain != null) {
-                for (DiskDef disk : disks) {
-                    MigrateDiskInfo migrateDiskInfo = 
searchDiskDefOnMigrateDiskInfoList(migrateDiskInfoList, disk);
-                    if (migrateDiskInfo != null && 
migrateDiskInfo.isSourceDiskOnStorageFileSystem()) {
-                        deleteLocalVolume(disk.getDiskPath());
-                    } else {
-                        libvirtComputingResource.cleanupDisk(disk);
-                    }
-                }
+                deleteOrDisconnectDisksOnSourcePool(libvirtComputingResource, 
migrateDiskInfoList, disks);
             }
 
         } catch (final LibvirtException e) {
@@ -289,7 +282,23 @@ public final class LibvirtMigrateCommandWrapper extends 
CommandWrapper<MigrateCo
     }
 
     /**
-     * Deletes the local volume from the storage pool
+     * In case of a local file, it deletes the file on the source host/storage 
pool. Otherwise (for instance iScsi) it disconnects the disk on the source 
storage pool. </br>
+     * This method must be executed after a successful migration to a target 
storage pool, cleaning up the source storage.
+     */
+    protected void deleteOrDisconnectDisksOnSourcePool(final 
LibvirtComputingResource libvirtComputingResource, final List<MigrateDiskInfo> 
migrateDiskInfoList,
+            List<DiskDef> disks) {
+        for (DiskDef disk : disks) {
+            MigrateDiskInfo migrateDiskInfo = 
searchDiskDefOnMigrateDiskInfoList(migrateDiskInfoList, disk);
+            if (migrateDiskInfo != null && 
migrateDiskInfo.isSourceDiskOnStorageFileSystem()) {
+                deleteLocalVolume(disk.getDiskPath());
+            } else {
+                libvirtComputingResource.cleanupDisk(disk);
+            }
+        }
+    }
+
+    /**
+     * Deletes the local volume from the storage pool.
      */
     protected void deleteLocalVolume(String localPath) {
         try {
diff --git 
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java
 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java
index da71e40..f703a4a 100644
--- 
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java
+++ 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java
@@ -21,11 +21,31 @@ package com.cloud.hypervisor.kvm.resource.wrapper;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Assert;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.libvirt.Connect;
+import org.libvirt.StorageVol;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
 
+import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo;
+import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo.DiskType;
+import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo.DriverType;
+import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo.Source;
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
+import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
 import com.cloud.utils.exception.CloudRuntimeException;
 
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({LibvirtConnection.class, LibvirtMigrateCommandWrapper.class})
 public class LibvirtMigrateCommandWrapperTest {
     String fullfile =
 "<domain type='kvm' id='4'>\n" +
@@ -252,11 +272,12 @@ public class LibvirtMigrateCommandWrapperTest {
 "  </devices>\n" +
 "</domain>";
 
+    LibvirtMigrateCommandWrapper libvirtMigrateCmdWrapper = new 
LibvirtMigrateCommandWrapper();
+
     @Test
     public void testReplaceIpForVNCInDescFile() {
         final String targetIp = "192.168.22.21";
-        final LibvirtMigrateCommandWrapper lw = new 
LibvirtMigrateCommandWrapper();
-        final String result = lw.replaceIpForVNCInDescFile(fullfile, targetIp);
+        final String result = 
libvirtMigrateCmdWrapper.replaceIpForVNCInDescFile(fullfile, targetIp);
         assertTrue("transformation does not live up to expectation:\n" + 
result, targetfile.equals(result));
     }
 
@@ -279,8 +300,7 @@ public class LibvirtMigrateCommandWrapperTest {
                 "  </devices>" +
                 "</domain>";
         final String targetIp = "10.10.10.10";
-        final LibvirtMigrateCommandWrapper lw = new 
LibvirtMigrateCommandWrapper();
-        final String result = lw.replaceIpForVNCInDescFile(xmlDesc, targetIp);
+        final String result = 
libvirtMigrateCmdWrapper.replaceIpForVNCInDescFile(xmlDesc, targetIp);
         assertTrue("transformation does not live up to expectation:\n" + 
result, expectedXmlDesc.equals(result));
     }
 
@@ -303,26 +323,116 @@ public class LibvirtMigrateCommandWrapperTest {
                 "  </devices>" +
                 "</domain>";
         final String targetIp = "localhost.localdomain";
-        final LibvirtMigrateCommandWrapper lw = new 
LibvirtMigrateCommandWrapper();
-        final String result = lw.replaceIpForVNCInDescFile(xmlDesc, targetIp);
+        final String result = 
libvirtMigrateCmdWrapper.replaceIpForVNCInDescFile(xmlDesc, targetIp);
         assertTrue("transformation does not live up to expectation:\n" + 
result, expectedXmlDesc.equals(result));
     }
 
     @Test
     public void testMigrationUri() {
         final String ip = "10.1.1.1";
-        LibvirtMigrateCommandWrapper lw = new LibvirtMigrateCommandWrapper();
         LibvirtComputingResource lcr = new LibvirtComputingResource();
         if (lcr.isHostSecured()) {
-            assertEquals(lw.createMigrationURI(ip, lcr), 
String.format("qemu+tls://%s/system", ip));
+            assertEquals(libvirtMigrateCmdWrapper.createMigrationURI(ip, lcr), 
String.format("qemu+tls://%s/system", ip));
         } else {
-            assertEquals(lw.createMigrationURI(ip, lcr), 
String.format("qemu+tcp://%s/system", ip));
+            assertEquals(libvirtMigrateCmdWrapper.createMigrationURI(ip, lcr), 
String.format("qemu+tcp://%s/system", ip));
         }
     }
 
     @Test(expected = CloudRuntimeException.class)
     public void testMigrationUriException() {
-        LibvirtMigrateCommandWrapper lw = new LibvirtMigrateCommandWrapper();
-        lw.createMigrationURI(null, new LibvirtComputingResource());
+        libvirtMigrateCmdWrapper.createMigrationURI(null, new 
LibvirtComputingResource());
+    }
+
+    @Test
+    public void deleteLocalVolumeTest() throws Exception {
+        PowerMockito.mockStatic(LibvirtConnection.class);
+        Connect conn = Mockito.mock(Connect.class);
+
+        PowerMockito.doReturn(conn).when(LibvirtConnection.class, 
"getConnection");
+
+        StorageVol storageVolLookupByPath = Mockito.mock(StorageVol.class);
+        
Mockito.when(conn.storageVolLookupByPath("localPath")).thenReturn(storageVolLookupByPath);
+
+        libvirtMigrateCmdWrapper.deleteLocalVolume("localPath");
+
+        PowerMockito.verifyStatic(Mockito.times(1));
+        LibvirtConnection.getConnection();
+        InOrder inOrder = Mockito.inOrder(conn, storageVolLookupByPath);
+        inOrder.verify(conn, 
Mockito.times(1)).storageVolLookupByPath("localPath");
+        inOrder.verify(storageVolLookupByPath, Mockito.times(1)).delete(0);
     }
+
+    @Test
+    public void searchDiskDefOnMigrateDiskInfoListTest() {
+        
configureAndVerifyTestSearchDiskDefOnMigrateDiskInfoList("f3d49ecc-870c-475a-89fa-fd0124420a9b",
 "/var/lib/libvirt/images/f3d49ecc-870c-475a-89fa-fd0124420a9b", false);
+    }
+
+    @Test
+    public void searchDiskDefOnMigrateDiskInfoListTestExpectNull() {
+        
configureAndVerifyTestSearchDiskDefOnMigrateDiskInfoList("f3d49ecc-870c-475a-89fa-fd0124420a9b",
 "/var/lib/libvirt/images/f3d49ecc-870c-89fa-fd0124420a9b", true);
+    }
+
+    private void 
configureAndVerifyTestSearchDiskDefOnMigrateDiskInfoList(String serialNumber, 
String diskPath, boolean isExpectedDiskInfoNull) {
+        MigrateDiskInfo migrateDiskInfo = new MigrateDiskInfo(serialNumber, 
DiskType.FILE, DriverType.QCOW2, Source.FILE, "sourceText");
+        List<MigrateDiskInfo> migrateDiskInfoList = new ArrayList<>();
+        migrateDiskInfoList.add(migrateDiskInfo);
+
+        DiskDef disk = new DiskDef();
+        disk.setDiskPath(diskPath);
+
+        MigrateDiskInfo returnedMigrateDiskInfo = 
libvirtMigrateCmdWrapper.searchDiskDefOnMigrateDiskInfoList(migrateDiskInfoList,
 disk);
+
+        if (isExpectedDiskInfoNull)
+            Assert.assertEquals(null, returnedMigrateDiskInfo);
+        else
+            Assert.assertEquals(migrateDiskInfo, returnedMigrateDiskInfo);
+    }
+
+    @Test
+    public void deleteOrDisconnectDisksOnSourcePoolTest() {
+        LibvirtMigrateCommandWrapper spyLibvirtMigrateCmdWrapper = 
PowerMockito.spy(libvirtMigrateCmdWrapper);
+        
Mockito.doNothing().when(spyLibvirtMigrateCmdWrapper).deleteLocalVolume("volPath");
+
+        List<MigrateDiskInfo> migrateDiskInfoList = new ArrayList<>();
+        MigrateDiskInfo migrateDiskInfo0 = createMigrateDiskInfo(true);
+        MigrateDiskInfo migrateDiskInfo2 = createMigrateDiskInfo(false);
+
+        List<DiskDef> disks = new ArrayList<>();
+        DiskDef diskDef0 = new DiskDef();
+        DiskDef diskDef1 = new DiskDef();
+        DiskDef diskDef2 = new DiskDef();
+
+        diskDef0.setDiskPath("volPath");
+        disks.add(diskDef0);
+        disks.add(diskDef1);
+        disks.add(diskDef2);
+
+        LibvirtComputingResource libvirtComputingResource = Mockito.spy(new 
LibvirtComputingResource());
+        
Mockito.doReturn(true).when(libvirtComputingResource).cleanupDisk(diskDef1);
+
+        
Mockito.doReturn(migrateDiskInfo0).when(spyLibvirtMigrateCmdWrapper).searchDiskDefOnMigrateDiskInfoList(migrateDiskInfoList,
 diskDef0);
+        
Mockito.doReturn(null).when(spyLibvirtMigrateCmdWrapper).searchDiskDefOnMigrateDiskInfoList(migrateDiskInfoList,
 diskDef1);
+        
Mockito.doReturn(migrateDiskInfo2).when(spyLibvirtMigrateCmdWrapper).searchDiskDefOnMigrateDiskInfoList(migrateDiskInfoList,
 diskDef2);
+
+        
spyLibvirtMigrateCmdWrapper.deleteOrDisconnectDisksOnSourcePool(libvirtComputingResource,
 migrateDiskInfoList, disks);
+
+        InOrder inOrder = Mockito.inOrder(spyLibvirtMigrateCmdWrapper, 
libvirtComputingResource);
+        inOrderVerifyDeleteOrDisconnect(inOrder, spyLibvirtMigrateCmdWrapper, 
libvirtComputingResource, migrateDiskInfoList, diskDef0, 1, 0);
+        inOrderVerifyDeleteOrDisconnect(inOrder, spyLibvirtMigrateCmdWrapper, 
libvirtComputingResource, migrateDiskInfoList, diskDef1, 0, 1);
+        inOrderVerifyDeleteOrDisconnect(inOrder, spyLibvirtMigrateCmdWrapper, 
libvirtComputingResource, migrateDiskInfoList, diskDef2, 0, 1);
+    }
+
+    private MigrateDiskInfo createMigrateDiskInfo(boolean 
isSourceDiskOnStorageFileSystem) {
+        MigrateDiskInfo migrateDiskInfo = new MigrateDiskInfo("serialNumber", 
DiskType.FILE, DriverType.QCOW2, Source.FILE, "sourceText");
+        
migrateDiskInfo.setSourceDiskOnStorageFileSystem(isSourceDiskOnStorageFileSystem);
+        return migrateDiskInfo;
+    }
+
+    private void inOrderVerifyDeleteOrDisconnect(InOrder inOrder, 
LibvirtMigrateCommandWrapper lw, LibvirtComputingResource virtResource, 
List<MigrateDiskInfo> diskInfoList,
+            DiskDef disk, int timesDelete, int timesCleanup) {
+        inOrder.verify(lw).searchDiskDefOnMigrateDiskInfoList(diskInfoList, 
disk);
+        inOrder.verify(lw, 
Mockito.times(timesDelete)).deleteLocalVolume("volPath");
+        inOrder.verify(virtResource, 
Mockito.times(timesCleanup)).cleanupDisk(disk);
+    }
+
 }

Reply via email to