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/BatchSyncActiveVolumeSizeOnHostReply.java b/header/src/main/java/org/zstack/header/volume/BatchSyncActiveVolumeSizeOnHostReply.java index 25dec3d2d87..50163ef4b3d 100644 --- a/header/src/main/java/org/zstack/header/volume/BatchSyncActiveVolumeSizeOnHostReply.java +++ b/header/src/main/java/org/zstack/header/volume/BatchSyncActiveVolumeSizeOnHostReply.java @@ -2,11 +2,16 @@ import org.zstack.header.message.MessageReply; +import java.util.HashSet; +import java.util.Set; + public class BatchSyncActiveVolumeSizeOnHostReply extends MessageReply { private Integer successCount = 0; private Integer failCount = 0; + private Set snapshotSizeSyncRequiredCacheKeys = new HashSet<>(); + public void setSuccessCount(Integer successCount) { this.successCount = successCount; } @@ -30,4 +35,12 @@ public synchronized void addSuccessCount(Integer successCount) { public synchronized void addFailCount(Integer failCount) { this.failCount = this.failCount + failCount; } + + public Set getSnapshotSizeSyncRequiredCacheKeys() { + return snapshotSizeSyncRequiredCacheKeys; + } + + public void setSnapshotSizeSyncRequiredCacheKeys(Set snapshotSizeSyncRequiredCacheKeys) { + this.snapshotSizeSyncRequiredCacheKeys = snapshotSizeSyncRequiredCacheKeys; + } } 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..4ce09b47cc8 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; @@ -726,6 +740,7 @@ private void handle(BatchSyncManagedActiveVolumeSizeMsg msg) { .listValues().stream() .filter(uuid -> destMaker.isManagedByUs((String) uuid)).map(String::valueOf) .collect(Collectors.toList()); + Set snapshotSizeSyncRequiredCacheKeys = Collections.newSetFromMap(new ConcurrentHashMap<>()); new While<>(hostUuids).step((hostUuid, completion) -> { BatchSyncActiveVolumeSizeOnHostMsg bmsg = new BatchSyncActiveVolumeSizeOnHostMsg(); @@ -739,6 +754,7 @@ public void run(MessageReply r) { BatchSyncActiveVolumeSizeOnHostReply br = r.castReply(); reply.addSuccessCount(br.getSuccessCount()); reply.addFailCount(br.getFailCount()); + snapshotSizeSyncRequiredCacheKeys.addAll(br.getSnapshotSizeSyncRequiredCacheKeys()); } else { completion.addError(r.getError()); } @@ -750,6 +766,8 @@ public void run(MessageReply r) { public void done(ErrorCodeList errorCodeList) { if (!errorCodeList.getCauses().isEmpty()) { reply.setError(errorCodeList.getCauses().get(0)); + } else { + pruneSnapshotSizeSyncRequiredCache(snapshotSizeSyncRequiredCacheKeys); } bus.reply(msg, reply); } @@ -768,69 +786,277 @@ 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); } + }); - completion.done(); + flow(new NoRollbackFlow() { + String __name__ = "batch-sync-active-volume-size"; + + @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))); + reply.setSnapshotSizeSyncRequiredCacheKeys(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(); + cacheSnapshotSizeSyncRequired(snapshotSizeSyncRequiredCacheKey(cacheMiss.primaryStorageType, cacheMiss.volumeType), + isCowSnapshotSupported(r.getCapability())); + compl.done(); + } + }); + }).run(new WhileDoneCompletion(trigger) { + @Override + public void done(ErrorCodeList errorCodeList) { + trigger.next(); } - } - }); - }).run(new WhileDoneCompletion(msg) { + }); + } + + 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())); + } + 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) { + if (volumeUuids == null || volumeUuids.isEmpty()) { + return Collections.emptyMap(); + } + + 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 void cacheSnapshotSizeSyncRequired(String cacheKey, boolean required) { + snapshotSizeSyncRequiredCache.put(cacheKey, required); + } + + private void pruneSnapshotSizeSyncRequiredCache(Collection currentCacheKeys) { + snapshotSizeSyncRequiredCache.keySet().retainAll(currentCacheKeys); + } + + 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 +1667,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/ScheduledVolumeSizeSyncCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ScheduledVolumeSizeSyncCase.groovy new file mode 100644 index 00000000000..9bd762c80c9 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ScheduledVolumeSizeSyncCase.groovy @@ -0,0 +1,432 @@ +package org.zstack.test.integration.storage.primary.addon.zbs + +import org.springframework.http.HttpEntity +import org.zstack.core.cloudbus.CloudBus +import org.zstack.core.config.GlobalConfigValidatorExtensionPoint +import org.zstack.core.db.Q +import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageHostProtocolRefVO +import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageHostProtocolRefVO_ +import org.zstack.header.storage.backup.UploadImageToRemoteTargetMsg +import org.zstack.header.storage.backup.UploadImageToRemoteTargetReply +import org.zstack.header.storage.primary.PrimaryStorageClusterRefVO +import org.zstack.header.storage.primary.PrimaryStorageClusterRefVO_ +import org.zstack.header.storage.primary.PrimaryStorageHostStatus +import org.zstack.header.storage.snapshot.VolumeSnapshotVO +import org.zstack.header.storage.snapshot.VolumeSnapshotVO_ +import org.zstack.header.volume.VolumeProtocol +import org.zstack.header.volume.VolumeVO +import org.zstack.header.volume.VolumeVO_ +import org.zstack.sdk.CephPrimaryStorageInventory +import org.zstack.sdk.ClusterInventory +import org.zstack.sdk.DiskOfferingInventory +import org.zstack.sdk.KVMHostInventory +import org.zstack.sdk.L3NetworkInventory +import org.zstack.sdk.PrimaryStorageInventory +import org.zstack.sdk.VmInstanceInventory +import org.zstack.sdk.VolumeSnapshotInventory +import org.zstack.storage.ceph.primary.CephPrimaryStorageBase +import org.zstack.storage.volume.VolumeGlobalConfig +import org.zstack.storage.volume.VolumeManagerImpl +import org.zstack.storage.zbs.ZbsStorageController +import org.zstack.test.integration.storage.StorageTest +import org.zstack.testlib.EnvSpec +import org.zstack.testlib.SubCase +import org.zstack.utils.data.SizeUnit +import org.zstack.utils.gson.JSONObjectUtil + +import java.lang.reflect.Field +import java.util.concurrent.atomic.AtomicInteger + +class ScheduledVolumeSizeSyncCase extends SubCase { + EnvSpec env + ClusterInventory cluster + PrimaryStorageInventory ps + CephPrimaryStorageInventory cephPs + DiskOfferingInventory diskOffering + KVMHostInventory kvm + + @Override + void setup() { + useSpring(StorageTest.springSpec) + } + + @Override + void environment() { + env = makeEnv { + instanceOffering { + name = "instanceOffering" + memory = SizeUnit.GIGABYTE.toByte(8) + cpu = 4 + } + + diskOffering { + name = "diskOffering" + diskSize = SizeUnit.GIGABYTE.toByte(2) + } + + sftpBackupStorage { + name = "sftp" + url = "/sftp" + username = "root" + password = "password" + hostname = "127.0.0.2" + + image { + name = "image" + url = "http://zstack.org/download/test.qcow2" + size = SizeUnit.GIGABYTE.toByte(1) + virtio = true + } + } + + zone { + name = "zone" + description = "test" + + cluster { + name = "cluster" + hypervisorType = "KVM" + + kvm { + name = "kvm-1" + managementIp = "127.0.0.1" + username = "root" + password = "password" + } + + attachPrimaryStorage("ceph-pri") + attachL2Network("l2") + } + + l2NoVlanNetwork { + name = "l2" + physicalInterface = "eth0" + + l3Network { + name = "l3" + + ip { + startIp = "192.168.100.10" + endIp = "192.168.100.100" + netmask = "255.255.255.0" + gateway = "192.168.100.1" + } + } + } + + externalPrimaryStorage { + name = "zbs-1" + identity = "zbs" + defaultOutputProtocol = "CBD" + config = "{\"mdsUrls\":[\"root:password@127.0.1.1\"],\"logicalPoolName\":\"lpool1\"}" + url = "zbs" + } + + cephPrimaryStorage { + name = "ceph-pri" + description = "Test" + totalCapacity = SizeUnit.GIGABYTE.toByte(200) + availableCapacity = SizeUnit.GIGABYTE.toByte(200) + url = "ceph://pri" + fsid = "7ff218d9-f525-435f-8a40-3618d1772a64" + monUrls = ["root:password@localhost/?monPort=7777"] + } + + attachBackupStorage("sftp") + attachBackupStorage("ceph-bk") + } + + cephBackupStorage { + name = "ceph-bk" + description = "Test" + totalCapacity = SizeUnit.GIGABYTE.toByte(100) + availableCapacity = SizeUnit.GIGABYTE.toByte(100) + url = "/bk" + fsid = "7ff218d9-f525-435f-8a40-3618d1772a64" + monUrls = ["root:password@localhost/?monPort=7777"] + + image { + name = "ceph-image" + url = "http://zstack.org/download/image.qcow2" + } + } + } + } + + @Override + void test() { + env.create { + cluster = env.inventoryByName("cluster") as ClusterInventory + ps = env.inventoryByName("zbs-1") as PrimaryStorageInventory + cephPs = env.inventoryByName("ceph-pri") as CephPrimaryStorageInventory + diskOffering = env.inventoryByName("diskOffering") as DiskOfferingInventory + kvm = env.inventoryByName("kvm-1") as KVMHostInventory + + try { + testScheduledSyncCachesAndPrunesSnapshotCapability() + testScheduledCephVolumeSyncDoesNotSyncSnapshotSize() + } finally { + disableScheduledVolumeSizeSyncForTest() + } + } + } + + @Override + void clean() { + disableScheduledVolumeSizeSyncForTest() + detachZbsPrimaryStorageFromClusterIfAttached() + env.delete() + } + + void testScheduledSyncCachesAndPrunesSnapshotCapability() { + long volumeActualSize = SizeUnit.MEGABYTE.toByte(11) + long snapshotInitialSize = SizeUnit.MEGABYTE.toByte(1) + long snapshotSyncedSize = SizeUnit.MEGABYTE.toByte(5) + AtomicInteger batchQueryWithSnapshotCount = new AtomicInteger(0) + + prepareZbsHost() + + env.afterSimulator(ZbsStorageController.CREATE_VOLUME_PATH) { rsp, HttpEntity e -> + ZbsStorageController.CreateVolumeCmd cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.CreateVolumeCmd) + ZbsStorageController.CreateVolumeRsp createVolumeRsp = new ZbsStorageController.CreateVolumeRsp() + createVolumeRsp.installPath = "zbs://${cmd.logicalPool}/${cmd.volume}" + createVolumeRsp.actualSize = 0L + createVolumeRsp.size = cmd.size + return createVolumeRsp + } + + env.message(UploadImageToRemoteTargetMsg.class) { UploadImageToRemoteTargetMsg msg, CloudBus bus -> + assert msg.remoteTargetUrl.startsWith("nbd://") + assert msg.format == "raw" + bus.reply(msg, new UploadImageToRemoteTargetReply()) + } + + VmInstanceInventory vm = createVmInstance { + name = "vm-for-scheduled-volume-size-sync" + instanceOfferingUuid = env.inventoryByName("instanceOffering").uuid + imageUuid = env.inventoryByName("image").uuid + l3NetworkUuids = [(env.inventoryByName("l3") as L3NetworkInventory).uuid] + primaryStorageUuidForRootVolume = ps.uuid + dataDiskOfferingUuids = [diskOffering.uuid] + hostUuid = kvm.uuid + systemTags = ["primaryStorageUuidForDataVolume::${ps.uuid}".toString()] + } as VmInstanceInventory + + String dataVolumeUuid = vm.allVolumes.find { it.uuid != vm.rootVolumeUuid }.uuid + String zbsPath = Q.New(VolumeVO.class) + .select(VolumeVO_.installPath) + .eq(VolumeVO_.uuid, dataVolumeUuid) + .findValue() + + env.afterSimulator(ZbsStorageController.CREATE_SNAPSHOT_PATH) { rsp, HttpEntity e -> + ZbsStorageController.CreateSnapshotCmd cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.CreateSnapshotCmd) + ZbsStorageController.CreateSnapshotRsp createSnapshotRsp = new ZbsStorageController.CreateSnapshotRsp() + createSnapshotRsp.installPath = "${cmd.path}@${cmd.snapshot}" + createSnapshotRsp.actualSize = snapshotInitialSize + return createSnapshotRsp + } + + VolumeSnapshotInventory snapshot = createVolumeSnapshot { + name = "snap-for-scheduled-volume-size-sync" + volumeUuid = dataVolumeUuid + } as VolumeSnapshotInventory + String snapshotUuid = snapshot.uuid + + String snapshotPath = Q.New(VolumeSnapshotVO.class) + .select(VolumeSnapshotVO_.primaryStorageInstallPath) + .eq(VolumeSnapshotVO_.uuid, snapshotUuid) + .findValue() + + env.cleanAfterSimulatorHandlers() + + env.simulator(ZbsStorageController.BATCH_QUERY_VOLUME_WITH_SNAPSHOT_PATH) { HttpEntity e, EnvSpec spec -> + batchQueryWithSnapshotCount.incrementAndGet() + def cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.BatchQueryVolumeCmd.class) + String volumeName = zbsPath.substring(zbsPath.lastIndexOf("/") + 1) + String snapshotName = snapshotPath.substring(snapshotPath.lastIndexOf("@") + 1) + String volumeAgentPath = cmd.installPaths.find { it.contains(volumeName) && !it.contains("@") } + String snapshotAgentPath = cmd.installPaths.find { it.contains(volumeName) && it.contains(snapshotName) } + assert volumeAgentPath != null + assert snapshotAgentPath != null + + def batchQueryVolumeRsp = new ZbsStorageController.BatchQueryVolumeRsp() + batchQueryVolumeRsp.setVolumes([(volumeAgentPath): ["length": SizeUnit.GIGABYTE.toByte(8), "usedSize": volumeActualSize]]) + batchQueryVolumeRsp.setSnapshots([(snapshotAgentPath): ["usedSize": snapshotSyncedSize]]) + return batchQueryVolumeRsp + } + + VolumeGlobalConfig.AUTO_REFRESH_VOLUME_SCOPE.updateValue("AllActive") + updateRefreshVolumeSizeIntervalForTest("2") + updateRefreshVolumeSizeIntervalForTest("1") + + retryInSecs { + assert Q.New(VolumeSnapshotVO.class) + .select(VolumeSnapshotVO_.size) + .eq(VolumeSnapshotVO_.uuid, snapshotUuid) + .findValue() == snapshotSyncedSize + assert Q.New(VolumeVO.class) + .select(VolumeVO_.actualSize) + .eq(VolumeVO_.uuid, dataVolumeUuid) + .findValue() == volumeActualSize + snapshotSyncedSize + assert batchQueryWithSnapshotCount.get() >= 1 + assert snapshotSizeSyncRequiredCache()["zbs-Data"] == true + assert snapshotSizeSyncRequiredCache()["zbs-Root"] == true + } + + stopVmInstance { + uuid = vm.uuid + } + + retryInSecs { + assert snapshotSizeSyncRequiredCache().isEmpty() + } + + env.cleanSimulatorAndMessageHandlers() + } + + void testScheduledCephVolumeSyncDoesNotSyncSnapshotSize() { + long snapshotSizeBeforeSync = SizeUnit.MEGABYTE.toByte(7) + long volumeActualSize = SizeUnit.MEGABYTE.toByte(17) + AtomicInteger batchGetVolumeSizeCount = new AtomicInteger(0) + + VmInstanceInventory vm = createVmInstance { + name = "vm-for-scheduled-ceph-volume-size-sync" + instanceOfferingUuid = env.inventoryByName("instanceOffering").uuid + imageUuid = env.inventoryByName("ceph-image").uuid + l3NetworkUuids = [(env.inventoryByName("l3") as L3NetworkInventory).uuid] + primaryStorageUuidForRootVolume = cephPs.uuid + dataDiskOfferingUuids = [diskOffering.uuid] + hostUuid = kvm.uuid + systemTags = ["primaryStorageUuidForDataVolume::${cephPs.uuid}".toString()] + } as VmInstanceInventory + + String dataVolumeUuid = vm.allVolumes.find { it.uuid != vm.rootVolumeUuid }.uuid + + env.afterSimulator(CephPrimaryStorageBase.CREATE_SNAPSHOT_PATH) { CephPrimaryStorageBase.CreateSnapshotRsp rsp, HttpEntity e -> + CephPrimaryStorageBase.CreateSnapshotCmd cmd = JSONObjectUtil.toObject(e.body, CephPrimaryStorageBase.CreateSnapshotCmd.class) + assert cmd.volumeUuid == dataVolumeUuid + rsp.actualSize = snapshotSizeBeforeSync + return rsp + } + + VolumeSnapshotInventory snapshot = createVolumeSnapshot { + name = "ceph-snap-for-scheduled-volume-size-sync" + volumeUuid = dataVolumeUuid + } as VolumeSnapshotInventory + String snapshotUuid = snapshot.uuid + + env.cleanAfterSimulatorHandlers() + + env.afterSimulator(CephPrimaryStorageBase.BATCH_GET_VOLUME_SIZE_PATH) { CephPrimaryStorageBase.GetBatchVolumeSizeRsp rsp, HttpEntity e -> + int count = batchGetVolumeSizeCount.incrementAndGet() + CephPrimaryStorageBase.GetBatchVolumeSizeCmd cmd = JSONObjectUtil.toObject(e.body, CephPrimaryStorageBase.GetBatchVolumeSizeCmd.class) + assert cmd.volumeUuidInstallPaths.containsKey(dataVolumeUuid) + rsp.actualSizes = new HashMap<>() + if (count == 1) { + rsp.actualSizes.put(dataVolumeUuid, volumeActualSize) + } + return rsp + } + + env.resetAllSimulatorSize() + VolumeGlobalConfig.AUTO_REFRESH_VOLUME_SCOPE.updateValue("AllActive") + updateRefreshVolumeSizeIntervalForTest("2") + updateRefreshVolumeSizeIntervalForTest("1") + + retryInSecs { + assert Q.New(VolumeSnapshotVO.class) + .select(VolumeSnapshotVO_.size) + .eq(VolumeSnapshotVO_.uuid, snapshotUuid) + .findValue() == snapshotSizeBeforeSync + assert Q.New(VolumeVO.class) + .select(VolumeVO_.actualSize) + .eq(VolumeVO_.uuid, dataVolumeUuid) + .findValue() == volumeActualSize + snapshotSizeBeforeSync + assert batchGetVolumeSizeCount.get() >= 1 + assert snapshotSizeSyncRequiredCache()["Ceph-Data"] == false + assert snapshotSizeSyncRequiredCache()["Ceph-Root"] == false + } + + stopVmInstance { + uuid = vm.uuid + } + + retryInSecs { + assert snapshotSizeSyncRequiredCache().isEmpty() + } + + env.resetAllSimulatorSize() + env.cleanSimulatorAndMessageHandlers() + } + + private void prepareZbsHost() { + if (!Q.New(PrimaryStorageClusterRefVO.class) + .eq(PrimaryStorageClusterRefVO_.primaryStorageUuid, ps.uuid) + .eq(PrimaryStorageClusterRefVO_.clusterUuid, cluster.uuid) + .isExists()) { + attachPrimaryStorageToCluster { + primaryStorageUuid = ps.uuid + clusterUuid = cluster.uuid + } + } + + env.afterSimulator(ZbsStorageController.CHECK_HOST_STORAGE_CONNECTION_PATH) { rsp, HttpEntity e -> + ZbsStorageController.CheckHostStorageConnectionRsp checkHostStorageConnectionRsp = new ZbsStorageController.CheckHostStorageConnectionRsp() + checkHostStorageConnectionRsp.success = true + return checkHostStorageConnectionRsp + } + + reconnectHost { + uuid = kvm.uuid + } + + retryInSecs { + assert Q.New(ExternalPrimaryStorageHostProtocolRefVO.class) + .eq(ExternalPrimaryStorageHostProtocolRefVO_.primaryStorageUuid, ps.uuid) + .eq(ExternalPrimaryStorageHostProtocolRefVO_.hostUuid, kvm.uuid) + .eq(ExternalPrimaryStorageHostProtocolRefVO_.protocol, VolumeProtocol.CBD.toString()) + .eq(ExternalPrimaryStorageHostProtocolRefVO_.status, PrimaryStorageHostStatus.Connected) + .isExists() + } + + env.cleanAfterSimulatorHandlers() + } + + private void updateRefreshVolumeSizeIntervalForTest(String interval) { + List validators = VolumeGlobalConfig.REFRESH_VOLUME_SIZE_INTERVAL.validators + VolumeGlobalConfig.REFRESH_VOLUME_SIZE_INTERVAL.validators = new ArrayList<>() + try { + VolumeGlobalConfig.REFRESH_VOLUME_SIZE_INTERVAL.updateValue(interval) + } finally { + VolumeGlobalConfig.REFRESH_VOLUME_SIZE_INTERVAL.validators = validators + } + } + + private void disableScheduledVolumeSizeSyncForTest() { + VolumeGlobalConfig.AUTO_REFRESH_VOLUME_SCOPE.updateValue("None") + updateRefreshVolumeSizeIntervalForTest("3600") + } + + private void detachZbsPrimaryStorageFromClusterIfAttached() { + if (ps == null || cluster == null) { + return + } + + if (Q.New(PrimaryStorageClusterRefVO.class) + .eq(PrimaryStorageClusterRefVO_.primaryStorageUuid, ps.uuid) + .eq(PrimaryStorageClusterRefVO_.clusterUuid, cluster.uuid) + .isExists()) { + detachPrimaryStorageFromCluster { + primaryStorageUuid = ps.uuid + clusterUuid = cluster.uuid + } + } + } + + private Map snapshotSizeSyncRequiredCache() { + VolumeManagerImpl volumeManager = bean(VolumeManagerImpl.class) + Field field = VolumeManagerImpl.class.getDeclaredField("snapshotSizeSyncRequiredCache") + field.setAccessible(true) + return field.get(volumeManager) as Map + } +} 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..b9c6ee8543b 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 @@ -4,20 +4,29 @@ import org.springframework.http.HttpEntity import org.zstack.core.cloudbus.CloudBus import org.zstack.core.cloudbus.EventCallback import org.zstack.core.cloudbus.EventFacade +import org.zstack.core.config.GlobalConfigValidatorExtensionPoint import org.zstack.core.db.DatabaseFacade import org.zstack.core.db.Q import org.zstack.header.errorcode.ErrorCode import org.zstack.header.errorcode.OperationFailureException import org.zstack.header.message.MessageReply +import org.zstack.header.storage.backup.UploadImageToRemoteTargetMsg +import org.zstack.header.storage.backup.UploadImageToRemoteTargetReply +import org.zstack.header.storage.snapshot.VolumeSnapshotVO +import org.zstack.header.storage.snapshot.VolumeSnapshotVO_ 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.volume.VolumeVO +import org.zstack.header.volume.VolumeVO_ import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageSpaceVO import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO_ import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageSpaceVO_ +import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageHostProtocolRefVO +import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageHostProtocolRefVO_ import org.zstack.header.storage.addon.primary.PrimaryStorageOutputProtocolRefVO import org.zstack.header.storage.primary.PrimaryStorageCapacityVO import org.zstack.header.storage.primary.PrimaryStorageCapacityVO_ @@ -27,6 +36,7 @@ import org.zstack.header.storage.primary.PrimaryStorageHostRefVO import org.zstack.header.storage.primary.PrimaryStorageHostRefVO_ import org.zstack.header.storage.primary.PrimaryStorageStatus import org.zstack.header.storage.primary.PrimaryStorageVO +import org.zstack.header.volume.VolumeProtocol import org.zstack.storage.zbs.MdsStatus import org.zstack.storage.zbs.MdsUri import org.zstack.sdk.* @@ -70,6 +80,8 @@ class ZbsPrimaryStorageCase extends SubCase { @Override void clean() { + disableScheduledVolumeSizeSyncForTest() + detachZbsPrimaryStorageFromClusterIfAttached() env.delete() } @@ -206,6 +218,8 @@ class ZbsPrimaryStorageCase extends SubCase { testMdsReconnectAfterMaximumPingFailures() testGetBackingChainNormalizesCbdParentUri() testBatchStatsNormalizesCbdInstallPath() + testBatchStatsReturnsSnapshotActualSize() + testManagedActiveVolumeSizeSyncUpdatesCowSnapshotSize() } } @@ -1221,20 +1235,227 @@ 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() + } + + void testManagedActiveVolumeSizeSyncUpdatesCowSnapshotSize() { + long volumeActualSize = SizeUnit.MEGABYTE.toByte(11) + long snapshotInitialSize = SizeUnit.MEGABYTE.toByte(1) + long snapshotSyncedSize = SizeUnit.MEGABYTE.toByte(5) + AtomicInteger batchQueryCount = new AtomicInteger(0) + + try { + env.afterSimulator(ZbsStorageController.CREATE_VOLUME_PATH) { rsp, HttpEntity e -> + ZbsStorageController.CreateVolumeCmd cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.CreateVolumeCmd) + ZbsStorageController.CreateVolumeRsp createVolumeRsp = new ZbsStorageController.CreateVolumeRsp() + createVolumeRsp.installPath = "zbs://${cmd.logicalPool}/${cmd.volume}" + createVolumeRsp.actualSize = 0L + createVolumeRsp.size = cmd.size + return createVolumeRsp + } + + if (!Q.New(PrimaryStorageClusterRefVO.class) + .eq(PrimaryStorageClusterRefVO_.primaryStorageUuid, ps.uuid) + .eq(PrimaryStorageClusterRefVO_.clusterUuid, cluster.uuid) + .isExists()) { + attachPrimaryStorageToCluster { + primaryStorageUuid = ps.uuid + clusterUuid = cluster.uuid + } + } + env.afterSimulator(ZbsStorageController.CHECK_HOST_STORAGE_CONNECTION_PATH) { rsp, HttpEntity e -> + ZbsStorageController.CheckHostStorageConnectionRsp checkHostStorageConnectionRsp = new ZbsStorageController.CheckHostStorageConnectionRsp() + checkHostStorageConnectionRsp.success = true + return checkHostStorageConnectionRsp + } + reconnectHost { + uuid = kvm.uuid + } + retryInSecs { + assert Q.New(ExternalPrimaryStorageHostProtocolRefVO.class) + .eq(ExternalPrimaryStorageHostProtocolRefVO_.primaryStorageUuid, ps.uuid) + .eq(ExternalPrimaryStorageHostProtocolRefVO_.hostUuid, kvm.uuid) + .eq(ExternalPrimaryStorageHostProtocolRefVO_.protocol, VolumeProtocol.CBD.toString()) + .eq(ExternalPrimaryStorageHostProtocolRefVO_.status, PrimaryStorageHostStatus.Connected) + .isExists() + } + env.cleanAfterSimulatorHandlers() + + env.message(UploadImageToRemoteTargetMsg.class) { UploadImageToRemoteTargetMsg msg, CloudBus bus -> + assert msg.remoteTargetUrl.startsWith("nbd://") + assert msg.format == "raw" + bus.reply(msg, new UploadImageToRemoteTargetReply()) + } + + VmInstanceInventory vm = createVmInstance { + name = "vm-for-managed-volume-size-sync" + instanceOfferingUuid = env.inventoryByName("instanceOffering").uuid + imageUuid = env.inventoryByName("image").uuid + l3NetworkUuids = [(env.inventoryByName("l3") as L3NetworkInventory).uuid] + primaryStorageUuidForRootVolume = ps.uuid + dataDiskOfferingUuids = [diskOffering.uuid] + hostUuid = kvm.uuid + } as VmInstanceInventory + + String volUuid = vm.allVolumes.find { it.uuid != vm.rootVolumeUuid }.uuid + String zbsPath = Q.New(VolumeVO.class) + .select(VolumeVO_.installPath) + .eq(VolumeVO_.uuid, volUuid) + .findValue() + String snapshotPath = "${zbsPath}@snap1" + + env.afterSimulator(ZbsStorageController.CREATE_SNAPSHOT_PATH) { rsp, HttpEntity e -> + ZbsStorageController.CreateSnapshotCmd cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.CreateSnapshotCmd) + ZbsStorageController.CreateSnapshotRsp createSnapshotRsp = new ZbsStorageController.CreateSnapshotRsp() + createSnapshotRsp.installPath = "${cmd.path}@${cmd.snapshot}" + createSnapshotRsp.actualSize = snapshotInitialSize + return createSnapshotRsp + } + + VolumeSnapshotInventory snapshot = createVolumeSnapshot { + name = "snap1" + volumeUuid = volUuid + } as VolumeSnapshotInventory + String snapshotUuid = snapshot.uuid + + snapshotPath = Q.New(VolumeSnapshotVO.class) + .select(VolumeSnapshotVO_.primaryStorageInstallPath) + .eq(VolumeSnapshotVO_.uuid, snapshotUuid) + .findValue() + + env.cleanAfterSimulatorHandlers() + + env.simulator(ZbsStorageController.BATCH_QUERY_VOLUME_WITH_SNAPSHOT_PATH) { HttpEntity e, EnvSpec spec -> + batchQueryCount.incrementAndGet() + def cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.BatchQueryVolumeCmd.class) + String volumeName = zbsPath.substring(zbsPath.lastIndexOf("/") + 1) + String snapshotName = snapshotPath.substring(snapshotPath.lastIndexOf("@") + 1) + String volumeAgentPath = cmd.installPaths.find { it.contains(volumeName) && !it.contains("@") } + String snapshotAgentPath = cmd.installPaths.find { it.contains(volumeName) && it.contains(snapshotName) } + assert volumeAgentPath != null + assert snapshotAgentPath != null + + def rsp = new ZbsStorageController.BatchQueryVolumeRsp() + rsp.setVolumes([(volumeAgentPath): ["length": SizeUnit.GIGABYTE.toByte(8), "usedSize": volumeActualSize]]) + rsp.setSnapshots([(snapshotAgentPath): ["usedSize": snapshotSyncedSize]]) + return rsp + } + + VolumeGlobalConfig.AUTO_REFRESH_VOLUME_SCOPE.updateValue("AllActive") + updateRefreshVolumeSizeIntervalForTest("2") + updateRefreshVolumeSizeIntervalForTest("1") + + retryInSecs { + assert Q.New(VolumeSnapshotVO.class) + .select(VolumeSnapshotVO_.size) + .eq(VolumeSnapshotVO_.uuid, snapshotUuid) + .findValue() == snapshotSyncedSize + assert Q.New(VolumeVO.class) + .select(VolumeVO_.actualSize) + .eq(VolumeVO_.uuid, volUuid) + .findValue() == volumeActualSize + snapshotSyncedSize + assert env.verifySimulator(ZbsStorageController.BATCH_QUERY_VOLUME_WITH_SNAPSHOT_PATH) + assert batchQueryCount.get() >= 1 + } + + stopVmInstance { + uuid = vm.uuid + } + } finally { + disableScheduledVolumeSizeSyncForTest() + detachZbsPrimaryStorageFromClusterIfAttached() + env.cleanSimulatorAndMessageHandlers() + } + } + + private void updateRefreshVolumeSizeIntervalForTest(String interval) { + List validators = VolumeGlobalConfig.REFRESH_VOLUME_SIZE_INTERVAL.validators + VolumeGlobalConfig.REFRESH_VOLUME_SIZE_INTERVAL.validators = new ArrayList<>() + try { + VolumeGlobalConfig.REFRESH_VOLUME_SIZE_INTERVAL.updateValue(interval) + } finally { + VolumeGlobalConfig.REFRESH_VOLUME_SIZE_INTERVAL.validators = validators + } + } + + private void disableScheduledVolumeSizeSyncForTest() { + VolumeGlobalConfig.AUTO_REFRESH_VOLUME_SCOPE.updateValue("None") + updateRefreshVolumeSizeIntervalForTest("3600") + } + + private void detachZbsPrimaryStorageFromClusterIfAttached() { + if (ps == null || cluster == null) { + return + } + + if (Q.New(PrimaryStorageClusterRefVO.class) + .eq(PrimaryStorageClusterRefVO_.primaryStorageUuid, ps.uuid) + .eq(PrimaryStorageClusterRefVO_.clusterUuid, cluster.uuid) + .isExists()) { + detachPrimaryStorageFromCluster { + primaryStorageUuid = ps.uuid + clusterUuid = cluster.uuid + } + } + } + void deleteVolume(String volUuid) { deleteDataVolume { uuid = volUuid