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