From e8a38f04f110b6078419c6c8c5d562d69f25ab28 Mon Sep 17 00:00:00 2001 From: "xiangheng.zhao" Date: Fri, 3 Jul 2026 18:04:27 +0800 Subject: [PATCH] [zbs]: sync cow snapshot size in scheduled volume resource sync ZBS COPY_ON_WRITE snapshot actual size is maintained by the storage backend and may change after snapshot creation. The console reads snapshot size from VolumeSnapshotVO, but the scheduled active-volume size sync refreshed only volume actual size before, so snapshot capacity could stay stale in the database. During scheduled active-volume size sync, only primary storages whose snapshot mode is COPY_ON_WRITE attach snapshot install paths to the batch volume resource size sync request. The reply carries snapshot actual sizes so VolumeSnapshotVO.size can be refreshed together with volume actual size, without adding a manual snapshot size query flow for the console. Rename BatchSyncVolumeSizeOnPrimaryStorageMsg to BatchSyncVolumeResourceSizeOnPrimaryStorageMsg because the scheduled batch sync now covers volume resources and, for COW snapshot backends, snapshot resources. Keep SyncVolumeSize and single-volume stats as volume-only flows. Query snapshot capability by primary storage identity and volume type. PrimaryStorageInventory#getIdentity provides the cache identity: normal primary storages use type, and external primary storages override it with their identity, so VolumeManagerImpl does not depend on external storage VOs or external/non-external type checks. Use StorageResourceStats as the batchStats return parent for VolumeStats and VolumeSnapshotStats. Batch stats accepts one installPaths collection; ZBS detects snapshot paths by @ and uses the volume-with-snapshot agent API only when COW snapshot size sync is required. Test: git diff --cached --check Test: mvn -pl header,storage -Dmaven.test.skip=true clean package Resolves: ZSTAC-67903 Change-Id: I0f4c2b8e3a9d167f5c0e2b6a1d9c8e7f3456abcd --- .../ExternalPrimaryStorageInventory.java | 1 + .../primary/PrimaryStorageControllerSvc.java | 3 +- .../AskVolumeSnapshotCapabilityMsg.java | 9 + .../primary/PrimaryStorageInventory.java | 4 + .../storage/primary/StorageResourceStats.java | 31 ++ .../primary/VolumeSnapshotCapability.java | 15 + .../storage/snapshot/VolumeSnapshotStats.java | 20 +- ...olumeResourceSizeOnPrimaryStorageMsg.java} | 22 +- ...lumeResourceSizeOnPrimaryStorageReply.java | 28 ++ ...chSyncVolumeSizeOnPrimaryStorageReply.java | 18 - .../org/zstack/header/volume/VolumeStats.java | 31 +- .../ceph/primary/CephPrimaryStorageBase.java | 17 +- .../zstack/expon/ExponStorageController.java | 8 +- .../primary/local/LocalStorageBase.java | 15 +- .../local/LocalStorageHypervisorBackend.java | 2 +- .../primary/local/LocalStorageKvmBackend.java | 6 +- .../primary/nfs/NfsPrimaryStorage.java | 34 +- .../primary/nfs/NfsPrimaryStorageBackend.java | 6 +- .../nfs/NfsPrimaryStorageKVMBackend.java | 6 +- .../primary/smp/SMPPrimaryStorageBase.java | 11 +- .../xinfini/XInfiniStorageController.java | 8 +- .../storage/zbs/ZbsStorageController.java | 63 +++- .../primary/SimulatorPrimaryStorage.java | 11 +- .../addon/primary/ExternalPrimaryStorage.java | 40 ++- .../storage/primary/PrimaryStorageBase.java | 6 +- .../storage/volume/VolumeManagerImpl.java | 314 +++++++++++++++--- .../addon/zbs/ZbsPrimaryStorageCase.groovy | 59 +++- 27 files changed, 588 insertions(+), 200 deletions(-) create mode 100644 header/src/main/java/org/zstack/header/storage/primary/StorageResourceStats.java rename header/src/main/java/org/zstack/header/volume/{BatchSyncVolumeSizeOnPrimaryStorageMsg.java => BatchSyncVolumeResourceSizeOnPrimaryStorageMsg.java} (58%) create mode 100644 header/src/main/java/org/zstack/header/volume/BatchSyncVolumeResourceSizeOnPrimaryStorageReply.java delete mode 100644 header/src/main/java/org/zstack/header/volume/BatchSyncVolumeSizeOnPrimaryStorageReply.java diff --git a/header/src/main/java/org/zstack/header/storage/addon/primary/ExternalPrimaryStorageInventory.java b/header/src/main/java/org/zstack/header/storage/addon/primary/ExternalPrimaryStorageInventory.java index a15ed211307..b170c0529d5 100644 --- a/header/src/main/java/org/zstack/header/storage/addon/primary/ExternalPrimaryStorageInventory.java +++ b/header/src/main/java/org/zstack/header/storage/addon/primary/ExternalPrimaryStorageInventory.java @@ -100,6 +100,7 @@ private static String desensitizeUrl(String url) { return url; } + @Override public String getIdentity() { return identity; } diff --git a/header/src/main/java/org/zstack/header/storage/addon/primary/PrimaryStorageControllerSvc.java b/header/src/main/java/org/zstack/header/storage/addon/primary/PrimaryStorageControllerSvc.java index feb4c5c11c7..e57f490ea91 100644 --- a/header/src/main/java/org/zstack/header/storage/addon/primary/PrimaryStorageControllerSvc.java +++ b/header/src/main/java/org/zstack/header/storage/addon/primary/PrimaryStorageControllerSvc.java @@ -5,6 +5,7 @@ import org.zstack.header.core.ReturnValueCompletion; import org.zstack.header.host.HostInventory; import org.zstack.header.storage.addon.*; +import org.zstack.header.storage.primary.StorageResourceStats; import org.zstack.header.storage.snapshot.VolumeSnapshotStats; import org.zstack.header.volume.VolumeProtocol; import org.zstack.header.volume.VolumeStats; @@ -64,7 +65,7 @@ public interface PrimaryStorageControllerSvc { // support uri or path void stats(String installPath, ReturnValueCompletion comp); - void batchStats(Collection installPath, ReturnValueCompletion> comp); + void batchStats(Collection installPaths, ReturnValueCompletion> comp); void expandVolume(String installPath, long size, ReturnValueCompletion comp); void setVolumeQos(BaseVolumeInfo v, Completion comp); diff --git a/header/src/main/java/org/zstack/header/storage/primary/AskVolumeSnapshotCapabilityMsg.java b/header/src/main/java/org/zstack/header/storage/primary/AskVolumeSnapshotCapabilityMsg.java index eef6a6cca8c..be74633d734 100755 --- a/header/src/main/java/org/zstack/header/storage/primary/AskVolumeSnapshotCapabilityMsg.java +++ b/header/src/main/java/org/zstack/header/storage/primary/AskVolumeSnapshotCapabilityMsg.java @@ -9,6 +9,7 @@ public class AskVolumeSnapshotCapabilityMsg extends NeedReplyMessage implements PrimaryStorageMessage { private String primaryStorageUuid; private VolumeInventory volume; + private String volumeType; @Override public String getPrimaryStorageUuid() { @@ -26,4 +27,12 @@ public VolumeInventory getVolume() { public void setVolume(VolumeInventory volume) { this.volume = volume; } + + public String getVolumeType() { + return volumeType; + } + + public void setVolumeType(String volumeType) { + this.volumeType = volumeType; + } } diff --git a/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageInventory.java b/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageInventory.java index 643c8709e68..44d153bc717 100755 --- a/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageInventory.java +++ b/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageInventory.java @@ -290,6 +290,10 @@ public String getType() { return type; } + public String getIdentity() { + return type; + } + public void setType(String type) { this.type = type; } diff --git a/header/src/main/java/org/zstack/header/storage/primary/StorageResourceStats.java b/header/src/main/java/org/zstack/header/storage/primary/StorageResourceStats.java new file mode 100644 index 00000000000..2d90b5ac9ad --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/StorageResourceStats.java @@ -0,0 +1,31 @@ +package org.zstack.header.storage.primary; + +public class StorageResourceStats { + protected String installPath; + protected Long actualSize; + protected Long size; + + public String getInstallPath() { + return installPath; + } + + public void setInstallPath(String installPath) { + this.installPath = installPath; + } + + public Long getActualSize() { + return actualSize; + } + + public void setActualSize(Long actualSize) { + this.actualSize = actualSize; + } + + public Long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/VolumeSnapshotCapability.java b/header/src/main/java/org/zstack/header/storage/primary/VolumeSnapshotCapability.java index 79050146db5..08d9c6a073d 100755 --- a/header/src/main/java/org/zstack/header/storage/primary/VolumeSnapshotCapability.java +++ b/header/src/main/java/org/zstack/header/storage/primary/VolumeSnapshotCapability.java @@ -16,6 +16,11 @@ public static enum VolumeSnapshotPlacementType { EXTERNAL, } + public static enum VolumeSnapshotMode { + REDIRECT_ON_WRITE, + COPY_ON_WRITE, + } + private boolean support; /*** @@ -34,6 +39,8 @@ public static enum VolumeSnapshotPlacementType { private VolumeSnapshotArrangementType arrangementType; private VolumeSnapshotPlacementType placementType; + + private VolumeSnapshotMode mode; /*** * If volume snapshot is inner snapshot on volume, it must be set. @@ -66,6 +73,14 @@ public void setPlacementType(VolumeSnapshotPlacementType placementType) { this.placementType = placementType; } + public VolumeSnapshotMode getMode() { + return mode; + } + + public void setMode(VolumeSnapshotMode mode) { + this.mode = mode; + } + public boolean isSupportCreateOnHypervisor() { return supportCreateOnHypervisor; } diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotStats.java b/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotStats.java index 8b4e3778ef3..4e6e4b860d9 100644 --- a/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotStats.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotStats.java @@ -1,22 +1,6 @@ package org.zstack.header.storage.snapshot; -public class VolumeSnapshotStats { - private String installPath; - private long actualSize; +import org.zstack.header.storage.primary.StorageResourceStats; - public String getInstallPath() { - return installPath; - } - - public void setInstallPath(String installPath) { - this.installPath = installPath; - } - - public long getActualSize() { - return actualSize; - } - - public void setActualSize(long actualSize) { - this.actualSize = actualSize; - } +public class VolumeSnapshotStats extends StorageResourceStats { } diff --git a/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeSizeOnPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeResourceSizeOnPrimaryStorageMsg.java similarity index 58% rename from header/src/main/java/org/zstack/header/volume/BatchSyncVolumeSizeOnPrimaryStorageMsg.java rename to header/src/main/java/org/zstack/header/volume/BatchSyncVolumeResourceSizeOnPrimaryStorageMsg.java index 4ab12a3b2a8..a21435a170c 100644 --- a/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeSizeOnPrimaryStorageMsg.java +++ b/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeResourceSizeOnPrimaryStorageMsg.java @@ -5,13 +5,17 @@ import java.util.Map; -public class BatchSyncVolumeSizeOnPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage { +public class BatchSyncVolumeResourceSizeOnPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage { private String hostUuid; private String primaryStorageUuid; private Map volumeUuidInstallPaths; + private Map snapshotUuidInstallPaths; + + private boolean withSnapshot; + public void setHostUuid(String hostUuid) { this.hostUuid = hostUuid; } @@ -35,4 +39,20 @@ public void setVolumeUuidInstallPaths(Map volumeUuidInstallPaths public Map getVolumeUuidInstallPaths() { return volumeUuidInstallPaths; } + + public Map getSnapshotUuidInstallPaths() { + return snapshotUuidInstallPaths; + } + + public void setSnapshotUuidInstallPaths(Map snapshotUuidInstallPaths) { + this.snapshotUuidInstallPaths = snapshotUuidInstallPaths; + } + + public boolean isWithSnapshot() { + return withSnapshot; + } + + public void setWithSnapshot(boolean withSnapshot) { + this.withSnapshot = withSnapshot; + } } diff --git a/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeResourceSizeOnPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeResourceSizeOnPrimaryStorageReply.java new file mode 100644 index 00000000000..fc5980bd51c --- /dev/null +++ b/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeResourceSizeOnPrimaryStorageReply.java @@ -0,0 +1,28 @@ +package org.zstack.header.volume; + +import org.zstack.header.message.MessageReply; + +import java.util.HashMap; +import java.util.Map; + +public class BatchSyncVolumeResourceSizeOnPrimaryStorageReply extends MessageReply { + private Map volumeActualSizes = new HashMap<>(); + + private Map snapshotActualSizes = new HashMap<>(); + + public void setVolumeActualSizes(Map actualSizes) { + this.volumeActualSizes = actualSizes; + } + + public Map getVolumeActualSizes() { + return volumeActualSizes; + } + + public Map getSnapshotActualSizes() { + return snapshotActualSizes; + } + + public void setSnapshotActualSizes(Map snapshotActualSizes) { + this.snapshotActualSizes = snapshotActualSizes; + } +} diff --git a/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeSizeOnPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeSizeOnPrimaryStorageReply.java deleted file mode 100644 index ee543ba398a..00000000000 --- a/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeSizeOnPrimaryStorageReply.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.zstack.header.volume; - -import org.zstack.header.message.MessageReply; - -import java.util.HashMap; -import java.util.Map; - -public class BatchSyncVolumeSizeOnPrimaryStorageReply extends MessageReply { - private Map actualSizes = new HashMap<>(); - - public void setActualSizes(Map actualSizes) { - this.actualSizes = actualSizes; - } - - public Map getActualSizes() { - return actualSizes; - } -} diff --git a/header/src/main/java/org/zstack/header/volume/VolumeStats.java b/header/src/main/java/org/zstack/header/volume/VolumeStats.java index 2931945e7ed..4cedca7541a 100644 --- a/header/src/main/java/org/zstack/header/volume/VolumeStats.java +++ b/header/src/main/java/org/zstack/header/volume/VolumeStats.java @@ -1,10 +1,9 @@ package org.zstack.header.volume; -public class VolumeStats { - protected String installPath; +import org.zstack.header.storage.primary.StorageResourceStats; + +public class VolumeStats extends StorageResourceStats { protected String format; - protected Long actualSize; - protected Long size; /** * The parent uri of the volume, vendor://pool/path@snapshot or snapshot://uuid */ @@ -29,30 +28,6 @@ public VolumeStats(String installPath, Long actualSize, Long size) { public VolumeStats() { } - public String getInstallPath() { - return installPath; - } - - public void setInstallPath(String installPath) { - this.installPath = installPath; - } - - public Long getActualSize() { - return actualSize; - } - - public void setActualSize(Long actualSize) { - this.actualSize = actualSize; - } - - public Long getSize() { - return size; - } - - public void setSize(long size) { - this.size = size; - } - public void setFormat(String format) { this.format = format; } 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 53816200465..ce1ce7d12fa 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 @@ -3325,11 +3325,16 @@ protected void handle(AskVolumeSnapshotCapabilityMsg msg) { AskVolumeSnapshotCapabilityReply reply = new AskVolumeSnapshotCapabilityReply(); VolumeSnapshotCapability cap = new VolumeSnapshotCapability(); - String volumeType = msg.getVolume().getType(); + String volumeType = msg.getVolumeType(); + if (volumeType == null) { + volumeType = msg.getVolume().getType(); + } + if (VolumeType.Data.toString().equals(volumeType) || VolumeType.Root.toString().equals(volumeType)) { cap.setSupport(true); cap.setArrangementType(VolumeSnapshotArrangementType.INDIVIDUAL); cap.setPlacementType(VolumeSnapshotCapability.VolumeSnapshotPlacementType.INTERNAL); + cap.setMode(VolumeSnapshotCapability.VolumeSnapshotMode.REDIRECT_ON_WRITE); cap.setVolumePathFromInternalSnapshotRegex("^[^@]+"); } else if (VolumeType.Memory.toString().equals(volumeType)) { cap.setSupport(false); @@ -3397,10 +3402,10 @@ public void fail(ErrorCode errorCode) { } @Override - protected void handle(BatchSyncVolumeSizeOnPrimaryStorageMsg msg) { + protected void handle(BatchSyncVolumeResourceSizeOnPrimaryStorageMsg msg) { inQueue().name(String.format("batch-sync-volume-size-on-primarystorage-%s", self.getUuid())) .asyncBackup(msg) - .run(chain -> BatchSyncVolumeSizeOnPrimaryStorage(msg, new NoErrorCompletion(chain) { + .run(chain -> BatchSyncVolumeResourceSizeOnPrimaryStorage(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); @@ -3419,8 +3424,8 @@ protected void syncVolumeSize(String volumeUuid, String installPath, final Retur httpCall(GET_VOLUME_SIZE_PATH, cmd, GetVolumeSizeRsp.class, completion); } - private void BatchSyncVolumeSizeOnPrimaryStorage(BatchSyncVolumeSizeOnPrimaryStorageMsg msg, NoErrorCompletion completion) { - BatchSyncVolumeSizeOnPrimaryStorageReply reply = new BatchSyncVolumeSizeOnPrimaryStorageReply(); + private void BatchSyncVolumeResourceSizeOnPrimaryStorage(BatchSyncVolumeResourceSizeOnPrimaryStorageMsg msg, NoErrorCompletion completion) { + BatchSyncVolumeResourceSizeOnPrimaryStorageReply reply = new BatchSyncVolumeResourceSizeOnPrimaryStorageReply(); GetBatchVolumeSizeCmd cmd = new GetBatchVolumeSizeCmd(); cmd.volumeUuidInstallPaths = msg.getVolumeUuidInstallPaths(); @@ -3436,7 +3441,7 @@ public void success(GetBatchVolumeSizeRsp rsp) { markVolumeActualSize(volumeUuid, actualSize); } - reply.setActualSizes(rsp.actualSizes); + reply.setVolumeActualSizes(rsp.actualSizes); bus.reply(msg, reply); completion.done(); } diff --git a/plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java b/plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java index ae0ca47de4c..e36a29036d6 100644 --- a/plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java +++ b/plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java @@ -36,6 +36,7 @@ import org.zstack.header.storage.addon.*; import org.zstack.header.storage.addon.primary.*; import org.zstack.header.storage.primary.ImageCacheInventory; +import org.zstack.header.storage.primary.StorageResourceStats; import org.zstack.header.storage.primary.VolumeSnapshotCapability; import org.zstack.header.storage.snapshot.VolumeSnapshotStats; import org.zstack.header.volume.*; @@ -93,6 +94,7 @@ public class ExponStorageController implements PrimaryStorageControllerSvc, Prim scap.setSupport(true); scap.setArrangementType(VolumeSnapshotCapability.VolumeSnapshotArrangementType.INDIVIDUAL); scap.setPlacementType(VolumeSnapshotCapability.VolumeSnapshotPlacementType.INTERNAL); + scap.setMode(VolumeSnapshotCapability.VolumeSnapshotMode.REDIRECT_ON_WRITE); scap.setSupportCreateOnHypervisor(false); scap.setSupportLazyDelete(true); scap.setVolumePathFromInternalSnapshotRegex("^[^@]+"); @@ -1158,15 +1160,15 @@ public void stats(String installPath, ReturnValueCompletion comp) { } @Override - public void batchStats(Collection installPath, ReturnValueCompletion> comp) { - List stats = installPath.stream().map(it -> { + public void batchStats(Collection installPaths, ReturnValueCompletion> comp) { + List stats = installPaths.stream().map(it -> { VolumeModule vol = apiHelper.getVolume(getVolIdFromPath(it)); VolumeStats s = new VolumeStats(); s.setInstallPath(it); s.setSize(vol.getVolumeSize()); s.setActualSize(vol.getDataSize()); s.setFormat(VolumeConstant.VOLUME_FORMAT_RAW); - return s; + return (StorageResourceStats) s; }).collect(Collectors.toList()); comp.success(stats); } diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java index 0f57a471769..10d6cdbebc9 100755 --- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java @@ -2677,10 +2677,15 @@ protected void handle(AskVolumeSnapshotCapabilityMsg msg) { VolumeSnapshotCapability capability = new VolumeSnapshotCapability(); capability.setSupport(true); - String volumeType = msg.getVolume().getType(); + String volumeType = msg.getVolumeType(); + if (volumeType == null) { + volumeType = msg.getVolume().getType(); + } + if (VolumeType.Data.toString().equals(volumeType) || VolumeType.Root.toString().equals(volumeType)) { capability.setArrangementType(VolumeSnapshotArrangementType.CHAIN); capability.setPlacementType(VolumeSnapshotCapability.VolumeSnapshotPlacementType.EXTERNAL); + capability.setMode(VolumeSnapshotCapability.VolumeSnapshotMode.REDIRECT_ON_WRITE); } else if (VolumeType.Memory.toString().equals(volumeType)) { capability.setArrangementType(VolumeSnapshotArrangementType.INDIVIDUAL); } else { @@ -2733,17 +2738,17 @@ public void fail(ErrorCode errorCode) { } @Override - protected void handle(BatchSyncVolumeSizeOnPrimaryStorageMsg msg) { + protected void handle(BatchSyncVolumeResourceSizeOnPrimaryStorageMsg msg) { LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(msg.getHostUuid()); LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self); - bkd.handle(msg, msg.getHostUuid(), new ReturnValueCompletion(msg) { + bkd.handle(msg, msg.getHostUuid(), new ReturnValueCompletion(msg) { @Override - public void success(BatchSyncVolumeSizeOnPrimaryStorageReply returnValue) { + public void success(BatchSyncVolumeResourceSizeOnPrimaryStorageReply returnValue) { bus.reply(msg, returnValue); } @Override public void fail(ErrorCode errorCode) { - BatchSyncVolumeSizeOnPrimaryStorageReply reply = new BatchSyncVolumeSizeOnPrimaryStorageReply(); + BatchSyncVolumeResourceSizeOnPrimaryStorageReply reply = new BatchSyncVolumeResourceSizeOnPrimaryStorageReply(); reply.setError(errorCode); bus.reply(msg, reply); } diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java index 7760e28de93..b502409aac2 100755 --- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java @@ -72,7 +72,7 @@ public LocalStorageHypervisorBackend(PrimaryStorageVO self) { abstract void handle(EstimateVolumeTemplateSizeOnPrimaryStorageMsg msg, String huuid, ReturnValueCompletion completion); - abstract void handle(BatchSyncVolumeSizeOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion); + abstract void handle(BatchSyncVolumeResourceSizeOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion); abstract void handle(CreateImageCacheFromVolumeOnPrimaryStorageMsg msg, ReturnValueCompletion completion); diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java index ec0b51711ba..14ad0510e56 100755 --- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java @@ -2495,15 +2495,15 @@ public void fail(ErrorCode errorCode) { } @Override - void handle(BatchSyncVolumeSizeOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { - final BatchSyncVolumeSizeOnPrimaryStorageReply reply = new BatchSyncVolumeSizeOnPrimaryStorageReply(); + void handle(BatchSyncVolumeResourceSizeOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + final BatchSyncVolumeResourceSizeOnPrimaryStorageReply reply = new BatchSyncVolumeResourceSizeOnPrimaryStorageReply(); GetBatchVolumeSizeCmd cmd = new GetBatchVolumeSizeCmd(); cmd.volumeUuidInstallPaths = msg.getVolumeUuidInstallPaths(); httpCall(BATCH_GET_VOLUME_SIZE, msg.getHostUuid(), cmd, GetBatchVolumeSizeRsp.class, new ReturnValueCompletion(completion) { @Override public void success(GetBatchVolumeSizeRsp rsp) { - reply.setActualSizes(rsp.actualSizes); + reply.setVolumeActualSizes(rsp.actualSizes); completion.success(reply); } diff --git a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java index 253d2c00d92..548fc9c9677 100755 --- a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java +++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java @@ -1339,21 +1339,21 @@ protected void handle(DeleteIsoFromPrimaryStorageMsg msg) { @Override protected void handle(AskVolumeSnapshotCapabilityMsg msg) { VolumeSnapshotCapability capability = new VolumeSnapshotCapability(); - HypervisorType hvType = VolumeFormat.getMasterHypervisorTypeByVolumeFormat(msg.getVolume().getFormat()); - if (hvType.toString().equals(KVMConstant.KVM_HYPERVISOR_TYPE)) { - String volumeType = msg.getVolume().getType(); - if (VolumeType.Data.toString().equals(volumeType) || VolumeType.Root.toString().equals(volumeType)) { - capability.setArrangementType(VolumeSnapshotArrangementType.CHAIN); - capability.setPlacementType(VolumeSnapshotCapability.VolumeSnapshotPlacementType.EXTERNAL); - } else if (VolumeType.Memory.toString().equals(volumeType)) { - capability.setArrangementType(VolumeSnapshotArrangementType.INDIVIDUAL); - } else { - throw new CloudRuntimeException(String.format("unknown volume type %s", volumeType)); - } - capability.setSupport(true); + String volumeType = msg.getVolumeType(); + if (volumeType == null) { + volumeType = msg.getVolume().getType(); + } + + if (VolumeType.Data.toString().equals(volumeType) || VolumeType.Root.toString().equals(volumeType)) { + capability.setArrangementType(VolumeSnapshotArrangementType.CHAIN); + capability.setPlacementType(VolumeSnapshotCapability.VolumeSnapshotPlacementType.EXTERNAL); + capability.setMode(VolumeSnapshotCapability.VolumeSnapshotMode.REDIRECT_ON_WRITE); + } else if (VolumeType.Memory.toString().equals(volumeType)) { + capability.setArrangementType(VolumeSnapshotArrangementType.INDIVIDUAL); } else { - capability.setSupport(false); + throw new CloudRuntimeException(String.format("unknown volume type %s", volumeType)); } + capability.setSupport(true); AskVolumeSnapshotCapabilityReply reply = new AskVolumeSnapshotCapabilityReply(); reply.setCapability(capability); @@ -1409,16 +1409,16 @@ public void fail(ErrorCode errorCode) { } @Override - protected void handle(BatchSyncVolumeSizeOnPrimaryStorageMsg msg) { + protected void handle(BatchSyncVolumeResourceSizeOnPrimaryStorageMsg msg) { NfsPrimaryStorageBackend backend = getBackendByHostUuid(msg.getHostUuid()); - backend.handle(getSelfInventory(), msg, new ReturnValueCompletion(msg) { + backend.handle(getSelfInventory(), msg, new ReturnValueCompletion(msg) { @Override - public void success(BatchSyncVolumeSizeOnPrimaryStorageReply reply) { + public void success(BatchSyncVolumeResourceSizeOnPrimaryStorageReply reply) { bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { - BatchSyncVolumeSizeOnPrimaryStorageReply reply = new BatchSyncVolumeSizeOnPrimaryStorageReply(); + BatchSyncVolumeResourceSizeOnPrimaryStorageReply reply = new BatchSyncVolumeResourceSizeOnPrimaryStorageReply(); reply.setError(errorCode); bus.reply(msg, reply); } diff --git a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java index 6c26e682ce6..5d9aad05c97 100755 --- a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java +++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java @@ -8,8 +8,8 @@ import org.zstack.header.storage.primary.*; import org.zstack.header.storage.snapshot.VolumeSnapshotInventory; import org.zstack.header.volume.VolumeStats; -import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageMsg; -import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageReply; +import org.zstack.header.volume.BatchSyncVolumeResourceSizeOnPrimaryStorageMsg; +import org.zstack.header.volume.BatchSyncVolumeResourceSizeOnPrimaryStorageReply; import org.zstack.header.volume.VolumeInventory; import org.zstack.storage.primary.EstimateVolumeTemplateSizeOnPrimaryStorageMsg; import org.zstack.storage.primary.EstimateVolumeTemplateSizeOnPrimaryStorageReply; @@ -30,7 +30,7 @@ public interface NfsPrimaryStorageBackend { void handle(PrimaryStorageInventory inv, EstimateVolumeTemplateSizeOnPrimaryStorageMsg msg, ReturnValueCompletion completion); - void handle(PrimaryStorageInventory inv, BatchSyncVolumeSizeOnPrimaryStorageMsg msg, ReturnValueCompletion completion); + void handle(PrimaryStorageInventory inv, BatchSyncVolumeResourceSizeOnPrimaryStorageMsg msg, ReturnValueCompletion completion); void handle(PrimaryStorageInventory inv, GetVolumeRootImageUuidFromPrimaryStorageMsg msg, ReturnValueCompletion completion); diff --git a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java index a1341ef4449..8a56b48c9d8 100755 --- a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java +++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java @@ -781,8 +781,8 @@ public void fail(ErrorCode errorCode) { } @Override - public void handle(PrimaryStorageInventory inv, BatchSyncVolumeSizeOnPrimaryStorageMsg msg, ReturnValueCompletion completion) { - BatchSyncVolumeSizeOnPrimaryStorageReply reply = new BatchSyncVolumeSizeOnPrimaryStorageReply(); + public void handle(PrimaryStorageInventory inv, BatchSyncVolumeResourceSizeOnPrimaryStorageMsg msg, ReturnValueCompletion completion) { + BatchSyncVolumeResourceSizeOnPrimaryStorageReply reply = new BatchSyncVolumeResourceSizeOnPrimaryStorageReply(); GetBatchVolumeActualSizeCmd cmd = new GetBatchVolumeActualSizeCmd(); cmd.volumeUuidInstallPaths = msg.getVolumeUuidInstallPaths(); @@ -791,7 +791,7 @@ public void handle(PrimaryStorageInventory inv, BatchSyncVolumeSizeOnPrimaryStor asyncHttpCall(BATCH_GET_VOLUME_SIZE_PATH, msg.getHostUuid(), cmd, GetBatchVolumeActualSizeRsp.class, inv, new ReturnValueCompletion(completion) { @Override public void success(GetBatchVolumeActualSizeRsp rsp) { - reply.setActualSizes(rsp.actualSizes); + reply.setVolumeActualSizes(rsp.actualSizes); completion.success(reply); } diff --git a/plugin/sharedMountPointPrimaryStorage/src/main/java/org/zstack/storage/primary/smp/SMPPrimaryStorageBase.java b/plugin/sharedMountPointPrimaryStorage/src/main/java/org/zstack/storage/primary/smp/SMPPrimaryStorageBase.java index 6f16e83eb3c..52296856d21 100755 --- a/plugin/sharedMountPointPrimaryStorage/src/main/java/org/zstack/storage/primary/smp/SMPPrimaryStorageBase.java +++ b/plugin/sharedMountPointPrimaryStorage/src/main/java/org/zstack/storage/primary/smp/SMPPrimaryStorageBase.java @@ -395,10 +395,15 @@ protected void handle(AskVolumeSnapshotCapabilityMsg msg) { VolumeSnapshotCapability capability = new VolumeSnapshotCapability(); capability.setSupport(true); - String volumeType = msg.getVolume().getType(); + String volumeType = msg.getVolumeType(); + if (volumeType == null) { + volumeType = msg.getVolume().getType(); + } + if (VolumeType.Data.toString().equals(volumeType) || VolumeType.Root.toString().equals(volumeType)) { capability.setArrangementType(VolumeSnapshotArrangementType.CHAIN); capability.setPlacementType(VolumeSnapshotCapability.VolumeSnapshotPlacementType.EXTERNAL); + capability.setMode(VolumeSnapshotCapability.VolumeSnapshotMode.REDIRECT_ON_WRITE); } else if (VolumeType.Memory.toString().equals(volumeType)) { capability.setArrangementType(VolumeSnapshotArrangementType.INDIVIDUAL); } else { @@ -461,8 +466,8 @@ public void fail(ErrorCode errorCode) { } @Override - protected void handle(BatchSyncVolumeSizeOnPrimaryStorageMsg msg) { - BatchSyncVolumeSizeOnPrimaryStorageReply reply = new BatchSyncVolumeSizeOnPrimaryStorageReply(); + protected void handle(BatchSyncVolumeResourceSizeOnPrimaryStorageMsg msg) { + BatchSyncVolumeResourceSizeOnPrimaryStorageReply reply = new BatchSyncVolumeResourceSizeOnPrimaryStorageReply(); bus.reply(msg, reply); logger.warn("Not supported at current edition"); } diff --git a/plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageController.java b/plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageController.java index 187ee73ff22..4265a7aaf7a 100644 --- a/plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageController.java +++ b/plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageController.java @@ -16,6 +16,7 @@ import org.zstack.header.host.HostVO_; import org.zstack.header.storage.addon.*; import org.zstack.header.storage.addon.primary.*; +import org.zstack.header.storage.primary.StorageResourceStats; import org.zstack.header.storage.primary.VolumeSnapshotCapability; import org.zstack.header.storage.snapshot.VolumeSnapshotStats; import org.zstack.header.volume.*; @@ -93,6 +94,7 @@ private String getVhostSocketDir() { scap.setSupport(true); scap.setArrangementType(VolumeSnapshotCapability.VolumeSnapshotArrangementType.INDIVIDUAL); scap.setPlacementType(VolumeSnapshotCapability.VolumeSnapshotPlacementType.INTERNAL); + scap.setMode(VolumeSnapshotCapability.VolumeSnapshotMode.REDIRECT_ON_WRITE); scap.setSupportCreateOnHypervisor(false); scap.setSupportLazyDelete(false); scap.setVolumePathFromInternalSnapshotRegex("^[^@]+"); @@ -872,15 +874,15 @@ private String getParentUri(VolumeModule vol) { } @Override - public void batchStats(Collection installPath, ReturnValueCompletion> comp) { - List stats = installPath.stream().map(it -> { + public void batchStats(Collection installPaths, ReturnValueCompletion> comp) { + List stats = installPaths.stream().map(it -> { VolumeModule vol = apiHelper.getVolume(getVolIdFromPath(it)); VolumeStats s = new VolumeStats(); s.setInstallPath(it); s.setSize(SizeUnit.MEGABYTE.toByte(vol.getSpec().getSizeMb())); s.setActualSize(vol.getStatus().getAllocatedSizeByte()); s.setFormat(VolumeConstant.VOLUME_FORMAT_RAW); - return s; + return (StorageResourceStats) s; }).collect(Collectors.toList()); comp.success(stats); } diff --git a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java index 054dfd572a0..0f673508b01 100644 --- a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java +++ b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java @@ -104,6 +104,7 @@ public class ZbsStorageController implements PrimaryStorageControllerSvc, Primar public static final String CLONE_VOLUME_PATH = "/zbs/primarystorage/volume/clone"; public static final String QUERY_VOLUME_PATH = "/zbs/primarystorage/volume/query"; public static final String BATCH_QUERY_VOLUME_PATH = "/zbs/primarystorage/volume/query/batch"; + public static final String BATCH_QUERY_VOLUME_WITH_SNAPSHOT_PATH = "/zbs/primarystorage/volume/query/batch/withsnapshot"; public static final String EXPAND_VOLUME_PATH = "/zbs/primarystorage/volume/expand"; public static final String FLATTEN_VOLUME_PATH = "/zbs/primarystorage/volume/flatten"; public static final String CBD_TO_NBD_PATH = "/zbs/primarystorage/volume/cbdtonbd"; @@ -129,6 +130,7 @@ public class ZbsStorageController implements PrimaryStorageControllerSvc, Primar scap.setSupport(true); scap.setArrangementType(VolumeSnapshotCapability.VolumeSnapshotArrangementType.INDIVIDUAL); scap.setPlacementType(VolumeSnapshotCapability.VolumeSnapshotPlacementType.INTERNAL); + scap.setMode(VolumeSnapshotCapability.VolumeSnapshotMode.COPY_ON_WRITE); scap.setSupportCreateOnHypervisor(false); scap.setSupportLazyDelete(false); scap.setVolumePathFromInternalSnapshotRegex("^[^@]+"); @@ -1246,13 +1248,7 @@ public void stats(String installPath, ReturnValueCompletion comp) { httpCall(QUERY_VOLUME_PATH, cmd, QueryVolumeRsp.class, new ReturnValueCompletion(comp) { @Override public void success(QueryVolumeRsp returnValue) { - VolumeStats stats = new VolumeStats(); - stats.setInstallPath(installPath); - stats.setSize(returnValue.getSize()); - stats.setActualSize(returnValue.getActualSize()); - stats.setFormat(VolumeConstant.VOLUME_FORMAT_RAW); - stats.setParentUri(ZbsHelper.normalizeToZbsPath(returnValue.getParentUri())); - comp.success(stats); + comp.success(toVolumeStats(installPath, returnValue)); } @Override @@ -1263,23 +1259,31 @@ public void fail(ErrorCode errorCode) { } @Override - public void batchStats(Collection installPaths, ReturnValueCompletion> comp) { + public void batchStats(Collection installPaths, ReturnValueCompletion> comp) { BatchQueryVolumeCmd cmd = new BatchQueryVolumeCmd(); + Collection paths = installPaths == null ? Collections.emptyList() : installPaths; + List cbdInstallPaths = paths.stream() + .map(it -> convertZbsPathToCbdPath(it, this::getPhysicalPoolName)) + .collect(Collectors.toList()); + boolean withSnapshot = cbdInstallPaths.stream().anyMatch(it -> it.contains("@")); - cmd.setInstallPaths(installPaths.stream().map(it -> convertZbsPathToCbdPath(it, this::getPhysicalPoolName)) - .collect(Collectors.toList())); + cmd.setInstallPaths(cbdInstallPaths); - httpCall(BATCH_QUERY_VOLUME_PATH, cmd, BatchQueryVolumeRsp.class, new ReturnValueCompletion(comp) { + String path = withSnapshot ? BATCH_QUERY_VOLUME_WITH_SNAPSHOT_PATH : BATCH_QUERY_VOLUME_PATH; + httpCall(path, cmd, BatchQueryVolumeRsp.class, new ReturnValueCompletion(comp) { @Override public void success(BatchQueryVolumeRsp returnValue) { - List stats = returnValue.getVolumes().entrySet().stream().map(v -> { + List stats = returnValue.getVolumes().entrySet().stream().map(v -> { VolumeStats s = new VolumeStats(); s.setInstallPath(ZbsHelper.normalizeToZbsPath(v.getKey())); s.setSize(v.getValue().get("length")); s.setActualSize(v.getValue().get("usedSize")); s.setFormat(VolumeConstant.VOLUME_FORMAT_RAW); - return s; + return (StorageResourceStats) s; }).collect(Collectors.toList()); + if (withSnapshot) { + stats.addAll(toVolumeSnapshotStats(returnValue.getSnapshots())); + } comp.success(stats); } @@ -1290,6 +1294,29 @@ public void fail(ErrorCode errorCode) { }); } + private VolumeStats toVolumeStats(String installPath, QueryVolumeRsp rsp) { + VolumeStats stats = new VolumeStats(); + stats.setInstallPath(installPath); + stats.setSize(rsp.getSize()); + stats.setActualSize(rsp.getActualSize()); + stats.setFormat(VolumeConstant.VOLUME_FORMAT_RAW); + stats.setParentUri(ZbsHelper.normalizeToZbsPath(rsp.getParentUri())); + return stats; + } + + private List toVolumeSnapshotStats(Map> snapshots) { + if (snapshots == null || snapshots.isEmpty()) { + return Collections.emptyList(); + } + + return snapshots.entrySet().stream().map(e -> { + VolumeSnapshotStats stats = new VolumeSnapshotStats(); + stats.setInstallPath(ZbsHelper.normalizeToZbsPath(e.getKey())); + stats.setActualSize(e.getValue().get("usedSize")); + return stats; + }).collect(Collectors.toList()); + } + @Override public void expandVolume(String installPath, long size, ReturnValueCompletion comp) { ExpandVolumeCmd cmd = new ExpandVolumeCmd(); @@ -1830,6 +1857,7 @@ public String getParentUri() { public void setParentUri(String parentUri) { this.parentUri = parentUri; } + } public static class FlattenVolumeRsp extends QueryVolumeRsp { @@ -1838,6 +1866,7 @@ public static class FlattenVolumeRsp extends QueryVolumeRsp { public static class BatchQueryVolumeRsp extends AgentResponse { private Map> volumes; + private Map> snapshots; public Map> getVolumes() { return volumes; @@ -1846,6 +1875,14 @@ public Map> getVolumes() { public void setVolumes(Map> volumes) { this.volumes = volumes; } + + public Map> getSnapshots() { + return snapshots; + } + + public void setSnapshots(Map> snapshots) { + this.snapshots = snapshots; + } } public static class CbdToNbdRsp extends AgentResponse { diff --git a/simulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/SimulatorPrimaryStorage.java b/simulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/SimulatorPrimaryStorage.java index 021eb33ebc6..7e41daf4298 100755 --- a/simulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/SimulatorPrimaryStorage.java +++ b/simulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/SimulatorPrimaryStorage.java @@ -9,8 +9,8 @@ import org.zstack.header.storage.primary.*; import org.zstack.header.storage.primary.VolumeSnapshotCapability.VolumeSnapshotArrangementType; import org.zstack.header.storage.snapshot.ShrinkVolumeSnapshotOnPrimaryStorageMsg; -import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageMsg; -import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageReply; +import org.zstack.header.volume.BatchSyncVolumeResourceSizeOnPrimaryStorageMsg; +import org.zstack.header.volume.BatchSyncVolumeResourceSizeOnPrimaryStorageReply; import org.zstack.header.volume.VolumeInventory; import org.zstack.storage.primary.EstimateVolumeTemplateSizeOnPrimaryStorageMsg; import org.zstack.storage.primary.PrimaryStorageBase; @@ -159,6 +159,7 @@ protected void handle(AskVolumeSnapshotCapabilityMsg msg) { VolumeSnapshotCapability capability = new VolumeSnapshotCapability(); capability.setArrangementType(VolumeSnapshotArrangementType.CHAIN); capability.setPlacementType(VolumeSnapshotCapability.VolumeSnapshotPlacementType.EXTERNAL); + capability.setMode(VolumeSnapshotCapability.VolumeSnapshotMode.REDIRECT_ON_WRITE); capability.setSupport(true); reply.setCapability(capability); bus.reply(msg, reply); @@ -179,11 +180,11 @@ protected void handle(EstimateVolumeTemplateSizeOnPrimaryStorageMsg msg) { } @Override - protected void handle(BatchSyncVolumeSizeOnPrimaryStorageMsg msg) { - BatchSyncVolumeSizeOnPrimaryStorageReply reply = new BatchSyncVolumeSizeOnPrimaryStorageReply(); + protected void handle(BatchSyncVolumeResourceSizeOnPrimaryStorageMsg msg) { + BatchSyncVolumeResourceSizeOnPrimaryStorageReply reply = new BatchSyncVolumeResourceSizeOnPrimaryStorageReply(); Map actualSize = msg.getVolumeUuidInstallPaths() .entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> 0L)); - reply.setActualSizes(actualSize); + reply.setVolumeActualSizes(actualSize); bus.reply(msg, reply); } diff --git a/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorage.java b/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorage.java index a73d593ecc3..150614a6094 100644 --- a/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorage.java +++ b/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorage.java @@ -1908,19 +1908,43 @@ public void fail(ErrorCode errorCode) { } @Override - protected void handle(BatchSyncVolumeSizeOnPrimaryStorageMsg msg) { - BatchSyncVolumeSizeOnPrimaryStorageReply reply = new BatchSyncVolumeSizeOnPrimaryStorageReply(); + protected void handle(BatchSyncVolumeResourceSizeOnPrimaryStorageMsg msg) { + BatchSyncVolumeResourceSizeOnPrimaryStorageReply reply = new BatchSyncVolumeResourceSizeOnPrimaryStorageReply(); Map installPathToUuids = msg.getVolumeUuidInstallPaths().entrySet().stream().collect(Collectors.toMap( Map.Entry::getValue, Map.Entry::getKey, (k1, k2) -> k1 )); - controller.batchStats(msg.getVolumeUuidInstallPaths().values(), new ReturnValueCompletion>(msg) { - @Override - public void success(List stats) { - Map actualSizeByUuids = stats.stream().collect(Collectors.toMap( - s -> installPathToUuids.get(s.getInstallPath()), VolumeStats::getActualSize, (k1, k2) -> k1 + Map snapshotInstallPathToUuids = !msg.isWithSnapshot() || msg.getSnapshotUuidInstallPaths() == null ? Collections.emptyMap() : + msg.getSnapshotUuidInstallPaths().entrySet().stream().collect(Collectors.toMap( + Map.Entry::getValue, Map.Entry::getKey, (k1, k2) -> k1 )); - reply.setActualSizes(actualSizeByUuids); + Collection installPaths = new ArrayList<>(msg.getVolumeUuidInstallPaths().values()); + if (msg.isWithSnapshot() && msg.getSnapshotUuidInstallPaths() != null) { + installPaths.addAll(msg.getSnapshotUuidInstallPaths().values()); + } + controller.batchStats(installPaths, new ReturnValueCompletion>(msg) { + @Override + public void success(List stats) { + Map actualSizeByUuids = stats.stream() + .filter(it -> it instanceof VolumeStats) + .filter(it -> it.getActualSize() != null) + .filter(it -> installPathToUuids.containsKey(it.getInstallPath())) + .collect(Collectors.toMap( + s -> installPathToUuids.get(s.getInstallPath()), + StorageResourceStats::getActualSize, + (k1, k2) -> k1 + )); + reply.setVolumeActualSizes(actualSizeByUuids); + reply.setSnapshotActualSizes(snapshotInstallPathToUuids.isEmpty() ? Collections.emptyMap() : + stats.stream() + .filter(it -> it instanceof VolumeSnapshotStats) + .filter(it -> it.getActualSize() != null) + .filter(it -> snapshotInstallPathToUuids.containsKey(it.getInstallPath())) + .collect(Collectors.toMap( + it -> snapshotInstallPathToUuids.get(it.getInstallPath()), + StorageResourceStats::getActualSize, + (k1, k2) -> k1 + ))); bus.reply(msg, reply); } diff --git a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java index 9920d4900ee..bcf546fa2bf 100755 --- a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java +++ b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java @@ -150,7 +150,7 @@ public void setNewAdded(boolean newAdded) { protected abstract void handle(EstimateVolumeTemplateSizeOnPrimaryStorageMsg msg); - protected abstract void handle(BatchSyncVolumeSizeOnPrimaryStorageMsg msg); + protected abstract void handle(BatchSyncVolumeResourceSizeOnPrimaryStorageMsg msg); protected abstract void handle(MergeVolumeSnapshotOnPrimaryStorageMsg msg); @@ -367,8 +367,8 @@ protected void handleLocalMessage(Message msg) { handle((SyncVolumeSizeOnPrimaryStorageMsg) msg); } else if (msg instanceof EstimateVolumeTemplateSizeOnPrimaryStorageMsg) { handle((EstimateVolumeTemplateSizeOnPrimaryStorageMsg) msg); - } else if (msg instanceof BatchSyncVolumeSizeOnPrimaryStorageMsg) { - handle((BatchSyncVolumeSizeOnPrimaryStorageMsg) msg); + } else if (msg instanceof BatchSyncVolumeResourceSizeOnPrimaryStorageMsg) { + handle((BatchSyncVolumeResourceSizeOnPrimaryStorageMsg) msg); } else if (msg instanceof PingPrimaryStorageMsg) { handle((PingPrimaryStorageMsg) msg); } else if (msg instanceof ChangePrimaryStorageStatusMsg) { diff --git a/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java b/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java index 4ee921dd65a..a7da4ff3722 100755 --- a/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java +++ b/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java @@ -62,6 +62,7 @@ import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -74,6 +75,19 @@ public class VolumeManagerImpl extends AbstractService implements VolumeManager, ResourceOwnerAfterChangeExtensionPoint, VmStateChangedExtensionPoint, VmDetachVolumeExtensionPoint, VmAttachVolumeExtensionPoint, HostAfterConnectedExtensionPoint { private static final CLogger logger = Utils.getLogger(VolumeManagerImpl.class); + private final Map snapshotSizeSyncRequiredCache = new ConcurrentHashMap<>(); + + private static class ActiveVolumeSizeSyncInfo { + private final Map volumeUuidInstallPaths = new HashMap<>(); + private final Set volumeTypes = new HashSet<>(); + + void add(String volumeUuid, String installPath, VolumeType volumeType) { + volumeUuidInstallPaths.put(volumeUuid, installPath); + if (VolumeType.Root == volumeType || VolumeType.Data == volumeType) { + volumeTypes.add(volumeType.toString()); + } + } + } @Autowired private CloudBus bus; @@ -768,69 +782,265 @@ private void handle(BatchSyncActiveVolumeSizeOnHostMsg msg) { bus.reply(msg, reply); return; } - Map> activeVolumesInPs = Q.New(VolumeVO.class) - .select(VolumeVO_.primaryStorageUuid, VolumeVO_.uuid, VolumeVO_.installPath) - .in(VolumeVO_.vmInstanceUuid, activeVmUuids).listTuple().stream() - .collect(Collectors.groupingBy(t -> t.get(0, String.class), Collectors.toMap( - t -> ((Tuple)t).get(1, String.class), t -> ((Tuple)t).get(2, String.class)))); - - new While<>(activeVolumesInPs.entrySet()).each((e, completion) -> { - BatchSyncVolumeSizeOnPrimaryStorageMsg bmsg = new BatchSyncVolumeSizeOnPrimaryStorageMsg(); - bmsg.setHostUuid(msg.getHostUuid()); - bmsg.setPrimaryStorageUuid(e.getKey()); - bmsg.setVolumeUuidInstallPaths(e.getValue()); - bus.makeTargetServiceIdByResourceUuid(bmsg, PrimaryStorageConstant.SERVICE_ID, e.getKey()); - bus.send(bmsg, new CloudBusCallBack(completion) { - @Override - public void run(MessageReply r) { - if (r.isSuccess()) { - BatchSyncVolumeSizeOnPrimaryStorageReply br = r.castReply(); - Map actualSizes = br.getActualSizes(); - reply.addSuccessCount(actualSizes.size()); - reply.addFailCount(e.getValue().size() - actualSizes.size()); + Map activeVolumesInPs = new HashMap<>(); + Q.New(VolumeVO.class) + .select(VolumeVO_.primaryStorageUuid, VolumeVO_.uuid, VolumeVO_.installPath, VolumeVO_.type) + .in(VolumeVO_.vmInstanceUuid, activeVmUuids) + .listTuple().forEach(t -> { + String primaryStorageUuid = t.get(0, String.class); + activeVolumesInPs.computeIfAbsent(primaryStorageUuid, k -> new ActiveVolumeSizeSyncInfo()) + .add(t.get(1, String.class), t.get(2, String.class), t.get(3, VolumeType.class)); + }); - refreshVolume(actualSizes); - } else { - reply.addFailCount(e.getValue().size()); + FlowChain chain = FlowChainBuilder.newShareFlowChain(); + chain.setName(String.format("batch-sync-active-volume-size-on-host-%s", msg.getHostUuid())); + chain.then(new ShareFlow() { + Map primaryStorageTypes; + + @Override + public void setup() { + flow(new NoRollbackFlow() { + String __name__ = "prepare-snapshot-size-sync-required-cache"; + + @Override + public void run(FlowTrigger trigger, Map data) { + primaryStorageTypes = getPrimaryStorageTypesForSnapshotCapabilityCache(activeVolumesInPs.keySet()); + prepareSnapshotSizeSyncRequiredCache(trigger); } + }); + + flow(new NoRollbackFlow() { + String __name__ = "batch-sync-active-volume-size"; - completion.done(); + @Override + public void run(FlowTrigger trigger, Map data) { + batchSyncActiveVolumeSize(trigger); + } + }); + } + + private void prepareSnapshotSizeSyncRequiredCache(FlowTrigger trigger) { + Set currentCacheKeys = new HashSet<>(); + List cacheMisses = new ArrayList<>(); + for (Map.Entry entry : activeVolumesInPs.entrySet()) { + String primaryStorageUuid = entry.getKey(); + ActiveVolumeSizeSyncInfo activeVolumeSizeSyncInfo = entry.getValue(); + String primaryStorageType = primaryStorageTypes.get(primaryStorageUuid); + if (primaryStorageType == null || activeVolumeSizeSyncInfo.volumeTypes.isEmpty()) { + continue; + } + + for (String volumeType : activeVolumeSizeSyncInfo.volumeTypes) { + String cacheKey = snapshotSizeSyncRequiredCacheKey(primaryStorageType, volumeType); + currentCacheKeys.add(cacheKey); + if (!snapshotSizeSyncRequiredCache.containsKey(cacheKey)) { + cacheMisses.add(new SnapshotSizeSyncCacheMiss(primaryStorageUuid, primaryStorageType, volumeType)); + } + } } - @Transactional(readOnly = true) - private Map calculateSnapshotSize(Collection volumeUuids) { - String sql = "select sp.uuid, sum(sp.size) from VolumeSnapshotVO sp where sp.volumeUuid in :uuids group by sp.uuid"; - TypedQuery q = dbf.getEntityManager().createQuery(sql, Tuple.class); - q.setParameter("uuids", volumeUuids); - List results = q.getResultList(); - return results.stream().collect(Collectors.toMap( - tuple -> tuple.get(0, String.class), tuple -> tuple.get(1, Long.class))); + snapshotSizeSyncRequiredCache.keySet().retainAll(currentCacheKeys); + if (cacheMisses.isEmpty()) { + trigger.next(); + return; } - @Transactional - private void refreshVolume(Map actualSizes) { - actualSizes.entrySet().removeIf(actualSize -> actualSize.getValue() == null); - Map uuidSnapshotSizes = calculateSnapshotSize(actualSizes.keySet()); - - for (Map.Entry entry : actualSizes.entrySet()) { - // the actual size = volume actual size + all snapshot size - long volActualSize = entry.getValue() + uuidSnapshotSizes.getOrDefault(entry.getKey(), 0L); - SQL.New(VolumeVO.class).eq(VolumeVO_.uuid, entry.getKey()) - .set(VolumeVO_.actualSize, volActualSize) - .update(); + new While<>(cacheMisses).each((cacheMiss, compl) -> { + AskVolumeSnapshotCapabilityMsg amsg = new AskVolumeSnapshotCapabilityMsg(); + amsg.setPrimaryStorageUuid(cacheMiss.primaryStorageUuid); + amsg.setVolumeType(cacheMiss.volumeType); + bus.makeTargetServiceIdByResourceUuid(amsg, PrimaryStorageConstant.SERVICE_ID, cacheMiss.primaryStorageUuid); + bus.send(amsg, new CloudBusCallBack(compl) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + compl.done(); + return; + } + + AskVolumeSnapshotCapabilityReply r = reply.castReply(); + snapshotSizeSyncRequiredCache.put(snapshotSizeSyncRequiredCacheKey(cacheMiss.primaryStorageType, cacheMiss.volumeType), + isCowSnapshotSupported(r.getCapability())); + compl.done(); + } + }); + }).run(new WhileDoneCompletion(trigger) { + @Override + public void done(ErrorCodeList errorCodeList) { + trigger.next(); + } + }); + } + + private void batchSyncActiveVolumeSize(FlowTrigger trigger) { + new While<>(activeVolumesInPs.entrySet()).each((e, compl) -> { + String primaryStorageUuid = e.getKey(); + ActiveVolumeSizeSyncInfo activeVolumeSizeSyncInfo = e.getValue(); + boolean syncSnapshotSize = isSnapshotSizeSyncRequired(primaryStorageTypes.get(primaryStorageUuid), + activeVolumeSizeSyncInfo.volumeTypes); + BatchSyncVolumeResourceSizeOnPrimaryStorageMsg bmsg = new BatchSyncVolumeResourceSizeOnPrimaryStorageMsg(); + bmsg.setHostUuid(msg.getHostUuid()); + bmsg.setPrimaryStorageUuid(primaryStorageUuid); + bmsg.setVolumeUuidInstallPaths(activeVolumeSizeSyncInfo.volumeUuidInstallPaths); + bmsg.setWithSnapshot(syncSnapshotSize); + if (syncSnapshotSize) { + bmsg.setSnapshotUuidInstallPaths(getSnapshotUuidInstallPaths(primaryStorageUuid, + activeVolumeSizeSyncInfo.volumeUuidInstallPaths.keySet())); } - } - }); - }).run(new WhileDoneCompletion(msg) { + bus.makeTargetServiceIdByResourceUuid(bmsg, PrimaryStorageConstant.SERVICE_ID, primaryStorageUuid); + bus.send(bmsg, new CloudBusCallBack(compl) { + @Override + public void run(MessageReply r) { + if (r.isSuccess()) { + BatchSyncVolumeResourceSizeOnPrimaryStorageReply br = r.castReply(); + Map volumeActualSizes = br.getVolumeActualSizes(); + + reply.addSuccessCount(volumeActualSizes.size()); + reply.addFailCount(activeVolumeSizeSyncInfo.volumeUuidInstallPaths.size() - volumeActualSizes.size()); + + if (syncSnapshotSize) { + updateSnapshotActualSizes(br.getSnapshotActualSizes()); + } + refreshVolume(volumeActualSizes); + } else { + reply.addFailCount(activeVolumeSizeSyncInfo.volumeUuidInstallPaths.size()); + } + compl.done(); + } + }); + }).run(new WhileDoneCompletion(trigger) { + @Override + public void done(ErrorCodeList errorCodeList) { + if (!errorCodeList.getCauses().isEmpty()) { + reply.setError(errorCodeList.getCauses().get(0)); + } + trigger.next(); + } + }); + } + + private boolean isSnapshotSizeSyncRequired(String primaryStorageType, Collection volumeTypes) { + return volumeTypes.stream() + .anyMatch(volumeType -> snapshotSizeSyncRequiredCache.getOrDefault(snapshotSizeSyncRequiredCacheKey(primaryStorageType, volumeType), false)); + } + }).done(new FlowDoneHandler(msg) { @Override - public void done(ErrorCodeList errorCodeList) { - if (!errorCodeList.getCauses().isEmpty()) { - reply.setError(errorCodeList.getCauses().get(0)); - } + public void handle(Map data) { bus.reply(msg, reply); } - }); + }).error(new FlowErrorHandler(msg) { + @Override + public void handle(ErrorCode errCode, Map data) { + reply.setError(errCode); + bus.reply(msg, reply); + } + }).start(); + } + + private static class SnapshotSizeSyncCacheMiss { + private final String primaryStorageUuid; + private final String primaryStorageType; + private final String volumeType; + + private SnapshotSizeSyncCacheMiss(String primaryStorageUuid, String primaryStorageType, String volumeType) { + this.primaryStorageUuid = primaryStorageUuid; + this.primaryStorageType = primaryStorageType; + this.volumeType = volumeType; + } + } + + @Transactional(readOnly = true) + private Map calculateSnapshotSize(Collection volumeUuids) { + String sql = "select sp.volumeUuid, sum(sp.size) from VolumeSnapshotVO sp where sp.volumeUuid in :uuids group by sp.volumeUuid"; + TypedQuery q = dbf.getEntityManager().createQuery(sql, Tuple.class); + q.setParameter("uuids", volumeUuids); + List results = q.getResultList(); + return results.stream().collect(Collectors.toMap( + tuple -> tuple.get(0, String.class), tuple -> tuple.get(1, Long.class))); + } + + @Transactional + private void refreshVolume(Map actualSizes) { + actualSizes.entrySet().removeIf(actualSize -> actualSize.getValue() == null); + Map uuidSnapshotSizes = calculateSnapshotSize(actualSizes.keySet()); + + for (Map.Entry entry : actualSizes.entrySet()) { + // the actual size = volume actual size + all snapshot size + long volActualSize = entry.getValue() + uuidSnapshotSizes.getOrDefault(entry.getKey(), 0L); + SQL.New(VolumeVO.class).eq(VolumeVO_.uuid, entry.getKey()) + .set(VolumeVO_.actualSize, volActualSize) + .update(); + } + } + + private Map getPrimaryStorageTypesForSnapshotCapabilityCache(Collection primaryStorageUuids) { + if (primaryStorageUuids == null || primaryStorageUuids.isEmpty()) { + return Collections.emptyMap(); + } + + return Q.New(PrimaryStorageVO.class) + .select(PrimaryStorageVO_.uuid, PrimaryStorageVO_.type) + .in(PrimaryStorageVO_.uuid, primaryStorageUuids) + .listTuple().stream() + .collect(Collectors.toMap( + t -> t.get(0, String.class), + t -> getPrimaryStorageTypeForSnapshotCapabilityCache(t.get(0, String.class), t.get(1, String.class)))); + } + + private String getPrimaryStorageTypeForSnapshotCapabilityCache(String primaryStorageUuid, String primaryStorageType) { + Optional factory = pluginRgty.getExtensionList(PrimaryStorageFactory.class).stream() + .filter(f -> f.getPrimaryStorageType().toString().equals(primaryStorageType)) + .findFirst(); + if (!factory.isPresent()) { + return primaryStorageType; + } + + String identity = factory.get().getInventory(primaryStorageUuid).getIdentity(); + return identity == null ? primaryStorageType : identity; + } + + private String snapshotSizeSyncRequiredCacheKey(String primaryStorageType, String volumeType) { + return String.format("%s-%s", primaryStorageType, volumeType); + } + + private boolean isCowSnapshotSupported(VolumeSnapshotCapability capability) { + return capability != null && capability.isSupport() && + capability.getMode() == VolumeSnapshotCapability.VolumeSnapshotMode.COPY_ON_WRITE; + } + + private Map getSnapshotUuidInstallPaths(String primaryStorageUuid, Collection volumeUuids) { + if (volumeUuids == null || volumeUuids.isEmpty()) { + return Collections.emptyMap(); + } + + String sql = "select sp.uuid, sp.primaryStorageInstallPath from VolumeSnapshotVO sp " + + "where sp.volumeUuid in :volumeUuids " + + "and sp.primaryStorageUuid = :primaryStorageUuid " + + "and sp.primaryStorageInstallPath is not null " + + "and sp.status = :status"; + TypedQuery q = dbf.getEntityManager().createQuery(sql, Tuple.class); + q.setParameter("volumeUuids", volumeUuids); + q.setParameter("primaryStorageUuid", primaryStorageUuid); + q.setParameter("status", VolumeSnapshotStatus.Ready); + return q.getResultList().stream().collect(Collectors.toMap( + t -> t.get(0, String.class), + t -> t.get(1, String.class), + (v1, v2) -> v1 + )); + } + + private void updateSnapshotActualSizes(Map snapshotActualSizes) { + if (snapshotActualSizes == null || snapshotActualSizes.isEmpty()) { + return; + } + + snapshotActualSizes.entrySet().stream() + .filter(e -> e.getValue() != null && e.getValue() > 0) + .forEach(e -> SQL.New(VolumeSnapshotVO.class) + .eq(VolumeSnapshotVO_.uuid, e.getKey()) + .set(VolumeSnapshotVO_.size, e.getValue()) + .update()); } private void handle(CreateDataVolumeFromVolumeSnapshotMsg msg) { @@ -1441,4 +1651,4 @@ public void run(MessageReply reply) { }); }); } -} \ No newline at end of file +} diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsPrimaryStorageCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsPrimaryStorageCase.groovy index 589e47918a0..e9bd8dbb370 100644 --- a/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsPrimaryStorageCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsPrimaryStorageCase.groovy @@ -12,8 +12,8 @@ import org.zstack.header.message.MessageReply import org.zstack.header.storage.primary.GetVolumeBackingChainFromPrimaryStorageMsg import org.zstack.header.storage.primary.GetVolumeBackingChainFromPrimaryStorageReply import org.zstack.header.storage.primary.PrimaryStorageConstant -import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageMsg -import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageReply +import org.zstack.header.volume.BatchSyncVolumeResourceSizeOnPrimaryStorageMsg +import org.zstack.header.volume.BatchSyncVolumeResourceSizeOnPrimaryStorageReply import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageSpaceVO import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO_ @@ -206,6 +206,7 @@ class ZbsPrimaryStorageCase extends SubCase { testMdsReconnectAfterMaximumPingFailures() testGetBackingChainNormalizesCbdParentUri() testBatchStatsNormalizesCbdInstallPath() + testBatchStatsReturnsSnapshotActualSize() } } @@ -1221,16 +1222,62 @@ class ZbsPrimaryStorageCase extends SubCase { } CloudBus bus = bean(CloudBus.class) - BatchSyncVolumeSizeOnPrimaryStorageMsg msg = new BatchSyncVolumeSizeOnPrimaryStorageMsg() + BatchSyncVolumeResourceSizeOnPrimaryStorageMsg msg = new BatchSyncVolumeResourceSizeOnPrimaryStorageMsg() msg.setPrimaryStorageUuid(ps.uuid) msg.setVolumeUuidInstallPaths([(volUuid): zbsPath]) bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, ps.uuid) MessageReply reply = bus.call(msg) assert reply.isSuccess() : "batch size sync must not fail on cbd-form install path: ${reply.error}" - BatchSyncVolumeSizeOnPrimaryStorageReply r = reply as BatchSyncVolumeSizeOnPrimaryStorageReply - assert r.actualSizes.get(volUuid) == usedSize : - "actualSize must map back to uuid via zbs:// install path, got ${r.actualSizes}" + BatchSyncVolumeResourceSizeOnPrimaryStorageReply r = reply as BatchSyncVolumeResourceSizeOnPrimaryStorageReply + assert r.volumeActualSizes.get(volUuid) == usedSize : + "actualSize must map back to uuid via zbs:// install path, got ${r.volumeActualSizes}" + + env.cleanSimulatorHandlers() + } + + void testBatchStatsReturnsSnapshotActualSize() { + String volUuid = "vol85707batchsnap" + String snapshotUuid = "snap85707batch" + String zbsPath = "zbs://lpool1/volume_batch_snapshot" + String snapshotPath = "${zbsPath}@snap1" + long usedSize = SizeUnit.MEGABYTE.toByte(7) + long snapshotUsedSize = SizeUnit.MEGABYTE.toByte(3) + + env.simulator(ZbsStorageController.BATCH_QUERY_VOLUME_WITH_SNAPSHOT_PATH) { HttpEntity e, EnvSpec spec -> + def cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.BatchQueryVolumeCmd.class) + assert cmd.installPaths.any { it.contains("@snap1") } + + def rsp = new ZbsStorageController.BatchQueryVolumeRsp() + Map> volumes = new HashMap<>() + Map> snapshots = new HashMap<>() + cmd.installPaths.each { cbd -> + if (cbd.contains("@")) { + snapshots.put(cbd, ["usedSize": snapshotUsedSize]) + } else { + volumes.put(cbd, ["length": SizeUnit.GIGABYTE.toByte(8), "usedSize": usedSize]) + } + } + rsp.setVolumes(volumes) + rsp.setSnapshots(snapshots) + return rsp + } + + CloudBus bus = bean(CloudBus.class) + BatchSyncVolumeResourceSizeOnPrimaryStorageMsg msg = new BatchSyncVolumeResourceSizeOnPrimaryStorageMsg() + msg.setPrimaryStorageUuid(ps.uuid) + msg.setVolumeUuidInstallPaths([(volUuid): zbsPath]) + msg.setWithSnapshot(true) + msg.setSnapshotUuidInstallPaths([(snapshotUuid): snapshotPath]) + bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, ps.uuid) + MessageReply reply = bus.call(msg) + + assert reply.isSuccess() : "batch size sync with snapshot must not fail: ${reply.error}" + BatchSyncVolumeResourceSizeOnPrimaryStorageReply r = reply as BatchSyncVolumeResourceSizeOnPrimaryStorageReply + assert r.volumeActualSizes.get(volUuid) == usedSize : + "volume actualSize must map back to uuid, got ${r.volumeActualSizes}" + assert r.snapshotActualSizes.get(snapshotUuid) == snapshotUsedSize : + "snapshot actualSize must map back to uuid, got ${r.snapshotActualSizes}" env.cleanSimulatorHandlers() }