From 0b6aa94984891c884858ed2981b1296af178e689 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 | 11 +- .../zstack/expon/ExponStorageController.java | 8 +- .../primary/local/LocalStorageBase.java | 11 +- .../local/LocalStorageHypervisorBackend.java | 2 +- .../primary/local/LocalStorageKvmBackend.java | 4 +- .../primary/nfs/NfsPrimaryStorage.java | 30 ++- .../primary/nfs/NfsPrimaryStorageBackend.java | 6 +- .../nfs/NfsPrimaryStorageKVMBackend.java | 4 +- .../primary/smp/SMPPrimaryStorageBase.java | 7 +- .../xinfini/XInfiniStorageController.java | 8 +- .../storage/zbs/ZbsStorageController.java | 63 +++-- .../primary/SimulatorPrimaryStorage.java | 9 +- .../addon/primary/ExternalPrimaryStorage.java | 38 ++- .../storage/primary/PrimaryStorageBase.java | 6 +- .../storage/volume/VolumeManagerImpl.java | 218 ++++++++++++++++-- .../addon/zbs/ZbsPrimaryStorageCase.groovy | 8 +- 27 files changed, 451 insertions(+), 164 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..fd8d221eb13 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 != null ? volumeType : volume == null ? null : volume.getType(); + } + + 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..5523012db86 --- /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 actualSizes = new HashMap<>(); + + private Map snapshotActualSizes = new HashMap<>(); + + public void setActualSizes(Map actualSizes) { + this.actualSizes = 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/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..b6c015d0166 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,12 @@ protected void handle(AskVolumeSnapshotCapabilityMsg msg) { AskVolumeSnapshotCapabilityReply reply = new AskVolumeSnapshotCapabilityReply(); VolumeSnapshotCapability cap = new VolumeSnapshotCapability(); - String volumeType = msg.getVolume().getType(); + String volumeType = msg.getVolumeType(); 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 +3398,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 +3420,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(); 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..a78d4e1c8aa 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..904bfc15b01 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,11 @@ protected void handle(AskVolumeSnapshotCapabilityMsg msg) { VolumeSnapshotCapability capability = new VolumeSnapshotCapability(); capability.setSupport(true); - String volumeType = msg.getVolume().getType(); + String volumeType = msg.getVolumeType(); 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 +2734,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..aa163ef141f 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,8 +2495,8 @@ 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(); 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..7e2f46e64e7 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,17 @@ 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.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 +1405,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..1524e1b3539 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(); 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..0d3c187718b 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,11 @@ protected void handle(AskVolumeSnapshotCapabilityMsg msg) { VolumeSnapshotCapability capability = new VolumeSnapshotCapability(); capability.setSupport(true); - String volumeType = msg.getVolume().getType(); + String volumeType = msg.getVolumeType(); 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 +462,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 c9efd282c05..965ddd2f5de 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 27810c6bb45..76e556cbb21 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,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); } @@ -1085,6 +1089,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(); @@ -1625,6 +1652,7 @@ public String getParentUri() { public void setParentUri(String parentUri) { this.parentUri = parentUri; } + } public static class FlattenVolumeRsp extends QueryVolumeRsp { @@ -1633,6 +1661,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 +1670,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..758c11b5ee6 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,8 +180,8 @@ 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); 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..9b089602b47 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 @@ -1837,19 +1837,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 )); + 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.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/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 101ec515b45..55f29a6fcab 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; @@ -761,34 +775,65 @@ 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)))); + + 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)); + }); + + Map primaryStorageTypes = getPrimaryStorageTypesForSnapshotCapabilityCache(activeVolumesInPs.keySet()); 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) { + String primaryStorageUuid = e.getKey(); + ActiveVolumeSizeSyncInfo activeVolumeSizeSyncInfo = e.getValue(); + isSnapshotSizeSyncRequired(primaryStorageUuid, primaryStorageTypes.get(primaryStorageUuid), + activeVolumeSizeSyncInfo.volumeTypes, 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) { + 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())); } - - completion.done(); + bus.makeTargetServiceIdByResourceUuid(bmsg, PrimaryStorageConstant.SERVICE_ID, primaryStorageUuid); + bus.send(bmsg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply r) { + if (r.isSuccess()) { + BatchSyncVolumeResourceSizeOnPrimaryStorageReply br = r.castReply(); + Map actualSizes = br.getActualSizes(); + + reply.addSuccessCount(actualSizes.size()); + reply.addFailCount(activeVolumeSizeSyncInfo.volumeUuidInstallPaths.size() - actualSizes.size()); + + if (syncSnapshotSize) { + updateSnapshotActualSizes(br.getSnapshotActualSizes()); + } + refreshVolume(actualSizes); + } else { + reply.addFailCount(activeVolumeSizeSyncInfo.volumeUuidInstallPaths.size()); + } + completion.done(); + } + }); } @Transactional(readOnly = true) @@ -826,6 +871,133 @@ public void done(ErrorCodeList errorCodeList) { }); } + private void isSnapshotSizeSyncRequired(String primaryStorageUuid, String primaryStorageType, + Collection volumeTypes, ReturnValueCompletion completion) { + if (primaryStorageType == null) { + completion.success(false); + return; + } + + if (volumeTypes.isEmpty()) { + completion.success(false); + return; + } + + List uncachedVolumeTypes = volumeTypes.stream() + .filter(volumeType -> !snapshotSizeSyncRequiredCache.containsKey(snapshotSizeSyncRequiredCacheKey(primaryStorageType, volumeType))) + .collect(Collectors.toList()); + if (uncachedVolumeTypes.isEmpty()) { + completion.success(isSnapshotSizeSyncRequired(primaryStorageType, volumeTypes)); + return; + } + + new While<>(uncachedVolumeTypes).each((volumeType, compl) -> { + AskVolumeSnapshotCapabilityMsg msg = new AskVolumeSnapshotCapabilityMsg(); + msg.setPrimaryStorageUuid(primaryStorageUuid); + msg.setVolumeType(volumeType); + bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, primaryStorageUuid); + bus.send(msg, new CloudBusCallBack(compl) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + compl.addError(reply.getError()); + compl.done(); + return; + } + + AskVolumeSnapshotCapabilityReply r = reply.castReply(); + snapshotSizeSyncRequiredCache.put(snapshotSizeSyncRequiredCacheKey(primaryStorageType, volumeType), + isCowSnapshotSupported(r.getCapability())); + compl.done(); + } + }); + }).run(new WhileDoneCompletion(completion) { + @Override + public void done(ErrorCodeList errorCodeList) { + if (!errorCodeList.getCauses().isEmpty()) { + completion.fail(errorCodeList.getCauses().get(0)); + return; + } + + completion.success(isSnapshotSizeSyncRequired(primaryStorageType, volumeTypes)); + } + }); + } + + 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 boolean isSnapshotSizeSyncRequired(String primaryStorageType, Collection volumeTypes) { + return volumeTypes.stream() + .anyMatch(volumeType -> snapshotSizeSyncRequiredCache.getOrDefault(snapshotSizeSyncRequiredCacheKey(primaryStorageType, volumeType), false)); + } + + 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) { final CreateDataVolumeFromVolumeSnapshotReply reply = new CreateDataVolumeFromVolumeSnapshotReply(); final VolumeVO vo = new VolumeVO(); @@ -1425,4 +1597,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 970f51dfadd..a5a56867693 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_ @@ -1221,14 +1221,14 @@ 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 + BatchSyncVolumeResourceSizeOnPrimaryStorageReply r = reply as BatchSyncVolumeResourceSizeOnPrimaryStorageReply assert r.actualSizes.get(volUuid) == usedSize : "actualSize must map back to uuid via zbs:// install path, got ${r.actualSizes}"