diff --git a/VERSION b/VERSION index 56798053668..7a56e76b2e3 100755 --- a/VERSION +++ b/VERSION @@ -1,3 +1,3 @@ MAJOR=4 MINOR=8 -UPDATE=38 +UPDATE=38-Elitery diff --git a/conf/db/upgrade/V4.8.38-Elitery__schema.sql b/conf/db/upgrade/V4.8.38-Elitery__schema.sql new file mode 100644 index 00000000000..e69de29bb2d diff --git a/conf/globalConfig/ceph.xml b/conf/globalConfig/ceph.xml index be1993a594f..4e7ad333170 100755 --- a/conf/globalConfig/ceph.xml +++ b/conf/globalConfig/ceph.xml @@ -16,6 +16,14 @@ java.lang.Long + + imageCache.poolStrategy + strategy to select ceph image cache pool when preparing root volume image cache. + ceph + DefaultImageCachePool + java.lang.String + + trash.cleanup.interval interval to cleanup image trash on primary storage, in seconds. diff --git a/header/src/main/java/org/zstack/header/storage/primary/DownloadVolumeTemplateToPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/DownloadVolumeTemplateToPrimaryStorageMsg.java index d4286ab8176..ff43efe1a33 100644 --- a/header/src/main/java/org/zstack/header/storage/primary/DownloadVolumeTemplateToPrimaryStorageMsg.java +++ b/header/src/main/java/org/zstack/header/storage/primary/DownloadVolumeTemplateToPrimaryStorageMsg.java @@ -7,6 +7,7 @@ public class DownloadVolumeTemplateToPrimaryStorageMsg extends NeedReplyMessage private String primaryStorageUuid; private ImageSpec templateSpec; private String hostUuid; + private String targetVolumeInstallUrl; @Override public String getPrimaryStorageUuid() { @@ -32,4 +33,12 @@ public String getHostUuid() { public void setHostUuid(String hostUuid) { this.hostUuid = hostUuid; } + + public String getTargetVolumeInstallUrl() { + return targetVolumeInstallUrl; + } + + public void setTargetVolumeInstallUrl(String targetVolumeInstallUrl) { + this.targetVolumeInstallUrl = targetVolumeInstallUrl; + } } diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/CephGlobalConfig.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/CephGlobalConfig.java index c3a42f79013..88d2664c046 100755 --- a/plugin/ceph/src/main/java/org/zstack/storage/ceph/CephGlobalConfig.java +++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/CephGlobalConfig.java @@ -14,6 +14,8 @@ public class CephGlobalConfig { @GlobalConfigValidation(numberGreaterThan = 1) public static GlobalConfig IMAGE_CACHE_CLEANUP_INTERVAL = new GlobalConfig(CATEGORY, "imageCache.cleanup.interval"); + @GlobalConfigValidation(validValues = {"DefaultImageCachePool", "PreferVolumePool", "PreferExistingCache"}) + public static GlobalConfig IMAGE_CACHE_POOL_STRATEGY = new GlobalConfig(CATEGORY, "imageCache.poolStrategy"); @GlobalConfigValidation public static GlobalConfig PRIMARY_STORAGE_DELETE_POOL = new GlobalConfig(CATEGORY, "primaryStorage.deletePool"); @GlobalConfigValidation(numberGreaterThan = 0) diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/APIAddCephPrimaryStoragePoolMsg.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/APIAddCephPrimaryStoragePoolMsg.java index 458a3060d51..a6550f225c9 100755 --- a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/APIAddCephPrimaryStoragePoolMsg.java +++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/APIAddCephPrimaryStoragePoolMsg.java @@ -28,7 +28,7 @@ public class APIAddCephPrimaryStoragePoolMsg extends APICreateMessage implements private String aliasName; @APIParam(maxLength = 2048, required = false) private String description; - @APIParam(validValues = {"Root", "Data"}) + @APIParam(validValues = {"Root", "Data", "ImageCache"}) private String type; private boolean isCreate; diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephImageCachePoolStrategy.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephImageCachePoolStrategy.java new file mode 100644 index 00000000000..decb0e4033b --- /dev/null +++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephImageCachePoolStrategy.java @@ -0,0 +1,7 @@ +package org.zstack.storage.ceph.primary; + +public enum CephImageCachePoolStrategy { + DefaultImageCachePool, + PreferVolumePool, + PreferExistingCache +} diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java index 1ceee4de46d..e0cb58e953e 100755 --- a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java +++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java @@ -41,6 +41,7 @@ import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.host.*; import org.zstack.header.image.ImageBackupStorageRefInventory; +import org.zstack.header.image.ImageConstant; import org.zstack.header.image.ImageConstant.ImageMediaType; import org.zstack.header.image.ImageInventory; import org.zstack.header.image.ImageStatus; @@ -805,6 +806,7 @@ public static class CpCmd extends AgentCommand implements HasThreadContext { String srcPath; String dstPath; boolean shareable; + boolean skipIfExisting; } public static class CpRsp extends AgentResponse { @@ -2118,21 +2120,216 @@ protected void handle(final InstantiateVolumeOnPrimaryStorageMsg msg) { } } + private static class ImageCacheSelection { + String selectedPoolName; + ImageCacheVO selectedCache; + } + + private String getPoolNameFromCephInstallUrl(String installUrl) { + if (StringUtils.isBlank(installUrl) || !installUrl.startsWith("ceph://")) { + return null; + } + + String path = installUrl.replaceFirst("ceph://", ""); + int index = path.indexOf("/"); + return index > 0 ? path.substring(0, index) : null; + } + + private List listImageCaches(String imageUuid) { + return Q.New(ImageCacheVO.class) + .eq(ImageCacheVO_.primaryStorageUuid, self.getUuid()) + .eq(ImageCacheVO_.imageUuid, imageUuid) + .list(); + } + + private boolean isCephImageCacheRecord(ImageCacheVO cache) { + return cache != null && cache.getInstallUrl() != null && cache.getInstallUrl().startsWith("ceph://"); + } + + private boolean isSnapshotReuseImage(ImageInventory image) { + return image != null && StringUtils.startsWith(image.getUrl(), ImageConstant.SNAPSHOT_REUSE_IMAGE_SCHEMA); + } + + private boolean hasImageCachePoolRole(String poolName) { + if (StringUtils.isBlank(poolName)) { + return false; + } + + return Q.New(CephPrimaryStoragePoolVO.class) + .eq(CephPrimaryStoragePoolVO_.primaryStorageUuid, self.getUuid()) + .eq(CephPrimaryStoragePoolVO_.poolName, poolName) + .eq(CephPrimaryStoragePoolVO_.type, CephPrimaryStoragePoolType.ImageCache.toString()) + .isExists(); + } + + private CephImageCachePoolStrategy getImageCachePoolStrategy() { + String strategy = CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.value(String.class); + try { + return CephImageCachePoolStrategy.valueOf(strategy); + } catch (RuntimeException e) { + logger.warn(String.format("invalid ceph image cache pool strategy[%s], use DefaultImageCachePool", + strategy)); + return CephImageCachePoolStrategy.DefaultImageCachePool; + } + } + + private String selectImageCachePool(String targetVolumeInstallUrl, List caches) { + String defaultPoolName = getDefaultImageCachePoolName(); + CephImageCachePoolStrategy strategy = getImageCachePoolStrategy(); + + if (strategy == CephImageCachePoolStrategy.PreferVolumePool) { + String targetPoolName = getPoolNameFromCephInstallUrl(targetVolumeInstallUrl); + return hasImageCachePoolRole(targetPoolName) ? targetPoolName : defaultPoolName; + } + + if (strategy == CephImageCachePoolStrategy.PreferExistingCache) { + Optional defaultPoolCache = caches.stream() + .filter(c -> defaultPoolName.equals(getPoolNameFromCephInstallUrl(c.getInstallUrl()))) + .findFirst(); + if (defaultPoolCache.isPresent()) { + return defaultPoolName; + } + + Optional existingPool = caches.stream() + .map(c -> getPoolNameFromCephInstallUrl(c.getInstallUrl())) + .filter(StringUtils::isNotBlank) + .findFirst(); + if (existingPool.isPresent()) { + return existingPool.get(); + } + } + + return defaultPoolName; + } + + private ImageCacheVO findCacheInPool(List caches, String poolName) { + return caches.stream() + .filter(c -> poolName.equals(getPoolNameFromCephInstallUrl(c.getInstallUrl()))) + .findFirst() + .orElse(null); + } + + private ImageCacheSelection selectImageCache(ImageInventory image, String targetVolumeInstallUrl) { + ImageCacheSelection selection = new ImageCacheSelection(); + String imageUuid = image.getUuid(); + + if (isSnapshotReuseImage(image)) { + selection.selectedPoolName = "snapshot-reuse"; + selection.selectedCache = findSnapshotReuseImageCache(image); + return selection; + } + + if (getImageCachePoolStrategy() == CephImageCachePoolStrategy.DefaultImageCachePool) { + List caches = listImageCaches(imageUuid); + selection.selectedPoolName = getDefaultImageCachePoolName(); + selection.selectedCache = findCacheInPool(caches, selection.selectedPoolName); + return selection; + } + + List caches = listImageCaches(imageUuid); + selection.selectedPoolName = selectImageCachePool(targetVolumeInstallUrl, caches); + selection.selectedCache = findCacheInPool(caches, selection.selectedPoolName); + return selection; + } + + private ImageCacheSelection selectImageCacheByImageUuid(String imageUuid, String targetVolumeInstallUrl) { + ImageCacheSelection selection = new ImageCacheSelection(); + + if (getImageCachePoolStrategy() == CephImageCachePoolStrategy.DefaultImageCachePool) { + List caches = listImageCaches(imageUuid); + selection.selectedPoolName = getDefaultImageCachePoolName(); + selection.selectedCache = findCacheInPool(caches, selection.selectedPoolName); + return selection; + } + + List caches = listImageCaches(imageUuid); + selection.selectedPoolName = selectImageCachePool(targetVolumeInstallUrl, caches); + selection.selectedCache = findCacheInPool(caches, selection.selectedPoolName); + return selection; + } + + private ImageCacheVO findSnapshotReuseImageCache(ImageInventory image) { + return Q.New(ImageCacheVO.class) + .eq(ImageCacheVO_.primaryStorageUuid, self.getUuid()) + .eq(ImageCacheVO_.imageUuid, image.getUuid()) + .eq(ImageCacheVO_.installUrl, image.getUrl()) + .find(); + } + + private void checkImageCacheBits(ImageCacheVO cache, ReturnValueCompletion completion) { + CheckIsBitsExistingCmd cmd = new CheckIsBitsExistingCmd(); + cmd.setInstallPath(ImageCacheUtil.getImageCachePath(cache.toInventory())); + httpCall(CHECK_BITS_PATH, cmd, CheckIsBitsExistingRsp.class, new ReturnValueCompletion(completion) { + @Override + public void success(CheckIsBitsExistingRsp returnValue) { + completion.success(returnValue.isExisting()); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } + + private void removeImageCacheRecord(ImageCacheVO cache) { + logger.debug(String.format("remove stale ceph image cache[imageUuid:%s, installUrl:%s]", cache.getImageUuid(), cache.getInstallUrl())); + SQL.New(ImageCacheVolumeRefVO.class).eq(ImageCacheVolumeRefVO_.imageCacheId, cache.getId()).delete(); + int deleted = SQL.New("delete from ImageCacheVO c where c.id = :id").param("id", cache.getId()).execute(); + if (deleted > 0 && isCephImageCacheRecord(cache)) { + osdHelper.releaseAvailableCapacity(cache.getInstallUrl(), cache.getSize()); + } + } + + private void findUsableSourceCache(List candidates, int index, boolean cleanupStale, + ReturnValueCompletion completion) { + if (index >= candidates.size()) { + completion.success(null); + return; + } + + ImageCacheVO candidate = candidates.get(index); + checkImageCacheBits(candidate, new ReturnValueCompletion(completion) { + @Override + public void success(Boolean existing) { + if (existing) { + completion.success(candidate); + } else { + if (cleanupStale) { + removeImageCacheRecord(candidate); + } + findUsableSourceCache(candidates, index + 1, cleanupStale, completion); + } + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } + + private boolean shouldCopyCephBackupStorageImageToSelectedPool(String backupStorageUuid, String backupStorageInstallPath, + String selectedImageCachePoolName) { + String backupStoragePoolName = getPoolNameFromCephInstallUrl(backupStorageInstallPath); + if (StringUtils.isBlank(backupStoragePoolName) || StringUtils.isBlank(selectedImageCachePoolName) + || backupStoragePoolName.equals(selectedImageCachePoolName)) { + return false; + } + + CephBackupStorageVO cephBS = dbf.findByUuid(backupStorageUuid, CephBackupStorageVO.class); + return cephBS != null && getSelf().getFsid().equals(cephBS.getFsid()); + } + class DownloadToCache { ImageSpec image; VolumeSnapshotInventory snapshot; boolean incremental; - private void doDownload(final ReturnValueCompletion completion) { - ImageCacheVO cache = Q.New(ImageCacheVO.class) - .eq(ImageCacheVO_.primaryStorageUuid, self.getUuid()) - .eq(ImageCacheVO_.imageUuid, image.getInventory().getUuid()) - .find(); - if (cache != null) { - completion.success(cache); - return; - } + String targetVolumeInstallUrl; + + private void doDownload(String selectedImageCachePoolName, ImageCacheVO sourceCache, final ReturnValueCompletion completion) { final FlowChain chain = FlowChainBuilder.newShareFlowChain(); - chain.setName(String.format("prepare-image-cache-ceph-%s", self.getUuid())); + chain.setName(String.format("prepare-image-cache-ceph-%s-%s", self.getUuid(), selectedImageCachePoolName)); chain.then(new ShareFlow() { String cachePath; String snapshotPath; @@ -2154,6 +2351,9 @@ public void run(final FlowTrigger trigger, Map data) { amsg.setPurpose(PrimaryStorageAllocationPurpose.DownloadImage.toString()); amsg.setImageUuid(image.getInventory().getUuid()); amsg.setNoOverProvisioning(true); + if (StringUtils.isNotBlank(selectedImageCachePoolName)) { + amsg.setRequiredInstallUri(String.format("ceph://%s/", selectedImageCachePoolName)); + } bus.makeLocalServiceId(amsg, PrimaryStorageConstant.SERVICE_ID); bus.send(amsg, new CloudBusCallBack(trigger) { @Override @@ -2187,7 +2387,7 @@ public void rollback(FlowRollback trigger, Map data) { }); flow(new Flow() { - String __name__ = "download-from-" + (snapshot != null ? "volume" : "backup-storage"); + String __name__ = "download-from-" + (sourceCache != null ? "image-cache" : (snapshot != null ? "volume" : "backup-storage")); boolean deleteOnRollback; String dstPath; @@ -2196,7 +2396,9 @@ public void rollback(FlowRollback trigger, Map data) { public void run(final FlowTrigger trigger, Map data) { dstPath = makeVolumeInstallPathByTargetPool(image.getInventory().getUuid(), getTargetPoolNameFromAllocatedUrl(allocatedInstall)); - if (snapshot != null) { + if (sourceCache != null) { + copyFromImageCache(trigger); + } else if (snapshot != null) { if (incremental) { incrementalCreateFromVolumeSnapshot(trigger); } else { @@ -2225,36 +2427,39 @@ public void fail(ErrorCode errorCode) { }); } + private void copyFromImageCache(FlowTrigger trigger) { + String destPoolName = getPoolNameFromCephInstallUrl(dstPath); + boolean imageShouldExistsOnCephBs = destPoolName != null && destPoolName.equals(Q.New(CephBackupStorageVO.class) + .eq(CephBackupStorageVO_.fsid, getSelf().getFsid()) + .select(CephBackupStorageVO_.poolName) + .findValue()); + copyToCache(trigger, ImageCacheUtil.getImageCachePath(sourceCache.toInventory()), imageShouldExistsOnCephBs); + } - private void createFromVolumeSnapshot(FlowTrigger trigger) { - deleteOnRollback = true; - CpCmd cmd = new CpCmd(); - cmd.srcPath = snapshot.getPrimaryStorageInstallPath(); - cmd.dstPath = dstPath; - cmd.shareable = false; - httpCall(CP_PATH, cmd, CpRsp.class, new ReturnValueCompletion(completion) { - @Override - public void success(CpRsp rsp) { - if (rsp.actualSize != null) { - actualSize = rsp.actualSize; - } - cachePath = rsp.installPath; - trigger.next(); - } - @Override - public void fail(ErrorCode errorCode) { - trigger.fail(errorCode); - } - }); + private void createFromVolumeSnapshot(FlowTrigger trigger) { + copyToCache(trigger, snapshot.getPrimaryStorageInstallPath(), false); } private void downloadFromBackupStorage(FlowTrigger trigger) { + if (image.getSelectedBackupStorage() == null) { + trigger.fail(operr("cannot find backupstorage to download image [%s] to primarystorage [%s] due to lack of Ready and accessible image", + image.getInventory().getUuid(), getSelf().getUuid())); + return; + } + + String backupStorageUuid = image.getSelectedBackupStorage().getBackupStorageUuid(); + String backupStorageInstallPath = image.getSelectedBackupStorage().getInstallPath(); + if (shouldCopyCephBackupStorageImageToSelectedPool(backupStorageUuid, backupStorageInstallPath, selectedImageCachePoolName)) { + copyFromCephBackupStorage(trigger, backupStorageInstallPath); + return; + } + MediatorDownloadParam param = new MediatorDownloadParam(); param.setImage(image); param.setInstallPath(dstPath); param.setPrimaryStorageUuid(self.getUuid()); - BackupStorageMediator mediator = getBackupStorageMediator(image.getSelectedBackupStorage().getBackupStorageUuid()); + BackupStorageMediator mediator = getBackupStorageMediator(backupStorageUuid); mediator.param = param; deleteOnRollback = mediator.deleteWhenRollbackDownload(); @@ -2272,6 +2477,35 @@ public void fail(ErrorCode errorCode) { }); } + private void copyFromCephBackupStorage(FlowTrigger trigger, String backupStorageInstallPath) { + copyToCache(trigger, backupStorageInstallPath, false); + } + + private void copyToCache(FlowTrigger trigger, String srcPath, boolean skipIfExisting) { + deleteOnRollback = !skipIfExisting; + CpCmd cmd = new CpCmd(); + cmd.resourceUuid = image.getInventory().getUuid(); + cmd.srcPath = srcPath; + cmd.dstPath = dstPath; + cmd.shareable = false; + cmd.skipIfExisting = skipIfExisting; + httpCall(CP_PATH, cmd, CpRsp.class, new ReturnValueCompletion(trigger) { + @Override + public void success(CpRsp rsp) { + if (rsp.actualSize != null) { + actualSize = rsp.actualSize; + } + cachePath = StringUtils.isNotBlank(rsp.installPath) ? rsp.installPath : dstPath; + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); + } + @Override public void rollback(FlowRollback trigger, Map data) { if (deleteOnRollback && cachePath != null) { @@ -2421,7 +2655,8 @@ void download(final ReturnValueCompletion completion) { thdf.chainSubmit(new ChainTask(completion) { @Override public String getSyncSignature() { - return String.format("ceph-p-%s-download-image-%s", self.getUuid(), image.getInventory().getUuid()); + ImageCacheSelection selection = selectImageCache(image.getInventory(), targetVolumeInstallUrl); + return String.format("ceph-p-%s-download-image-%s-%s", self.getUuid(), image.getInventory().getUuid(), selection.selectedPoolName); } private void checkEncryptImageCache(ImageCacheVO cacheVO, final SyncTaskChain chain) { @@ -2451,48 +2686,22 @@ public void fail(ErrorCode errorCode) { @Override public void run(final SyncTaskChain chain) { - ImageCacheVO cache = Q.New(ImageCacheVO.class) - .eq(ImageCacheVO_.primaryStorageUuid, self.getUuid()) - .eq(ImageCacheVO_.imageUuid, image.getInventory().getUuid()) - .find(); - - if (cache != null) { - final CheckIsBitsExistingCmd cmd = new CheckIsBitsExistingCmd(); - cmd.setInstallPath(ImageCacheUtil.getImageCachePath(cache.toInventory())); - httpCall(CHECK_BITS_PATH, cmd, CheckIsBitsExistingRsp.class, new ReturnValueCompletion(chain) { + prepareInSelectedPool(chain, (cacheVO, taskChain) -> checkEncryptImageCache(cacheVO, taskChain)); + } + + private void prepareInSelectedPool(final SyncTaskChain chain, final java.util.function.BiConsumer cacheConsumer) { + ImageCacheSelection selection = selectImageCache(image.getInventory(), targetVolumeInstallUrl); + if (selection.selectedCache != null) { + checkImageCacheBits(selection.selectedCache, new ReturnValueCompletion(chain) { @Override - public void success(CheckIsBitsExistingRsp returnValue) { - if (returnValue.isExisting()) { - logger.debug("image has been existing"); - checkEncryptImageCache(cache, chain); - return; + public void success(Boolean existing) { + if (existing) { + logger.debug(String.format("image cache[installUrl:%s] has been existing", selection.selectedCache.getInstallUrl())); + cacheConsumer.accept(selection.selectedCache, chain); } else { - logger.debug("image not found, remove vo and re-download"); - SimpleQuery q = dbf.createQuery(ImageCacheVO.class); - q.add(ImageCacheVO_.primaryStorageUuid, Op.EQ, self.getUuid()); - q.add(ImageCacheVO_.imageUuid, Op.EQ, image.getInventory().getUuid()); - ImageCacheVO cvo = q.find(); - - IncreasePrimaryStorageCapacityMsg imsg = new IncreasePrimaryStorageCapacityMsg(); - imsg.setDiskSize(cvo.getSize()); - imsg.setPrimaryStorageUuid(cvo.getPrimaryStorageUuid()); - bus.makeTargetServiceIdByResourceUuid(imsg, PrimaryStorageConstant.SERVICE_ID, cvo.getPrimaryStorageUuid()); - bus.send(imsg); - dbf.remove(cvo); - - doDownload(new ReturnValueCompletion(chain) { - @Override - public void success(ImageCacheVO returnValue) { - completion.success(returnValue); - chain.next(); - } - - @Override - public void fail(ErrorCode errorCode) { - completion.fail(errorCode); - chain.next(); - } - }); + logger.debug(String.format("image cache[installUrl:%s] not found, remove vo and re-prepare", selection.selectedCache.getInstallUrl())); + removeImageCacheRecord(selection.selectedCache); + prepareInSelectedPool(chain, cacheConsumer); } } @@ -2502,22 +2711,44 @@ public void fail(ErrorCode errorCode) { chain.next(); } }); + return; + } - } else { - doDownload(new ReturnValueCompletion(chain) { - @Override - public void success(ImageCacheVO returnValue) { - completion.success(returnValue); - chain.next(); - } - - @Override - public void fail(ErrorCode errorCode) { - completion.fail(errorCode); - chain.next(); - } - }); + if (isSnapshotReuseImage(image.getInventory())) { + completion.fail(operr("cannot find snapshot reuse image cache[imageUuid:%s, installUrl:%s] on primary storage[uuid:%s]", + image.getInventory().getUuid(), image.getInventory().getUrl(), self.getUuid())); + chain.next(); + return; } + + List sourceCandidates = listImageCaches(image.getInventory().getUuid()).stream() + .filter(CephPrimaryStorageBase.this::isCephImageCacheRecord) + .filter(c -> !StringUtils.equals(selection.selectedPoolName, getPoolNameFromCephInstallUrl(c.getInstallUrl()))) + .collect(Collectors.toList()); + findUsableSourceCache(sourceCandidates, 0, true, new ReturnValueCompletion(chain) { + @Override + public void success(ImageCacheVO sourceCache) { + doDownload(selection.selectedPoolName, sourceCache, new ReturnValueCompletion(chain) { + @Override + public void success(ImageCacheVO returnValue) { + completion.success(returnValue); + chain.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + chain.next(); + } + }); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + chain.next(); + } + }); } @Override @@ -2552,6 +2783,7 @@ public void run(final FlowTrigger trigger, Map data) { dmsg.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); dmsg.setHostUuid(msg.getDestHost().getUuid()); dmsg.setTemplateSpec(ispec); + dmsg.setTargetVolumeInstallUrl(volumePath); bus.makeTargetServiceIdByResourceUuid(dmsg, PrimaryStorageConstant.SERVICE_ID, dmsg.getPrimaryStorageUuid()); bus.send(dmsg, new CloudBusCallBack(trigger) { @Override @@ -2707,6 +2939,7 @@ protected void handle(DownloadVolumeTemplateToPrimaryStorageMsg msg) { final DownloadVolumeTemplateToPrimaryStorageReply reply = new DownloadVolumeTemplateToPrimaryStorageReply(); DownloadToCache downloadToCache = new DownloadToCache(); downloadToCache.image = msg.getTemplateSpec(); + downloadToCache.targetVolumeInstallUrl = msg.getTargetVolumeInstallUrl(); downloadToCache.download(new ReturnValueCompletion(msg) { @Override public void success(ImageCacheVO cache) { @@ -4687,12 +4920,22 @@ public void done() { private void deleteImageCacheOnPrimaryStorage(DeleteImageCacheOnPrimaryStorageMsg msg, final NoErrorCompletion completion) { DeleteImageCacheOnPrimaryStorageReply reply = new DeleteImageCacheOnPrimaryStorageReply(); + deleteImageCacheOnPrimaryStorage(msg.getInstallPath(), msg, reply, completion); + } + + private void deleteImageCacheOnPrimaryStorage(String installPath, Message msg, MessageReply reply, final NoErrorCompletion completion) { + if (StringUtils.isBlank(installPath) || !installPath.startsWith("ceph://")) { + logger.debug(String.format("skip deleting non-ceph image cache[installUrl:%s] on ceph primary storage[uuid:%s]", installPath, self.getUuid())); + bus.reply(msg, reply); + completion.done(); + return; + } DeleteImageCacheCmd cmd = new DeleteImageCacheCmd(); cmd.setFsId(getSelf().getFsid()); cmd.setUuid(self.getUuid()); - cmd.imagePath = msg.getInstallPath().split("@")[0]; - cmd.snapshotPath = msg.getInstallPath(); + cmd.imagePath = installPath.split("@")[0]; + cmd.snapshotPath = installPath; httpCall(DELETE_IMAGE_CACHE, cmd, AgentResponse.class, new ReturnValueCompletion(msg) { @Override public void success(AgentResponse rsp) { @@ -5187,6 +5430,19 @@ public void handle(ErrorCode errCode, Map data) { } + private ImageSpec makeImageSpec(String imageUuid, ImageCacheVO sourceCache) { + ImageInventory inventory = new ImageInventory(); + inventory.setUuid(imageUuid); + inventory.setActualSize(sourceCache.getSize()); + inventory.setSize(sourceCache.getSize()); + inventory.setMediaType(sourceCache.getMediaType() == null ? + ImageMediaType.RootVolumeTemplate.toString() : sourceCache.getMediaType().toString()); + + ImageSpec imageSpec = new ImageSpec(); + imageSpec.setInventory(inventory); + return imageSpec; + } + private ImageSpec makeImageSpec(VolumeInventory volume) { ImageVO image = dbf.findByUuid(volume.getRootImageUuid(), ImageVO.class); if (image == null) { @@ -5242,29 +5498,76 @@ public void setup() { @Override public void run(final FlowTrigger trigger, Map data) { - installUrl = Q.New(ImageCacheVO.class).eq(ImageCacheVO_.imageUuid, msg.getVolume().getRootImageUuid()). - eq(ImageCacheVO_.primaryStorageUuid, msg.getPrimaryStorageUuid()).select(ImageCacheVO_.installUrl).findValue(); + ImageCacheSelection selection = selectImageCacheByImageUuid(msg.getVolume().getRootImageUuid(), volumePath); + if (selection.selectedCache != null) { + checkImageCacheBits(selection.selectedCache, new ReturnValueCompletion(trigger) { + @Override + public void success(Boolean existing) { + if (existing) { + installUrl = ImageCacheUtil.getImageCachePath(selection.selectedCache.toInventory()); + trigger.next(); + } else { + downloadImageToCache(trigger); + } + } - if (installUrl != null) { - trigger.next(); + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); return; } - DownloadVolumeTemplateToPrimaryStorageMsg dmsg = new DownloadVolumeTemplateToPrimaryStorageMsg(); - dmsg.setTemplateSpec(makeImageSpec(msg.getVolume())); - dmsg.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); - bus.makeTargetServiceIdByResourceUuid(dmsg, PrimaryStorageConstant.SERVICE_ID, dmsg.getPrimaryStorageUuid()); - bus.send(dmsg, new CloudBusCallBack(trigger) { + downloadImageToCache(trigger); + } + + private void downloadImageToCache(final FlowTrigger trigger) { + ImageCacheSelection selection = selectImageCacheByImageUuid(msg.getVolume().getRootImageUuid(), volumePath); + List sourceCandidates = listImageCaches(msg.getVolume().getRootImageUuid()).stream() + .filter(CephPrimaryStorageBase.this::isCephImageCacheRecord) + .filter(c -> !StringUtils.equals(selection.selectedPoolName, getPoolNameFromCephInstallUrl(c.getInstallUrl()))) + .collect(Collectors.toList()); + findUsableSourceCache(sourceCandidates, 0, false, new ReturnValueCompletion(trigger) { @Override - public void run(MessageReply reply) { - if (!reply.isSuccess()) { - trigger.fail(reply.getError()); - return; + public void success(ImageCacheVO sourceCache) { + ImageSpec imageSpec; + if (sourceCache != null) { + imageSpec = makeImageSpec(msg.getVolume().getRootImageUuid(), sourceCache); + } else { + try { + imageSpec = makeImageSpec(msg.getVolume()); + } catch (OperationFailureException e) { + trigger.fail(e.getErrorCode()); + return; + } } - installUrl = ((DownloadVolumeTemplateToPrimaryStorageReply) reply).getImageCache().getInstallUrl(); + downloadImageToCache(imageSpec, trigger); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); + } + + private void downloadImageToCache(ImageSpec imageSpec, final FlowTrigger trigger) { + DownloadToCache downloadToCache = new DownloadToCache(); + downloadToCache.image = imageSpec; + downloadToCache.targetVolumeInstallUrl = volumePath; + downloadToCache.download(new ReturnValueCompletion(trigger) { + @Override + public void success(ImageCacheVO cache) { + installUrl = ImageCacheUtil.getImageCachePath(cache.toInventory()); trigger.next(); } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } }); } }); diff --git a/sdk/src/main/java/org/zstack/sdk/AddCephPrimaryStoragePoolAction.java b/sdk/src/main/java/org/zstack/sdk/AddCephPrimaryStoragePoolAction.java index 8261e41d8e9..4e98ede8ccd 100644 --- a/sdk/src/main/java/org/zstack/sdk/AddCephPrimaryStoragePoolAction.java +++ b/sdk/src/main/java/org/zstack/sdk/AddCephPrimaryStoragePoolAction.java @@ -37,7 +37,7 @@ public Result throwExceptionIfError() { @Param(required = false, maxLength = 2048, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String description; - @Param(required = true, validValues = {"Root","Data"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + @Param(required = true, validValues = {"Root","Data","ImageCache"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String type; @Param(required = false) diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/ceph/CephOperationCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/ceph/CephOperationCase.groovy index 3be03f0332a..e52e751efcd 100644 --- a/test/src/test/groovy/org/zstack/test/integration/storage/ceph/CephOperationCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/storage/ceph/CephOperationCase.groovy @@ -177,9 +177,10 @@ class CephOperationCase extends SubCase { ps = env.inventoryByName("ceph-pri") as CephPrimaryStorageInventory bs = env.inventoryByName("ceph-bk") as CephBackupStorageInventory - assert ps.pools.securityPolicy == [DataSecurityPolicy.Copy.toString()] * 3 - assert ps.pools.diskUtilization == [0.33f] * 3 - assert ps.pools.replicatedSize == [3] * 3 + assert ps.pools.size() == 3 + assert ps.pools.securityPolicy.every { it == DataSecurityPolicy.Copy.toString() } + assert ps.pools.diskUtilization.every { it == 0.33f } + assert ps.pools.replicatedSize.every { it == 3 } assert bs.poolSecurityPolicy == DataSecurityPolicy.ErasureCode.toString() assert bs.poolDiskUtilization == 0.67f diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/CephPrimaryStorageVolumePoolsCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/CephPrimaryStorageVolumePoolsCase.groovy index 6f95c49e3e7..e6a4d9aeb71 100755 --- a/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/CephPrimaryStorageVolumePoolsCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/CephPrimaryStorageVolumePoolsCase.groovy @@ -2,13 +2,25 @@ package org.zstack.test.integration.storage.primary.ceph import org.springframework.http.HttpEntity import org.zstack.compute.vm.VmSystemTags +import org.zstack.core.Platform +import org.zstack.core.db.DatabaseFacade import org.zstack.core.db.Q +import org.zstack.header.image.ImageConstant.ImageMediaType +import org.zstack.header.storage.primary.ImageCacheInventory +import org.zstack.header.storage.primary.ImageCacheShadowVO +import org.zstack.header.storage.primary.ImageCacheShadowVO_ +import org.zstack.header.storage.primary.ImageCacheVolumeRefVO +import org.zstack.header.storage.primary.ImageCacheVO +import org.zstack.header.storage.primary.ImageCacheVO_ +import org.zstack.header.storage.snapshot.reference.VolumeSnapshotReferenceTreeVO import org.zstack.header.volume.VolumeVO import org.zstack.header.volume.VolumeVO_ +import org.zstack.storage.ceph.CephGlobalConfig import org.zstack.kvm.KVMConstant import org.zstack.sdk.* import org.zstack.storage.ceph.CephSystemTags import org.zstack.storage.ceph.primary.CephPrimaryStorageBase +import org.zstack.storage.ceph.primary.CephImageCachePoolStrategy import org.zstack.storage.ceph.primary.CephPrimaryStoragePoolVO import org.zstack.storage.ceph.primary.CephPrimaryStoragePoolVO_ import org.zstack.test.integration.storage.StorageTest @@ -29,7 +41,9 @@ class CephPrimaryStorageVolumePoolsCase extends SubCase { String NEW_ROOT_POOL_NAME = "new_root_pool" String ROOT_POOL_TYPE = "Root" String DATA_POOL_TYPE = "Data" + String IMAGE_CACHE_POOL_TYPE = "ImageCache" String NEW_DATA_POOL_NAME = "new_data_pool" + String ROOT_ONLY_POOL_NAME = "root_only_pool" @Override void setup() { @@ -85,10 +99,21 @@ class CephPrimaryStorageVolumePoolsCase extends SubCase { type = ROOT_POOL_TYPE } + pool { + poolName = NEW_ROOT_POOL_NAME + type = IMAGE_CACHE_POOL_TYPE + isCreate = false + } + pool { poolName = NEW_DATA_POOL_NAME type = DATA_POOL_TYPE } + + pool { + poolName = ROOT_ONLY_POOL_NAME + type = ROOT_POOL_TYPE + } } attachBackupStorage("ceph-bk") @@ -103,6 +128,27 @@ class CephPrimaryStorageVolumePoolsCase extends SubCase { name = "image" url = "http://zstack.org/download/test.qcow2" } + + image { + name = "image2" + url = "http://zstack.org/download/test2.qcow2" + } + + image { + name = "image3" + url = "http://zstack.org/download/test3.qcow2" + } + + image { + name = "image4" + url = "http://zstack.org/download/test4.qcow2" + } + + image { + name = "image5" + url = "http://zstack.org/download/test5.qcow2" + } + } diskOffering { @@ -140,6 +186,37 @@ class CephPrimaryStorageVolumePoolsCase extends SubCase { InstanceOfferingInventory instanceOffering2 ImageInventory image + void ensureNewRootPoolIsImageCachePool() { + if (Q.New(CephPrimaryStoragePoolVO.class) + .eq(CephPrimaryStoragePoolVO_.primaryStorageUuid, primaryStorage.uuid) + .eq(CephPrimaryStoragePoolVO_.poolName, NEW_ROOT_POOL_NAME) + .eq(CephPrimaryStoragePoolVO_.type, IMAGE_CACHE_POOL_TYPE) + .isExists()) { + return + } + + addCephPrimaryStoragePool { + primaryStorageUuid = primaryStorage.uuid + poolName = NEW_ROOT_POOL_NAME + type = IMAGE_CACHE_POOL_TYPE + isCreate = false + } + } + + void restoreCheckBitsSimulator() { + env.simulator(CephPrimaryStorageBase.CHECK_BITS_PATH) { + CephPrimaryStorageBase.CheckIsBitsExistingRsp rsp = new CephPrimaryStorageBase.CheckIsBitsExistingRsp() + rsp.setExisting(true) + return rsp + } + env.afterSimulator(CephPrimaryStorageBase.CHECK_BITS_PATH) { rsp, HttpEntity e, EnvSpec spec -> + CephPrimaryStorageBase.CheckIsBitsExistingCmd cmd = json(e.body, CephPrimaryStorageBase.CheckIsBitsExistingCmd.class) + VFS vfs = CephPrimaryStorageSpec.vfs(cmd, spec) + vfs.Assert(vfs.isFile(CephPrimaryStorageSpec.cephPathToVFSPath(cmd.installPath)), "cannot find ${cmd.installPath}") + return rsp + } + } + void testCreateDataVolumeInPool() { CephPrimaryStorageBase.CreateEmptyVolumeCmd cmd = null @@ -250,15 +327,15 @@ class CephPrimaryStorageVolumePoolsCase extends SubCase { acmd = json(e.body, CephPrimaryStorageBase.AddPoolCmd.class) } - AddCephPrimaryStoragePoolAction a = new AddCephPrimaryStoragePoolAction() - a.isCreate = true - a.poolName = LOW_POOL_NAME - a.primaryStorageUuid = primaryStorage.uuid - a.type = DATA_POOL_TYPE - a.sessionId = adminSession() - def res = a.call() + expect(AssertionError.class) { + addCephPrimaryStoragePool { + isCreate = true + poolName = LOW_POOL_NAME + primaryStorageUuid = primaryStorage.uuid + type = DATA_POOL_TYPE + } + } - assert res.error != null assert !Q.New(CephPrimaryStoragePoolVO.class).eq(CephPrimaryStoragePoolVO_.poolName, LOW_POOL_NAME).isExists() assert acmd != null assert acmd.isCreate @@ -278,22 +355,635 @@ class CephPrimaryStorageVolumePoolsCase extends SubCase { } void testAddSameCephPool() { - AddCephPrimaryStoragePoolAction action = new AddCephPrimaryStoragePoolAction() - action.primaryStorageUuid = primaryStorage.uuid - action.poolName = HIGH_POOL_NAME - action.sessionId = adminSession() - action.type = DATA_POOL_TYPE - def ret = action.call() - - AddCephPrimaryStoragePoolAction rootPoolAction = new AddCephPrimaryStoragePoolAction() - rootPoolAction.primaryStorageUuid = primaryStorage.uuid - rootPoolAction.poolName = NEW_ROOT_POOL_NAME - rootPoolAction.sessionId = adminSession() - rootPoolAction.type = ROOT_POOL_TYPE - def rootRet = rootPoolAction.call() - - assert ret.error != null - assert rootRet.error != null + expect(AssertionError.class) { + addCephPrimaryStoragePool { + primaryStorageUuid = primaryStorage.uuid + poolName = HIGH_POOL_NAME + type = DATA_POOL_TYPE + } + } + + expect(AssertionError.class) { + addCephPrimaryStoragePool { + primaryStorageUuid = primaryStorage.uuid + poolName = NEW_ROOT_POOL_NAME + type = ROOT_POOL_TYPE + } + } + } + + void testPreferVolumePoolImageCacheStrategy() { + ensureNewRootPoolIsImageCachePool() + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.PreferVolumePool.toString()) + + CephPrimaryStorageBase.CpCmd cpCmd = null + CephPrimaryStorageBase.CloneCmd cloneCmd = null + env.hijackSimulator(CephPrimaryStorageBase.CP_PATH) { rsp, HttpEntity e -> + cpCmd = json(e.body, CephPrimaryStorageBase.CpCmd.class) + return rsp + } + env.preSimulator(CephPrimaryStorageBase.CLONE_PATH) { HttpEntity e -> + cloneCmd = json(e.body, CephPrimaryStorageBase.CloneCmd.class) + } + + try { + VmInstanceInventory imageCachePoolVm = createVmInstance { + name = "image-cache-pool-vm" + instanceOfferingUuid = vm.instanceOfferingUuid + imageUuid = vm.imageUuid + l3NetworkUuids = asList(l3.uuid) + sessionId = adminSession() + rootVolumeSystemTags = [CephSystemTags.USE_CEPH_ROOT_POOL.instantiateTag([(CephSystemTags.USE_CEPH_ROOT_POOL_TOKEN): NEW_ROOT_POOL_NAME])] + } as VmInstanceInventory + + assert cpCmd != null + assert cpCmd.dstPath.contains(NEW_ROOT_POOL_NAME) + assert !cpCmd.skipIfExisting + assert cloneCmd != null + assert cloneCmd.srcPath.contains(NEW_ROOT_POOL_NAME) + + List caches = Q.New(ImageCacheVO.class) + .eq(ImageCacheVO_.primaryStorageUuid, primaryStorage.uuid) + .eq(ImageCacheVO_.imageUuid, vm.imageUuid) + .list() + assert caches.find { it.installUrl.contains(NEW_ROOT_POOL_NAME) } != null + assert caches.size() >= 2 + + List queriedCaches = queryImageCache { + conditions = asList("primaryStorageUuid=${primaryStorage.uuid}".toString(), "imageUuid=${vm.imageUuid}".toString()) + } + assert queriedCaches.size() >= 2 + + destroyVmInstance { + uuid = imageCachePoolVm.uuid + } + expungeVmInstance { + uuid = imageCachePoolVm.uuid + } + } finally { + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) + } + } + + void testDefaultImageCachePoolStrategyUsesDefaultPool() { + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) + String defaultImageCachePoolName = CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_IMAGE_CACHE_POOL.getTokenByResourceUuid(primaryStorage.uuid, CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_IMAGE_CACHE_POOL_TOKEN) + + List existingCaches = Q.New(ImageCacheVO.class) + .eq(ImageCacheVO_.primaryStorageUuid, primaryStorage.uuid) + .eq(ImageCacheVO_.imageUuid, vm.imageUuid) + .list() + assert !existingCaches.isEmpty() + + CephPrimaryStorageBase.CloneCmd cloneCmd = null + env.preSimulator(CephPrimaryStorageBase.CLONE_PATH) { HttpEntity e -> + cloneCmd = json(e.body, CephPrimaryStorageBase.CloneCmd.class) + } + + VmInstanceInventory defaultStrategyVm = createVmInstance { + name = "default-image-cache-pool-vm" + instanceOfferingUuid = vm.instanceOfferingUuid + imageUuid = vm.imageUuid + l3NetworkUuids = asList(l3.uuid) + sessionId = adminSession() + rootVolumeSystemTags = [CephSystemTags.USE_CEPH_ROOT_POOL.instantiateTag([(CephSystemTags.USE_CEPH_ROOT_POOL_TOKEN): NEW_ROOT_POOL_NAME])] + } as VmInstanceInventory + + assert cloneCmd != null + assert existingCaches.find { it.installUrl == cloneCmd.srcPath } != null + assert cloneCmd.srcPath.contains(defaultImageCachePoolName) + assert cloneCmd.dstPath.contains(NEW_ROOT_POOL_NAME) + + destroyVmInstance { + uuid = defaultStrategyVm.uuid + } + expungeVmInstance { + uuid = defaultStrategyVm.uuid + } + } + + void testDefaultStrategyCopiesCephBackupStorageImageToDefaultPool() { + ImageInventory image5 = env.inventoryByName("image5") as ImageInventory + String defaultImageCachePoolName = CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_IMAGE_CACHE_POOL.getTokenByResourceUuid(primaryStorage.uuid, CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_IMAGE_CACHE_POOL_TOKEN) + String backupStorageInstallPath = image5.backupStorageRefs[0].installPath + CephPrimaryStorageBase.CpCmd cpCmd = null + CephPrimaryStorageBase.CloneCmd cloneCmd = null + VmInstanceInventory defaultStrategyVm = null + + env.hijackSimulator(CephPrimaryStorageBase.CP_PATH) { rsp, HttpEntity e -> + cpCmd = json(e.body, CephPrimaryStorageBase.CpCmd.class) + return rsp + } + env.preSimulator(CephPrimaryStorageBase.CLONE_PATH) { HttpEntity e -> + cloneCmd = json(e.body, CephPrimaryStorageBase.CloneCmd.class) + } + + try { + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) + defaultStrategyVm = createVmInstance { + name = "default-strategy-copy-ceph-bs-image-vm" + instanceOfferingUuid = vm.instanceOfferingUuid + imageUuid = image5.uuid + l3NetworkUuids = asList(l3.uuid) + sessionId = adminSession() + rootVolumeSystemTags = [CephSystemTags.USE_CEPH_ROOT_POOL.instantiateTag([(CephSystemTags.USE_CEPH_ROOT_POOL_TOKEN): NEW_ROOT_POOL_NAME])] + } as VmInstanceInventory + + assert cpCmd != null + assert cpCmd.srcPath == backupStorageInstallPath + assert cpCmd.dstPath.contains(defaultImageCachePoolName) + assert cloneCmd != null + assert cloneCmd.srcPath.contains(defaultImageCachePoolName) + assert cloneCmd.dstPath.contains(NEW_ROOT_POOL_NAME) + } finally { + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) + if (defaultStrategyVm != null) { + destroyVmInstance { + uuid = defaultStrategyVm.uuid + } + expungeVmInstance { + uuid = defaultStrategyVm.uuid + } + } + } + } + + void testPreferVolumePoolFallbackToDefaultPool() { + String defaultImageCachePoolName = CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_IMAGE_CACHE_POOL.getTokenByResourceUuid(primaryStorage.uuid, CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_IMAGE_CACHE_POOL_TOKEN) + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.PreferVolumePool.toString()) + + CephPrimaryStorageBase.CloneCmd cloneCmd = null + env.preSimulator(CephPrimaryStorageBase.CLONE_PATH) { HttpEntity e -> + cloneCmd = json(e.body, CephPrimaryStorageBase.CloneCmd.class) + } + + try { + VmInstanceInventory fallbackVm = createVmInstance { + name = "prefer-volume-pool-fallback-vm" + instanceOfferingUuid = vm.instanceOfferingUuid + imageUuid = vm.imageUuid + l3NetworkUuids = asList(l3.uuid) + sessionId = adminSession() + rootVolumeSystemTags = [CephSystemTags.USE_CEPH_ROOT_POOL.instantiateTag([(CephSystemTags.USE_CEPH_ROOT_POOL_TOKEN): ROOT_ONLY_POOL_NAME])] + } as VmInstanceInventory + + assert cloneCmd != null + assert cloneCmd.srcPath.contains(defaultImageCachePoolName) + assert cloneCmd.dstPath.contains(ROOT_ONLY_POOL_NAME) + + destroyVmInstance { + uuid = fallbackVm.uuid + } + expungeVmInstance { + uuid = fallbackVm.uuid + } + } finally { + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) + } + } + + void testPreferExistingCacheStrategyPrefersDefaultPoolCache() { + ensureNewRootPoolIsImageCachePool() + + String defaultImageCachePoolName = CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_IMAGE_CACHE_POOL.getTokenByResourceUuid(primaryStorage.uuid, CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_IMAGE_CACHE_POOL_TOKEN) + VmInstanceInventory defaultCacheVm = null + CephPrimaryStorageBase.CloneCmd cloneCmd = null + + try { + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.PreferVolumePool.toString()) + defaultCacheVm = createVmInstance { + name = "default-cache-vm" + instanceOfferingUuid = vm.instanceOfferingUuid + imageUuid = vm.imageUuid + l3NetworkUuids = asList(l3.uuid) + sessionId = adminSession() + } as VmInstanceInventory + + ImageCacheVO defaultCache = Q.New(ImageCacheVO.class) + .eq(ImageCacheVO_.primaryStorageUuid, primaryStorage.uuid) + .eq(ImageCacheVO_.imageUuid, vm.imageUuid) + .like(ImageCacheVO_.installUrl, String.format("ceph://%s/%%", defaultImageCachePoolName)) + .find() + assert defaultCache != null + + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.PreferExistingCache.toString()) + env.preSimulator(CephPrimaryStorageBase.CLONE_PATH) { HttpEntity e -> + cloneCmd = json(e.body, CephPrimaryStorageBase.CloneCmd.class) + } + + VmInstanceInventory imageCachePoolVm = createVmInstance { + name = "prefer-existing-cache-vm" + instanceOfferingUuid = vm.instanceOfferingUuid + imageUuid = vm.imageUuid + l3NetworkUuids = asList(l3.uuid) + sessionId = adminSession() + rootVolumeSystemTags = [CephSystemTags.USE_CEPH_ROOT_POOL.instantiateTag([(CephSystemTags.USE_CEPH_ROOT_POOL_TOKEN): NEW_ROOT_POOL_NAME])] + } as VmInstanceInventory + + assert cloneCmd != null + assert cloneCmd.srcPath.contains(defaultImageCachePoolName) + + destroyVmInstance { + uuid = imageCachePoolVm.uuid + } + expungeVmInstance { + uuid = imageCachePoolVm.uuid + } + } finally { + if (defaultCacheVm != null) { + destroyVmInstance { + uuid = defaultCacheVm.uuid + } + expungeVmInstance { + uuid = defaultCacheVm.uuid + } + } + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) + } + } + + void testPreferExistingCacheStrategyUsesNonDefaultPoolCache() { + ensureNewRootPoolIsImageCachePool() + ImageInventory image2 = env.inventoryByName("image2") as ImageInventory + String defaultRootPoolName = CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_ROOT_VOLUME_POOL.getTokenByResourceUuid(primaryStorage.uuid, CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_ROOT_VOLUME_POOL_TOKEN) + VmInstanceInventory nonDefaultCacheVm = null + VmInstanceInventory preferExistingVm = null + + try { + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.PreferVolumePool.toString()) + nonDefaultCacheVm = createVmInstance { + name = "non-default-cache-vm" + instanceOfferingUuid = vm.instanceOfferingUuid + imageUuid = image2.uuid + l3NetworkUuids = asList(l3.uuid) + sessionId = adminSession() + rootVolumeSystemTags = [CephSystemTags.USE_CEPH_ROOT_POOL.instantiateTag([(CephSystemTags.USE_CEPH_ROOT_POOL_TOKEN): NEW_ROOT_POOL_NAME])] + } as VmInstanceInventory + + assert Q.New(ImageCacheVO.class) + .eq(ImageCacheVO_.primaryStorageUuid, primaryStorage.uuid) + .eq(ImageCacheVO_.imageUuid, image2.uuid) + .like(ImageCacheVO_.installUrl, String.format("ceph://%s/%%", NEW_ROOT_POOL_NAME)) + .isExists() + + destroyVmInstance { + uuid = nonDefaultCacheVm.uuid + } + expungeVmInstance { + uuid = nonDefaultCacheVm.uuid + } + nonDefaultCacheVm = null + + CephPrimaryStorageBase.CloneCmd cloneCmd = null + env.preSimulator(CephPrimaryStorageBase.CLONE_PATH) { HttpEntity e -> + cloneCmd = json(e.body, CephPrimaryStorageBase.CloneCmd.class) + } + + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.PreferExistingCache.toString()) + preferExistingVm = createVmInstance { + name = "prefer-existing-non-default-cache-vm" + instanceOfferingUuid = vm.instanceOfferingUuid + imageUuid = image2.uuid + l3NetworkUuids = asList(l3.uuid) + sessionId = adminSession() + } as VmInstanceInventory + + assert cloneCmd != null + assert cloneCmd.srcPath.contains(NEW_ROOT_POOL_NAME) + assert cloneCmd.dstPath.contains(defaultRootPoolName) + } finally { + if (preferExistingVm != null) { + destroyVmInstance { + uuid = preferExistingVm.uuid + } + expungeVmInstance { + uuid = preferExistingVm.uuid + } + } + if (nonDefaultCacheVm != null) { + destroyVmInstance { + uuid = nonDefaultCacheVm.uuid + } + expungeVmInstance { + uuid = nonDefaultCacheVm.uuid + } + } + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) + } + } + + void testPreferExistingCacheStrategySkipsStaleDefaultPoolCache() { + ensureNewRootPoolIsImageCachePool() + ImageInventory image2 = env.inventoryByName("image2") as ImageInventory + String defaultImageCachePoolName = CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_IMAGE_CACHE_POOL.getTokenByResourceUuid(primaryStorage.uuid, CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_IMAGE_CACHE_POOL_TOKEN) + String defaultRootPoolName = CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_ROOT_VOLUME_POOL.getTokenByResourceUuid(primaryStorage.uuid, CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_ROOT_VOLUME_POOL_TOKEN) + VmInstanceInventory defaultCacheVm = null + VmInstanceInventory nonDefaultCacheVm = null + VmInstanceInventory skipStaleDefaultVm = null + ImageCacheVO staleDefaultCache = null + ImageCacheVO nonDefaultCache = null + ImageCacheVolumeRefVO staleRef = null + CephPrimaryStorageBase.CloneCmd cloneCmd = null + + try { + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) + defaultCacheVm = createVmInstance { + name = "prefer-existing-stale-default-source-vm" + instanceOfferingUuid = vm.instanceOfferingUuid + imageUuid = image2.uuid + l3NetworkUuids = asList(l3.uuid) + sessionId = adminSession() + } as VmInstanceInventory + destroyVmInstance { + uuid = defaultCacheVm.uuid + } + expungeVmInstance { + uuid = defaultCacheVm.uuid + } + defaultCacheVm = null + + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.PreferVolumePool.toString()) + nonDefaultCacheVm = createVmInstance { + name = "prefer-existing-non-default-source-vm" + instanceOfferingUuid = vm.instanceOfferingUuid + imageUuid = image2.uuid + l3NetworkUuids = asList(l3.uuid) + sessionId = adminSession() + rootVolumeSystemTags = [CephSystemTags.USE_CEPH_ROOT_POOL.instantiateTag([(CephSystemTags.USE_CEPH_ROOT_POOL_TOKEN): NEW_ROOT_POOL_NAME])] + } as VmInstanceInventory + destroyVmInstance { + uuid = nonDefaultCacheVm.uuid + } + expungeVmInstance { + uuid = nonDefaultCacheVm.uuid + } + nonDefaultCacheVm = null + + staleDefaultCache = Q.New(ImageCacheVO.class) + .eq(ImageCacheVO_.primaryStorageUuid, primaryStorage.uuid) + .eq(ImageCacheVO_.imageUuid, image2.uuid) + .like(ImageCacheVO_.installUrl, String.format("ceph://%s/%%", defaultImageCachePoolName)) + .find() + nonDefaultCache = Q.New(ImageCacheVO.class) + .eq(ImageCacheVO_.primaryStorageUuid, primaryStorage.uuid) + .eq(ImageCacheVO_.imageUuid, image2.uuid) + .like(ImageCacheVO_.installUrl, String.format("ceph://%s/%%", NEW_ROOT_POOL_NAME)) + .find() + assert nonDefaultCache != null + + if (staleDefaultCache == null) { + staleDefaultCache = new ImageCacheVO() + staleDefaultCache.setPrimaryStorageUuid(primaryStorage.uuid) + staleDefaultCache.setImageUuid(image2.uuid) + staleDefaultCache.setInstallUrl(nonDefaultCache.installUrl.replaceFirst(String.format("ceph://%s/", NEW_ROOT_POOL_NAME), String.format("ceph://%s/", defaultImageCachePoolName))) + staleDefaultCache.setMediaType(nonDefaultCache.mediaType) + staleDefaultCache.setSize(nonDefaultCache.size) + staleDefaultCache.setMd5sum(nonDefaultCache.md5sum) + staleDefaultCache = bean(DatabaseFacade.class).persistAndRefresh(staleDefaultCache) + } + + staleRef = new ImageCacheVolumeRefVO() + staleRef.setImageCacheId(staleDefaultCache.id) + staleRef.setPrimaryStorageUuid(primaryStorage.uuid) + staleRef.setVolumeUuid(vm.rootVolumeUuid) + bean(DatabaseFacade.class).persist(staleRef) + + env.simulator(CephPrimaryStorageBase.CHECK_BITS_PATH) { HttpEntity e -> + CephPrimaryStorageBase.CheckIsBitsExistingCmd cmd = json(e.body, CephPrimaryStorageBase.CheckIsBitsExistingCmd.class) + CephPrimaryStorageBase.CheckIsBitsExistingRsp rsp = new CephPrimaryStorageBase.CheckIsBitsExistingRsp() + rsp.setExisting(!cmd.installPath.contains(defaultImageCachePoolName)) + return rsp + } + env.afterSimulator(CephPrimaryStorageBase.CHECK_BITS_PATH) { rsp, HttpEntity e -> + return rsp + } + env.preSimulator(CephPrimaryStorageBase.CLONE_PATH) { HttpEntity e -> + cloneCmd = json(e.body, CephPrimaryStorageBase.CloneCmd.class) + } + + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.PreferExistingCache.toString()) + skipStaleDefaultVm = createVmInstance { + name = "prefer-existing-skip-stale-default-cache-vm" + instanceOfferingUuid = vm.instanceOfferingUuid + imageUuid = image2.uuid + l3NetworkUuids = asList(l3.uuid) + sessionId = adminSession() + } as VmInstanceInventory + + assert dbFindById(staleDefaultCache.id, ImageCacheVO.class) == null + assert dbFindById(staleRef.id, ImageCacheVolumeRefVO.class) == null + assert dbFindById(nonDefaultCache.id, ImageCacheVO.class) != null + assert cloneCmd != null + assert cloneCmd.srcPath.contains(NEW_ROOT_POOL_NAME) + assert cloneCmd.dstPath.contains(defaultRootPoolName) + } finally { + if (skipStaleDefaultVm != null) { + destroyVmInstance { + uuid = skipStaleDefaultVm.uuid + } + expungeVmInstance { + uuid = skipStaleDefaultVm.uuid + } + } + if (nonDefaultCacheVm != null) { + destroyVmInstance { + uuid = nonDefaultCacheVm.uuid + } + expungeVmInstance { + uuid = nonDefaultCacheVm.uuid + } + } + if (defaultCacheVm != null) { + destroyVmInstance { + uuid = defaultCacheVm.uuid + } + expungeVmInstance { + uuid = defaultCacheVm.uuid + } + } + restoreCheckBitsSimulator() + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) + } + } + + void testMissingSelectedCacheBitsCopiesFromOtherPool() { + ensureNewRootPoolIsImageCachePool() + ImageInventory image3 = env.inventoryByName("image3") as ImageInventory + String defaultImageCachePoolName = CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_IMAGE_CACHE_POOL.getTokenByResourceUuid(primaryStorage.uuid, CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_IMAGE_CACHE_POOL_TOKEN) + VFS vfs = CephPrimaryStorageSpec.vfs1("7ff218d9-f525-435f-8a40-3618d1772a64", env) + + ImageCacheVO sourceCache = new ImageCacheVO() + sourceCache.setPrimaryStorageUuid(primaryStorage.uuid) + sourceCache.setImageUuid(image3.uuid) + sourceCache.setInstallUrl(String.format("ceph://%s/%s-source@%s-source", defaultImageCachePoolName, image3.uuid, image3.uuid)) + sourceCache.setMediaType(ImageMediaType.RootVolumeTemplate) + sourceCache.setSize(SizeUnit.GIGABYTE.toByte(1)) + sourceCache.setMd5sum("not calculated") + sourceCache = bean(DatabaseFacade.class).persistAndRefresh(sourceCache) + + ImageCacheVO staleCache = new ImageCacheVO() + staleCache.setPrimaryStorageUuid(primaryStorage.uuid) + staleCache.setImageUuid(image3.uuid) + staleCache.setInstallUrl(String.format("ceph://%s/%s-stale@%s-stale", NEW_ROOT_POOL_NAME, image3.uuid, image3.uuid)) + staleCache.setMediaType(ImageMediaType.RootVolumeTemplate) + staleCache.setSize(SizeUnit.GIGABYTE.toByte(1)) + staleCache.setMd5sum("not calculated") + staleCache = bean(DatabaseFacade.class).persistAndRefresh(staleCache) + + String sourceCachePath = CephPrimaryStorageSpec.cephPathToVFSPath(sourceCache.installUrl) + vfs.createDirectories(String.format("/%s", defaultImageCachePoolName)) + if (!vfs.exists(sourceCachePath)) { + vfs.createCephRaw(sourceCachePath, 0L) + } + String staleCachePath = CephPrimaryStorageSpec.cephPathToVFSPath(staleCache.installUrl) + String staleCacheVolumePath = CephPrimaryStorageSpec.cephPathToVFSPath(staleCache.installUrl.split("@")[0]) + if (vfs.exists(staleCachePath)) { + vfs.delete(staleCachePath) + } + if (vfs.exists(staleCacheVolumePath)) { + vfs.delete(staleCacheVolumePath) + } + + CephPrimaryStorageBase.CpCmd cpCmd = null + CephPrimaryStorageBase.CloneCmd cloneCmd = null + env.simulator(CephPrimaryStorageBase.CHECK_BITS_PATH) { HttpEntity e -> + CephPrimaryStorageBase.CheckIsBitsExistingCmd cmd = json(e.body, CephPrimaryStorageBase.CheckIsBitsExistingCmd.class) + CephPrimaryStorageBase.CheckIsBitsExistingRsp rsp = new CephPrimaryStorageBase.CheckIsBitsExistingRsp() + rsp.setExisting(!cmd.installPath.contains(NEW_ROOT_POOL_NAME)) + return rsp + } + env.afterSimulator(CephPrimaryStorageBase.CHECK_BITS_PATH) { rsp, HttpEntity e -> + return rsp + } + env.hijackSimulator(CephPrimaryStorageBase.CP_PATH) { rsp, HttpEntity e -> + cpCmd = json(e.body, CephPrimaryStorageBase.CpCmd.class) + return rsp + } + env.preSimulator(CephPrimaryStorageBase.CLONE_PATH) { HttpEntity e -> + cloneCmd = json(e.body, CephPrimaryStorageBase.CloneCmd.class) + } + + try { + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.PreferVolumePool.toString()) + VmInstanceInventory staleCacheVm = createVmInstance { + name = "missing-selected-cache-bits-vm" + instanceOfferingUuid = vm.instanceOfferingUuid + imageUuid = image3.uuid + l3NetworkUuids = asList(l3.uuid) + sessionId = adminSession() + rootVolumeSystemTags = [CephSystemTags.USE_CEPH_ROOT_POOL.instantiateTag([(CephSystemTags.USE_CEPH_ROOT_POOL_TOKEN): NEW_ROOT_POOL_NAME])] + } as VmInstanceInventory + + assert dbFindById(staleCache.id, ImageCacheVO.class) == null + assert cpCmd != null + assert cpCmd.srcPath.contains(defaultImageCachePoolName) + assert cpCmd.dstPath.contains(NEW_ROOT_POOL_NAME) + assert cloneCmd != null + assert cloneCmd.srcPath.contains(NEW_ROOT_POOL_NAME) + + destroyVmInstance { + uuid = staleCacheVm.uuid + } + expungeVmInstance { + uuid = staleCacheVm.uuid + } + } finally { + restoreCheckBitsSimulator() + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) + } + } + + void testCleanupImageCacheKeepsReferencedCacheOnly() { + restoreCheckBitsSimulator() + String cleanupImageUuid = "cleanup-image-cache-image" + String reuseImageUuid = "cleanup-image-cache-reuse-image" + String reuseTreeUuid = null + String defaultImageCachePoolName = CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_IMAGE_CACHE_POOL.getTokenByResourceUuid(primaryStorage.uuid, CephSystemTags.DEFAULT_CEPH_PRIMARY_STORAGE_IMAGE_CACHE_POOL_TOKEN) + + try { + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) + + ImageCacheVO protectedCache = new ImageCacheVO() + protectedCache.setPrimaryStorageUuid(primaryStorage.uuid) + protectedCache.setImageUuid(cleanupImageUuid) + protectedCache.setInstallUrl(String.format("ceph://%s/%s-protected@%s-protected", NEW_ROOT_POOL_NAME, cleanupImageUuid, cleanupImageUuid)) + protectedCache.setMediaType(ImageMediaType.RootVolumeTemplate) + protectedCache.setSize(SizeUnit.GIGABYTE.toByte(1)) + protectedCache.setMd5sum("not calculated") + protectedCache = bean(DatabaseFacade.class).persistAndRefresh(protectedCache) + + ImageCacheVO deletedCache = new ImageCacheVO() + deletedCache.setPrimaryStorageUuid(primaryStorage.uuid) + deletedCache.setImageUuid(cleanupImageUuid) + deletedCache.setInstallUrl(String.format("ceph://%s/%s-deleted@%s-deleted", defaultImageCachePoolName, cleanupImageUuid, cleanupImageUuid)) + deletedCache.setMediaType(ImageMediaType.RootVolumeTemplate) + deletedCache.setSize(SizeUnit.GIGABYTE.toByte(1)) + deletedCache.setMd5sum("not calculated") + deletedCache = bean(DatabaseFacade.class).persistAndRefresh(deletedCache) + + ImageCacheVO reuseCache = new ImageCacheVO() + reuseCache.setPrimaryStorageUuid(primaryStorage.uuid) + reuseCache.setImageUuid(reuseImageUuid) + reuseCache.setInstallUrl(String.format("volumeSnapshotReuse://%s", reuseImageUuid)) + reuseCache.setMediaType(ImageMediaType.RootVolumeTemplate) + reuseCache.setSize(SizeUnit.GIGABYTE.toByte(1)) + reuseCache.setMd5sum("not calculated") + reuseCache = bean(DatabaseFacade.class).persistAndRefresh(reuseCache) + + VolumeSnapshotReferenceTreeVO reuseTree = new VolumeSnapshotReferenceTreeVO() + reuseTree.setUuid(Platform.getUuid()) + reuseTree.setRootImageUuid(reuseImageUuid) + reuseTree.setRootInstallUrl(reuseCache.installUrl) + reuseTree.setPrimaryStorageUuid(primaryStorage.uuid) + bean(DatabaseFacade.class).persist(reuseTree) + reuseTreeUuid = reuseTree.uuid + + VFS vfs = CephPrimaryStorageSpec.vfs1("7ff218d9-f525-435f-8a40-3618d1772a64", env) + vfs.createDirectories(String.format("/%s", NEW_ROOT_POOL_NAME)) + vfs.createDirectories(String.format("/%s", defaultImageCachePoolName)) + [protectedCache, deletedCache].each { ImageCacheVO cache -> + String imagePath = CephPrimaryStorageSpec.cephPathToVFSPath(cache.installUrl.split("@")[0]) + String snapshotPath = CephPrimaryStorageSpec.cephPathToVFSPath(cache.installUrl) + if (!vfs.exists(imagePath)) { + vfs.createCephRaw(imagePath, 0L) + } + if (!vfs.exists(snapshotPath)) { + vfs.createCephRaw(snapshotPath, 0L) + } + } + + ImageCacheVolumeRefVO ref = new ImageCacheVolumeRefVO() + ref.setImageCacheId(protectedCache.id) + ref.setPrimaryStorageUuid(primaryStorage.uuid) + ref.setVolumeUuid(vm.rootVolumeUuid) + bean(DatabaseFacade.class).persist(ref) + + List deleteCmds = [] + env.afterSimulator(CephPrimaryStorageBase.DELETE_IMAGE_CACHE) { rsp, HttpEntity e -> + deleteCmds.add(json(e.body, CephPrimaryStorageBase.DeleteImageCacheCmd.class)) + return rsp + } + + cleanUpImageCacheOnPrimaryStorage { + uuid = primaryStorage.uuid + force = true + } + + retryInSecs { + assert dbFindById(protectedCache.id, ImageCacheVO.class) != null + assert dbFindById(deletedCache.id, ImageCacheVO.class) == null + assert dbFindById(reuseCache.id, ImageCacheVO.class) != null + assert !Q.New(ImageCacheShadowVO.class).eq(ImageCacheShadowVO_.installUrl, deletedCache.installUrl).isExists() + assert deleteCmds.find { it.snapshotPath == deletedCache.installUrl } != null + assert deleteCmds.find { it.snapshotPath == protectedCache.installUrl } == null + assert deleteCmds.find { it.snapshotPath == reuseCache.installUrl } == null + } + } finally { + if (reuseTreeUuid != null) { + bean(DatabaseFacade.class).removeByPrimaryKey(reuseTreeUuid, VolumeSnapshotReferenceTreeVO.class) + } + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) + } } void testAddCephPoolWithChinese(){ @@ -330,6 +1020,14 @@ class CephPrimaryStorageVolumePoolsCase extends SubCase { } void testReimageVmAndAllocatePool() { + ensureNewRootPoolIsImageCachePool() + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.PreferVolumePool.toString()) + + CephPrimaryStorageBase.CloneCmd cloneCmd = null + env.preSimulator(CephPrimaryStorageBase.CLONE_PATH) { HttpEntity e -> + cloneCmd = json(e.body, CephPrimaryStorageBase.CloneCmd.class) + } + L3NetworkSpec l3Spec = env.specByName("l3") as L3NetworkSpec VmInstanceInventory new_root_pool_vm = createVmInstance { name = "new_root_pool_vm" @@ -345,12 +1043,77 @@ class CephPrimaryStorageVolumePoolsCase extends SubCase { uuid = new_root_pool_vm.uuid } - reimageVmInstance { - vmInstanceUuid = new_root_pool_vm.uuid + cloneCmd = null + try { + reimageVmInstance { + vmInstanceUuid = new_root_pool_vm.uuid + } + } finally { + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) } String VolumeInstallPath = Q.New(VolumeVO.class).select(VolumeVO_.installPath).eq(VolumeVO_.uuid, new_root_pool_vm.rootVolumeUuid).findValue() assert VolumeInstallPath.contains(NEW_ROOT_POOL_NAME) + assert cloneCmd != null + assert cloneCmd.srcPath.contains(NEW_ROOT_POOL_NAME) + } + + void testReimageVmCopiesImageToSelectedPoolAfterImageDeleted() { + ensureNewRootPoolIsImageCachePool() + ImageInventory image4 = env.inventoryByName("image4") as ImageInventory + VmInstanceInventory reimageVm = null + CephPrimaryStorageBase.CpCmd cpCmd = null + CephPrimaryStorageBase.CloneCmd cloneCmd = null + env.hijackSimulator(CephPrimaryStorageBase.CP_PATH) { rsp, HttpEntity e -> + cpCmd = json(e.body, CephPrimaryStorageBase.CpCmd.class) + return rsp + } + env.preSimulator(CephPrimaryStorageBase.CLONE_PATH) { HttpEntity e -> + cloneCmd = json(e.body, CephPrimaryStorageBase.CloneCmd.class) + } + + try { + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) + reimageVm = createVmInstance { + name = "reimage-vm-after-image-deleted" + instanceOfferingUuid = vm.instanceOfferingUuid + imageUuid = image4.uuid + l3NetworkUuids = asList(l3.uuid) + sessionId = adminSession() + rootVolumeSystemTags = [CephSystemTags.USE_CEPH_ROOT_POOL.instantiateTag([(CephSystemTags.USE_CEPH_ROOT_POOL_TOKEN): NEW_ROOT_POOL_NAME])] + } as VmInstanceInventory + + stopVmInstance { + uuid = reimageVm.uuid + } + + deleteImage { + uuid = image4.uuid + } + + cpCmd = null + cloneCmd = null + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.PreferVolumePool.toString()) + reimageVmInstance { + vmInstanceUuid = reimageVm.uuid + } + + assert cpCmd != null + assert cpCmd.dstPath.contains(NEW_ROOT_POOL_NAME) + assert cloneCmd != null + assert cloneCmd.srcPath.contains(NEW_ROOT_POOL_NAME) + } finally { + CephGlobalConfig.IMAGE_CACHE_POOL_STRATEGY.updateValue(CephImageCachePoolStrategy.DefaultImageCachePool.toString()) + + if (reimageVm != null) { + destroyVmInstance { + uuid = reimageVm.uuid + } + expungeVmInstance { + uuid = reimageVm.uuid + } + } + } } void testCreateVmInstanceWithCustomDiskOffering() { @@ -433,8 +1196,18 @@ class CephPrimaryStorageVolumePoolsCase extends SubCase { testAddPoolWithCheckExistenceFailure() testQueryPool() testAddSameCephPool() + testPreferVolumePoolImageCacheStrategy() + testDefaultImageCachePoolStrategyUsesDefaultPool() + testDefaultStrategyCopiesCephBackupStorageImageToDefaultPool() + testPreferVolumePoolFallbackToDefaultPool() + testPreferExistingCacheStrategyPrefersDefaultPoolCache() + testPreferExistingCacheStrategyUsesNonDefaultPoolCache() + testPreferExistingCacheStrategySkipsStaleDefaultPoolCache() + testMissingSelectedCacheBitsCopiesFromOtherPool() + testCleanupImageCacheKeepsReferencedCacheOnly() testAddCephPoolWithChinese() testReimageVmAndAllocatePool() + testReimageVmCopiesImageToSelectedPoolAfterImageDeleted() testCreateVmInstanceWithCustomDiskOffering() } } diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/capacity/CephOpenSourcePoolCapacityCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/capacity/CephOpenSourcePoolCapacityCase.groovy index 09c74368ad3..edf752b09b1 100644 --- a/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/capacity/CephOpenSourcePoolCapacityCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/capacity/CephOpenSourcePoolCapacityCase.groovy @@ -212,4 +212,4 @@ class CephOpenSourcePoolCapacityCase extends SubCase { assert bsCapacity.totalCapacity == 106300440576 // 99G env.cleanSimulatorAndMessageHandlers() } -} \ No newline at end of file +} diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/sandstone/capacity/CephSandStonePoolCapacityCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/sandstone/capacity/CephSandStonePoolCapacityCase.groovy index df1fc1d7d0e..0eb92a7cfd7 100644 --- a/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/sandstone/capacity/CephSandStonePoolCapacityCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/sandstone/capacity/CephSandStonePoolCapacityCase.groovy @@ -163,4 +163,5 @@ class CephSandStonePoolCapacityCase extends SubCase { assert afterBs.availableCapacity == bs.availableCapacity + addSize assert afterBs.totalCapacity == bs.totalCapacity + addSize } + } diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/xsky/capacity/CephXskyPoolCapacityCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/xsky/capacity/CephXskyPoolCapacityCase.groovy index 356599909f6..f23ed4a3c3a 100644 --- a/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/xsky/capacity/CephXskyPoolCapacityCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/ceph/xsky/capacity/CephXskyPoolCapacityCase.groovy @@ -205,4 +205,5 @@ class CephXskyPoolCapacityCase extends SubCase { SQL.New(VolumeSnapshotVO.class).eq(VolumeSnapshotVO_.uuid, rootSnapshot.uuid) .set(VolumeSnapshotVO_.primaryStorageInstallPath, volumeSnapshotInstallPath).update() } + } diff --git a/testlib/src/main/java/org/zstack/testlib/CephPrimaryStoragePoolSpec.groovy b/testlib/src/main/java/org/zstack/testlib/CephPrimaryStoragePoolSpec.groovy index c0cfec41607..12d6eb293b4 100755 --- a/testlib/src/main/java/org/zstack/testlib/CephPrimaryStoragePoolSpec.groovy +++ b/testlib/src/main/java/org/zstack/testlib/CephPrimaryStoragePoolSpec.groovy @@ -13,6 +13,8 @@ class CephPrimaryStoragePoolSpec extends Spec { String description @SpecParam(required = true) String type + @SpecParam + boolean isCreate = true CephPrimaryStoragePoolInventory inventory @@ -40,7 +42,7 @@ class CephPrimaryStoragePoolSpec extends Spec { delegate.resourceUuid = uuid delegate.sessionId = sessionId delegate.type = type - delegate.isCreate = true + delegate.isCreate = isCreate } return id(poolName, inventory.uuid) diff --git a/testlib/src/main/java/org/zstack/testlib/CephPrimaryStorageSpec.groovy b/testlib/src/main/java/org/zstack/testlib/CephPrimaryStorageSpec.groovy index d3e5f964415..f7cd77ab451 100755 --- a/testlib/src/main/java/org/zstack/testlib/CephPrimaryStorageSpec.groovy +++ b/testlib/src/main/java/org/zstack/testlib/CephPrimaryStorageSpec.groovy @@ -35,8 +35,10 @@ class CephPrimaryStorageSpec extends PrimaryStorageSpec { String rootVolumePoolName = "pri-c-" + Platform.getUuid() @SpecParam String dataVolumePoolName = "pri-v-d-" + Platform.getUuid() - @SpecParam - String imageCachePoolName = "pri-v-r-" + Platform.getUuid() + @SpecParam + String imageCachePoolName = "pri-v-r-" + Platform.getUuid() + @SpecParam + String extraImageCachePoolName CephPrimaryStorageSpec(EnvSpec envSpec) { super(envSpec) @@ -76,9 +78,10 @@ class CephPrimaryStorageSpec extends PrimaryStorageSpec { } class CephPrimaryStorageStruct { - String rootVolumePoolName - String dataVolumePoolName - String imageCachePoolName + String rootVolumePoolName + String dataVolumePoolName + String imageCachePoolName + String extraImageCachePoolName } static class Simulators implements Simulator { @@ -489,18 +492,19 @@ class CephPrimaryStorageSpec extends PrimaryStorageSpec { return new CephPrimaryStorageBase.AgentResponse() } - simulator(CephPrimaryStorageBase.ADD_POOL_PATH) { HttpEntity entity, EnvSpec spec -> - def cmd = JSONObjectUtil.toObject(entity.body, CephPrimaryStorageBase.AddPoolCmd.class) + simulator(CephPrimaryStorageBase.ADD_POOL_PATH) { HttpEntity entity, EnvSpec spec -> + def cmd = JSONObjectUtil.toObject(entity.body, CephPrimaryStorageBase.AddPoolCmd.class) CephPrimaryStorageSpec cspec = spec.specByUuid(cmd.uuid) CephPrimaryStorageBase.AddPoolRsp rsp = new CephPrimaryStorageBase.AddPoolRsp() - rsp.totalCapacity = cspec.totalCapacity - rsp.availableCapacity = cspec.availableCapacity - long rootSize = cspec.availableCapacity / 3 - long dataSize = cspec.availableCapacity / 3 - long cacheSize = cspec.totalCapacity - rootSize - dataSize - rsp.setAvailableCapacity(SizeUnit.GIGABYTE.toByte(100)) - rsp.setTotalCapacity(SizeUnit.GIGABYTE.toByte(100)) + def newPoolCapacity = SizeUnit.GIGABYTE.toByte(100) + rsp.totalCapacity = newPoolCapacity + rsp.availableCapacity = newPoolCapacity + long rootSize = cspec.availableCapacity / 3 + long dataSize = cspec.availableCapacity / 3 + long cacheSize = cspec.totalCapacity - rootSize - dataSize + rsp.setAvailableCapacity(newPoolCapacity) + rsp.setTotalCapacity(newPoolCapacity) List poolCapacities = [ new CephPoolCapacity( name: cspec.rootVolumePoolName, @@ -532,17 +536,17 @@ class CephPrimaryStorageSpec extends PrimaryStorageSpec { diskUtilization: 0.33, relatedOsds: 'osd.3' ), - new CephPoolCapacity( - name: cmd.poolName, - availableCapacity: SizeUnit.GIGABYTE.toByte(100), - usedCapacity: 0, - totalCapacity: SizeUnit.GIGABYTE.toByte(100), - securityPolicy: DataSecurityPolicy.Copy.toString(), - replicatedSize: 3, - diskUtilization: 0.33, - relatedOsds: "osd.4" - ) - ] + new CephPoolCapacity( + name: cmd.poolName, + availableCapacity: newPoolCapacity, + usedCapacity: 0, + totalCapacity: newPoolCapacity, + securityPolicy: DataSecurityPolicy.Copy.toString(), + replicatedSize: 3, + diskUtilization: 0.33, + relatedOsds: 'osd.4' + ) + ] rsp.setPoolCapacities(poolCapacities) return rsp @@ -801,14 +805,24 @@ class CephPrimaryStorageSpec extends PrimaryStorageSpec { delegate.imageCachePoolName = imageCachePoolName } as PrimaryStorageInventory - postCreate { - inventory = queryCephPrimaryStorage { - conditions=["uuid=${inventory.uuid}".toString()] - }[0] - } - - return id(name, inventory.uuid) - } + postCreate { + inventory = queryCephPrimaryStorage { + conditions=["uuid=${inventory.uuid}".toString()] + }[0] + } + + if (extraImageCachePoolName != null) { + addCephPrimaryStoragePool { + delegate.primaryStorageUuid = inventory.uuid + delegate.poolName = extraImageCachePoolName + delegate.type = CephPrimaryStoragePoolType.ImageCache.toString() + delegate.isCreate = true + delegate.sessionId = sessionId + } + } + + return id(name, inventory.uuid) + } CephPrimaryStoragePoolSpec pool(@DelegatesTo(strategy = Closure.DELEGATE_FIRST, value = CephPrimaryStoragePoolSpec.class) Closure c) { def spec = new CephPrimaryStoragePoolSpec(envSpec)