diff --git a/plugins/storage/volume/storpool/pom.xml b/plugins/storage/volume/storpool/pom.xml index 5a1a6257115..648a4fb971d 100644 --- a/plugins/storage/volume/storpool/pom.xml +++ b/plugins/storage/volume/storpool/pom.xml @@ -51,6 +51,16 @@ commons-collections4 ${cs.commons-collections.version} + + org.mockito + mockito-core + 4.7.0 + + + org.mockito + mockito-inline + 4.7.0 + diff --git a/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolDownloadTemplateCommandWrapper.java b/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolDownloadTemplateCommandWrapper.java index 07b08a1da2b..87a46ba62c9 100644 --- a/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolDownloadTemplateCommandWrapper.java +++ b/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolDownloadTemplateCommandWrapper.java @@ -25,6 +25,7 @@ import java.io.File; import java.util.List; import org.apache.cloudstack.storage.command.CopyCmdAnswer; +import org.apache.cloudstack.storage.to.TemplateObjectTO; import org.apache.cloudstack.utils.qemu.QemuImg; import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat; import org.apache.cloudstack.utils.qemu.QemuImgFile; @@ -106,6 +107,10 @@ public final class StorPoolDownloadTemplateCommandWrapper extends CommandWrapper final QemuImg qemu = new QemuImg(cmd.getWaitInMillSeconds()); StorPoolStorageAdaptor.resize( Long.toString(srcDisk.getVirtualSize()), dst.getPath()); + if (dst instanceof TemplateObjectTO) { + ((TemplateObjectTO) dst).setSize(srcDisk.getVirtualSize()); + } + dstPath = dst.getPath(); StorPoolStorageAdaptor.attachOrDetachVolume("attach", cmd.getObjectType(), dstPath); diff --git a/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolModifyStorageCommandWrapper.java b/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolModifyStorageCommandWrapper.java index b3579708335..b797b3c20d1 100644 --- a/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolModifyStorageCommandWrapper.java +++ b/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolModifyStorageCommandWrapper.java @@ -49,7 +49,7 @@ public final class StorPoolModifyStorageCommandWrapper extends CommandWrapper details) { - SP_LOG("StorpooolStorageAdaptor.createStoragePool: uuid=%s, host=%s:%d, path=%s, userInfo=%s, type=%s", uuid, host, port, path, userInfo, storagePoolType); + SP_LOG("StorPoolStorageAdaptor.createStoragePool: uuid=%s, host=%s:%d, path=%s, userInfo=%s, type=%s", uuid, host, port, path, userInfo, storagePoolType); StorPoolStoragePool storagePool = new StorPoolStoragePool(uuid, host, port, storagePoolType, this); storageUuidToStoragePool.put(uuid, storagePool); @@ -67,30 +67,30 @@ public class StorPoolStorageAdaptor implements StorageAdaptor { @Override public KVMStoragePool getStoragePool(String uuid) { - SP_LOG("StorpooolStorageAdaptor.getStoragePool: uuid=%s", uuid); + SP_LOG("StorPoolStorageAdaptor.getStoragePool: uuid=%s", uuid); return storageUuidToStoragePool.get(uuid); } @Override public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) { - SP_LOG("StorpooolStorageAdaptor.getStoragePool: uuid=%s, refresh=%s", uuid, refreshInfo); + SP_LOG("StorPoolStorageAdaptor.getStoragePool: uuid=%s, refresh=%s", uuid, refreshInfo); return storageUuidToStoragePool.get(uuid); } @Override public boolean deleteStoragePool(String uuid) { - SP_LOG("StorpooolStorageAdaptor.deleteStoragePool: uuid=%s", uuid); + SP_LOG("StorPoolStorageAdaptor.deleteStoragePool: uuid=%s", uuid); return storageUuidToStoragePool.remove(uuid) != null; } @Override public boolean deleteStoragePool(KVMStoragePool pool) { - SP_LOG("StorpooolStorageAdaptor.deleteStoragePool: uuid=%s", pool.getUuid()); + SP_LOG("StorPoolStorageAdaptor.deleteStoragePool: uuid=%s", pool.getUuid()); return deleteStoragePool(pool.getUuid()); } private static long getDeviceSize(final String devPath) { - SP_LOG("StorpooolStorageAdaptor.getDeviceSize: path=%s", devPath); + SP_LOG("StorPoolStorageAdaptor.getDeviceSize: path=%s", devPath); if (getVolumeNameFromPath(devPath, true) == null) { return 0; @@ -149,7 +149,7 @@ public class StorPoolStorageAdaptor implements StorageAdaptor { return false; } - SP_LOG("StorpooolStorageAdaptor.attachOrDetachVolume: cmd=%s, type=%s, uuid=%s, name=%s", command, type, volumeUuid, name); + SP_LOG("StorPoolStorageAdaptor.attachOrDetachVolume: cmd=%s, type=%s, uuid=%s, name=%s", command, type, volumeUuid, name); final int numTries = 10; final int sleepTime = 1000; @@ -205,7 +205,7 @@ public class StorPoolStorageAdaptor implements StorageAdaptor { return false; } - SP_LOG("StorpooolStorageAdaptor.resize: size=%s, uuid=%s, name=%s", newSize, volumeUuid, name); + SP_LOG("StorPoolStorageAdaptor.resize: size=%s, uuid=%s, name=%s", newSize, volumeUuid, name); Script sc = new Script("storpool", 0, log); sc.add("-M"); @@ -230,7 +230,7 @@ public class StorPoolStorageAdaptor implements StorageAdaptor { @Override public KVMPhysicalDisk getPhysicalDisk(String volumeUuid, KVMStoragePool pool) { - SP_LOG("StorpooolStorageAdaptor.getPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool); + SP_LOG("StorPoolStorageAdaptor.getPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool); log.debug(String.format("getPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool)); @@ -245,7 +245,7 @@ public class StorPoolStorageAdaptor implements StorageAdaptor { @Override public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map details) { - SP_LOG("StorpooolStorageAdaptor.connectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool); + SP_LOG("StorPoolStorageAdaptor.connectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool); log.debug(String.format("connectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool)); @@ -254,7 +254,7 @@ public class StorPoolStorageAdaptor implements StorageAdaptor { @Override public boolean disconnectPhysicalDisk(String volumeUuid, KVMStoragePool pool) { - SP_LOG("StorpooolStorageAdaptor.disconnectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool); + SP_LOG("StorPoolStorageAdaptor.disconnectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool); log.debug(String.format("disconnectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool)); return attachOrDetachVolume("detach", "volume", volumeUuid); @@ -262,32 +262,23 @@ public class StorPoolStorageAdaptor implements StorageAdaptor { public boolean disconnectPhysicalDisk(Map volumeToDisconnect) { String volumeUuid = volumeToDisconnect.get(DiskTO.UUID); - SP_LOG("StorpooolStorageAdaptor.disconnectPhysicalDisk: map. uuid=%s", volumeUuid); + log.debug(String.format("StorPoolStorageAdaptor.disconnectPhysicalDisk: map. uuid=%s", volumeUuid)); return attachOrDetachVolume("detach", "volume", volumeUuid); } @Override public boolean disconnectPhysicalDiskByPath(String localPath) { - SP_LOG("StorpooolStorageAdaptor.disconnectPhysicalDiskByPath: localPath=%s", localPath); - log.debug(String.format("disconnectPhysicalDiskByPath: localPath=%s", localPath)); return attachOrDetachVolume("detach", "volume", localPath); } - // The following do not apply for StorpoolStorageAdaptor? - @Override - public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, KVMStoragePool pool, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase) { - SP_LOG("StorpooolStorageAdaptor.createPhysicalDisk: uuid=%s, pool=%s, format=%s, size=%d", volumeUuid, pool, format, size); - throw new UnsupportedOperationException("Creating a physical disk is not supported."); - } - @Override public boolean deletePhysicalDisk(String volumeUuid, KVMStoragePool pool, Storage.ImageFormat format) { // Should only come here when cleaning-up StorPool snapshots associated with CloudStack templates. - SP_LOG("StorpooolStorageAdaptor.deletePhysicalDisk: uuid=%s, pool=%s, format=%s", volumeUuid, pool, format); + SP_LOG("StorPoolStorageAdaptor.deletePhysicalDisk: uuid=%s, pool=%s, format=%s", volumeUuid, pool, format); final String name = getVolumeNameFromPath(volumeUuid, true); if (name == null) { - final String err = String.format("StorpooolStorageAdaptor.deletePhysicalDisk: '%s' is not a StorPool volume?", volumeUuid); + final String err = String.format("StorPoolStorageAdaptor.deletePhysicalDisk: '%s' is not a StorPool volume?", volumeUuid); SP_LOG(err); throw new UnsupportedOperationException(err); } @@ -311,21 +302,13 @@ public class StorPoolStorageAdaptor implements StorageAdaptor { @Override public List listPhysicalDisks(String storagePoolUuid, KVMStoragePool pool) { - SP_LOG("StorpooolStorageAdaptor.listPhysicalDisks: uuid=%s, pool=%s", storagePoolUuid, pool); + SP_LOG("StorPoolStorageAdaptor.listPhysicalDisks: uuid=%s, pool=%s", storagePoolUuid, pool); throw new UnsupportedOperationException("Listing disks is not supported for this configuration."); } - @Override - public KVMPhysicalDisk createDiskFromTemplate(KVMPhysicalDisk template, String name, PhysicalDiskFormat format, - ProvisioningType provisioningType, long size, KVMStoragePool destPool, int timeout, byte[] passphrase) { - SP_LOG("StorpooolStorageAdaptor.createDiskFromTemplate: template=%s, name=%s, fmt=%s, ptype=%s, size=%d, dst_pool=%s, to=%d", - template, name, format, provisioningType, size, destPool.getUuid(), timeout); - throw new UnsupportedOperationException("Creating a disk from a template is not yet supported for this configuration."); - } - @Override public KVMPhysicalDisk createTemplateFromDisk(KVMPhysicalDisk disk, String name, PhysicalDiskFormat format, long size, KVMStoragePool destPool) { - SP_LOG("StorpooolStorageAdaptor.createTemplateFromDisk: disk=%s, name=%s, fmt=%s, size=%d, dst_pool=%s", disk, name, format, size, destPool.getUuid()); + SP_LOG("StorPoolStorageAdaptor.createTemplateFromDisk: disk=%s, name=%s, fmt=%s, size=%d, dst_pool=%s", disk, name, format, size, destPool.getUuid()); throw new UnsupportedOperationException("Creating a template from a disk is not yet supported for this configuration."); } @@ -336,51 +319,27 @@ public class StorPoolStorageAdaptor implements StorageAdaptor { @Override public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout) { - SP_LOG("StorpooolStorageAdaptor.copyPhysicalDisk: disk=%s, name=%s, dst_pool=%s, to=%d", disk, name, destPool.getUuid(), timeout); + SP_LOG("StorPoolStorageAdaptor.copyPhysicalDisk: disk=%s, name=%s, dst_pool=%s, to=%d", disk, name, destPool.getUuid(), timeout); throw new UnsupportedOperationException("Copying a disk is not supported in this configuration."); } public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool) { - SP_LOG("StorpooolStorageAdaptor.createDiskFromSnapshot: snap=%s, snap_name=%s, name=%s, dst_pool=%s", snapshot, snapshotName, name, destPool.getUuid()); + SP_LOG("StorPoolStorageAdaptor.createDiskFromSnapshot: snap=%s, snap_name=%s, name=%s, dst_pool=%s", snapshot, snapshotName, name, destPool.getUuid()); throw new UnsupportedOperationException("Creating a disk from a snapshot is not supported in this configuration."); } @Override public boolean refresh(KVMStoragePool pool) { - SP_LOG("StorpooolStorageAdaptor.refresh: pool=%s", pool); + SP_LOG("StorPoolStorageAdaptor.refresh: pool=%s", pool); return true; } @Override public boolean createFolder(String uuid, String path) { - SP_LOG("StorpooolStorageAdaptor.createFolder: uuid=%s, path=%s", uuid, path); + SP_LOG("StorPoolStorageAdaptor.createFolder: uuid=%s, path=%s", uuid, path); throw new UnsupportedOperationException("A folder cannot be created in this configuration."); } - public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, - KVMStoragePool destPool, int timeout) { - SP_LOG("StorpooolStorageAdaptor.createDiskFromSnapshot: snap=%s, snap_name=%s, name=%s, dst_pool=%s", snapshot, - snapshotName, name, destPool.getUuid()); - throw new UnsupportedOperationException( - "Creating a disk from a snapshot is not supported in this configuration."); - } - - public KVMPhysicalDisk createDiskFromTemplateBacking(KVMPhysicalDisk template, String name, - PhysicalDiskFormat format, long size, KVMStoragePool destPool, int timeout, byte[] passphrase) { - SP_LOG("StorpooolStorageAdaptor.createDiskFromTemplateBacking: template=%s, name=%s, dst_pool=%s", template, - name, destPool.getUuid()); - throw new UnsupportedOperationException( - "Creating a disk from a template is not supported in this configuration."); - } - - public KVMPhysicalDisk createTemplateFromDirectDownloadFile(String templateFilePath, KVMStoragePool destPool, - boolean isIso) { - SP_LOG("StorpooolStorageAdaptor.createTemplateFromDirectDownloadFile: templateFilePath=%s, dst_pool=%s", - templateFilePath, destPool.getUuid()); - throw new UnsupportedOperationException( - "Creating a template from direct download is not supported in this configuration."); - } - public KVMPhysicalDisk createTemplateFromDirectDownloadFile(String templateFilePath, String destTemplatePath, KVMStoragePool destPool, ImageFormat format, int timeout) { return null; @@ -390,4 +349,22 @@ public class StorPoolStorageAdaptor implements StorageAdaptor { public boolean createFolder(String uuid, String path, String localPath) { return false; } + + @Override + public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, PhysicalDiskFormat format, + ProvisioningType provisioningType, long size, byte[] passphrase) { + return null; + } + + @Override + public KVMPhysicalDisk createDiskFromTemplate(KVMPhysicalDisk template, String name, PhysicalDiskFormat format, + ProvisioningType provisioningType, long size, KVMStoragePool destPool, int timeout, byte[] passphrase) { + return null; + } + + @Override + public KVMPhysicalDisk createDiskFromTemplateBacking(KVMPhysicalDisk template, String name, + PhysicalDiskFormat format, long size, KVMStoragePool destPool, int timeout, byte[] passphrase) { + return null; + } } diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java index c62680a956a..6eced6fc5d0 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java @@ -17,10 +17,47 @@ * under the License. */ package org.apache.cloudstack.storage.datastore.driver; -import java.util.Map; - -import javax.inject.Inject; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.storage.ResizeVolumeAnswer; +import com.cloud.agent.api.storage.StorPoolBackupSnapshotCommand; +import com.cloud.agent.api.storage.StorPoolBackupTemplateFromSnapshotCommand; +import com.cloud.agent.api.storage.StorPoolCopyVolumeToSecondaryCommand; +import com.cloud.agent.api.storage.StorPoolDownloadTemplateCommand; +import com.cloud.agent.api.storage.StorPoolDownloadVolumeCommand; +import com.cloud.agent.api.storage.StorPoolResizeVolumeCommand; +import com.cloud.agent.api.to.DataObjectType; +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.DataTO; +import com.cloud.agent.api.to.StorageFilerTO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.host.Host; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor; +import com.cloud.server.ResourceTag; +import com.cloud.server.ResourceTag.ResourceObjectType; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.ResizeVolumePayload; +import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.storage.StorageManager; +import com.cloud.storage.StoragePool; +import com.cloud.storage.VMTemplateDetailVO; +import com.cloud.storage.VMTemplateStoragePoolVO; +import com.cloud.storage.VolumeDetailVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.SnapshotDetailsDao; +import com.cloud.storage.dao.SnapshotDetailsVO; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.storage.dao.VMTemplateDetailsDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.dao.VolumeDetailsDao; +import com.cloud.tags.dao.ResourceTagDao; +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.dao.VMInstanceDao; import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; @@ -58,45 +95,8 @@ import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.cloudstack.storage.volume.VolumeObject; import org.apache.log4j.Logger; -import com.cloud.agent.api.Answer; -import com.cloud.agent.api.storage.ResizeVolumeAnswer; -import com.cloud.agent.api.storage.StorPoolBackupSnapshotCommand; -import com.cloud.agent.api.storage.StorPoolBackupTemplateFromSnapshotCommand; -import com.cloud.agent.api.storage.StorPoolCopyVolumeToSecondaryCommand; -import com.cloud.agent.api.storage.StorPoolDownloadTemplateCommand; -import com.cloud.agent.api.storage.StorPoolDownloadVolumeCommand; -import com.cloud.agent.api.storage.StorPoolResizeVolumeCommand; -import com.cloud.agent.api.to.DataObjectType; -import com.cloud.agent.api.to.DataStoreTO; -import com.cloud.agent.api.to.DataTO; -import com.cloud.agent.api.to.StorageFilerTO; -import com.cloud.dc.dao.ClusterDao; -import com.cloud.host.Host; -import com.cloud.host.dao.HostDao; -import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor; -import com.cloud.server.ResourceTag; -import com.cloud.server.ResourceTag.ResourceObjectType; -import com.cloud.storage.DataStoreRole; -import com.cloud.storage.ResizeVolumePayload; -import com.cloud.storage.Storage.StoragePoolType; -import com.cloud.storage.StorageManager; -import com.cloud.storage.StoragePool; -import com.cloud.storage.VMTemplateDetailVO; -import com.cloud.storage.VMTemplateStoragePoolVO; -import com.cloud.storage.VolumeDetailVO; -import com.cloud.storage.VolumeVO; -import com.cloud.storage.dao.SnapshotDetailsDao; -import com.cloud.storage.dao.SnapshotDetailsVO; -import com.cloud.storage.dao.VMTemplateDetailsDao; -import com.cloud.storage.dao.VMTemplatePoolDao; -import com.cloud.storage.dao.VolumeDao; -import com.cloud.storage.dao.VolumeDetailsDao; -import com.cloud.tags.dao.ResourceTagDao; -import com.cloud.utils.Pair; -import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.vm.VMInstanceVO; -import com.cloud.vm.VirtualMachineManager; -import com.cloud.vm.dao.VMInstanceDao; +import javax.inject.Inject; +import java.util.Map; public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { @@ -133,7 +133,7 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { @Inject private StoragePoolDetailsDao storagePoolDetailsDao; @Inject - private VMTemplatePoolDao vmTemplatePoolDao; + private StoragePoolHostDao storagePoolHostDao; @Override public Map getCapabilities() { @@ -629,33 +629,26 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { final String name = vinfo.getUuid(); SpConnectionDesc conn = StorPoolUtil.getSpConnection(vinfo.getDataStore().getUuid(), vinfo.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao); - Long snapshotSize = StorPoolUtil.snapshotSize(parentName, conn); - if (snapshotSize == null) { - err = String.format("Snapshot=%s does not exist on StorPool. Will recreate it first on primary", parentName); - vmTemplatePoolDao.remove(templStoragePoolVO.getId()); + Long snapshotSize = templStoragePoolVO.getTemplateSize(); + long size = vinfo.getSize(); + if (snapshotSize != null && size < snapshotSize) { + StorPoolUtil.spLog(String.format("provided size is too small for snapshot. Provided %d, snapshot %d. Using snapshot size", size, snapshotSize)); + size = snapshotSize; } - if (err == null) { - long size = vinfo.getSize(); - if( size < snapshotSize ) - { - StorPoolUtil.spLog(String.format("provided size is too small for snapshot. Provided %d, snapshot %d. Using snapshot size", size, snapshotSize)); - size = snapshotSize; - } - StorPoolUtil.spLog(String.format("volume size is: %d", size)); - Long vmId = vinfo.getInstanceId(); - SpApiResponse resp = StorPoolUtil.volumeCreate(name, parentName, size, getVMInstanceUUID(vmId), - getVcPolicyTag(vmId), "volume", vinfo.getMaxIops(), conn); - if (resp.getError() == null) { - updateStoragePool(dstData.getDataStore().getId(), vinfo.getSize()); + StorPoolUtil.spLog(String.format("volume size is: %d", size)); + Long vmId = vinfo.getInstanceId(); + SpApiResponse resp = StorPoolUtil.volumeCreate(name, parentName, size, getVMInstanceUUID(vmId), + getVcPolicyTag(vmId), "volume", vinfo.getMaxIops(), conn); + if (resp.getError() == null) { + updateStoragePool(dstData.getDataStore().getId(), vinfo.getSize()); - VolumeObjectTO to = (VolumeObjectTO) vinfo.getTO(); - to.setSize(vinfo.getSize()); - to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false))); + VolumeObjectTO to = (VolumeObjectTO) vinfo.getTO(); + to.setSize(vinfo.getSize()); + to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false))); - answer = new CopyCmdAnswer(to); - } else { - err = String.format("Could not create Storpool volume %s. Error: %s", name, resp.getError()); - } + answer = new CopyCmdAnswer(to); + } else { + err = String.format("Could not create Storpool volume %s. Error: %s", name, resp.getError()); } } else if (srcType == DataObjectType.VOLUME && dstType == DataObjectType.VOLUME) { StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriver.copyAsync src Data Store=%s", srcData.getDataStore().getDriver()); @@ -684,9 +677,9 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { EndPoint ep = selector.select(srcData, dstData); - if( ep == null) { - StorPoolUtil.spLog("select(srcData, dstData) returned NULL. trying srcOnly"); - ep = selector.select(srcData); // Storpool is zone + if (ep == null || storagePoolHostDao.findByPoolHost(dstData.getId(), ep.getId()) == null) { + StorPoolUtil.spLog("select(srcData, dstData) returned NULL or the destination pool is not connected to the selected host. Trying dstData"); + ep = selector.select(dstData); // Storpool is zone } if (ep == null) { err = "No remote endpoint to send command, check if host or ssvm is down?"; @@ -711,27 +704,15 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { } else { // download volume - first copies to secondary VolumeObjectTO srcTO = (VolumeObjectTO)srcData.getTO(); - StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc SRC path=%s ", srcTO.getPath()); - StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc DST canonicalName=%s ", dstData.getDataStore().getClass().getCanonicalName()); + StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc SRC path=%s DST canonicalName=%s ", srcTO.getPath(), dstData.getDataStore().getClass().getCanonicalName()); PrimaryDataStoreTO checkStoragePool = dstData.getTO().getDataStore() instanceof PrimaryDataStoreTO ? (PrimaryDataStoreTO)dstData.getTO().getDataStore() : null; - final String name = StorPoolStorageAdaptor.getVolumeNameFromPath(srcTO.getPath(), true); - StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc DST tmpSnapName=%s ,srcUUID=%s", name, srcTO.getUuid()); + final String volumeName = StorPoolStorageAdaptor.getVolumeNameFromPath(srcTO.getPath(), true); if (checkStoragePool != null && checkStoragePool.getPoolType().equals(StoragePoolType.StorPool)) { - SpConnectionDesc conn = StorPoolUtil.getSpConnection(dstData.getDataStore().getUuid(), dstData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao); - String baseOn = StorPoolStorageAdaptor.getVolumeNameFromPath(srcTO.getPath(), true); - //uuid tag will be the same as srcData.uuid - String volumeName = srcData.getUuid(); - StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc volumeName=%s, baseOn=%s", volumeName, baseOn); - final SpApiResponse response = StorPoolUtil.volumeCopy(volumeName, baseOn, "volume", srcInfo.getMaxIops(), conn); - srcTO.setSize(srcData.getSize()); - srcTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(response, false))); - StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc DST to=%s", srcTO); - - answer = new CopyCmdAnswer(srcTO); + answer = migrateVolumeToStorPool(srcData, dstData, srcInfo, srcTO, volumeName); } else { SpConnectionDesc conn = StorPoolUtil.getSpConnection(srcData.getDataStore().getUuid(), srcData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao); - final SpApiResponse resp = StorPoolUtil.volumeSnapshot(name, srcTO.getUuid(), srcInfo.getInstanceId() != null ? getVMInstanceUUID(srcInfo.getInstanceId()) : null, "temporary", null, conn); + final SpApiResponse resp = StorPoolUtil.volumeSnapshot(volumeName, srcTO.getUuid(), srcInfo.getInstanceId() != null ? getVMInstanceUUID(srcInfo.getInstanceId()) : null, "temporary", null, conn); String snapshotName = StorPoolUtil.getSnapshotNameFromResponse(resp, true, StorPoolUtil.GLOBAL_ID); if (resp.getError() == null) { srcTO.setPath(StorPoolUtil.devPath( @@ -794,6 +775,96 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { callback.complete(res); } + /** + * Live migrate/copy volume from one StorPool storage to another + * @param srcData The source volume data + * @param dstData The destination volume data + * @param srcInfo The source volume info + * @param srcTO The source Volume TO + * @param volumeName The name of the volume + * @return Answer + */ + private Answer migrateVolumeToStorPool(DataObject srcData, DataObject dstData, VolumeInfo srcInfo, + VolumeObjectTO srcTO, final String volumeName) { + Answer answer; + SpConnectionDesc conn = StorPoolUtil.getSpConnection(dstData.getDataStore().getUuid(), dstData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao); + String baseOn = StorPoolStorageAdaptor.getVolumeNameFromPath(srcTO.getPath(), true); + + String vmUuid = null; + String vcPolicyTag = null; + + VMInstanceVO vm = null; + if (srcInfo.getInstanceId() != null) { + vm = vmInstanceDao.findById(srcInfo.getInstanceId()); + } + + if (vm != null) { + vmUuid = vm.getUuid(); + vcPolicyTag = getVcPolicyTag(vm.getId()); + } + + if (vm != null && vm.getState().equals(State.Running)) { + answer = migrateVolume(srcData, dstData, volumeName, conn); + } else { + answer = copyVolume(srcInfo, srcTO, conn, baseOn, vmUuid, vcPolicyTag); + } + return answer; + } + + /** + * Copy the volume from StorPool primary storage to another StorPool primary storage + * @param srcInfo The source volume info + * @param srcTO The source Volume TO + * @param conn StorPool connection + * @param baseOn The name of an already existing volume that the new volume is to be a copy of. + * @param vmUuid The UUID of the VM + * @param vcPolicyTag The VC policy tag + * @return Answer + */ + private Answer copyVolume(VolumeInfo srcInfo, VolumeObjectTO srcTO, SpConnectionDesc conn, String baseOn, String vmUuid, String vcPolicyTag) { + //uuid tag will be the same as srcData.uuid + String volumeName = srcInfo.getUuid(); + Long iops = (srcInfo.getMaxIops() != null && srcInfo.getMaxIops().longValue() > 0) ? srcInfo.getMaxIops() : null; + SpApiResponse response = StorPoolUtil.volumeCopy(volumeName, baseOn, "volume", iops, vmUuid, vcPolicyTag, conn); + if (response.getError() != null) { + return new CopyCmdAnswer(String.format("Could not copy volume [%s] due to %s", baseOn, response.getError())); + } + String newVolume = StorPoolUtil.getNameFromResponse(response, false); + + StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc copy volume[%s] from pool[%s] with a new name [%s]", + baseOn, srcInfo.getDataStore().getName(), newVolume); + + srcTO.setSize(srcInfo.getSize()); + srcTO.setPath(StorPoolUtil.devPath(newVolume)); + + return new CopyCmdAnswer(srcTO); + } + + /** + * Live migrate the StorPool's volume to another StorPool template + * @param srcData The source data volume + * @param dstData The destination data volume + * @param name The volume's name + * @param conn StorPool's connection + * @return Answer + */ + private Answer migrateVolume(DataObject srcData, DataObject dstData, String name, SpConnectionDesc conn) { + Answer answer; + SpApiResponse resp = StorPoolUtil.volumeUpdateTemplate(name, conn); + if (resp.getError() != null) { + answer = new Answer(null, false, String.format("Could not migrate volume %s to %s due to %s", name, conn.getTemplateName(), resp.getError())); + } else { + StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc migrate volume[%s] from pool[%s] to pool[%s]", + name, srcData.getDataStore().getName(),dstData.getDataStore().getName()); + VolumeVO updatedVolume = volumeDao.findById(srcData.getId()); + updatedVolume.setPoolId(dstData.getDataStore().getId()); + updatedVolume.setLastPoolId(srcData.getDataStore().getId()); + volumeDao.update(updatedVolume.getId(), updatedVolume); + answer = new Answer(null, true, null); + } + return answer; + } + @Override public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback) { String snapshotName = snapshot.getUuid(); @@ -883,7 +954,7 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { } if (vinfo.getMaxIops() != null) { - response = StorPoolUtil.volumeUpadateTags(volumeName, null, vinfo.getMaxIops(), conn, null); + response = StorPoolUtil.volumeUpdateTags(volumeName, null, vinfo.getMaxIops(), conn, null); if (response.getError() != null) { StorPoolUtil.spLog("Volume was reverted successfully but max iops could not be set due to %s", response.getError().getDescr()); } @@ -942,7 +1013,7 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { SpConnectionDesc conn = StorPoolUtil.getSpConnection(poolVO.getUuid(), poolVO.getId(), storagePoolDetailsDao, primaryStoreDao); String volName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true); VMInstanceVO userVM = vmInstanceDao.findById(vmId); - SpApiResponse resp = StorPoolUtil.volumeUpadateTags(volName, volume.getInstanceId() != null ? userVM.getUuid() : "", null, conn, getVcPolicyTag(vmId)); + SpApiResponse resp = StorPoolUtil.volumeUpdateTags(volName, volume.getInstanceId() != null ? userVM.getUuid() : "", null, conn, getVcPolicyTag(vmId)); if (resp.getError() != null) { log.warn(String.format("Could not update VC policy tags of a volume with id [%s]", volume.getUuid())); } @@ -965,7 +1036,7 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { try { SpConnectionDesc conn = StorPoolUtil.getSpConnection(poolVO.getUuid(), poolVO.getId(), storagePoolDetailsDao, primaryStoreDao); String volName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true); - SpApiResponse resp = StorPoolUtil.volumeUpadateVCTags(volName, conn, getVcPolicyTag(vmId)); + SpApiResponse resp = StorPoolUtil.volumeUpdateVCTags(volName, conn, getVcPolicyTag(vmId)); if (resp.getError() != null) { log.warn(String.format("Could not update VC policy tags of a volume with id [%s]", volume.getUuid())); } diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/provider/StorPoolHostListener.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/provider/StorPoolHostListener.java index 4a5ce4012d7..9b5320df6d8 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/provider/StorPoolHostListener.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/provider/StorPoolHostListener.java @@ -37,6 +37,7 @@ import org.apache.cloudstack.storage.datastore.util.StorPoolHelper; import org.apache.cloudstack.storage.datastore.util.StorPoolUtil; import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse; import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import com.cloud.agent.AgentManager; @@ -118,21 +119,22 @@ public class StorPoolHostListener implements HypervisorHostListener { final Answer answer = agentMgr.easySend(hostId, cmd); StoragePoolHostVO poolHost = storagePoolHostDao.findByPoolHost(pool.getId(), hostId); + boolean isPoolConnectedToTheHost = poolHost != null; if (answer == null) { + StorPoolUtil.spLog("Storage pool [%s] is not connected to the host [%s]", poolVO.getName(), host.getName()); + deleteVolumeWhenHostCannotConnectPool(conn, volumeOnPool); + removePoolOnHost(poolHost, isPoolConnectedToTheHost); throw new CloudRuntimeException("Unable to get an answer to the modify storage pool command" + pool.getId()); } if (!answer.getResult()) { - if (answer.getDetails() != null) { - if (answer.getDetails().equals("objectDoesNotExist")) { - StorPoolUtil.volumeDelete(StorPoolStorageAdaptor.getVolumeNameFromPath(volumeOnPool.getValue(), true), conn); - storagePoolDetailsDao.remove(volumeOnPool.getId()); - return false; - } else if (answer.getDetails().equals("spNotFound")) { - return false; - } + StorPoolUtil.spLog("Storage pool [%s] is not connected to the host [%s]", poolVO.getName(), host.getName()); + removePoolOnHost(poolHost, isPoolConnectedToTheHost); + if (answer.getDetails() != null && isStorPoolVolumeOrStorageNotExistsOnHost(answer)) { + deleteVolumeWhenHostCannotConnectPool(conn, volumeOnPool); + return false; } String msg = "Unable to attach storage pool" + poolId + " to the host" + hostId; alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, pool.getDataCenterId(), pool.getPodId(), msg, msg); @@ -140,8 +142,6 @@ public class StorPoolHostListener implements HypervisorHostListener { pool.getId()); } - StorPoolUtil.spLog("hostConnect: hostId=%d, poolId=%d", hostId, poolId); - StorPoolModifyStoragePoolAnswer mspAnswer = (StorPoolModifyStoragePoolAnswer)answer; if (mspAnswer.getLocalDatastoreName() != null && pool.isShared()) { String datastoreName = mspAnswer.getLocalDatastoreName(); @@ -155,7 +155,7 @@ public class StorPoolHostListener implements HypervisorHostListener { } } - if (poolHost == null) { + if (!isPoolConnectedToTheHost) { poolHost = new StoragePoolHostVO(pool.getId(), hostId, mspAnswer.getPoolInfo().getLocalPath().replaceAll("//", "/")); storagePoolHostDao.persist(poolHost); } else { @@ -164,10 +164,25 @@ public class StorPoolHostListener implements HypervisorHostListener { StorPoolHelper.setSpClusterIdIfNeeded(hostId, mspAnswer.getClusterId(), clusterDao, hostDao, clusterDetailsDao); - log.info("Connection established between storage pool " + pool + " and host " + hostId); + StorPoolUtil.spLog("Connection established between storage pool [%s] and host [%s]", poolVO.getName(), host.getName()); return true; } + private boolean isStorPoolVolumeOrStorageNotExistsOnHost(final Answer answer) { + return StringUtils.equalsAny(answer.getDetails(), "objectDoesNotExist", "spNotFound"); + } + + private void deleteVolumeWhenHostCannotConnectPool(SpConnectionDesc conn, StoragePoolDetailVO volumeOnPool) { + StorPoolUtil.volumeDelete(StorPoolStorageAdaptor.getVolumeNameFromPath(volumeOnPool.getValue(), true), conn); + storagePoolDetailsDao.remove(volumeOnPool.getId()); + } + + private void removePoolOnHost(StoragePoolHostVO poolHost, boolean isPoolConnectedToTheHost) { + if (isPoolConnectedToTheHost) { + storagePoolHostDao.remove(poolHost.getId()); + } + } + private synchronized StoragePoolDetailVO verifyVolumeIsOnCluster(long poolId, SpConnectionDesc conn, long clusterId) { StoragePoolDetailVO volumeOnPool = storagePoolDetailsDao.findDetail(poolId, StorPoolUtil.SP_VOLUME_ON_CLUSTER + "-" + clusterId); if (volumeOnPool == null) { diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java index 484a9b98931..f859a46ba36 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java @@ -18,24 +18,16 @@ */ package org.apache.cloudstack.storage.datastore.util; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.sql.Timestamp; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - +import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.OutputInterpreter; +import com.cloud.utils.script.Script; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; @@ -54,16 +46,23 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.log4j.Logger; -import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor; -import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.utils.script.OutputInterpreter; -import com.cloud.utils.script.Script; -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; public class StorPoolUtil { private static final Logger log = Logger.getLogger(StorPoolUtil.class); @@ -467,7 +466,7 @@ public class StorPoolUtil { return POST("MultiCluster/VolumeCreate", json, conn); } - public static SpApiResponse volumeCopy(final String name, final String baseOn, String csTag, Long iops, + public static SpApiResponse volumeCopy(final String name, final String baseOn, String csTag, Long iops, String cvmTag, String vcPolicyTag, SpConnectionDesc conn) { Map json = new HashMap<>(); json.put("baseOn", baseOn); @@ -475,7 +474,7 @@ public class StorPoolUtil { json.put("iops", iops); } json.put("template", conn.getTemplateName()); - Map tags = StorPoolHelper.addStorPoolTags(name, null, csTag, null); + Map tags = StorPoolHelper.addStorPoolTags(name, cvmTag, csTag, vcPolicyTag); json.put("tags", tags); return POST("MultiCluster/VolumeCreate", json, conn); } @@ -501,7 +500,7 @@ public class StorPoolUtil { return POST("MultiCluster/VolumeUpdate/" + name, json, conn); } - public static SpApiResponse volumeUpadateTags(final String name, final String uuid, Long iops, + public static SpApiResponse volumeUpdateTags(final String name, final String uuid, Long iops, SpConnectionDesc conn, String vcPolicy) { Map json = new HashMap<>(); Map tags = StorPoolHelper.addStorPoolTags(null, uuid, null, vcPolicy); @@ -510,20 +509,26 @@ public class StorPoolUtil { return POST("MultiCluster/VolumeUpdate/" + name, json, conn); } - public static SpApiResponse volumeUpadateCvmTags(final String name, final String uuid, SpConnectionDesc conn) { + public static SpApiResponse volumeUpdateCvmTags(final String name, final String uuid, SpConnectionDesc conn) { Map json = new HashMap<>(); Map tags = StorPoolHelper.addStorPoolTags(null, uuid, null, null); json.put("tags", tags); return POST("MultiCluster/VolumeUpdate/" + name, json, conn); } - public static SpApiResponse volumeUpadateVCTags(final String name, SpConnectionDesc conn, String vcPolicy) { + public static SpApiResponse volumeUpdateVCTags(final String name, SpConnectionDesc conn, String vcPolicy) { Map json = new HashMap<>(); Map tags = StorPoolHelper.addStorPoolTags(null, null, null, vcPolicy); json.put("tags", tags); return POST("MultiCluster/VolumeUpdate/" + name, json, conn); } + public static SpApiResponse volumeUpdateTemplate(final String name, SpConnectionDesc conn) { + Map json = new HashMap<>(); + json.put("template", conn.getTemplateName()); + return POST("MultiCluster/VolumeUpdate/" + name, json, conn); + } + public static SpApiResponse volumeSnapshot(final String volumeName, final String snapshotName, String vmUuid, String csTag, String vcPolicy, SpConnectionDesc conn) { Map json = new HashMap<>(); diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolVMSnapshotStrategy.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolVMSnapshotStrategy.java index ec7e89a2391..1172600c342 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolVMSnapshotStrategy.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolVMSnapshotStrategy.java @@ -335,7 +335,7 @@ public class StorPoolVMSnapshotStrategy extends DefaultVMSnapshotStrategy { } VolumeInfo vinfo = volFactory.getVolume(volumeObjectTO.getId()); if (vinfo.getMaxIops() != null) { - resp = StorPoolUtil.volumeUpadateTags(volumeName, null, vinfo.getMaxIops(), conn, null); + resp = StorPoolUtil.volumeUpdateTags(volumeName, null, vinfo.getMaxIops(), conn, null); if (resp.getError() != null) { StorPoolUtil.spLog("Volume was reverted successfully but max iops could not be set due to %s", diff --git a/plugins/storage/volume/storpool/src/test/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriverTest.java b/plugins/storage/volume/storpool/src/test/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriverTest.java new file mode 100644 index 00000000000..356cac9f569 --- /dev/null +++ b/plugins/storage/volume/storpool/src/test/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriverTest.java @@ -0,0 +1,245 @@ +/* + * 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.datastore.driver; + +import com.cloud.agent.api.to.DataObjectType; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.tags.dao.ResourceTagDao; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.VMInstanceDao; +import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; +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.VolumeInfo; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; +import org.apache.cloudstack.storage.datastore.util.StorPoolUtil; +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.junit.After; +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.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.powermock.core.classloader.annotations.PrepareForTest; + +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; +import static org.powermock.api.mockito.PowerMockito.mock; + + +@RunWith(MockitoJUnitRunner.class) +@PrepareForTest(StorPoolUtil.class) +public class StorPoolPrimaryDataStoreDriverTest { + + @Mock + private VMInstanceDao vmInstanceDao; + + @Mock + private ResourceTagDao _resourceTagDao; + + @Mock + private AsyncCompletionCallback callback; + @Mock + private PrimaryDataStoreDao storagePool; + @Mock + private StoragePoolDetailsDao detailsDao; + @Mock + private VolumeDao volumeDao; + + DataStore srcStore; + DataStore destStore; + + DataObject srcObj; + DataObject destObj; + + VolumeObjectTO srcTO; + VolumeObjectTO dstTO; + + PrimaryDataStoreTO dstPrimaryTo; + MockedStatic utilities; + StorPoolUtil.SpConnectionDesc conn; + + @Before + public void setUp(){ + utilities = Mockito.mockStatic(StorPoolUtil.class); + conn = new StorPoolUtil.SpConnectionDesc("1.1.1.1:81", "123", "tmp"); + + srcStore = mock(DataStore.class); + destStore = mock(DataStore.class); + + srcObj = mock(VolumeInfo.class); + destObj = mock(VolumeInfo.class); + + srcTO = mock(VolumeObjectTO.class); + dstTO = mock(VolumeObjectTO.class); + + dstPrimaryTo = mock(PrimaryDataStoreTO.class); + } + + @After + public void tearDown(){ + utilities.close(); + } + @InjectMocks + private StorPoolPrimaryDataStoreDriver storPoolPrimaryDataStoreDriver; + + @Test + public void testMigrateVolumePassed(){ + + + VMInstanceVO vm = mock(VMInstanceVO.class); + setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, vm); + when(vm.getState()).thenReturn(State.Running); + + when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn); + StorPoolUtil.SpApiResponse resp = new StorPoolUtil.SpApiResponse(); + when(StorPoolUtil.volumeUpdateTemplate("~t.t.t", conn)).thenReturn(resp); + + when(volumeDao.findById(srcObj.getId())).thenReturn(mock(VolumeVO.class)); + storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback); + utilities.verify(() -> StorPoolUtil.volumeUpdateTemplate("~t.t.t", conn), times(1)); + Assert.assertNull(resp.getError()); + } + @Test + public void testMigrateVolumeNotPassed() { + + VMInstanceVO vm = mock(VMInstanceVO.class); + + setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, vm); + when(vm.getState()).thenReturn(State.Running); + + when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn); + StorPoolUtil.SpApiResponse resp = new StorPoolUtil.SpApiResponse(); + setResponseError(resp); + when(StorPoolUtil.volumeUpdateTemplate("~t.t.t", conn)).thenReturn(resp); + storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback); + Assert.assertNotNull(resp.getError()); + } + + @Test + public void testCopyVolumeAttachedToVmPassed() { + + VMInstanceVO vm = mock(VMInstanceVO.class); + + setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, vm); + when(vm.getState()).thenReturn(State.Stopped); + String vmUuid = UUID.randomUUID().toString(); + when(vm.getUuid()).thenReturn(vmUuid); + + when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn); + + StorPoolUtil.SpApiResponse response = new StorPoolUtil.SpApiResponse(); + String volumeUuid = UUID.randomUUID().toString(); + when(srcObj.getUuid()).thenReturn(volumeUuid); + when(StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, vmUuid, "", conn)).thenReturn(response); + storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback); + Assert.assertNull(response.getError()); + } + + @Test + public void testCopyVolumeAttachedToVmNotPassed() { + + VMInstanceVO vm = mock(VMInstanceVO.class); + + setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, vm); + when(vm.getState()).thenReturn(State.Stopped); + String vmUuid = UUID.randomUUID().toString(); + when(vm.getUuid()).thenReturn(vmUuid); + + when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn); + StorPoolUtil.SpApiResponse response = new StorPoolUtil.SpApiResponse(); + setResponseError(response); + String volumeUuid = UUID.randomUUID().toString(); + when(srcObj.getUuid()).thenReturn(volumeUuid); + when(StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, vmUuid, "", conn)).thenReturn(response); + storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback); + Assert.assertNotNull(response.getError()); + } + @Test + public void testCopyVolumeNotAttachedToVmNotPassed() { + setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, null); + + when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn); + StorPoolUtil.SpApiResponse response = new StorPoolUtil.SpApiResponse(); + setResponseError(response); + String volumeUuid = UUID.randomUUID().toString(); + when(srcObj.getUuid()).thenReturn(volumeUuid); + when(StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, null, null, conn)).thenReturn(response); + storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback); + utilities.verify(() -> StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, null, null, conn), times(1)); + + Assert.assertNotNull(response.getError()); + } + + @Test + public void testCopyVolumeNotAttachedToVmPassed() { + + setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, null); + + when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn); + StorPoolUtil.SpApiResponse response = new StorPoolUtil.SpApiResponse(); + String volumeUuid = UUID.randomUUID().toString(); + when(srcObj.getUuid()).thenReturn(volumeUuid); + when(StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, null, null, conn)).thenReturn(response); + storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback); + utilities.verify(() -> StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, null, null, conn), times(1)); + Assert.assertNull(response.getError()); + } + + private void setReturnsWhenSourceAndDestinationAreVolumes(DataStore srcStore, DataStore destStore, DataObject srcObj, DataObject destObj, VolumeObjectTO srcTO, VolumeObjectTO dstTO, PrimaryDataStoreTO dstPrimaryTo, VMInstanceVO vm) { + when(srcStore.getRole()).thenReturn(DataStoreRole.Primary); + when(destStore.getRole()).thenReturn(DataStoreRole.Primary); + when(srcObj.getDataStore()).thenReturn(srcStore); + when(destObj.getDataStore()).thenReturn(destStore); + when(srcObj.getType()).thenReturn(DataObjectType.VOLUME); + when(destObj.getType()).thenReturn(DataObjectType.VOLUME); + when(destObj.getTO()).thenReturn(dstTO); + when(srcObj.getTO()).thenReturn(srcTO); + + when(srcObj.getDataStore().getDriver()).thenReturn(storPoolPrimaryDataStoreDriver); + when(destObj.getTO().getDataStore()).thenReturn(dstPrimaryTo); + when(destObj.getDataStore().getUuid()).thenReturn("SP_API_HTTP=1.1.1.1:81;SP_AUTH_TOKEN=token;SP_TEMPLATE=template_name"); + when(destObj.getDataStore().getId()).thenReturn(1L); + + when(srcTO.getPath()).thenReturn("/dev/storpool-byid/t.t.t"); + when(dstPrimaryTo.getPoolType()).thenReturn(StoragePoolType.StorPool); + when(vmInstanceDao.findById(anyLong())).thenReturn(vm); + } + + private static void setResponseError(StorPoolUtil.SpApiResponse resp) { + StorPoolUtil.SpApiError respErr = new StorPoolUtil.SpApiError(); + respErr.setName("error"); + respErr.setDescr("Failed"); + resp.setError(respErr); + } +} diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 86849a87a91..5f7b7fb7a83 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -2927,7 +2927,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic throw new InvalidParameterValueException("Volume must be in ready state"); } - if (vol.getPoolId() == storagePoolId) { + if (vol.getPoolId() == storagePoolId.longValue()) { throw new InvalidParameterValueException("Volume " + vol + " is already on the destination storage pool"); } @@ -2976,9 +2976,13 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } if (liveMigrateVolume && HypervisorType.KVM.equals(host.getHypervisorType())) { - throw new InvalidParameterValueException("KVM does not support volume live migration due to the limited possibility to refresh VM XML domain. " + - "Therefore, to live migrate a volume between storage pools, one must migrate the VM to a different host as well to force the VM XML domain update. " + - "Use 'migrateVirtualMachineWithVolumes' instead."); + StoragePoolVO destinationStoragePoolVo = _storagePoolDao.findById(storagePoolId); + + if (isSourceOrDestNotOnStorPool(storagePoolVO, destinationStoragePoolVo)) { + throw new InvalidParameterValueException("KVM does not support volume live migration due to the limited possibility to refresh VM XML domain. " + + "Therefore, to live migrate a volume between storage pools, one must migrate the VM to a different host as well to force the VM XML domain update. " + + "Use 'migrateVirtualMachineWithVolumes' instead."); + } } } @@ -3125,6 +3129,11 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic return orchestrateMigrateVolume(vol, destPool, liveMigrateVolume, newDiskOffering); } + private boolean isSourceOrDestNotOnStorPool(StoragePoolVO storagePoolVO, StoragePoolVO destinationStoragePoolVo) { + return storagePoolVO.getPoolType() != Storage.StoragePoolType.StorPool + || destinationStoragePoolVo.getPoolType() != Storage.StoragePoolType.StorPool; + } + /** * Retrieves the new disk offering UUID that might be sent to replace the current one in the volume being migrated. * If no disk offering UUID is provided we return null. Otherwise, we perform the following checks. diff --git a/test/integration/plugins/storpool/MigrateVolumeToStorPool.py b/test/integration/plugins/storpool/MigrateVolumeToStorPool.py index 1849718f3de..a7f87d9fa87 100644 --- a/test/integration/plugins/storpool/MigrateVolumeToStorPool.py +++ b/test/integration/plugins/storpool/MigrateVolumeToStorPool.py @@ -194,9 +194,12 @@ class TestMigrateVolumeToAnotherPool(cloudstackTestCase): securitygroup = SecurityGroup.list(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid)[0] cls.helper.set_securityGroups(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid, id = securitygroup.id) + cls.clusters = cls.helper.getClustersWithStorPool(cls.apiclient, cls.zone.id,) + cls.vm = VirtualMachine.create(cls.apiclient, {"name":"StorPool-%s" % uuid.uuid4() }, zoneid=cls.zone.id, + clusterid=random.choice(cls.clusters), templateid=template.id, accountid=cls.account.name, domainid=cls.account.domainid, @@ -207,6 +210,7 @@ class TestMigrateVolumeToAnotherPool(cloudstackTestCase): cls.vm2 = VirtualMachine.create(cls.apiclient, {"name":"StorPool-%s" % uuid.uuid4() }, zoneid=cls.zone.id, + clusterid=random.choice(cls.clusters), templateid=template.id, accountid=cls.account.name, domainid=cls.account.domainid, @@ -217,6 +221,7 @@ class TestMigrateVolumeToAnotherPool(cloudstackTestCase): cls.vm3 = VirtualMachine.create(cls.apiclient, {"name":"StorPool-%s" % uuid.uuid4() }, zoneid=cls.zone.id, + clusterid=random.choice(cls.clusters), templateid=template.id, accountid=cls.account.name, domainid=cls.account.domainid, @@ -227,6 +232,7 @@ class TestMigrateVolumeToAnotherPool(cloudstackTestCase): cls.vm4 = VirtualMachine.create(cls.apiclient, {"name":"StorPool-%s" % uuid.uuid4() }, zoneid=cls.zone.id, + clusterid=random.choice(cls.clusters), templateid=template.id, accountid=cls.account.name, domainid=cls.account.domainid, @@ -237,6 +243,7 @@ class TestMigrateVolumeToAnotherPool(cloudstackTestCase): cls.vm5 = VirtualMachine.create(cls.apiclient, {"name":"StorPool-%s" % uuid.uuid4() }, zoneid=cls.zone.id, + clusterid=random.choice(cls.clusters), templateid=template.id, accountid=cls.account.name, domainid=cls.account.domainid, diff --git a/test/integration/plugins/storpool/sp_util.py b/test/integration/plugins/storpool/sp_util.py index 68da18d0075..76edba075ed 100644 --- a/test/integration/plugins/storpool/sp_util.py +++ b/test/integration/plugins/storpool/sp_util.py @@ -44,6 +44,7 @@ from marvin.lib.common import (get_zone, from marvin.cloudstackAPI import (listOsTypes, listTemplates, listHosts, + listClusters, createTemplate, createVolume, getVolumeSnapshotDetails, @@ -745,3 +746,33 @@ class StorPoolHelper(): cmd.id = vmid cmd.hostid = hostid return (apiclient.startVirtualMachine(cmd)) + + @classmethod + def getClustersWithStorPool(cls, apiclient, zoneId,): + cmd = listClusters.listClustersCmd() + cmd.zoneid = zoneId + cmd.allocationstate = "Enabled" + clusters = apiclient.listClusters(cmd) + clustersToDeploy = [] + for cluster in clusters: + if cluster.resourcedetails['sp.cluster.id']: + clustersToDeploy.append(cluster.id) + + return clustersToDeploy + + @classmethod + def getHostToDeployOrMigrate(cls, apiclient, hostsToavoid, clustersToDeploy): + hostsOnCluster = [] + for c in clustersToDeploy: + hostsOnCluster.append(cls.list_hosts_by_cluster_id(apiclient, c)) + + destinationHost = None + for host in hostsOnCluster: + + if hostsToavoid is None: + return host[0] + if host[0].id not in hostsToavoid: + destinationHost = host[0] + break + + return destinationHost \ No newline at end of file diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index e22bfafa4df..c9633210aa0 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -518,7 +518,7 @@ class VirtualMachine: serviceofferingid=None, securitygroupids=None, projectid=None, startvm=None, diskofferingid=None, affinitygroupnames=None, affinitygroupids=None, group=None, - hostid=None, keypair=None, ipaddress=None, mode='default', + hostid=None, clusterid=None, keypair=None, ipaddress=None, mode='default', method='GET', hypervisor=None, customcpunumber=None, customcpuspeed=None, custommemory=None, rootdisksize=None, rootdiskcontroller=None, vpcid=None, macaddress=None, datadisktemplate_diskoffering_list={}, @@ -609,6 +609,9 @@ class VirtualMachine: if hostid: cmd.hostid = hostid + if clusterid: + cmd.clusterid = clusterid + if "userdata" in services: cmd.userdata = base64.urlsafe_b64encode(services["userdata"].encode()).decode()