From fec68a95d6b1ffdcd1311acd4170550ecfffa5c4 Mon Sep 17 00:00:00 2001 From: "xiangheng.zhao" Date: Thu, 2 Jul 2026 13:57:57 +0800 Subject: [PATCH] [zbs]: sync cow snapshot size in scheduled batch volume sync During the scheduled active-volume size sync, primary storages whose snapshot mode is COPY_ON_WRITE attach snapshot install paths to BatchSyncVolumeSizeOnPrimaryStorageMsg. The batch volume sync reply then carries snapshot actual sizes so VolumeSnapshotVO.size can be refreshed together with volume actual size. Use BatchStatsSpec and StorageResourceStats for external primary storage batchStats so one scheduled batch call can return both VolumeStats and VolumeSnapshotStats. REDIRECT_ON_WRITE snapshot storages keep volume-only batch sync. Single-volume stats and SyncVolumeSize remain volume-only flows. Use VolumeSnapshotSizeSyncHelper as a lightweight helper object with CloudBus and DatabaseFacade dependencies passed by constructor. Snapshot size sync is decided by reported snapshot mode instead of primary storage type. Route ZBS COPY_ON_WRITE withSnapshot batch stats to the volume-with-snapshot agent API. Test: git diff --check Test: mvn -pl storage,plugin/zbs,plugin/xinfini,plugin/expon -am -DskipTests compile Verified: - In cloud environment, zstack-cli BatchSyncVolumeSize clusterUuid= triggers the same batch volume size sync path as the scheduled task and updates ZBS COPY_ON_WRITE VolumeSnapshotVO.size from backend usedSize. Resolves: ZSTAC-67903 Change-Id: I66339da9d8cede3dd9fa4e5e262cc01a3784ba6e --- .../storage/addon/primary/BatchStatsSpec.java | 29 +++++ .../primary/PrimaryStorageControllerSvc.java | 3 +- .../storage/primary/StorageResourceStats.java | 31 +++++ .../primary/VolumeSnapshotCapability.java | 15 +++ .../storage/snapshot/VolumeSnapshotStats.java | 20 +--- ...atchSyncVolumeSizeOnPrimaryStorageMsg.java | 20 ++++ ...chSyncVolumeSizeOnPrimaryStorageReply.java | 10 ++ .../org/zstack/header/volume/VolumeStats.java | 31 +---- .../ceph/primary/CephPrimaryStorageBase.java | 1 + .../zstack/expon/ExponStorageController.java | 8 +- .../primary/local/LocalStorageBase.java | 1 + .../primary/nfs/NfsPrimaryStorage.java | 1 + .../primary/smp/SMPPrimaryStorageBase.java | 1 + .../xinfini/XInfiniStorageController.java | 8 +- .../storage/zbs/ZbsStorageController.java | 79 +++++++++++-- .../primary/SimulatorPrimaryStorage.java | 1 + .../addon/primary/ExternalPrimaryStorage.java | 35 +++++- .../VolumeSnapshotSizeSyncHelper.java | 85 ++++++++++++++ .../org/zstack/storage/volume/VolumeBase.java | 36 +++--- .../storage/volume/VolumeManagerImpl.java | 61 +++++++--- .../addon/zbs/ZbsPrimaryStorageCase.groovy | 108 +++++++++++++++++- 21 files changed, 479 insertions(+), 105 deletions(-) create mode 100644 header/src/main/java/org/zstack/header/storage/addon/primary/BatchStatsSpec.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/StorageResourceStats.java create mode 100644 storage/src/main/java/org/zstack/storage/snapshot/VolumeSnapshotSizeSyncHelper.java diff --git a/header/src/main/java/org/zstack/header/storage/addon/primary/BatchStatsSpec.java b/header/src/main/java/org/zstack/header/storage/addon/primary/BatchStatsSpec.java new file mode 100644 index 00000000000..2d8ad97ed73 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/addon/primary/BatchStatsSpec.java @@ -0,0 +1,29 @@ +package org.zstack.header.storage.addon.primary; + +import java.util.Collection; +import java.util.Collections; + +public class BatchStatsSpec { + private Collection installPaths = Collections.emptyList(); + private Collection snapshotInstallPaths = Collections.emptyList(); + + public Collection getInstallPaths() { + return installPaths; + } + + public void setInstallPaths(Collection installPaths) { + this.installPaths = installPaths == null ? Collections.emptyList() : installPaths; + } + + public Collection getSnapshotInstallPaths() { + return snapshotInstallPaths; + } + + public void setSnapshotInstallPaths(Collection snapshotInstallPaths) { + this.snapshotInstallPaths = snapshotInstallPaths == null ? Collections.emptyList() : snapshotInstallPaths; + } + + public boolean isWithSnapshot() { + return !snapshotInstallPaths.isEmpty(); + } +} 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..c61ca8d00d4 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(BatchStatsSpec spec, 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/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/BatchSyncVolumeSizeOnPrimaryStorageMsg.java index 4ab12a3b2a8..f6a7bf6fee2 100644 --- a/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeSizeOnPrimaryStorageMsg.java +++ b/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeSizeOnPrimaryStorageMsg.java @@ -12,6 +12,10 @@ public class BatchSyncVolumeSizeOnPrimaryStorageMsg extends NeedReplyMessage imp 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/BatchSyncVolumeSizeOnPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeSizeOnPrimaryStorageReply.java index ee543ba398a..d6e3047ffdd 100644 --- a/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeSizeOnPrimaryStorageReply.java +++ b/header/src/main/java/org/zstack/header/volume/BatchSyncVolumeSizeOnPrimaryStorageReply.java @@ -8,6 +8,8 @@ public class BatchSyncVolumeSizeOnPrimaryStorageReply extends MessageReply { private Map actualSizes = new HashMap<>(); + private Map snapshotActualSizes = new HashMap<>(); + public void setActualSizes(Map actualSizes) { this.actualSizes = actualSizes; } @@ -15,4 +17,12 @@ public void setActualSizes(Map actualSizes) { public Map getActualSizes() { return actualSizes; } + + public Map getSnapshotActualSizes() { + return snapshotActualSizes; + } + + public void setSnapshotActualSizes(Map snapshotActualSizes) { + this.snapshotActualSizes = snapshotActualSizes; + } } 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..0c29cfb904c 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 @@ -3330,6 +3330,7 @@ protected void handle(AskVolumeSnapshotCapabilityMsg msg) { 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); 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 2f24bc3feaf..c3991143df0 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(BatchStatsSpec spec, ReturnValueCompletion> comp) { + List stats = spec.getInstallPaths().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..ce63537aed2 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 @@ -2681,6 +2681,7 @@ protected void handle(AskVolumeSnapshotCapabilityMsg msg) { 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 { 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..588e559eacc 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 @@ -1345,6 +1345,7 @@ protected void handle(AskVolumeSnapshotCapabilityMsg msg) { 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 { 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..fb19d63f0f5 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 @@ -399,6 +399,7 @@ protected void handle(AskVolumeSnapshotCapabilityMsg msg) { 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 { 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 c9efd282c05..f739476177e 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(BatchStatsSpec spec, ReturnValueCompletion> comp) { + List stats = spec.getInstallPaths().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 27810c6bb45..c8bc8b349da 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 @@ -100,6 +100,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"; @@ -118,6 +119,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("^[^@]+"); @@ -1041,13 +1043,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 @@ -1058,23 +1054,30 @@ public void fail(ErrorCode errorCode) { } @Override - public void batchStats(Collection installPaths, ReturnValueCompletion> comp) { + public void batchStats(BatchStatsSpec spec, ReturnValueCompletion> comp) { BatchQueryVolumeCmd cmd = new BatchQueryVolumeCmd(); - cmd.setInstallPaths(installPaths.stream().map(it -> convertZbsPathToCbdPath(it, this::getPhysicalPoolName)) + cmd.setInstallPaths(spec.getInstallPaths().stream().map(it -> convertZbsPathToCbdPath(it, this::getPhysicalPoolName)) .collect(Collectors.toList())); + if (spec.isWithSnapshot()) { + cmd.setSnapshotInstallPaths(normalizeSnapshotInstallPaths(spec.getSnapshotInstallPaths())); + } - httpCall(BATCH_QUERY_VOLUME_PATH, cmd, BatchQueryVolumeRsp.class, new ReturnValueCompletion(comp) { + String path = spec.isWithSnapshot() ? 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 (spec.isWithSnapshot()) { + stats.addAll(toVolumeSnapshotStats(returnValue.getSnapshots())); + } comp.success(stats); } @@ -1085,6 +1088,39 @@ public void fail(ErrorCode errorCode) { }); } + private Collection normalizeSnapshotInstallPaths(Collection snapshotInstallPaths) { + if (snapshotInstallPaths == null || snapshotInstallPaths.isEmpty()) { + return Collections.emptyList(); + } + + return snapshotInstallPaths.stream() + .map(it -> convertZbsPathToCbdPath(it, this::getPhysicalPoolName)) + .collect(Collectors.toList()); + } + + 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(); @@ -1625,6 +1661,7 @@ public String getParentUri() { public void setParentUri(String parentUri) { this.parentUri = parentUri; } + } public static class FlattenVolumeRsp extends QueryVolumeRsp { @@ -1633,6 +1670,7 @@ public static class FlattenVolumeRsp extends QueryVolumeRsp { public static class BatchQueryVolumeRsp extends AgentResponse { private Map> volumes; + private Map> snapshots; public Map> getVolumes() { return volumes; @@ -1641,6 +1679,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 { @@ -1739,6 +1785,7 @@ public static class FlattenVolumeCmd extends VolumeCommand { public static class BatchQueryVolumeCmd extends AgentCommand { private Collection installPaths; + private Collection snapshotInstallPaths; public Collection getInstallPaths() { return installPaths; @@ -1747,6 +1794,14 @@ public Collection getInstallPaths() { public void setInstallPaths(Collection installPaths) { this.installPaths = installPaths; } + + public Collection getSnapshotInstallPaths() { + return snapshotInstallPaths; + } + + public void setSnapshotInstallPaths(Collection snapshotInstallPaths) { + this.snapshotInstallPaths = snapshotInstallPaths; + } } public static class CleanNbdCmd extends AgentCommand { 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..42a91e6b42d 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 @@ -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); 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 921c8431326..80f45b482de 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 @@ -1843,13 +1843,38 @@ protected void handle(BatchSyncVolumeSizeOnPrimaryStorageMsg msg) { 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 )); + Collection snapshotInstallPaths = msg.isWithSnapshot() && msg.getSnapshotUuidInstallPaths() != null ? + msg.getSnapshotUuidInstallPaths().values() : Collections.emptyList(); + BatchStatsSpec spec = new BatchStatsSpec(); + spec.setInstallPaths(msg.getVolumeUuidInstallPaths().values()); + spec.setSnapshotInstallPaths(snapshotInstallPaths); + controller.batchStats(spec, 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.setActualSizes(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/snapshot/VolumeSnapshotSizeSyncHelper.java b/storage/src/main/java/org/zstack/storage/snapshot/VolumeSnapshotSizeSyncHelper.java new file mode 100644 index 00000000000..6c117ad5147 --- /dev/null +++ b/storage/src/main/java/org/zstack/storage/snapshot/VolumeSnapshotSizeSyncHelper.java @@ -0,0 +1,85 @@ +package org.zstack.storage.snapshot; + +import org.zstack.core.cloudbus.CloudBus; +import org.zstack.core.cloudbus.CloudBusCallBack; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.core.db.SQL; +import org.zstack.header.core.ReturnValueCompletion; +import org.zstack.header.message.MessageReply; +import org.zstack.header.storage.primary.AskVolumeSnapshotCapabilityMsg; +import org.zstack.header.storage.primary.AskVolumeSnapshotCapabilityReply; +import org.zstack.header.storage.primary.PrimaryStorageConstant; +import org.zstack.header.storage.primary.VolumeSnapshotCapability; +import org.zstack.header.storage.snapshot.VolumeSnapshotStatus; +import org.zstack.header.storage.snapshot.VolumeSnapshotVO; +import org.zstack.header.storage.snapshot.VolumeSnapshotVO_; + +import javax.persistence.Tuple; +import javax.persistence.TypedQuery; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; + +public class VolumeSnapshotSizeSyncHelper { + private final CloudBus bus; + private final DatabaseFacade dbf; + + public VolumeSnapshotSizeSyncHelper(CloudBus bus, DatabaseFacade dbf) { + this.bus = bus; + this.dbf = dbf; + } + + public void isSnapshotSizeSyncRequired(String primaryStorageUuid, ReturnValueCompletion completion) { + AskVolumeSnapshotCapabilityMsg msg = new AskVolumeSnapshotCapabilityMsg(); + msg.setPrimaryStorageUuid(primaryStorageUuid); + bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, primaryStorageUuid); + bus.send(msg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + completion.fail(reply.getError()); + return; + } + + AskVolumeSnapshotCapabilityReply r = reply.castReply(); + VolumeSnapshotCapability cap = r.getCapability(); + completion.success(cap != null && cap.getMode() == VolumeSnapshotCapability.VolumeSnapshotMode.COPY_ON_WRITE); + } + }); + } + + public 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 + )); + } + + public 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()); + } +} diff --git a/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java b/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java index 85efec20c2d..ef995835be8 100755 --- a/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java +++ b/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java @@ -2460,6 +2460,7 @@ private void syncVolumeVolumeSize(final ReturnValueCompletion comple smsg.setPrimaryStorageUuid(self.getPrimaryStorageUuid()); smsg.setVolumeUuid(self.getUuid()); smsg.setInstallPath(self.getInstallPath()); + bus.makeTargetServiceIdByResourceUuid(smsg, PrimaryStorageConstant.SERVICE_ID, self.getPrimaryStorageUuid()); bus.send(smsg, new CloudBusCallBack(completion) { @Override @@ -2471,24 +2472,29 @@ public void run(MessageReply reply) { refreshVO(); SyncVolumeSizeOnPrimaryStorageReply r = reply.castReply(); - self.setSize(r.getSize()); + updateVolumeSize(r, completion); + } + }); + } - if (!r.isWithInternalSnapshot()) { - // the actual size = volume actual size + all snapshot size - long snapshotSize = calculateSnapshotSize(); - self.setActualSize(r.getActualSize() + snapshotSize); - } else { - self.setActualSize(r.getActualSize()); - } + private void updateVolumeSize(SyncVolumeSizeOnPrimaryStorageReply r, ReturnValueCompletion completion) { + refreshVO(); + self.setSize(r.getSize()); - self = dbf.updateAndRefresh(self); + if (!r.isWithInternalSnapshot()) { + // the actual size = volume actual size + all snapshot size + long snapshotSize = calculateSnapshotSize(); + self.setActualSize(r.getActualSize() + snapshotSize); + } else { + self.setActualSize(r.getActualSize()); + } - VolumeSize size = new VolumeSize(); - size.actualSize = self.getActualSize(); - size.size = self.getSize(); - completion.success(size); - } - }); + self = dbf.updateAndRefresh(self); + + VolumeSize size = new VolumeSize(); + size.actualSize = self.getActualSize(); + size.size = self.getSize(); + completion.success(size); } @Transactional(readOnly = true) 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 101ec515b45..73fffffbfe0 100755 --- a/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java +++ b/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java @@ -23,6 +23,7 @@ import org.zstack.header.AbstractService; import org.zstack.header.configuration.userconfig.DiskOfferingUserConfig; import org.zstack.header.core.Completion; +import org.zstack.header.core.NoErrorCompletion; import org.zstack.header.core.ReturnValueCompletion; import org.zstack.header.core.WhileDoneCompletion; import org.zstack.header.core.workflow.*; @@ -51,6 +52,7 @@ import org.zstack.identity.AccountManager; import org.zstack.storage.primary.PrimaryStorageDeleteBitGC; import org.zstack.storage.primary.PrimaryStorageGlobalConfig; +import org.zstack.storage.snapshot.VolumeSnapshotSizeSyncHelper; import org.zstack.tag.TagManager; import org.zstack.utils.CollectionUtils; import org.zstack.utils.Utils; @@ -766,34 +768,55 @@ private void handle(BatchSyncActiveVolumeSizeOnHostMsg msg) { .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)))); + VolumeSnapshotSizeSyncHelper snapshotSizeSyncHelper = new VolumeSnapshotSizeSyncHelper(bus, dbf); 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) { + snapshotSizeSyncHelper.isSnapshotSizeSyncRequired(e.getKey(), new ReturnValueCompletion(completion) { @Override - public void run(MessageReply r) { - if (r.isSuccess()) { - BatchSyncVolumeSizeOnPrimaryStorageReply br = r.castReply(); - Map actualSizes = br.getActualSizes(); + public void success(Boolean syncSnapshotSize) { + syncVolumeSize(syncSnapshotSize); + } - reply.addSuccessCount(actualSizes.size()); - reply.addFailCount(e.getValue().size() - actualSizes.size()); + @Override + public void fail(ErrorCode errorCode) { + syncVolumeSize(false); + } - refreshVolume(actualSizes); - } else { - reply.addFailCount(e.getValue().size()); + private void syncVolumeSize(boolean syncSnapshotSize) { + BatchSyncVolumeSizeOnPrimaryStorageMsg bmsg = new BatchSyncVolumeSizeOnPrimaryStorageMsg(); + bmsg.setHostUuid(msg.getHostUuid()); + bmsg.setPrimaryStorageUuid(e.getKey()); + bmsg.setVolumeUuidInstallPaths(e.getValue()); + bmsg.setWithSnapshot(syncSnapshotSize); + if (syncSnapshotSize) { + bmsg.setSnapshotUuidInstallPaths(snapshotSizeSyncHelper.getSnapshotUuidInstallPaths(e.getKey(), e.getValue().keySet())); } - - completion.done(); + 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()); + + if (syncSnapshotSize) { + snapshotSizeSyncHelper.updateSnapshotActualSizes(br.getSnapshotActualSizes()); + } + refreshVolume(actualSizes); + } else { + reply.addFailCount(e.getValue().size()); + } + completion.done(); + } + }); } @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"; + 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(); @@ -1425,4 +1448,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 487c308eb4b..1e21e548ac4 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 @@ -25,6 +25,8 @@ import org.zstack.header.storage.primary.PrimaryStorageHostRefVO import org.zstack.header.storage.primary.PrimaryStorageHostRefVO_ import org.zstack.header.storage.primary.PrimaryStorageStatus import org.zstack.header.storage.primary.PrimaryStorageVO +import org.zstack.header.storage.snapshot.VolumeSnapshotVO +import org.zstack.header.storage.snapshot.VolumeSnapshotVO_ import org.zstack.storage.zbs.MdsStatus import org.zstack.storage.zbs.MdsUri import org.zstack.sdk.* @@ -203,6 +205,8 @@ class ZbsPrimaryStorageCase extends SubCase { testMdsReconnectAfterMaximumPingFailures() testGetBackingChainNormalizesCbdParentUri() testBatchStatsNormalizesCbdInstallPath() + testGetVolumeSnapshotSizeFromVolumeStats() + testSyncVolumeSizeRefreshesSnapshotSizeFromVolumeStats() } } @@ -1174,17 +1178,22 @@ class ZbsPrimaryStorageCase extends SubCase { void testBatchStatsNormalizesCbdInstallPath() { String volUuid = "vol85707batch" + String snapshotUuid = "snap85707batch" String zbsPath = "zbs://lpool1/volume_batch" + String snapshotPath = "zbs://lpool1/volume_batch@snapshot_batch" long usedSize = SizeUnit.MEGABYTE.toByte(7) + long snapshotUsedSize = SizeUnit.MEGABYTE.toByte(3) - env.simulator(ZbsStorageController.BATCH_QUERY_VOLUME_PATH) { HttpEntity e, EnvSpec spec -> + env.simulator(ZbsStorageController.BATCH_QUERY_VOLUME_WITH_SNAPSHOT_PATH) { HttpEntity e, EnvSpec spec -> def cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.BatchQueryVolumeCmd.class) + assert cmd.snapshotInstallPaths.any { it.endsWith("@snapshot_batch") } def rsp = new ZbsStorageController.BatchQueryVolumeRsp() Map> volumes = new HashMap<>() cmd.installPaths.each { cbd -> volumes.put(cbd, ["length": SizeUnit.GIGABYTE.toByte(8), "usedSize": usedSize]) } rsp.setVolumes(volumes) + rsp.setSnapshots([(cmd.snapshotInstallPaths[0]): ["usedSize": snapshotUsedSize]]) return rsp } @@ -1192,6 +1201,8 @@ class ZbsPrimaryStorageCase extends SubCase { BatchSyncVolumeSizeOnPrimaryStorageMsg msg = new BatchSyncVolumeSizeOnPrimaryStorageMsg() 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) @@ -1199,10 +1210,105 @@ class ZbsPrimaryStorageCase extends SubCase { BatchSyncVolumeSizeOnPrimaryStorageReply r = reply as BatchSyncVolumeSizeOnPrimaryStorageReply assert r.actualSizes.get(volUuid) == usedSize : "actualSize must map back to uuid via zbs:// install path, got ${r.actualSizes}" + assert r.snapshotActualSizes.get(snapshotUuid) == snapshotUsedSize : + "snapshot actualSize must map back to uuid via zbs:// snapshot path, got ${r.snapshotActualSizes}" env.cleanSimulatorHandlers() } + void testGetVolumeSnapshotSizeFromVolumeStats() { + vol = createDataVolume { + name = "snapshot-size-test" + diskOfferingUuid = diskOffering.uuid + primaryStorageUuid = ps.uuid + systemTags = [ExternalPrimaryStorageSystemTags.REQUIRED_INSTALL_URL.instantiateTag( + Collections.singletonMap(ExternalPrimaryStorageSystemTags.REQUIRED_INSTALL_URL_TOKEN, "zbs://lpool2"))] + } as VolumeInventory + + long actualSize = SizeUnit.MEGABYTE.toByte(33) + AtomicBoolean queriedSnapshotSize = new AtomicBoolean(false) + + env.simulator(ZbsStorageController.QUERY_VOLUME_PATH) { HttpEntity e, EnvSpec spec -> + ZbsStorageController.QueryVolumeCmd cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.QueryVolumeCmd.class) + assert cmd.snapshotInstallPaths.any { it.endsWith("@snapshot-size-agent") } + queriedSnapshotSize.set(true) + + def rsp = new ZbsStorageController.QueryVolumeRsp() + rsp.size = vol.size + rsp.actualSize = SizeUnit.MEGABYTE.toByte(2) + rsp.snapshots = [(cmd.snapshotInstallPaths[0]): ["usedSize": actualSize]] + return rsp + } + + VolumeSnapshotInventory snapshot = createVolumeSnapshot { + volumeUuid = vol.uuid + name = "snapshot-size-agent" + } as VolumeSnapshotInventory + + GetVolumeSnapshotSizeResult result = getVolumeSnapshotSize { + uuid = snapshot.uuid + } + + assert queriedSnapshotSize.get() + assert result.size == actualSize + assert result.actualSize == actualSize + + env.cleanSimulatorHandlers() + deleteVolume(vol.uuid) + } + + void testSyncVolumeSizeRefreshesSnapshotSizeFromVolumeStats() { + vol = createDataVolume { + name = "sync-volume-size-refresh-snapshot" + diskOfferingUuid = diskOffering.uuid + primaryStorageUuid = ps.uuid + systemTags = [ExternalPrimaryStorageSystemTags.REQUIRED_INSTALL_URL.instantiateTag( + Collections.singletonMap(ExternalPrimaryStorageSystemTags.REQUIRED_INSTALL_URL_TOKEN, "zbs://lpool2"))] + } as VolumeInventory + + long oldSnapshotSize = SizeUnit.MEGABYTE.toByte(1) + long refreshedSnapshotSize = SizeUnit.MEGABYTE.toByte(23) + long volumeActualSize = SizeUnit.MEGABYTE.toByte(11) + AtomicInteger snapshotSizeQueryCount = new AtomicInteger(0) + + env.simulator(ZbsStorageController.QUERY_VOLUME_PATH) { HttpEntity e, EnvSpec spec -> + ZbsStorageController.QueryVolumeCmd cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.QueryVolumeCmd.class) + assert cmd.path == vol.installPath + assert cmd.snapshotInstallPaths.any { it.endsWith("@sync-volume-size-refresh-snapshot-size") } + snapshotSizeQueryCount.incrementAndGet() + + def rsp = new ZbsStorageController.QueryVolumeRsp() + rsp.size = vol.size + rsp.actualSize = volumeActualSize + rsp.snapshots = [(cmd.snapshotInstallPaths[0]): ["usedSize": refreshedSnapshotSize]] + return rsp + } + + VolumeSnapshotInventory snapshot = createVolumeSnapshot { + volumeUuid = vol.uuid + name = "sync-volume-size-refresh-snapshot-size" + } as VolumeSnapshotInventory + + VolumeSnapshotVO snapshotVO = dbf.findByUuid(snapshot.uuid, VolumeSnapshotVO.class) + snapshotVO.setSize(oldSnapshotSize) + dbf.update(snapshotVO) + + int snapshotSizeQueriesBeforeSyncVolumeSize = snapshotSizeQueryCount.get() + + VolumeInventory synced = syncVolumeSize { + uuid = vol.uuid + } as VolumeInventory + + assert snapshotSizeQueryCount.get() > snapshotSizeQueriesBeforeSyncVolumeSize + assert Q.New(VolumeSnapshotVO.class).eq(VolumeSnapshotVO_.uuid, snapshot.uuid) + .select(VolumeSnapshotVO_.size) + .findValue() == refreshedSnapshotSize + assert synced.actualSize == volumeActualSize + refreshedSnapshotSize + + env.cleanSimulatorHandlers() + deleteVolume(vol.uuid) + } + void deleteVolume(String volUuid) { deleteDataVolume { uuid = volUuid