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