vmInstanceMetadata) {
+ this.vmInstanceMetadata = vmInstanceMetadata;
+ }
+
+ public static APIScanVmInstanceMetadataFromPrimaryStorageReply __example__() {
+ APIScanVmInstanceMetadataFromPrimaryStorageReply reply = new APIScanVmInstanceMetadataFromPrimaryStorageReply();
+ return reply;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
new file mode 100644
index 00000000000..7cfe306d3e8
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
@@ -0,0 +1,32 @@
+package org.zstack.header.storage.primary
+
+import org.zstack.header.storage.primary.VmMetadataScanEntry
+import org.zstack.header.errorcode.ErrorCode
+
+doc {
+
+ title "扫描主存储上的云主机元数据返回"
+
+ ref {
+ name "vmInstanceMetadata"
+ path "org.zstack.header.storage.primary.APIScanVmInstanceMetadataFromPrimaryStorageReply.vmInstanceMetadata"
+ desc "云主机元数据摘要列表"
+ type "List"
+ since "5.0.0"
+ clz VmMetadataScanEntry.class
+ }
+ field {
+ name "success"
+ desc "操作是否成功"
+ type "boolean"
+ since "5.0.0"
+ }
+ ref {
+ name "error"
+ path "org.zstack.header.storage.primary.APIScanVmInstanceMetadataFromPrimaryStorageReply.error"
+ desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null"
+ type "ErrorCode"
+ since "5.0.0"
+ clz ErrorCode.class
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
new file mode 100644
index 00000000000..c50b17223d0
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
@@ -0,0 +1,43 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.NeedReplyMessage;
+
+public class CleanupVmInstanceMetadataOnPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage {
+ private String primaryStorageUuid;
+ private String vmUuid;
+ private String rootVolumeUuid;
+ private String metadataPath;
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ return primaryStorageUuid;
+ }
+
+ public void setPrimaryStorageUuid(String primaryStorageUuid) {
+ this.primaryStorageUuid = primaryStorageUuid;
+ }
+
+ public String getVmUuid() {
+ return vmUuid;
+ }
+
+ public void setVmUuid(String vmUuid) {
+ this.vmUuid = vmUuid;
+ }
+
+ public String getRootVolumeUuid() {
+ return rootVolumeUuid;
+ }
+
+ public void setRootVolumeUuid(String rootVolumeUuid) {
+ this.rootVolumeUuid = rootVolumeUuid;
+ }
+
+ public String getMetadataPath() {
+ return metadataPath;
+ }
+
+ public void setMetadataPath(String metadataPath) {
+ this.metadataPath = metadataPath;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
new file mode 100644
index 00000000000..05bba3ac430
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
@@ -0,0 +1,6 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.MessageReply;
+
+public class CleanupVmInstanceMetadataOnPrimaryStorageReply extends MessageReply {
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
new file mode 100644
index 00000000000..67ea49e5e18
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
@@ -0,0 +1,44 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.NeedReplyMessage;
+
+
+public class GetVmInstanceMetadataFromPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage {
+ private String primaryStorageUuid;
+ private String metadataPath;
+ private String rootVolumeUuid;
+ private String hostUuid;
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ return primaryStorageUuid;
+ }
+
+ public void setPrimaryStorageUuid(String primaryStorageUuid) {
+ this.primaryStorageUuid = primaryStorageUuid;
+ }
+
+ public String getMetadataPath() {
+ return metadataPath;
+ }
+
+ public void setMetadataPath(String metadataPath) {
+ this.metadataPath = metadataPath;
+ }
+
+ public String getRootVolumeUuid() {
+ return rootVolumeUuid;
+ }
+
+ public void setRootVolumeUuid(String rootVolumeUuid) {
+ this.rootVolumeUuid = rootVolumeUuid;
+ }
+
+ public String getHostUuid() {
+ return hostUuid;
+ }
+
+ public void setHostUuid(String hostUuid) {
+ this.hostUuid = hostUuid;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
new file mode 100644
index 00000000000..c164a99792d
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
@@ -0,0 +1,15 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.MessageReply;
+
+public class GetVmInstanceMetadataFromPrimaryStorageReply extends MessageReply {
+ private String metadata;
+
+ public String getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(String metadata) {
+ this.metadata = metadata;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java b/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
new file mode 100644
index 00000000000..ed09f15eb4d
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
@@ -0,0 +1,21 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.NeedReplyMessage;
+import org.zstack.header.vm.VmInstanceMessage;
+
+public class ReadVmInstanceMetadataMsg extends NeedReplyMessage implements VmInstanceMessage {
+ private String uuid;
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ @Override
+ public String getVmInstanceUuid() {
+ return uuid;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java b/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
new file mode 100644
index 00000000000..04462f849ad
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
@@ -0,0 +1,15 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.MessageReply;
+
+public class ReadVmInstanceMetadataReply extends MessageReply {
+ private String vmMetadata;
+
+ public String getVmMetadata() {
+ return vmMetadata;
+ }
+
+ public void setVmMetadata(String vmMetadata) {
+ this.vmMetadata = vmMetadata;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
new file mode 100644
index 00000000000..f9dd68d2934
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
@@ -0,0 +1,70 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.NeedReplyMessage;
+
+import java.util.List;
+
+/**
+ * 请求目标主存储对指定文件做 backing file 前缀替换(prefix rebase)。
+ *
+ * 各存储插件(LocalStorage / SharedBlock / NFS)自行选择 host、构造 agent command 并发送。
+ */
+public class RebaseVolumeBackingFileOnPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage {
+ private String primaryStorageUuid;
+
+ /**
+ * volume + snapshot 的 installPath 列表(已做路径替换的逻辑路径)。
+ * LocalStorage / NFS 下即绝对路径;SharedBlock 下为 sharedblock:// scheme 路径,由插件内部转绝对路径。
+ */
+ private List installPaths;
+
+ /** 旧路径前缀 */
+ private String oldPrefix;
+
+ /** 新路径前缀 */
+ private String newPrefix;
+
+ /** 注册请求指定的 hostUuid(LocalStorage 需要,其他存储可忽略) */
+ private String hostUuid;
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ return primaryStorageUuid;
+ }
+
+ public void setPrimaryStorageUuid(String primaryStorageUuid) {
+ this.primaryStorageUuid = primaryStorageUuid;
+ }
+
+ public List getInstallPaths() {
+ return installPaths;
+ }
+
+ public void setInstallPaths(List installPaths) {
+ this.installPaths = installPaths;
+ }
+
+ public String getOldPrefix() {
+ return oldPrefix;
+ }
+
+ public void setOldPrefix(String oldPrefix) {
+ this.oldPrefix = oldPrefix;
+ }
+
+ public String getNewPrefix() {
+ return newPrefix;
+ }
+
+ public void setNewPrefix(String newPrefix) {
+ this.newPrefix = newPrefix;
+ }
+
+ public String getHostUuid() {
+ return hostUuid;
+ }
+
+ public void setHostUuid(String hostUuid) {
+ this.hostUuid = hostUuid;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
new file mode 100644
index 00000000000..44043421f09
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
@@ -0,0 +1,15 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.MessageReply;
+
+public class RebaseVolumeBackingFileOnPrimaryStorageReply extends MessageReply {
+ private int rebasedCount;
+
+ public int getRebasedCount() {
+ return rebasedCount;
+ }
+
+ public void setRebasedCount(int rebasedCount) {
+ this.rebasedCount = rebasedCount;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
new file mode 100644
index 00000000000..052e77f7507
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
@@ -0,0 +1,25 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.NeedReplyMessage;
+
+public class ScanVmInstanceMetadataFromPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage {
+ private String primaryStorageUuid;
+ private String metadataDir;
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ return primaryStorageUuid;
+ }
+
+ public void setPrimaryStorageUuid(String primaryStorageUuid) {
+ this.primaryStorageUuid = primaryStorageUuid;
+ }
+
+ public String getMetadataDir() {
+ return metadataDir;
+ }
+
+ public void setMetadataDir(String metadataDir) {
+ this.metadataDir = metadataDir;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
new file mode 100644
index 00000000000..655de95bc6c
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
@@ -0,0 +1,18 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.MessageReply;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ScanVmInstanceMetadataFromPrimaryStorageReply extends MessageReply {
+ private List vmInstanceMetadata = new ArrayList<>();
+
+ public List getVmInstanceMetadata() {
+ return vmInstanceMetadata;
+ }
+
+ public void setVmInstanceMetadata(List vmInstanceMetadata) {
+ this.vmInstanceMetadata = vmInstanceMetadata;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java b/header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java
new file mode 100644
index 00000000000..9859bc2aca7
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java
@@ -0,0 +1,94 @@
+package org.zstack.header.storage.primary;
+
+public class VmMetadataScanEntry {
+ private String vmUuid;
+ private String vmName;
+ private String vmCategory;
+ private String architecture;
+ private String schemaVersion;
+ private String metadataPath;
+ private String hostUuid;
+ private long sizeBytes;
+ private long lastUpdateTime;
+ private boolean incomplete;
+
+ public String getVmUuid() {
+ return vmUuid;
+ }
+
+ public void setVmUuid(String vmUuid) {
+ this.vmUuid = vmUuid;
+ }
+
+ public String getVmName() {
+ return vmName;
+ }
+
+ public void setVmName(String vmName) {
+ this.vmName = vmName;
+ }
+
+ public String getVmCategory() {
+ return vmCategory;
+ }
+
+ public void setVmCategory(String vmCategory) {
+ this.vmCategory = vmCategory;
+ }
+
+ public String getArchitecture() {
+ return architecture;
+ }
+
+ public void setArchitecture(String architecture) {
+ this.architecture = architecture;
+ }
+
+ public String getSchemaVersion() {
+ return schemaVersion;
+ }
+
+ public void setSchemaVersion(String schemaVersion) {
+ this.schemaVersion = schemaVersion;
+ }
+
+ public String getMetadataPath() {
+ return metadataPath;
+ }
+
+ public void setMetadataPath(String metadataPath) {
+ this.metadataPath = metadataPath;
+ }
+
+ public String getHostUuid() {
+ return hostUuid;
+ }
+
+ public void setHostUuid(String hostUuid) {
+ this.hostUuid = hostUuid;
+ }
+
+ public long getSizeBytes() {
+ return sizeBytes;
+ }
+
+ public void setSizeBytes(long sizeBytes) {
+ this.sizeBytes = sizeBytes;
+ }
+
+ public long getLastUpdateTime() {
+ return lastUpdateTime;
+ }
+
+ public void setLastUpdateTime(long lastUpdateTime) {
+ this.lastUpdateTime = lastUpdateTime;
+ }
+
+ public boolean isIncomplete() {
+ return incomplete;
+ }
+
+ public void setIncomplete(boolean incomplete) {
+ this.incomplete = incomplete;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java
index 966a7d1030c..4a15afd4a6b 100755
--- a/header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java
+++ b/header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.*;
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -44,6 +45,7 @@
responseClass = APIDeleteVolumeSnapshotEvent.class
)
@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 6)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotBasedVmUuidFromApiResolver")
public class APIDeleteVolumeSnapshotMsg extends APIDeleteMessage implements DeleteVolumeSnapshotMessage {
/**
* @desc volume snapshot uuid
diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
index 744f13038b6..28f7fae4106 100755
--- a/header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
+++ b/header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
@@ -8,6 +8,7 @@
import org.zstack.header.other.APIAuditor;
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import org.zstack.header.volume.VolumeVO;
import java.util.concurrent.TimeUnit;
@@ -46,6 +47,7 @@
responseClass = APIRevertVolumeFromSnapshotEvent.class
)
@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 24)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotBasedVmUuidFromApiResolver")
public class APIRevertVolumeFromSnapshotMsg extends APIMessage implements RevertVolumeSnapshotMessage, APIAuditor {
/**
* @desc volume snapshot uuid
diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java
index d5f00be9237..70a8169e96e 100644
--- a/header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java
+++ b/header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java
@@ -6,6 +6,7 @@
import org.zstack.header.message.DefaultTimeout;
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.concurrent.TimeUnit;
@@ -20,6 +21,7 @@
isAction = true
)
@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 24)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotBasedVmUuidFromApiResolver")
public class APIShrinkVolumeSnapshotMsg extends APIMessage implements VolumeSnapshotMessage {
@APIParam(resourceType = VolumeSnapshotVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java
index 870a9ceacd4..cfb61ac8d35 100755
--- a/header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java
+++ b/header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java
@@ -5,6 +5,7 @@
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by frank on 6/14/2015.
@@ -15,6 +16,7 @@
isAction = true,
responseClass = APIUpdateVolumeSnapshotEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "SnapshotBasedVmUuidFromApiResolver")
public class APIUpdateVolumeSnapshotMsg extends APIMessage implements VolumeSnapshotMessage {
@APIParam(resourceType = VolumeSnapshotVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java
index 4afc5170734..59c532f138a 100644
--- a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java
+++ b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java
@@ -7,6 +7,7 @@
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
import org.zstack.header.storage.snapshot.SnapshotBackendOperation;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.concurrent.TimeUnit;
@@ -19,6 +20,7 @@
responseClass = APIDeleteVolumeSnapshotGroupEvent.class
)
@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 3)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotGroupBasedVmUuidFromApiResolver", updateOnFailure = true)
public class APIDeleteVolumeSnapshotGroupMsg extends APIDeleteMessage implements VolumeSnapshotGroupMessage {
@APIParam(resourceType = VolumeSnapshotGroupVO.class, successIfResourceNotExisting = true)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java
index d042ee6229f..d6c9ad986e2 100644
--- a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java
+++ b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java
@@ -14,6 +14,7 @@
import org.zstack.header.rest.RestRequest;
import org.zstack.header.storage.snapshot.SnapshotBackendOperation;
import org.zstack.header.vm.VmInstanceVO;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.concurrent.TimeUnit;
@@ -25,6 +26,7 @@
)
@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 24)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotGroupBasedVmUuidFromApiResolver", updateOnFailure = true)
public class APIRevertVmFromSnapshotGroupMsg extends APIMessage implements VolumeSnapshotGroupMessage, APIAuditor {
@APIParam(resourceType = VolumeSnapshotGroupVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java
index daa39380ac1..fca12b2d074 100644
--- a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java
+++ b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java
@@ -5,6 +5,7 @@
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
import org.zstack.header.storage.snapshot.SnapshotBackendOperation;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by MaJin on 2019/7/9.
@@ -14,6 +15,7 @@
method = HttpMethod.DELETE,
responseClass = APIUngroupVolumeSnapshotGroupEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotGroupBasedVmUuidFromApiResolver")
public class APIUngroupVolumeSnapshotGroupMsg extends APIMessage implements VolumeSnapshotGroupMessage {
@APIParam(resourceType = VolumeSnapshotGroupVO.class, successIfResourceNotExisting = true)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java
index 0cee28cd6ea..34d52d63a14 100644
--- a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java
+++ b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import org.zstack.header.storage.snapshot.SnapshotBackendOperation;
/**
@@ -15,6 +16,7 @@
isAction = true,
responseClass = APIUpdateVolumeSnapshotGroupEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "SnapshotGroupBasedVmUuidFromApiResolver")
public class APIUpdateVolumeSnapshotGroupMsg extends APIMessage implements VolumeSnapshotGroupMessage {
@APIParam(required = false)
private String name;
diff --git a/header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java b/header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java
index a02a6001cf1..09c06014229 100755
--- a/header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java
+++ b/header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java
@@ -2,6 +2,7 @@
import org.springframework.http.HttpMethod;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
*/
@@ -11,6 +12,7 @@
responseClass = APICreateSystemTagEvent.class,
parameterName = "params"
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "ResourceBasedVmUuidFromApiResolver")
public class APICreateSystemTagMsg extends APIAbstractCreateTagMsg {
public static APICreateSystemTagMsg __example__() {
diff --git a/header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java b/header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java
index 170076094d9..98196f68c20 100644
--- a/header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java
+++ b/header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import org.zstack.header.vo.ResourceVO;
import java.util.List;
@@ -16,6 +17,7 @@
responseClass = APICreateSystemTagsEvent.class,
parameterName = "params"
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "ResourceBasedVmUuidFromApiResolver")
public class APICreateSystemTagsMsg extends APIMessage {
@APIParam
private String resourceType;
diff --git a/header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java b/header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
index b1159b20945..deef8615efa 100755
--- a/header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
+++ b/header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIDeleteMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
*/
@@ -12,6 +13,7 @@
method = HttpMethod.DELETE,
responseClass = APIDeleteTagEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "ResourceBasedVmUuidFromApiResolver")
public class APIDeleteTagMsg extends APIDeleteMessage {
@APIParam
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java b/header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java
index 2962ce07061..0413eeaff95 100755
--- a/header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java
+++ b/header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by frank on 8/17/2015.
@@ -14,6 +15,7 @@
isAction = true,
method = HttpMethod.PUT
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "ResourceBasedVmUuidFromApiResolver")
public class APIUpdateSystemTagMsg extends APIMessage {
@APIParam(resourceType = SystemTagVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java
index c28f55a3c6c..2c132de7731 100755
--- a/header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java
@@ -6,6 +6,7 @@
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by frank on 10/17/2015.
@@ -16,6 +17,7 @@
responseClass = APIAttachIsoToVmInstanceEvent.class,
parameterName = "null"
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIAttachIsoToVmInstanceMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String vmInstanceUuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java b/header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java
index 7bab9be06f9..3264c6de796 100755
--- a/header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java
@@ -6,6 +6,7 @@
import org.zstack.header.network.l3.L3NetworkVO;
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import org.zstack.utils.network.NicIpAddressInfo;
import java.util.List;
@@ -45,6 +46,7 @@
method = HttpMethod.POST,
responseClass = APIAttachL3NetworkToVmEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIAttachL3NetworkToVmMsg extends APIMessage implements VmInstanceMessage {
/**
* @desc vm uuid
diff --git a/header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java b/header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java
index 05b86a2bedf..d0b61bfed5b 100644
--- a/header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
@RestRequest(
path = "/vm-instances/{vmInstanceUuid}/nices/{vmNicUuid}",
@@ -11,6 +12,7 @@
responseClass = APIAttachVmNicToVmEvent.class,
parameterName = "params"
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIAttachVmNicToVmMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmNicVO.class)
diff --git a/header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java b/header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
index aa0e68f9211..5ad4fb2c7b8 100755
--- a/header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
@@ -5,6 +5,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by frank on 7/16/2015.
@@ -15,6 +16,7 @@
method = HttpMethod.PUT,
responseClass = APIChangeInstanceOfferingEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIChangeInstanceOfferingMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String vmInstanceUuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java b/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
index 60ded8149d2..a1647122d8c 100644
--- a/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
@@ -6,6 +6,7 @@
import org.zstack.header.network.l3.L3NetworkVO;
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.List;
import java.util.Map;
@@ -16,6 +17,7 @@
method = HttpMethod.POST,
responseClass = APIChangeVmNicNetworkEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "NicBasedVmUuidFromApiResolver")
public class APIChangeVmNicNetworkMsg extends APIMessage implements VmInstanceMessage{
@APIParam(resourceType = VmNicVO.class)
private String vmNicUuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java b/header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
index 6c1594fed23..051dfcc785c 100644
--- a/header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
@@ -9,6 +9,7 @@
import org.zstack.header.other.APIMultiAuditor;
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.ArrayList;
import java.util.List;
@@ -22,6 +23,7 @@
responseClass = APIChangeVmNicStateEvent.class,
isAction = true
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "NicBasedVmUuidFromApiResolver")
public class APIChangeVmNicStateMsg extends APIMessage implements VmInstanceMessage, APIMultiAuditor {
@APIParam(resourceType = VmNicVO.class)
private String vmNicUuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
index 1a128bfaf84..df22290f8dc 100644
--- a/header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
@@ -7,6 +7,7 @@
import org.zstack.header.other.APIAuditor;
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
@RestRequest(
path = "/vm-instances/{templatedVmInstanceUuid}/convert-to-vmInstance",
@@ -14,6 +15,7 @@
responseClass = APIConvertTemplatedVmInstanceToVmInstanceEvent.class,
parameterName = "params"
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIConvertTemplatedVmInstanceToVmInstanceMsg extends APIMessage implements VmInstanceMessage, APIAuditor {
@APIParam(resourceType = TemplatedVmInstanceVO.class)
private String templatedVmInstanceUuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
index 2b9191824b7..798b54c1b3e 100644
--- a/header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
@@ -6,6 +6,7 @@
import org.zstack.header.message.APIParam;
import org.zstack.header.other.APIAuditor;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
@RestRequest(
path = "/vm-instances/{vmInstanceUuid}/convert-to-templatedVmInstance",
@@ -13,6 +14,7 @@
responseClass = APIConvertVmInstanceToTemplatedVmInstanceEvent.class,
parameterName = "params"
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIConvertVmInstanceToTemplatedVmInstanceMsg extends APIMessage implements VmInstanceMessage, APIAuditor {
@APIParam(resourceType = VmInstanceVO.class)
private String vmInstanceUuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.java b/header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.java
index 4e9cd9c8545..0c5c6698250 100644
--- a/header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.java
@@ -9,6 +9,7 @@
import org.zstack.header.other.APIAuditor;
import org.zstack.header.rest.RestRequest;
import org.zstack.header.tag.TagResourceType;
+import org.zstack.header.vm.metadata.MetadataImpact;
@TagResourceType(VmNicVO.class)
@@ -18,6 +19,7 @@
responseClass = APICreateVmNicEvent.class,
parameterName = "params"
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APICreateVmNicMsg extends APICreateMessage implements APIAuditor {
/**
diff --git a/header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java b/header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java
index a2b73a69527..1ffc496f483 100644
--- a/header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java
@@ -4,12 +4,14 @@
import org.zstack.header.message.APIDeleteMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
@RestRequest(
path = "/vm-instances/{uuid}/bootmode",
method = HttpMethod.DELETE,
responseClass = APIDeleteVmBootModeEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIDeleteVmBootModeMsg extends APIDeleteMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java b/header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java
index e9d6152c7ef..4bd48ed4b37 100755
--- a/header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by root on 8/2/16.
@@ -13,6 +14,7 @@
method = HttpMethod.DELETE,
responseClass = APIDeleteVmConsolePasswordEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIDeleteVmConsolePasswordMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java b/header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java
index 611c1c13da4..fa20a680192 100755
--- a/header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIDeleteMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by frank on 2/26/2016.
@@ -13,6 +14,7 @@
method = HttpMethod.DELETE,
responseClass = APIDeleteVmHostnameEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIDeleteVmHostnameMsg extends APIDeleteMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.java b/header/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.java
index 19fc5f5e8bc..950c6ff9bff 100644
--- a/header/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.java
@@ -4,12 +4,14 @@
import org.zstack.header.message.APIDeleteMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
@RestRequest(
path = "/nics/{uuid}",
method = HttpMethod.DELETE,
responseClass = APIDeleteVmNicEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "PreCaptureNicBasedVmUuidFromApiResolver")
public class APIDeleteVmNicMsg extends APIDeleteMessage {
@APIParam(resourceType = VmNicVO.class, successIfResourceNotExisting = true)
diff --git a/header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java b/header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
index 0372c7526ab..8e78f904961 100755
--- a/header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
@@ -3,6 +3,7 @@
import org.springframework.http.HttpMethod;
import org.zstack.header.message.APIMessage;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by luchukun on 8/4/16.
@@ -12,6 +13,7 @@
method = HttpMethod.DELETE,
responseClass = APIDeleteVmSshKeyEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIDeleteVmSshKeyMsg extends APIMessage implements VmInstanceMessage {
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java b/header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
index 15d11f96647..29d2f2dc9f9 100755
--- a/header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIDeleteMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by frank on 2/26/2016.
@@ -13,6 +14,7 @@
method = HttpMethod.DELETE,
responseClass = APIDeleteVmStaticIpEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIDeleteVmStaticIpMsg extends APIDeleteMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String vmInstanceUuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java
index b93c5eb4361..b25ba80cf50 100755
--- a/header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIDeleteMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.List;
@@ -41,6 +42,7 @@
method = HttpMethod.DELETE,
responseClass = APIDestroyVmInstanceEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "DefaultVmUuidFromApiResolver")
public class APIDestroyVmInstanceMsg extends APIDeleteMessage implements VmInstanceMessage {
/**
* @desc vm uuid
diff --git a/header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java
index 52d17b61fec..8fd524ff08b 100755
--- a/header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java
@@ -7,6 +7,7 @@
import org.zstack.header.message.APIParam;
import org.zstack.header.other.APIAuditor;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by frank on 10/17/2015.
@@ -16,6 +17,7 @@
method = HttpMethod.DELETE,
responseClass = APIDetachIsoFromVmInstanceEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIDetachIsoFromVmInstanceMsg extends APIMessage implements VmInstanceMessage, APIAuditor {
@APIParam(resourceType = VmInstanceVO.class)
private String vmInstanceUuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java b/header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java
index 0d2c7d2d38f..605b5bbf24c 100755
--- a/header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java
@@ -9,6 +9,7 @@
import org.zstack.header.other.APIMultiAuditor;
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.ArrayList;
import java.util.List;
@@ -21,6 +22,7 @@
method = HttpMethod.DELETE,
responseClass = APIDetachL3NetworkFromVmEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIDetachL3NetworkFromVmMsg extends APIMessage implements VmInstanceMessage, APIMultiAuditor {
@APIParam(resourceType = VmNicVO.class)
private String vmNicUuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java
index 7b9ab119a4f..187813eb339 100755
--- a/header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java
@@ -5,6 +5,7 @@
import org.zstack.header.message.APIParam;
import org.zstack.header.message.DefaultTimeout;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.concurrent.TimeUnit;
@@ -18,6 +19,7 @@
method = HttpMethod.PUT
)
@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 3)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "DefaultVmUuidFromApiResolver")
public class APIExpungeVmInstanceMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java b/header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java
index e6ed3bbf44b..f7e361bc633 100755
--- a/header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java
@@ -6,6 +6,7 @@
import org.zstack.header.message.APIParam;
import org.zstack.header.message.DefaultTimeout;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.concurrent.TimeUnit;
@@ -44,6 +45,7 @@
)
@SkipVmTracer(replyClass = APIMigrateVmEvent.class)
@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 1)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "DefaultVmUuidFromApiResolver")
public class APIMigrateVmMsg extends APIMessage implements VmInstanceMessage, MigrateVmMessage, CheckAttachedVolumesMessage {
/**
* @desc vm uuid
diff --git a/header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java
index 7de84b5dccd..7c6c8e5fdf6 100755
--- a/header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by frank on 11/12/2015.
@@ -14,6 +15,7 @@
method = HttpMethod.PUT,
responseClass = APIRecoverVmInstanceEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIRecoverVmInstanceMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java
index 53ad2c26f4f..5c37bb7b577 100755
--- a/header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by miao on 11/3/16.
@@ -15,6 +16,7 @@
responseClass = APIReimageVmInstanceEvent.class,
category = "vmInstance"
)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "DefaultVmUuidFromApiResolver")
public class APIReimageVmInstanceMsg extends APIMessage implements VmInstanceMessage {
public String getVmInstanceUuid() {
return vmInstanceUuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java
index 0aa6e025c3c..ff9f0324892 100644
--- a/header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
@RestRequest(
path = "/vm-instances/{uuid}/actions",
@@ -11,6 +12,7 @@
isAction = true,
responseClass = APISetVmBootModeEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APISetVmBootModeMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java
index fb3d980e807..10d01b3ce49 100755
--- a/header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.List;
@@ -18,6 +19,7 @@
method = HttpMethod.PUT,
responseClass = APISetVmBootOrderEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APISetVmBootOrderMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java
index a72619161e0..296bff71204 100644
--- a/header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import org.zstack.header.volume.VolumeVO;
/**
@@ -16,6 +17,7 @@
method = HttpMethod.PUT,
responseClass = APISetVmBootVolumeEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "DefaultVmUuidFromApiResolver")
public class APISetVmBootVolumeMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String vmInstanceUuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java
index 1f25eead1db..68352ad2806 100644
--- a/header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
@RestRequest(
path = "/vm-instances/{uuid}/actions",
@@ -11,6 +12,7 @@
method = HttpMethod.PUT,
responseClass = APISetVmClockTrackEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APISetVmClockTrackMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java
index 0812289e799..18e45c98b18 100755
--- a/header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java
@@ -5,6 +5,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.io.Serializable;
@@ -18,6 +19,7 @@
method = HttpMethod.PUT,
responseClass = APISetVmConsolePasswordEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APISetVmConsolePasswordMsg extends APIMessage implements VmInstanceMessage, Serializable {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java
index 2b2e54afabc..e25665ad564 100755
--- a/header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by frank on 2/26/2016.
@@ -14,6 +15,7 @@
isAction = true,
responseClass = APISetVmHostnameEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APISetVmHostnameMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java
index c5d7ebd8e72..3033445398a 100644
--- a/header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
@RestRequest(
path = "/vm-instances/{uuid}/actions",
@@ -11,6 +12,7 @@
method = HttpMethod.PUT,
responseClass = APISetVmQxlMemoryEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APISetVmQxlMemoryMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java
index 686697b009c..ed7a167176f 100644
--- a/header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
@RestRequest(
path = "/vm-instances/{uuid}/actions",
@@ -11,6 +12,7 @@
method = HttpMethod.PUT,
responseClass = APISetVmSoundTypeEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APISetVmSoundTypeMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java
index 3b2d315ea06..d87244cd8c6 100755
--- a/header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java
@@ -5,6 +5,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by luchukun on 8/4/16.
@@ -15,6 +16,7 @@
method = HttpMethod.PUT,
responseClass = APISetVmSshKeyEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APISetVmSshKeyMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
index 094f9d4a54f..b4d67a8770f 100755
--- a/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
@@ -5,6 +5,7 @@
import org.zstack.header.message.APIParam;
import org.zstack.header.network.l3.L3NetworkVO;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by frank on 2/26/2016.
@@ -15,6 +16,7 @@
method = HttpMethod.PUT,
responseClass = APISetVmStaticIpEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APISetVmStaticIpMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String vmInstanceUuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java
index 60e9343ff38..82856bebf31 100755
--- a/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java
@@ -5,6 +5,7 @@
import org.zstack.header.message.APIParam;
import org.zstack.header.network.l3.L3NetworkVO;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by frank on 6/14/2015.
@@ -15,6 +16,7 @@
isAction = true,
responseClass = APIUpdateVmInstanceEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIUpdateVmInstanceMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java b/header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java
index de2fd27289c..508a5882666 100644
--- a/header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* @ Author : yh.w
@@ -15,6 +16,7 @@
isAction = true,
responseClass = APIUpdateVmNicDriverEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "NicBasedVmUuidFromApiResolver")
public class APIUpdateVmNicDriverMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String vmInstanceUuid;
diff --git a/header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java b/header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
index a8c6f821450..a6127313a12 100644
--- a/header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
@RestRequest(
path = "/vm-instances/{uuid}/actions",
@@ -11,6 +12,7 @@
isAction = true,
responseClass = APIUpdateVmPriorityEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "DefaultVmUuidFromApiResolver", updateOnFailure = false)
public class APIUpdateVmPriorityMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceState.java b/header/src/main/java/org/zstack/header/vm/VmInstanceState.java
index b71d8f3be45..1e5a9947ec9 100755
--- a/header/src/main/java/org/zstack/header/vm/VmInstanceState.java
+++ b/header/src/main/java/org/zstack/header/vm/VmInstanceState.java
@@ -30,7 +30,8 @@ public enum VmInstanceState {
Error(null),
NoState(VmInstanceStateEvent.noState),
Unknown(VmInstanceStateEvent.unknown),
- Crashed(VmInstanceStateEvent.crashed);
+ Crashed(VmInstanceStateEvent.crashed),
+ Registering(null);
public static List intermediateStates = new ArrayList<>();
@@ -52,6 +53,7 @@ public enum VmInstanceState {
offlineStates.add(Destroyed);
offlineStates.add(VolumeMigrating);
offlineStates.add(Crashed);
+ offlineStates.add(Registering);
Created.transactions(
new Transaction(VmInstanceStateEvent.starting, VmInstanceState.Starting),
diff --git a/header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java b/header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java
index 72f0c8e9d5a..c020f6817ef 100644
--- a/header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java
@@ -10,6 +10,7 @@
import org.zstack.header.rest.RestRequest;
import org.zstack.header.tag.TagResourceType;
import org.zstack.header.vm.VmInstanceMessage;
+import org.zstack.header.vm.metadata.MetadataImpact;
import org.zstack.header.vm.VmInstanceVO;
/**
@@ -22,6 +23,7 @@
responseClass = APICreateVmCdRomEvent.class,
parameterName = "params"
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APICreateVmCdRomMsg extends APICreateMessage implements APIAuditor, VmInstanceMessage {
@APIParam(maxLength = 255)
private String name;
diff --git a/header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java b/header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
index f5f9b0afa76..763fc8f2206 100644
--- a/header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
@@ -8,6 +8,7 @@
import org.zstack.header.rest.RestRequest;
import org.zstack.header.vm.VmInstanceMessage;
import org.zstack.header.vm.VmInstanceVO;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Create by lining at 2018/12/29
@@ -18,6 +19,7 @@
isAction = true,
responseClass = APISetVmInstanceDefaultCdRomEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APISetVmInstanceDefaultCdRomMsg extends APIMessage implements VmInstanceMessage, APIAuditor {
@APIParam(resourceType = VmCdRomVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/cdrom/APIUpdateVmCdRomMsg.java b/header/src/main/java/org/zstack/header/vm/cdrom/APIUpdateVmCdRomMsg.java
index 927426e5e9a..8dbc6157c8b 100644
--- a/header/src/main/java/org/zstack/header/vm/cdrom/APIUpdateVmCdRomMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/cdrom/APIUpdateVmCdRomMsg.java
@@ -6,6 +6,7 @@
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
import org.zstack.header.vm.VmInstanceMessage;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Create by lining at 2018/12/30
@@ -16,6 +17,7 @@
isAction = true,
responseClass = APIUpdateVmCdRomEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver")
public class APIUpdateVmCdRomMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmCdRomVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
new file mode 100644
index 00000000000..39313f598cb
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
@@ -0,0 +1,53 @@
+package org.zstack.header.vm.metadata;
+
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
+
+import java.util.List;
+
+@RestResponse(fieldsTo = {"all"})
+public class APICleanupVmInstanceMetadataEvent extends APIEvent {
+ private Integer totalCleaned;
+ private Integer totalFailed;
+ private List failedVmUuids;
+
+ public APICleanupVmInstanceMetadataEvent() {
+ super(null);
+ }
+
+ public APICleanupVmInstanceMetadataEvent(String apiId) {
+ super(apiId);
+ }
+
+ public Integer getTotalCleaned() {
+ return totalCleaned;
+ }
+
+ public void setTotalCleaned(Integer totalCleaned) {
+ this.totalCleaned = totalCleaned;
+ }
+
+ public Integer getTotalFailed() {
+ return totalFailed;
+ }
+
+ public void setTotalFailed(Integer totalFailed) {
+ this.totalFailed = totalFailed;
+ }
+
+ public List getFailedVmUuids() {
+ return failedVmUuids;
+ }
+
+ public void setFailedVmUuids(List failedVmUuids) {
+ this.failedVmUuids = failedVmUuids;
+ }
+
+ public static APICleanupVmInstanceMetadataEvent __example__() {
+ APICleanupVmInstanceMetadataEvent evt = new APICleanupVmInstanceMetadataEvent();
+ evt.totalCleaned = 5;
+ evt.totalFailed = 0;
+ evt.failedVmUuids = java.util.Collections.emptyList();
+ return evt;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
new file mode 100644
index 00000000000..3684e27594a
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
@@ -0,0 +1,42 @@
+package org.zstack.header.vm.metadata
+
+import java.lang.Integer
+import org.zstack.header.errorcode.ErrorCode
+
+doc {
+
+ title "清理云主机元数据返回"
+
+ field {
+ name "totalCleaned"
+ desc "成功清理的元数据数量"
+ type "Integer"
+ since "5.0.0"
+ }
+ field {
+ name "totalFailed"
+ desc "清理失败的元数据数量"
+ type "Integer"
+ since "5.0.0"
+ }
+ field {
+ name "failedVmUuids"
+ desc "清理失败的云主机UUID列表"
+ type "List"
+ since "5.0.0"
+ }
+ field {
+ name "success"
+ desc ""
+ type "boolean"
+ since "5.0.0"
+ }
+ ref {
+ name "error"
+ path "org.zstack.header.vm.metadata.APICleanupVmInstanceMetadataEvent.error"
+ desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null"
+ type "ErrorCode"
+ since "5.0.0"
+ clz ErrorCode.class
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
new file mode 100644
index 00000000000..5823f66b78f
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
@@ -0,0 +1,34 @@
+package org.zstack.header.vm.metadata;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.VmInstanceVO;
+
+import java.util.List;
+
+@RestRequest(
+ path = "/vm-instances/metadata/cleanup",
+ method = HttpMethod.PUT,
+ responseClass = APICleanupVmInstanceMetadataEvent.class,
+ isAction = true
+)
+public class APICleanupVmInstanceMetadataMsg extends APIMessage {
+ @APIParam(resourceType = VmInstanceVO.class, nonempty = true)
+ private List vmUuids;
+
+ public List getVmUuids() {
+ return vmUuids;
+ }
+
+ public void setVmUuids(List vmUuids) {
+ this.vmUuids = vmUuids;
+ }
+
+ public static APICleanupVmInstanceMetadataMsg __example__() {
+ APICleanupVmInstanceMetadataMsg msg = new APICleanupVmInstanceMetadataMsg();
+ msg.vmUuids = java.util.Arrays.asList(uuid(), uuid());
+ return msg;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..0c29f0d6106
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
@@ -0,0 +1,58 @@
+package org.zstack.header.vm.metadata
+
+import org.zstack.header.vm.metadata.APICleanupVmInstanceMetadataEvent
+
+doc {
+ title "清理云主机元数据"
+
+ category "云主机"
+
+ desc """清理指定云主机在主存储上的元数据文件"""
+
+ rest {
+ request {
+ url "PUT /v1/vm-instances/metadata/cleanup"
+
+ header (Authorization: 'OAuth the-session-uuid')
+
+ clz APICleanupVmInstanceMetadataMsg.class
+
+ desc """"""
+
+ params {
+
+ column {
+ name "vmUuids"
+ enclosedIn "cleanupVmInstanceMetadata"
+ desc "需要清理元数据的云主机UUID列表"
+ location "body"
+ type "List"
+ optional false
+ since "5.0.0"
+ }
+ column {
+ name "systemTags"
+ enclosedIn ""
+ desc "系统标签"
+ location "body"
+ type "List"
+ optional true
+ since "5.0.0"
+ }
+ column {
+ name "userTags"
+ enclosedIn ""
+ desc "用户标签"
+ location "body"
+ type "List"
+ optional true
+ since "5.0.0"
+ }
+ }
+ }
+
+ response {
+ clz APICleanupVmInstanceMetadataEvent.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
new file mode 100644
index 00000000000..6aacc147e13
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
@@ -0,0 +1,37 @@
+package org.zstack.header.vm.metadata;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.message.APISyncCallMessage;
+import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.VmInstanceMessage;
+import org.zstack.header.vm.VmInstanceVO;
+
+@RestRequest(
+ path = "/primary-storage/vm-instances/metadata",
+ method = HttpMethod.GET,
+ responseClass = APIGetVmInstanceMetadataFromPrimaryStorageReply.class
+)
+public class APIGetVmInstanceMetadataFromPrimaryStorageMsg extends APISyncCallMessage implements VmInstanceMessage {
+ @APIParam(resourceType = VmInstanceVO.class)
+ private String uuid;
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ @Override
+ public String getVmInstanceUuid() {
+ return uuid;
+ }
+
+ public static APIGetVmInstanceMetadataFromPrimaryStorageMsg __example__() {
+ APIGetVmInstanceMetadataFromPrimaryStorageMsg msg = new APIGetVmInstanceMetadataFromPrimaryStorageMsg();
+ msg.setUuid(uuid(VmInstanceVO.class));
+ return msg;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..b8f87cb3ee1
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
@@ -0,0 +1,58 @@
+package org.zstack.header.vm.metadata
+
+import org.zstack.header.vm.metadata.APIGetVmInstanceMetadataFromPrimaryStorageReply
+
+doc {
+ title "获取云主机元数据"
+
+ category "主存储"
+
+ desc """从主存储获取指定云主机的元数据内容"""
+
+ rest {
+ request {
+ url "GET /v1/primary-storage/vm-instances/metadata"
+
+ header (Authorization: 'OAuth the-session-uuid')
+
+ clz APIGetVmInstanceMetadataFromPrimaryStorageMsg.class
+
+ desc """"""
+
+ params {
+
+ column {
+ name "uuid"
+ enclosedIn ""
+ desc "云主机UUID"
+ location "query"
+ type "String"
+ optional false
+ since "5.0.0"
+ }
+ column {
+ name "systemTags"
+ enclosedIn ""
+ desc "系统标签"
+ location "query"
+ type "List"
+ optional true
+ since "5.0.0"
+ }
+ column {
+ name "userTags"
+ enclosedIn ""
+ desc "用户标签"
+ location "query"
+ type "List"
+ optional true
+ since "5.0.0"
+ }
+ }
+ }
+
+ response {
+ clz APIGetVmInstanceMetadataFromPrimaryStorageReply.class
+ }
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReply.java
new file mode 100644
index 00000000000..b87e09d2dd4
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReply.java
@@ -0,0 +1,23 @@
+package org.zstack.header.vm.metadata;
+
+import org.zstack.header.message.APIReply;
+import org.zstack.header.rest.RestResponse;
+
+
+@RestResponse(fieldsTo = {"all"})
+public class APIGetVmInstanceMetadataFromPrimaryStorageReply extends APIReply {
+ private String metadata;
+
+ public String getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(String metadata) {
+ this.metadata = metadata;
+ }
+
+ public static APIGetVmInstanceMetadataFromPrimaryStorageReply __example__() {
+ APIGetVmInstanceMetadataFromPrimaryStorageReply reply = new APIGetVmInstanceMetadataFromPrimaryStorageReply();
+ return reply;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
new file mode 100644
index 00000000000..1da3de4ee9e
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
@@ -0,0 +1,29 @@
+package org.zstack.header.vm.metadata
+
+import org.zstack.header.errorcode.ErrorCode
+
+doc {
+
+ title "获取云主机元数据返回"
+
+ field {
+ name "metadata"
+ desc "云主机元数据内容"
+ type "String"
+ since "5.0.0"
+ }
+ field {
+ name "success"
+ desc ""
+ type "boolean"
+ since "5.0.0"
+ }
+ ref {
+ name "error"
+ path "org.zstack.header.vm.metadata.APIGetVmInstanceMetadataFromPrimaryStorageReply.error"
+ desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null"
+ type "ErrorCode"
+ since "5.0.0"
+ clz ErrorCode.class
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
new file mode 100644
index 00000000000..e0e62c75eea
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
@@ -0,0 +1,46 @@
+package org.zstack.header.vm.metadata;
+
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
+import org.zstack.header.vm.VmInstanceInventory;
+
+import java.util.List;
+
+@RestResponse(fieldsTo = {"all"})
+public class APIRegisterVmInstanceFromMetadataEvent extends APIEvent {
+ private VmInstanceInventory inventory;
+ private List warnings;
+
+ public APIRegisterVmInstanceFromMetadataEvent() {
+ super(null);
+ }
+
+ public APIRegisterVmInstanceFromMetadataEvent(String apiId) {
+ super(apiId);
+ }
+
+ public VmInstanceInventory getInventory() {
+ return inventory;
+ }
+
+ public void setInventory(VmInstanceInventory inventory) {
+ this.inventory = inventory;
+ }
+
+ public List getWarnings() {
+ return warnings;
+ }
+
+ public void setWarnings(List warnings) {
+ this.warnings = warnings;
+ }
+
+ public static APIRegisterVmInstanceFromMetadataEvent __example__() {
+ APIRegisterVmInstanceFromMetadataEvent evt = new APIRegisterVmInstanceFromMetadataEvent();
+ VmInstanceInventory vm = new VmInstanceInventory();
+ vm.setUuid(uuid());
+ vm.setName("recovered-vm");
+ evt.setInventory(vm);
+ return evt;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
new file mode 100644
index 00000000000..fc9a8f43001
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
@@ -0,0 +1,38 @@
+package org.zstack.header.vm.metadata
+
+import org.zstack.header.vm.VmInstanceInventory
+import org.zstack.header.errorcode.ErrorCode
+
+doc {
+
+ title "从元数据注册云主机返回"
+
+ ref {
+ name "inventory"
+ path "org.zstack.header.vm.metadata.APIRegisterVmInstanceFromMetadataEvent.inventory"
+ desc "云主机详情"
+ type "VmInstanceInventory"
+ since "5.0.0"
+ clz VmInstanceInventory.class
+ }
+ field {
+ name "warnings"
+ desc "注册过程中的警告信息列表"
+ type "List"
+ since "5.0.0"
+ }
+ field {
+ name "success"
+ desc "操作是否成功"
+ type "boolean"
+ since "5.0.0"
+ }
+ ref {
+ name "error"
+ path "org.zstack.header.vm.metadata.APIRegisterVmInstanceFromMetadataEvent.error"
+ desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null"
+ type "ErrorCode"
+ since "5.0.0"
+ clz ErrorCode.class
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java
new file mode 100644
index 00000000000..544ee0c2c79
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java
@@ -0,0 +1,102 @@
+package org.zstack.header.vm.metadata;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.message.DefaultTimeout;
+import org.zstack.header.rest.RestRequest;
+import org.zstack.header.storage.primary.PrimaryStorageVO;
+import org.zstack.header.vm.VmInstanceVO;
+import org.zstack.header.zone.ZoneVO;
+import org.zstack.header.cluster.ClusterVO;
+import org.zstack.header.host.HostVO;
+import org.zstack.header.tag.TagResourceType;
+
+import java.util.concurrent.TimeUnit;
+
+@TagResourceType(VmInstanceVO.class)
+@RestRequest(
+ path = "/vm-instances/metadata/register",
+ method = HttpMethod.POST,
+ responseClass = APIRegisterVmInstanceFromMetadataEvent.class,
+ parameterName = "params"
+)
+@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 3)
+public class APIRegisterVmInstanceFromMetadataMsg extends APIMessage {
+ @APIParam(nonempty = true, maxLength = 2048)
+ private String metadataPath;
+
+ @APIParam(resourceType = PrimaryStorageVO.class)
+ private String primaryStorageUuid;
+
+ @APIParam(resourceType = ZoneVO.class)
+ private String zoneUuid;
+
+ @APIParam(resourceType = ClusterVO.class)
+ private String clusterUuid;
+
+ @APIParam(resourceType = HostVO.class)
+ private String hostUuid;
+
+ @APIParam(required = false, maxLength = 255)
+ private String name;
+
+ public String getMetadataPath() {
+ return metadataPath;
+ }
+
+ public void setMetadataPath(String metadataPath) {
+ this.metadataPath = metadataPath;
+ }
+
+ public String getPrimaryStorageUuid() {
+ return primaryStorageUuid;
+ }
+
+ public void setPrimaryStorageUuid(String primaryStorageUuid) {
+ this.primaryStorageUuid = primaryStorageUuid;
+ }
+
+ public String getZoneUuid() {
+ return zoneUuid;
+ }
+
+ public void setZoneUuid(String zoneUuid) {
+ this.zoneUuid = zoneUuid;
+ }
+
+ public String getClusterUuid() {
+ return clusterUuid;
+ }
+
+ public void setClusterUuid(String clusterUuid) {
+ this.clusterUuid = clusterUuid;
+ }
+
+ public String getHostUuid() {
+ return hostUuid;
+ }
+
+ public void setHostUuid(String hostUuid) {
+ this.hostUuid = hostUuid;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public static APIRegisterVmInstanceFromMetadataMsg __example__() {
+ APIRegisterVmInstanceFromMetadataMsg msg = new APIRegisterVmInstanceFromMetadataMsg();
+ msg.metadataPath = "/vm-metadata/vm-uuid/metadata.json";
+ msg.primaryStorageUuid = uuid(PrimaryStorageVO.class);
+ msg.zoneUuid = uuid(ZoneVO.class);
+ msg.clusterUuid = uuid(ClusterVO.class);
+ msg.hostUuid = uuid(HostVO.class);
+ msg.name = "my-restored-vm";
+ return msg;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..b14a849ce15
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
@@ -0,0 +1,112 @@
+package org.zstack.header.vm.metadata
+
+import org.zstack.header.vm.metadata.APIRegisterVmInstanceFromMetadataEvent
+
+doc {
+ title "从元数据注册云主机(RegisterVmInstanceFromMetadata)"
+
+ category "云主机"
+
+ desc """根据主存储上的元数据文件注册(恢复)一台云主机"""
+
+ rest {
+ request {
+ url "POST /v1/vm-instances/metadata/register"
+
+ header (Authorization: 'OAuth the-session-uuid')
+
+ clz APIRegisterVmInstanceFromMetadataMsg.class
+
+ desc """"""
+
+ params {
+
+ column {
+ name "metadataPath"
+ enclosedIn "params"
+ desc "元数据文件在主存储上的路径"
+ location "body"
+ type "String"
+ optional false
+ since "5.0.0"
+ }
+ column {
+ name "primaryStorageUuid"
+ enclosedIn "params"
+ desc "目标主存储UUID"
+ location "body"
+ type "String"
+ optional false
+ since "5.0.0"
+ }
+ column {
+ name "zoneUuid"
+ enclosedIn "params"
+ desc "区域UUID"
+ location "body"
+ type "String"
+ optional false
+ since "5.0.0"
+ }
+ column {
+ name "clusterUuid"
+ enclosedIn "params"
+ desc "集群UUID"
+ location "body"
+ type "String"
+ optional false
+ since "5.0.0"
+ }
+ column {
+ name "hostUuid"
+ enclosedIn "params"
+ desc "物理机UUID"
+ location "body"
+ type "String"
+ optional false
+ since "5.0.0"
+ }
+ column {
+ name "name"
+ enclosedIn "params"
+ desc "云主机名称"
+ location "body"
+ type "String"
+ optional true
+ since "5.0.0"
+ }
+ column {
+ name "tagUuids"
+ enclosedIn "params"
+ desc "标签UUID列表"
+ location "body"
+ type "List"
+ optional true
+ since "5.0.0"
+ }
+ column {
+ name "systemTags"
+ enclosedIn ""
+ desc "系统标签"
+ location "body"
+ type "List"
+ optional true
+ since "5.0.0"
+ }
+ column {
+ name "userTags"
+ enclosedIn ""
+ desc "用户标签"
+ location "body"
+ type "List"
+ optional true
+ since "5.0.0"
+ }
+ }
+ }
+
+ response {
+ clz APIRegisterVmInstanceFromMetadataEvent.class
+ }
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
new file mode 100644
index 00000000000..897f3a0ef4e
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
@@ -0,0 +1,19 @@
+package org.zstack.header.vm.metadata;
+
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
+
+@RestResponse(fieldsTo = {"all"})
+public class APIUpdateVmInstanceMetadataEvent extends APIEvent {
+ public APIUpdateVmInstanceMetadataEvent() {
+ super(null);
+ }
+
+ public APIUpdateVmInstanceMetadataEvent(String apiId) {
+ super(apiId);
+ }
+
+ public static APIUpdateVmInstanceMetadataEvent __example__() {
+ return new APIUpdateVmInstanceMetadataEvent();
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
new file mode 100644
index 00000000000..e6d90be232e
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
@@ -0,0 +1,23 @@
+package org.zstack.header.vm.metadata
+
+import org.zstack.header.errorcode.ErrorCode
+
+doc {
+
+ title "更新云主机元数据返回"
+
+ field {
+ name "success"
+ desc "操作是否成功"
+ type "boolean"
+ since "5.0.0"
+ }
+ ref {
+ name "error"
+ path "org.zstack.header.vm.metadata.APIUpdateVmInstanceMetadataEvent.error"
+ desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null"
+ type "ErrorCode"
+ since "5.0.0"
+ clz ErrorCode.class
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
new file mode 100644
index 00000000000..46e529b4fbd
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
@@ -0,0 +1,35 @@
+package org.zstack.header.vm.metadata;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.VmInstanceVO;
+
+import java.util.Collections;
+import java.util.List;
+
+@RestRequest(
+ path = "/vm-instances/metadata/actions",
+ method = HttpMethod.PUT,
+ responseClass = APIUpdateVmInstanceMetadataEvent.class,
+ isAction = true
+)
+public class APIUpdateVmInstanceMetadataMsg extends APIMessage {
+ @APIParam(resourceType = VmInstanceVO.class, nonempty = true)
+ private List vmUuids;
+
+ public List getVmUuids() {
+ return vmUuids;
+ }
+
+ public void setVmUuids(List vmUuids) {
+ this.vmUuids = vmUuids;
+ }
+
+ public static APIUpdateVmInstanceMetadataMsg __example__() {
+ APIUpdateVmInstanceMetadataMsg msg = new APIUpdateVmInstanceMetadataMsg();
+ msg.vmUuids = Collections.singletonList(uuid());
+ return msg;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..4ac5e855638
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
@@ -0,0 +1,56 @@
+package org.zstack.header.vm.metadata
+
+doc {
+ title "更新云主机元数据"
+
+ category "云主机"
+
+ desc """立即触发指定云主机的元数据更新"""
+
+ rest {
+ request {
+ url "PUT /v1/vm-instances/metadata/actions"
+
+ header (Authorization: 'OAuth the-session-uuid')
+
+ clz APIUpdateVmInstanceMetadataMsg.class
+
+ desc """"""
+
+ params {
+
+ column {
+ name "vmUuids"
+ enclosedIn "updateVmMetadata"
+ desc "需要更新元数据的云主机UUID列表"
+ location "body"
+ type "List"
+ optional false
+ since "5.0.0"
+ }
+ column {
+ name "systemTags"
+ enclosedIn ""
+ desc "系统标签"
+ location "body"
+ type "List"
+ optional true
+ since "5.0.0"
+ }
+ column {
+ name "userTags"
+ enclosedIn ""
+ desc "用户标签"
+ location "body"
+ type "List"
+ optional true
+ since "5.0.0"
+ }
+ }
+ }
+
+ response {
+ clz APIUpdateVmInstanceMetadataEvent.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java b/header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
new file mode 100644
index 00000000000..8cc152d01bd
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
@@ -0,0 +1,28 @@
+package org.zstack.header.vm.metadata;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface MetadataImpact {
+ Impact value();
+
+ /**
+ * Spring bean name of the {@link VmUuidFromApiResolver} that extracts vmUuid(s)
+ * from this API message. Must be specified for {@link Impact#CONFIG} and
+ * {@link Impact#STORAGE}; ignored for {@link Impact#NONE}.
+ *
+ * The bean must be registered in Spring XML. Interceptor looks it up at
+ * startup via {@code ComponentLoader.getComponentByBeanName()}.
+ */
+ String resolver() default "";
+
+ boolean updateOnFailure() default false;
+
+ enum Impact {
+ NONE,
+ CONFIG,
+ STORAGE
+ }
+}
\ No newline at end of file
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java b/header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
new file mode 100644
index 00000000000..06b63852add
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
@@ -0,0 +1,8 @@
+package org.zstack.header.vm.metadata;
+
+public class ResourceMetadata {
+ public String resourceUuid;
+ public String vo;
+ public String systemTags;
+ public String resourceConfigs;
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
new file mode 100644
index 00000000000..bf3956f41fc
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
@@ -0,0 +1,26 @@
+package org.zstack.header.vm.metadata;
+
+import org.zstack.header.message.NeedReplyMessage;
+import org.zstack.header.vm.VmInstanceMessage;
+
+public class UpdateVmInstanceMetadataMsg extends NeedReplyMessage implements VmInstanceMessage {
+ private String vmInstanceUuid;
+ private boolean storageStructureChange;
+
+ @Override
+ public String getVmInstanceUuid() {
+ return vmInstanceUuid;
+ }
+
+ public void setVmInstanceUuid(String vmInstanceUuid) {
+ this.vmInstanceUuid = vmInstanceUuid;
+ }
+
+ public boolean isStorageStructureChange() {
+ return storageStructureChange;
+ }
+
+ public void setStorageStructureChange(boolean storageStructureChange) {
+ this.storageStructureChange = storageStructureChange;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
new file mode 100644
index 00000000000..ba12e4b918e
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
@@ -0,0 +1,98 @@
+package org.zstack.header.vm.metadata;
+
+import org.zstack.header.message.NeedReplyMessage;
+import org.zstack.header.storage.primary.PrimaryStorageMessage;
+
+public class UpdateVmInstanceMetadataOnPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage {
+ private String primaryStorageUuid;
+ private String vmInstanceUuid;
+ private String rootVolumeUuid;
+ private String vmInstanceName;
+ private String vmCategory;
+ private String architecture;
+ private String metadata;
+ private String schemaVersion;
+ private boolean storageStructureChange;
+ private String metadataPath;
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ return primaryStorageUuid;
+ }
+
+ public void setPrimaryStorageUuid(String primaryStorageUuid) {
+ this.primaryStorageUuid = primaryStorageUuid;
+ }
+
+ public String getVmInstanceUuid() {
+ return vmInstanceUuid;
+ }
+
+ public void setVmInstanceUuid(String vmInstanceUuid) {
+ this.vmInstanceUuid = vmInstanceUuid;
+ }
+
+ public String getRootVolumeUuid() {
+ return rootVolumeUuid;
+ }
+
+ public void setRootVolumeUuid(String rootVolumeUuid) {
+ this.rootVolumeUuid = rootVolumeUuid;
+ }
+
+ public String getVmInstanceName() {
+ return vmInstanceName;
+ }
+
+ public void setVmInstanceName(String vmInstanceName) {
+ this.vmInstanceName = vmInstanceName;
+ }
+
+ public String getVmCategory() {
+ return vmCategory;
+ }
+
+ public void setVmCategory(String vmCategory) {
+ this.vmCategory = vmCategory;
+ }
+
+ public String getArchitecture() {
+ return architecture;
+ }
+
+ public void setArchitecture(String architecture) {
+ this.architecture = architecture;
+ }
+
+ public String getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(String metadata) {
+ this.metadata = metadata;
+ }
+
+ public String getSchemaVersion() {
+ return schemaVersion;
+ }
+
+ public void setSchemaVersion(String schemaVersion) {
+ this.schemaVersion = schemaVersion;
+ }
+
+ public boolean isStorageStructureChange() {
+ return storageStructureChange;
+ }
+
+ public void setStorageStructureChange(boolean storageStructureChange) {
+ this.storageStructureChange = storageStructureChange;
+ }
+
+ public String getMetadataPath() {
+ return metadataPath;
+ }
+
+ public void setMetadataPath(String metadataPath) {
+ this.metadataPath = metadataPath;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
new file mode 100644
index 00000000000..d8323171909
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
@@ -0,0 +1,6 @@
+package org.zstack.header.vm.metadata;
+
+import org.zstack.header.message.MessageReply;
+
+public class UpdateVmInstanceMetadataOnPrimaryStorageReply extends MessageReply {
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
new file mode 100644
index 00000000000..98296a42b3c
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
@@ -0,0 +1,15 @@
+package org.zstack.header.vm.metadata;
+
+import org.zstack.header.message.MessageReply;
+
+public class UpdateVmInstanceMetadataReply extends MessageReply {
+ private String metadata;
+
+ public String getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(String metadata) {
+ this.metadata = metadata;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java b/header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
new file mode 100644
index 00000000000..3460cbfdc4f
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
@@ -0,0 +1,10 @@
+package org.zstack.header.vm.metadata;
+
+public class VmInstanceMetadataConstants {
+ private VmInstanceMetadataConstants() {
+ }
+
+ public static final String SBLK_METADATA_LV_SUFFIX = "_vmmeta";
+ public static final String FILE_METADATA_SUFFIX = ".vmmeta";
+ public static final String METADATA_DIR_NAME = "vm-metadata";
+}
\ No newline at end of file
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java b/header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
new file mode 100644
index 00000000000..b002f4a06d2
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
@@ -0,0 +1,15 @@
+package org.zstack.header.vm.metadata;
+
+import java.util.List;
+
+public class VmInstanceMetadataDTO {
+ public String schemaVersion;
+ public VmMetadataCategory vmCategory;
+ public String cacheVmInstanceUuid;
+ public ResourceMetadata vm;
+ public List volumes;
+ public List nics;
+ public List snapshots;
+ public List snapshotGroups;
+ public List snapshotGroupRefs;
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
new file mode 100644
index 00000000000..e793a1dfd74
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
@@ -0,0 +1,7 @@
+package org.zstack.header.vm.metadata;
+
+public enum VmMetadataCategory {
+ REGULAR,
+ TEMPLATE,
+ TEMPLATE_CACHE
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.java
new file mode 100644
index 00000000000..822901e63da
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.java
@@ -0,0 +1,25 @@
+package org.zstack.header.vm.metadata;
+
+public final class VmMetadataConstants {
+ private VmMetadataConstants() {
+ // utility class
+ }
+
+ public static final long SBLK_HEADER_SIZE = 4096L;
+
+ public static final long SBLK_SLOT_HEADER_SIZE = 36L;
+
+ public static final long SBLK_MAX_LV_SIZE = 64L * 1024 * 1024;
+
+ public static long slotCapacity(long lvSize) {
+ return ((lvSize - SBLK_HEADER_SIZE) / 2 / 4096) * 4096;
+ }
+
+ public static final long SBLK_MAX_SLOT_CAPACITY = slotCapacity(SBLK_MAX_LV_SIZE);
+
+ public static final long SBLK_MAX_PAYLOAD_SIZE = SBLK_MAX_SLOT_CAPACITY - SBLK_SLOT_HEADER_SIZE;
+
+ public static final long PAYLOAD_WARN_THRESHOLD = 8L * 1024 * 1024;
+
+ public static final long PAYLOAD_REJECT_THRESHOLD = 30L * 1024 * 1024;
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
new file mode 100644
index 00000000000..ff17ffcf7f4
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
@@ -0,0 +1,29 @@
+package org.zstack.header.vm.metadata;
+
+/**
+ * Service interface for marking VM metadata as dirty.
+ *
+ * Implementations live in the premium module (VmMetadataDirtyMarker).
+ * The zstack core module uses this interface via {@code @Autowired(required = false)}
+ * so that the feature degrades gracefully when the premium module is not loaded.
+ */
+public interface VmMetadataDirtyService {
+ /**
+ * Mark the VM's metadata as dirty so that it will be flushed
+ * to primary storage on the next poll cycle.
+ *
+ * @param vmInstanceUuid the VM whose metadata changed
+ * @return true if the mark was actually written (feature enabled), false otherwise
+ */
+ boolean markDirty(String vmInstanceUuid);
+
+ /**
+ * Mark the VM's metadata as dirty, optionally flagging a storage-structure change
+ * (e.g. volume attach/detach, snapshot, migration).
+ *
+ * @param vmInstanceUuid the VM whose metadata changed
+ * @param storageStructureChange true if the change affects storage topology
+ * @return true if the mark was actually written (feature enabled), false otherwise
+ */
+ boolean markDirty(String vmInstanceUuid, boolean storageStructureChange);
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java
new file mode 100644
index 00000000000..f313699b82d
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java
@@ -0,0 +1,121 @@
+package org.zstack.header.vm.metadata;
+
+import org.zstack.header.managementnode.ManagementNodeVO;
+import org.zstack.header.vm.VmInstanceEO;
+import org.zstack.header.vm.VmInstanceVO;
+import org.zstack.header.vo.ForeignKey;
+import org.zstack.header.vo.ForeignKey.ReferenceOption;
+
+import javax.persistence.*;
+import java.sql.Timestamp;
+
+@Entity
+@Table
+public class VmMetadataDirtyVO {
+ @Id
+ @Column
+ @ForeignKey(parentEntityClass = VmInstanceEO.class, onDeleteAction = ReferenceOption.CASCADE)
+ private String vmInstanceUuid;
+
+ @Column
+ @ForeignKey(parentEntityClass = ManagementNodeVO.class, onDeleteAction = ReferenceOption.SET_NULL)
+ private String managementNodeUuid;
+
+ @Column
+ private long dirtyVersion;
+
+ @Column
+ private Timestamp lastClaimTime;
+
+ @Column
+ private boolean storageStructureChange;
+
+ @Column
+ private int retryCount;
+
+ @Column
+ private Timestamp nextRetryTime;
+
+ @Column
+ private Timestamp createDate;
+
+ @Column
+ private Timestamp lastOpDate;
+
+ @PreUpdate
+ private void preUpdate() {
+ lastOpDate = null;
+ }
+
+ public String getVmInstanceUuid() {
+ return vmInstanceUuid;
+ }
+
+ public void setVmInstanceUuid(String vmInstanceUuid) {
+ this.vmInstanceUuid = vmInstanceUuid;
+ }
+
+ public String getManagementNodeUuid() {
+ return managementNodeUuid;
+ }
+
+ public void setManagementNodeUuid(String managementNodeUuid) {
+ this.managementNodeUuid = managementNodeUuid;
+ }
+
+ public long getDirtyVersion() {
+ return dirtyVersion;
+ }
+
+ public void setDirtyVersion(long dirtyVersion) {
+ this.dirtyVersion = dirtyVersion;
+ }
+
+ public Timestamp getLastClaimTime() {
+ return lastClaimTime;
+ }
+
+ public void setLastClaimTime(Timestamp lastClaimTime) {
+ this.lastClaimTime = lastClaimTime;
+ }
+
+ public boolean isStorageStructureChange() {
+ return storageStructureChange;
+ }
+
+ public void setStorageStructureChange(boolean storageStructureChange) {
+ this.storageStructureChange = storageStructureChange;
+ }
+
+ public int getRetryCount() {
+ return retryCount;
+ }
+
+ public void setRetryCount(int retryCount) {
+ this.retryCount = retryCount;
+ }
+
+ public Timestamp getNextRetryTime() {
+ return nextRetryTime;
+ }
+
+ public void setNextRetryTime(Timestamp nextRetryTime) {
+ this.nextRetryTime = nextRetryTime;
+ }
+
+ public Timestamp getCreateDate() {
+ return createDate;
+ }
+
+ public void setCreateDate(Timestamp createDate) {
+ this.createDate = createDate;
+ }
+
+ public Timestamp getLastOpDate() {
+ return lastOpDate;
+ }
+
+ public void setLastOpDate(Timestamp lastOpDate) {
+ this.lastOpDate = lastOpDate;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
new file mode 100644
index 00000000000..059ff6ad20f
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
@@ -0,0 +1,18 @@
+package org.zstack.header.vm.metadata;
+
+import javax.persistence.metamodel.SingularAttribute;
+import javax.persistence.metamodel.StaticMetamodel;
+import java.sql.Timestamp;
+
+@StaticMetamodel(VmMetadataDirtyVO.class)
+public class VmMetadataDirtyVO_ {
+ public static volatile SingularAttribute vmInstanceUuid;
+ public static volatile SingularAttribute managementNodeUuid;
+ public static volatile SingularAttribute dirtyVersion;
+ public static volatile SingularAttribute storageStructureChange;
+ public static volatile SingularAttribute retryCount;
+ public static volatile SingularAttribute nextRetryTime;
+ public static volatile SingularAttribute lastClaimTime;
+ public static volatile SingularAttribute createDate;
+ public static volatile SingularAttribute lastOpDate;
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java
new file mode 100644
index 00000000000..f06ad8362dd
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java
@@ -0,0 +1,28 @@
+package org.zstack.header.vm.metadata;
+
+public enum VmMetadataErrors {
+ METADATA_INVALID_FORMAT(1300),
+ METADATA_SCHEMA_VERSION_MISMATCH(1301),
+ METADATA_UUID_CONFLICT(1302),
+ METADATA_STORAGE_NOT_SUPPORTED(1303),
+ METADATA_CROSS_STORAGE_FORBIDDEN(1304),
+ METADATA_INSTALL_PATH_NOT_FOUND(1305),
+ METADATA_CACHE_VM_NOT_REGISTERABLE(1306),
+ METADATA_VM_REGISTERING(1307),
+ METADATA_READ_CORRUPTED(1308),
+ METADATA_PAYLOAD_TOO_LARGE(1309),
+ METADATA_PS_UNREACHABLE(1310),
+ METADATA_FEATURE_DISABLED(1311),
+ ;
+
+ private String code;
+
+ private VmMetadataErrors(int id) {
+ code = String.format("VM_METADATA.%s", id);
+ }
+
+ @Override
+ public String toString() {
+ return code;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
new file mode 100644
index 00000000000..ff2853e2971
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
@@ -0,0 +1,88 @@
+package org.zstack.header.vm.metadata;
+
+import org.zstack.header.vm.VmInstanceEO;
+import org.zstack.header.vo.ForeignKey;
+import org.zstack.header.vo.ForeignKey.ReferenceOption;
+
+import javax.persistence.*;
+import java.sql.Timestamp;
+
+/**
+ * 元数据指纹:记录每台 VM 上次成功刷写元数据时的完整快照。
+ *
+ * 设计要点
+ *
+ * - metadataSnapshot:完整元数据 JSON(确定性序列化),
+ * 由 {@code MetadataContentDriftDetector} 每 6 小时低频对比,
+ * 发现漂移则触发 markDirty 重新刷写。
+ * - lastFlushFailed:Poller 重试耗尽时置 true(C-SR-05),
+ * 仅由 {@code MetadataStaleRecoveryTask} 重置为 false(C-02B-8)。
+ * - staleRecoveryCount:熔断计数器,{@code MetadataStaleRecoveryTask} 每次
+ * 重入队递增,达到上限(默认 10 × 5 小时)后停止自动恢复。
+ * 管理员可通过 {@code APIUpdateVmMetadataMsg} 手动重置为 0。
+ * - vmInstanceUuid 作 PK:一台 VM 最多一行,
+ * FK CASCADE 保证 VM 物理删除时自动清理。
+ *
+ */
+@Entity
+@Table(name = "VmMetadataFingerprintVO")
+public class VmMetadataFingerprintVO {
+
+ @Id
+ @Column
+ @ForeignKey(parentEntityClass = VmInstanceEO.class, onDeleteAction = ReferenceOption.CASCADE)
+ private String vmInstanceUuid;
+
+ @Column
+ private Timestamp lastFlushTime;
+
+ @Column
+ private boolean lastFlushFailed;
+
+ @Column
+ private int staleRecoveryCount;
+
+ @Column
+ @Lob
+ private String metadataSnapshot;
+
+ public String getVmInstanceUuid() {
+ return vmInstanceUuid;
+ }
+
+ public void setVmInstanceUuid(String vmInstanceUuid) {
+ this.vmInstanceUuid = vmInstanceUuid;
+ }
+
+ public Timestamp getLastFlushTime() {
+ return lastFlushTime;
+ }
+
+ public void setLastFlushTime(Timestamp lastFlushTime) {
+ this.lastFlushTime = lastFlushTime;
+ }
+
+ public boolean isLastFlushFailed() {
+ return lastFlushFailed;
+ }
+
+ public void setLastFlushFailed(boolean lastFlushFailed) {
+ this.lastFlushFailed = lastFlushFailed;
+ }
+
+ public int getStaleRecoveryCount() {
+ return staleRecoveryCount;
+ }
+
+ public void setStaleRecoveryCount(int staleRecoveryCount) {
+ this.staleRecoveryCount = staleRecoveryCount;
+ }
+
+ public String getMetadataSnapshot() {
+ return metadataSnapshot;
+ }
+
+ public void setMetadataSnapshot(String metadataSnapshot) {
+ this.metadataSnapshot = metadataSnapshot;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java
new file mode 100644
index 00000000000..d1debc03930
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java
@@ -0,0 +1,7 @@
+package org.zstack.header.vm.metadata;
+
+public interface VmMetadataPathBuildExtensionPoint {
+ String getPrimaryStorageType();
+ String buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid);
+ String buildMetadataDir(String primaryStorageUuid);
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java
new file mode 100644
index 00000000000..38465deb9ca
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java
@@ -0,0 +1,44 @@
+package org.zstack.header.vm.metadata;
+
+import java.util.List;
+import java.util.Map;
+
+public interface VmMetadataPathReplacementExtensionPoint {
+ String getPrimaryStorageType();
+ PathReplacementResult calculatePathReplacements(String targetPsUuid, List allOldPaths);
+ class PathReplacementResult {
+ /** old path → new path 完整映射 */
+ private Map pathMap;
+ /** rebase 前缀替换用的旧路径前缀 */
+ private String oldPrefix;
+ /** rebase 前缀替换用的新路径前缀 */
+ private String newPrefix;
+
+ public PathReplacementResult() {
+ }
+
+ public Map getPathMap() {
+ return pathMap;
+ }
+
+ public void setPathMap(Map pathMap) {
+ this.pathMap = pathMap;
+ }
+
+ public String getOldPrefix() {
+ return oldPrefix;
+ }
+
+ public void setOldPrefix(String oldPrefix) {
+ this.oldPrefix = oldPrefix;
+ }
+
+ public String getNewPrefix() {
+ return newPrefix;
+ }
+
+ public void setNewPrefix(String newPrefix) {
+ this.newPrefix = newPrefix;
+ }
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
new file mode 100644
index 00000000000..bda1d0a09ed
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
@@ -0,0 +1,11 @@
+package org.zstack.header.vm.metadata;
+
+import java.sql.Timestamp;
+
+public interface VmMetadataResourcePersistExtensionPoint {
+ String getPrimaryStorageType();
+ void afterVolumePersist(String primaryStorageUuid, String resourceUuid,
+ String resourceType, String hostUuid, long size, Timestamp now);
+ void afterSnapshotPersist(String primaryStorageUuid, String resourceUuid,
+ String resourceType, String hostUuid, long size, Timestamp now);
+}
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java b/header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
new file mode 100644
index 00000000000..5db019052cd
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
@@ -0,0 +1,11 @@
+package org.zstack.header.vm.metadata;
+
+import org.zstack.header.message.APIMessage;
+
+import java.util.List;
+
+public interface VmUuidFromApiResolver {
+ boolean supports(APIMessage msg);
+
+ List resolveVmUuids(APIMessage msg);
+}
\ No newline at end of file
diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java b/header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
new file mode 100644
index 00000000000..bc08c457b17
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
@@ -0,0 +1,8 @@
+package org.zstack.header.vm.metadata;
+
+import java.util.List;
+
+public class VolumeResourceMetadata extends ResourceMetadata {
+ public List snapshotReferences;
+ public List snapshotReferenceTrees;
+}
diff --git a/header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java b/header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java
index 6ae817723f4..46471a0bb8a 100755
--- a/header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java
+++ b/header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java
@@ -5,6 +5,7 @@
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
import org.zstack.header.vm.VmInstanceVO;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* @api api event for message :ref:`APIAttachVolumeToVmEvent`
@@ -39,6 +40,7 @@
parameterName = "params",
responseClass = APIAttachDataVolumeToVmEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "VolumeBasedVmUuidFromApiResolver")
public class APIAttachDataVolumeToVmMsg extends APIMessage implements VolumeMessage {
/**
* @desc vm uuid. see :ref:`VmInstanceInventory`
diff --git a/header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java b/header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java
index 30478454c67..cd69d10122b 100755
--- a/header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java
+++ b/header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* @api change data volume state
@@ -38,6 +39,7 @@
method = HttpMethod.PUT,
responseClass = APIChangeVolumeStateEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "VolumeBasedVmUuidFromApiResolver")
public class APIChangeVolumeStateMsg extends APIMessage implements VolumeMessage {
/**
* @desc data volume uuid
diff --git a/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java b/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java
index 9b497a73fe4..39b8bd0afbe 100644
--- a/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java
+++ b/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java
@@ -13,6 +13,7 @@
import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO;
import org.zstack.header.vm.VmInstanceInventory;
import org.zstack.header.vm.VmInstanceVO;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.ArrayList;
import java.util.List;
@@ -27,6 +28,7 @@
responseClass = APICreateVolumeSnapshotGroupEvent.class,
parameterName = "params"
)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotGroupBasedVmUuidFromApiResolver", updateOnFailure = true)
@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 3)
public class APICreateVolumeSnapshotGroupMsg extends APICreateMessage implements VolumeMessage, CreateVolumeSnapshotGroupMessage, APIMultiAuditor {
/**
diff --git a/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java b/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java
index 35f57d205f6..65dacf0c367 100755
--- a/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java
+++ b/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java
@@ -5,6 +5,7 @@
import org.zstack.header.other.APIAuditor;
import org.zstack.header.rest.RestRequest;
import org.zstack.header.storage.snapshot.VolumeSnapshotVO;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.concurrent.TimeUnit;
@@ -45,6 +46,7 @@
parameterName = "params"
)
@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 3)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "VolumeBasedVmUuidFromApiResolver", updateOnFailure = true)
public class APICreateVolumeSnapshotMsg extends APICreateMessage implements VolumeMessage, APIAuditor {
/**
* @desc volume uuid. See :ref:`VolumeInventory`
diff --git a/header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java b/header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java
index fdbb1be9847..927149674a0 100755
--- a/header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java
+++ b/header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIDeleteMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.List;
@@ -42,6 +43,7 @@
method = HttpMethod.DELETE,
responseClass = APIDeleteDataVolumeEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "PreCaptureVolumeBasedVmUuidFromApiResolver")
public class APIDeleteDataVolumeMsg extends APIDeleteMessage implements VolumeMessage {
/**
* @desc data volume uuid
diff --git a/header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java b/header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java
index 5164ffca076..5c3a7d91864 100755
--- a/header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java
+++ b/header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.vm.VmInstanceVO;
+import org.zstack.header.vm.metadata.MetadataImpact;
import org.zstack.header.rest.RestRequest;
/**
@@ -36,6 +37,7 @@
method = HttpMethod.DELETE,
responseClass = APIDetachDataVolumeFromVmEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "PreCaptureVolumeBasedVmUuidFromApiResolver")
public class APIDetachDataVolumeFromVmMsg extends APIMessage implements VolumeMessage {
/**
* @desc data volume uuid. See :ref:`VolumeInventory`
diff --git a/header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java b/header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java
index 272036c9378..04e318b1fbd 100755
--- a/header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java
+++ b/header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by frank on 11/16/2015.
@@ -14,6 +15,7 @@
method = HttpMethod.PUT,
responseClass = APIExpungeDataVolumeEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "PreCaptureVolumeBasedVmUuidFromApiResolver")
public class APIExpungeDataVolumeMsg extends APIMessage implements VolumeMessage {
@APIParam(resourceType = VolumeVO.class, successIfResourceNotExisting = true)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java b/header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java
index daeb56b44eb..b969660f42f 100644
--- a/header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java
+++ b/header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java
@@ -7,6 +7,7 @@
import org.zstack.header.message.DefaultTimeout;
import org.zstack.header.other.APIAuditor;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import java.util.concurrent.TimeUnit;
@@ -17,6 +18,7 @@
method = HttpMethod.PUT,
responseClass = APIFlattenVolumeEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "VolumeBasedVmUuidFromApiResolver")
public class APIFlattenVolumeMsg extends APIMessage implements VolumeMessage, APIAuditor {
@APIParam(resourceType = VolumeVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java b/header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java
index eec77334a37..df55f75651a 100755
--- a/header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java
+++ b/header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by frank on 11/12/2015.
@@ -14,6 +15,7 @@
method = HttpMethod.PUT,
responseClass = APIRecoverDataVolumeEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "VolumeBasedVmUuidFromApiResolver")
public class APIRecoverDataVolumeMsg extends APIMessage implements VolumeMessage {
@APIParam(resourceType = VolumeVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java b/header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java
index d4780c73be0..fc311d4a0d1 100755
--- a/header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java
+++ b/header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by xing5 on 2016/4/24.
@@ -14,6 +15,7 @@
responseClass = APISyncVolumeSizeEvent.class,
isAction = true
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "VolumeBasedVmUuidFromApiResolver")
public class APISyncVolumeSizeMsg extends APIMessage implements VolumeMessage {
@APIParam(resourceType = VolumeVO.class)
private String uuid;
diff --git a/header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java b/header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java
index 0d27cfaa18a..81c30b9ec79 100644
--- a/header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java
+++ b/header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java
@@ -5,6 +5,7 @@
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
import org.zstack.header.storage.snapshot.VolumeSnapshotVO;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* @ Author : yh.w
@@ -16,6 +17,7 @@
method = HttpMethod.PUT,
responseClass = APIUndoSnapshotCreationEvent.class
)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "VolumeBasedVmUuidFromApiResolver")
public class APIUndoSnapshotCreationMsg extends APIMessage implements VolumeMessage {
@APIParam(resourceType = VolumeVO.class)
diff --git a/header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java b/header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java
index 785cf824abd..8e7f3da65fc 100755
--- a/header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java
+++ b/header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java
@@ -4,6 +4,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
/**
* Created by frank on 6/14/2015.
@@ -14,6 +15,7 @@
responseClass = APIUpdateVolumeEvent.class,
isAction = true
)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "VolumeBasedVmUuidFromApiResolver")
public class APIUpdateVolumeMsg extends APIMessage implements VolumeMessage {
@APIParam(resourceType = VolumeVO.class)
private String uuid;
diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java
index e1864931171..6f2316fa9cf 100755
--- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java
+++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java
@@ -10,6 +10,7 @@
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
import org.zstack.header.storage.primary.PrimaryStorageMessage;
+import org.zstack.header.vm.metadata.MetadataImpact;
import org.zstack.header.storage.primary.PrimaryStorageVO;
import org.zstack.header.volume.VolumeVO;
@@ -25,6 +26,7 @@
isAction = true
)
@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 24)
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "VolumeBasedVmUuidFromApiResolver", updateOnFailure = true)
public class APILocalStorageMigrateVolumeMsg extends APIMessage implements PrimaryStorageMessage, APIAuditor {
@APIParam(resourceType = VolumeVO.class)
private String volumeUuid;
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 d4665a86a06..e2f5e82ff48 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
@@ -3,6 +3,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.compute.host.VolumeMigrationTargetHostFilter;
+import org.zstack.compute.vm.VmGlobalConfig;
import org.zstack.core.asyncbatch.While;
import org.zstack.core.cloudbus.CloudBusCallBack;
import org.zstack.core.cloudbus.EventFacade;
@@ -39,6 +40,8 @@
import org.zstack.header.storage.primary.VolumeSnapshotCapability.VolumeSnapshotArrangementType;
import org.zstack.header.storage.snapshot.*;
import org.zstack.header.vm.*;
+import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg;
+import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply;
import org.zstack.header.vo.ResourceVO;
import org.zstack.header.volume.*;
import org.zstack.storage.primary.*;
@@ -902,6 +905,8 @@ public void handleLocalMessage(Message msg) {
handle((CommitVolumeSnapshotOnPrimaryStorageMsg) msg);
} else if (msg instanceof PullVolumeSnapshotOnPrimaryStorageMsg) {
handle((PullVolumeSnapshotOnPrimaryStorageMsg) msg);
+ } else if (msg instanceof RebaseVolumeBackingFileOnPrimaryStorageMsg) {
+ handle((RebaseVolumeBackingFileOnPrimaryStorageMsg) msg);
} else {
super.handleLocalMessage(msg);
}
@@ -1496,6 +1501,24 @@ protected String scripts() {
return huuid;
}
+ /**
+ * Resolve the target host for local storage operations.
+ * Priority: rootVolumeUuid (via LocalStorageResourceRefVO) > fallbackHostUuid.
+ * Returns null if neither can determine a host.
+ */
+ private String resolveHostUuid(String rootVolumeUuid, String fallbackHostUuid) {
+ if (rootVolumeUuid != null) {
+ String hostUuid = Q.New(LocalStorageResourceRefVO.class)
+ .select(LocalStorageResourceRefVO_.hostUuid)
+ .eq(LocalStorageResourceRefVO_.resourceUuid, rootVolumeUuid)
+ .findValue();
+ if (hostUuid != null) {
+ return hostUuid;
+ }
+ }
+ return fallbackHostUuid;
+ }
+
@Override
protected void handle(final DeleteSnapshotOnPrimaryStorageMsg msg) {
final String hostUuid = getHostUuidByResourceUuid(msg.getSnapshot().getUuid());
@@ -1640,6 +1663,24 @@ public void fail(ErrorCode errorCode) {
});
}
+ protected void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg) {
+ LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(msg.getHostUuid());
+ LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self);
+ bkd.handle(msg, msg.getHostUuid(), new ReturnValueCompletion(msg) {
+ @Override
+ public void success(RebaseVolumeBackingFileOnPrimaryStorageReply returnValue) {
+ bus.reply(msg, returnValue);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ RebaseVolumeBackingFileOnPrimaryStorageReply reply = new RebaseVolumeBackingFileOnPrimaryStorageReply();
+ reply.setError(errorCode);
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
private void handle(RemoveHostFromLocalStorageMsg msg) {
RemoveHostFromLocalStorageReply reply = new RemoveHostFromLocalStorageReply();
thdf.chainSubmit(new ChainTask(msg) {
@@ -3329,4 +3370,156 @@ public void fail(ErrorCode errorCode) {
public static class LocalStoragePhysicalCapacityUsage extends PrimaryStorageBase.PhysicalCapacityUsage {
public long localStorageUsedSize;
}
+
+ @Override
+ protected void handle(final UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) {
+ thdf.chainSubmit(new ChainTask(msg) {
+ @Override
+ public String getSyncSignature() {
+ return "update-metadata-on-ps-" + self.getUuid();
+ }
+
+ @Override
+ public int getSyncLevel() {
+ return VmGlobalConfig.VM_METADATA_PS_MAX_CONCURRENT.value(Integer.class);
+ }
+
+ @Override
+ public void run(SyncTaskChain chain) {
+ final String hostUuid = getHostUuidByResourceUuid(msg.getRootVolumeUuid());
+ LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid);
+ LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self);
+ bkd.handle(msg, hostUuid, new ReturnValueCompletion(msg, chain) {
+ @Override
+ public void success(UpdateVmInstanceMetadataOnPrimaryStorageReply returnValue) {
+ bus.reply(msg, returnValue);
+ chain.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply();
+ reply.setError(errorCode);
+ bus.reply(msg, reply);
+ chain.next();
+ }
+ });
+ }
+
+ @Override
+ public String getName() {
+ return "update-metadata-on-ps-" + self.getUuid();
+ }
+ });
+ }
+
+ @Override
+ protected void handle(final GetVmInstanceMetadataFromPrimaryStorageMsg msg) {
+ GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply();
+
+ String hostUuid = resolveHostUuid(msg.getRootVolumeUuid(), msg.getHostUuid());
+ if (hostUuid == null) {
+ reply.setError(operr("cannot determine host for metadata get on local primary storage[uuid:%s]," +
+ " rootVolumeUuid and hostUuid are both absent", self.getUuid()));
+ bus.reply(msg, reply);
+ return;
+ }
+
+ LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid);
+ LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self);
+ bkd.handle(msg, hostUuid, new ReturnValueCompletion(msg) {
+ @Override
+ public void success(GetVmInstanceMetadataFromPrimaryStorageReply returnValue) {
+ bus.reply(msg, returnValue);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ reply.setError(errorCode);
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
+ @Override
+ protected void handle(final ScanVmInstanceMetadataFromPrimaryStorageMsg msg) {
+ ScanVmInstanceMetadataFromPrimaryStorageReply reply = new ScanVmInstanceMetadataFromPrimaryStorageReply();
+
+ List connectedHostUuids = SQL.New(
+ "select h.hostUuid from LocalStorageHostRefVO h, HostVO host" +
+ " where h.primaryStorageUuid = :psUuid" +
+ " and h.hostUuid = host.uuid" +
+ " and host.status = :hstatus", String.class)
+ .param("psUuid", self.getUuid())
+ .param("hstatus", HostStatus.Connected)
+ .list();
+ if (connectedHostUuids.isEmpty()) {
+ reply.setError(operr("no connected host found for local primary storage[uuid:%s]", self.getUuid()));
+ bus.reply(msg, reply);
+ return;
+ }
+
+ List allSummaries = Collections.synchronizedList(new ArrayList<>());
+
+ new While<>(connectedHostUuids).all((hostUuid, com) -> {
+ LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid);
+ LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self);
+ bkd.handle(msg, hostUuid, new ReturnValueCompletion(com) {
+ @Override
+ public void success(ScanVmInstanceMetadataFromPrimaryStorageReply returnValue) {
+ if (returnValue.getVmInstanceMetadata() != null) {
+ allSummaries.addAll(returnValue.getVmInstanceMetadata());
+ }
+ com.done();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ logger.warn(String.format("failed to scan vm metadata from host[uuid:%s] on local primary storage[uuid:%s]: %s",
+ hostUuid, self.getUuid(), errorCode));
+ com.addError(errorCode);
+ com.done();
+ }
+ });
+ }).run(new WhileDoneCompletion(msg) {
+ @Override
+ public void done(ErrorCodeList errorCodeList) {
+ if (!errorCodeList.getCauses().isEmpty() && errorCodeList.getCauses().size() == connectedHostUuids.size()) {
+ reply.setError(operr("failed to scan vm metadata from all hosts on local primary storage[uuid:%s], causes: %s",
+ self.getUuid(), errorCodeList));
+ } else {
+ reply.setVmInstanceMetadata(new ArrayList<>(allSummaries));
+ }
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
+ @Override
+ protected void handle(final CleanupVmInstanceMetadataOnPrimaryStorageMsg msg) {
+ CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply();
+
+ String hostUuid = resolveHostUuid(msg.getRootVolumeUuid(), null);
+ if (hostUuid == null) {
+ reply.setError(operr("cannot determine host for metadata cleanup on local primary storage[uuid:%s]," +
+ " rootVolumeUuid is absent or has no LocalStorageResourceRefVO", self.getUuid()));
+ bus.reply(msg, reply);
+ return;
+ }
+
+ LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid);
+ LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self);
+ bkd.handle(msg, hostUuid, new ReturnValueCompletion(msg) {
+ @Override
+ public void success(CleanupVmInstanceMetadataOnPrimaryStorageReply returnValue) {
+ bus.reply(msg, returnValue);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ 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..751e6e24c87 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
@@ -7,6 +7,12 @@
import org.zstack.header.image.ImageInventory;
import org.zstack.header.storage.primary.*;
import org.zstack.header.storage.snapshot.VolumeSnapshotInventory;
+import org.zstack.header.storage.primary.CleanupVmInstanceMetadataOnPrimaryStorageMsg;
+import org.zstack.header.storage.primary.CleanupVmInstanceMetadataOnPrimaryStorageReply;
+import org.zstack.header.storage.primary.GetVmInstanceMetadataFromPrimaryStorageMsg;
+import org.zstack.header.storage.primary.GetVmInstanceMetadataFromPrimaryStorageReply;
+import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg;
+import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply;
import org.zstack.header.volume.*;
import org.zstack.storage.primary.EstimateVolumeTemplateSizeOnPrimaryStorageMsg;
import org.zstack.storage.primary.EstimateVolumeTemplateSizeOnPrimaryStorageReply;
@@ -121,4 +127,14 @@ public LocalStorageHypervisorBackend(PrimaryStorageVO self) {
abstract void handle(CommitVolumeSnapshotOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion);
abstract void handle(PullVolumeSnapshotOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion);
+
+ abstract void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion);
+
+ abstract void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion);
+
+ abstract void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion);
+
+ abstract void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion);
+
+ abstract void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg, String hostUuid, 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 e8d268e518a..729951f8c5d 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
@@ -42,11 +42,15 @@
import org.zstack.header.rest.RESTFacade;
import org.zstack.header.storage.backup.*;
import org.zstack.header.storage.primary.*;
-import org.zstack.header.storage.snapshot.*;
+import org.zstack.header.storage.snapshot.VolumeSnapshotConstant;
+import org.zstack.header.storage.snapshot.VolumeSnapshotInventory;
+import org.zstack.header.storage.snapshot.VolumeSnapshotVO;
import org.zstack.header.vm.VmInstanceSpec.ImageSpec;
import org.zstack.header.vm.VmInstanceState;
import org.zstack.header.vm.VmInstanceVO;
import org.zstack.header.vm.VmInstanceVO_;
+import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg;
+import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply;
import org.zstack.header.volume.*;
import org.zstack.identity.AccountManager;
import org.zstack.kvm.*;
@@ -67,9 +71,7 @@
import java.util.*;
import java.util.stream.Collectors;
-import static org.zstack.core.Platform.inerr;
-import static org.zstack.core.Platform.multiErr;
-import static org.zstack.core.Platform.operr;
+import static org.zstack.core.Platform.*;
import static org.zstack.utils.CollectionDSL.list;
import static org.zstack.utils.CollectionUtils.transformAndRemoveNull;
@@ -903,6 +905,52 @@ public void setHashValue(String hashValue) {
}
}
+ public static class WriteVmMetadataCmd extends AgentCommand {
+ public String metadata;
+ public String metadataPath;
+ public String vmUuid;
+ public String vmName;
+ public String vmCategory;
+ public String architecture;
+ public String schemaVersion;
+ }
+
+ public static class WriteVmMetadataRsp extends AgentResponse {
+ }
+
+ public static class GetVmInstanceMetadataCmd extends AgentCommand {
+ public String metadataPath;
+ }
+
+ public static class GetVmInstanceMetadataRsp extends AgentResponse {
+ public String metadata;
+ }
+
+ public static class ScanVmMetadataCmd extends AgentCommand {
+ public String metadataDir;
+ }
+
+ public static class ScanVmMetadataRsp extends AgentResponse {
+ public List metadataEntries = new ArrayList<>();
+ }
+
+ public static class CleanupVmMetadataCmd extends AgentCommand {
+ public String metadataPath;
+ }
+
+ public static class CleanupVmMetadataRsp extends AgentResponse {
+ }
+
+ public static class PrefixRebaseBackingFilesCmd extends LocalStorageKvmBackend.AgentCommand {
+ public List filePaths;
+ public String oldPrefix;
+ public String newPrefix;
+ }
+
+ public static class PrefixRebaseBackingFilesRsp extends LocalStorageKvmBackend.AgentResponse {
+ public int rebasedCount;
+ }
+
public static final String INIT_PATH = "/localstorage/init";
public static final String GET_PHYSICAL_CAPACITY_PATH = "/localstorage/getphysicalcapacity";
public static final String CREATE_EMPTY_VOLUME_PATH = "/localstorage/volume/createempty";
@@ -935,6 +983,11 @@ public void setHashValue(String hashValue) {
public static final String CANCEL_DOWNLOAD_BITS_FROM_KVM_HOST_PATH = "/localstorage/kvmhost/download/cancel";
public static final String GET_DOWNLOAD_BITS_FROM_KVM_HOST_PROGRESS_PATH = "/localstorage/kvmhost/download/progress";
public static final String GET_QCOW2_HASH_VALUE_PATH = "/localstorage/getqcow2hash";
+ public static final String WRITE_VM_METADATA_PATH = "/localstorage/vm/metadata/write";
+ public static final String GET_VM_INSTANCE_METADATA_PATH = "/localstorage/vm/metadata/get";
+ public static final String SCAN_VM_METADATA_PATH = "/localstorage/vm/metadata/scan";
+ public static final String CLEANUP_VM_METADATA_PATH = "/localstorage/vm/metadata/cleanup";
+ public static final String PREFIX_REBASE_BACKING_FILES_PATH = "/localstorage/snapshot/prefixrebasebackingfiles";
public LocalStorageKvmBackend() {
}
@@ -3797,4 +3850,107 @@ public void fail(ErrorCode errorCode) {
}
});
}
+
+ @Override
+ void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) {
+ WriteVmMetadataCmd cmd = new WriteVmMetadataCmd();
+ cmd.metadata = msg.getMetadata();
+ cmd.metadataPath = msg.getMetadataPath();
+ cmd.vmUuid = msg.getVmInstanceUuid();
+ cmd.vmName = msg.getVmInstanceName();
+ cmd.vmCategory = msg.getVmCategory();
+ cmd.architecture = msg.getArchitecture();
+ cmd.schemaVersion = msg.getSchemaVersion();
+ httpCall(WRITE_VM_METADATA_PATH, hostUuid, cmd, WriteVmMetadataRsp.class, new ReturnValueCompletion(completion) {
+ @Override
+ public void success(WriteVmMetadataRsp rsp) {
+ completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply());
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ completion.fail(errorCode);
+ }
+ });
+ }
+
+ @Override
+ void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) {
+ GetVmInstanceMetadataCmd cmd = new GetVmInstanceMetadataCmd();
+ cmd.metadataPath = msg.getMetadataPath();
+ httpCall(GET_VM_INSTANCE_METADATA_PATH, hostUuid, cmd, GetVmInstanceMetadataRsp.class, new ReturnValueCompletion(completion) {
+ @Override
+ public void success(GetVmInstanceMetadataRsp rsp) {
+ GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply();
+ reply.setMetadata(rsp.metadata);
+ completion.success(reply);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ completion.fail(errorCode);
+ }
+ });
+ }
+
+ @Override
+ void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) {
+ ScanVmMetadataCmd cmd = new ScanVmMetadataCmd();
+ cmd.metadataDir = msg.getMetadataDir();
+ httpCall(SCAN_VM_METADATA_PATH, hostUuid, cmd, ScanVmMetadataRsp.class, new ReturnValueCompletion(completion) {
+ @Override
+ public void success(ScanVmMetadataRsp rsp) {
+ ScanVmInstanceMetadataFromPrimaryStorageReply reply = new ScanVmInstanceMetadataFromPrimaryStorageReply();
+ rsp.metadataEntries.forEach(entry -> entry.setHostUuid(hostUuid));
+ reply.setVmInstanceMetadata(rsp.metadataEntries);
+ completion.success(reply);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ completion.fail(errorCode);
+ }
+ });
+ }
+
+ @Override
+ void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) {
+ CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd();
+ cmd.metadataPath = msg.getMetadataPath();
+
+ httpCall(CLEANUP_VM_METADATA_PATH, hostUuid, cmd, CleanupVmMetadataRsp.class, new ReturnValueCompletion(completion) {
+ @Override
+ public void success(CleanupVmMetadataRsp rsp) {
+ CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply();
+ completion.success(reply);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ completion.fail(errorCode);
+ }
+ });
+ }
+
+ @Override
+ void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) {
+ PrefixRebaseBackingFilesCmd cmd = new PrefixRebaseBackingFilesCmd();
+ cmd.filePaths = msg.getInstallPaths();
+ cmd.oldPrefix = msg.getOldPrefix();
+ cmd.newPrefix = msg.getNewPrefix();
+
+ httpCall(PREFIX_REBASE_BACKING_FILES_PATH, hostUuid, cmd, PrefixRebaseBackingFilesRsp.class, new ReturnValueCompletion(completion) {
+ @Override
+ public void success(PrefixRebaseBackingFilesRsp rsp) {
+ RebaseVolumeBackingFileOnPrimaryStorageReply reply = new RebaseVolumeBackingFileOnPrimaryStorageReply();
+ reply.setRebasedCount(rsp.rebasedCount);
+ completion.success(reply);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ completion.fail(errorCode);
+ }
+ });
+ }
}
diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java
index dbe774888f0..3edb2e916db 100755
--- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java
+++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java
@@ -334,6 +334,44 @@ String offlineMerge(HttpEntity entity) {
return null;
}
+ @RequestMapping(value=LocalStorageKvmBackend.WRITE_VM_METADATA_PATH, method= RequestMethod.POST)
+ public @ResponseBody
+ String writeVmMetadata(HttpEntity entity) {
+ WriteVmMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), WriteVmMetadataCmd.class);
+ config.writeVmMetadataCmds.add(cmd);
+ reply(entity, new WriteVmMetadataRsp());
+ return null;
+ }
+
+ @RequestMapping(value=LocalStorageKvmBackend.GET_VM_INSTANCE_METADATA_PATH, method= RequestMethod.POST)
+ public @ResponseBody
+ String getVmInstanceMetadata(HttpEntity entity) {
+ GetVmInstanceMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetVmInstanceMetadataCmd.class);
+ config.getVmInstanceMetadataCmds.add(cmd);
+ GetVmInstanceMetadataRsp rsp = new GetVmInstanceMetadataRsp();
+ reply(entity, rsp);
+ return null;
+ }
+
+ @RequestMapping(value=LocalStorageKvmBackend.SCAN_VM_METADATA_PATH, method= RequestMethod.POST)
+ public @ResponseBody
+ String scanVmMetadata(HttpEntity entity) {
+ ScanVmMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), ScanVmMetadataCmd.class);
+ config.scanVmMetadataCmds.add(cmd);
+ ScanVmMetadataRsp rsp = new ScanVmMetadataRsp();
+ reply(entity, rsp);
+ return null;
+ }
+
+ @RequestMapping(value=LocalStorageKvmBackend.CLEANUP_VM_METADATA_PATH, method= RequestMethod.POST)
+ public @ResponseBody
+ String cleanupVmMetadata(HttpEntity entity) {
+ CleanupVmMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CleanupVmMetadataCmd.class);
+ config.cleanupVmMetadataCmds.add(cmd);
+ reply(entity, new CleanupVmMetadataRsp());
+ return null;
+ }
+
@ExceptionHandler(Exception.class)
public ModelAndView handleAllException(Exception ex) {
logger.warn(ex.getMessage(), ex);
diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
index 53a02339833..34463b46c6e 100755
--- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
+++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
@@ -59,4 +59,9 @@ public static class Capacity {
public List getQCOW2ReferenceCmds = new ArrayList<>();
public List getQCOW2ReferenceCmdReference = new ArrayList<>();
+
+ public List writeVmMetadataCmds = new ArrayList<>();
+ public List getVmInstanceMetadataCmds = new ArrayList<>();
+ public List scanVmMetadataCmds = new ArrayList<>();
+ public List cleanupVmMetadataCmds = new ArrayList<>();
}
diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java
new file mode 100644
index 00000000000..767657dab6b
--- /dev/null
+++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java
@@ -0,0 +1,127 @@
+package org.zstack.storage.primary.local;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.zstack.core.db.DatabaseFacade;
+import org.zstack.core.db.Q;
+import org.zstack.header.exception.CloudRuntimeException;
+import org.zstack.header.storage.primary.PrimaryStorageVO;
+import org.zstack.header.storage.primary.PrimaryStorageVO_;
+import org.zstack.header.vm.metadata.VmInstanceMetadataConstants;
+import org.zstack.header.vm.metadata.VmMetadataPathBuildExtensionPoint;
+import org.zstack.header.vm.metadata.VmMetadataPathReplacementExtensionPoint;
+import org.zstack.header.vm.metadata.VmMetadataResourcePersistExtensionPoint;
+import org.zstack.utils.Utils;
+import org.zstack.utils.logging.CLogger;
+
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LocalStorageVmMetadataExtension implements VmMetadataPathBuildExtensionPoint,
+ VmMetadataPathReplacementExtensionPoint, VmMetadataResourcePersistExtensionPoint {
+ private static final CLogger logger = Utils.getLogger(LocalStorageVmMetadataExtension.class);
+
+ @Autowired
+ private DatabaseFacade dbf;
+
+ @Override
+ public String getPrimaryStorageType() {
+ return LocalStorageConstants.LOCAL_STORAGE_TYPE;
+ }
+
+ @Override
+ public String buildMetadataDir(String primaryStorageUuid) {
+ String url = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue();
+ if (url == null) {
+ throw new CloudRuntimeException(String.format("cannot find url for primary storage[uuid:%s]", primaryStorageUuid));
+ }
+ return String.format("%s/%s", url, VmInstanceMetadataConstants.METADATA_DIR_NAME);
+ }
+
+ @Override
+ public String buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid) {
+ String url = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue();
+ if (url == null) {
+ throw new CloudRuntimeException(String.format("cannot find url for primary storage[uuid:%s]", primaryStorageUuid));
+ }
+ return String.format("%s/%s/%s%s", url, VmInstanceMetadataConstants.METADATA_DIR_NAME, vmInstanceUuid, VmInstanceMetadataConstants.FILE_METADATA_SUFFIX);
+ }
+
+ @Override
+ public PathReplacementResult calculatePathReplacements(String targetPsUuid, List allOldPaths) {
+ String baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, targetPsUuid).findValue();
+ if (baseDir == null) {
+ logger.warn(String.format("LocalStorage PS[uuid:%s] has no url, path replacement disabled", targetPsUuid));
+ PathReplacementResult result = new PathReplacementResult();
+ result.setPathMap(Collections.emptyMap());
+ return result;
+ }
+ String newPrefix = baseDir.endsWith("/") ? baseDir : baseDir + "/";
+
+ // Extract old prefix from the first recognizable path
+ String oldPrefix = null;
+ for (String path : allOldPaths) {
+ oldPrefix = extractOldPrefix(path);
+ if (oldPrefix != null) break;
+ }
+
+ Map pathMap = new LinkedHashMap<>();
+ if (oldPrefix != null) {
+ for (String oldPath : allOldPaths) {
+ if (oldPath != null && oldPath.startsWith(oldPrefix)) {
+ pathMap.put(oldPath, newPrefix + oldPath.substring(oldPrefix.length()));
+ }
+ }
+ } else {
+ logger.warn(String.format("cannot extract old path prefix from any path for LocalStorage PS[uuid:%s], " +
+ "path replacement disabled", targetPsUuid));
+ }
+
+ PathReplacementResult result = new PathReplacementResult();
+ result.setPathMap(pathMap);
+ result.setOldPrefix(oldPrefix);
+ result.setNewPrefix(newPrefix);
+ return result;
+ }
+
+ private String extractOldPrefix(String path) {
+ if (path == null || !path.startsWith("/")) {
+ return null;
+ }
+ String[] markers = {"/rootVolumes/", "/dataVolumes/", "/volumeSnapshots/", "/memory/"};
+ for (String marker : markers) {
+ int idx = path.indexOf(marker);
+ if (idx > 0) {
+ return path.substring(0, idx + 1);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void afterVolumePersist(String primaryStorageUuid, String resourceUuid,
+ String resourceType, String hostUuid, long size, Timestamp now) {
+ createResourceRef(primaryStorageUuid, resourceUuid, resourceType, hostUuid, size, now);
+ }
+
+ @Override
+ public void afterSnapshotPersist(String primaryStorageUuid, String resourceUuid,
+ String resourceType, String hostUuid, long size, Timestamp now) {
+ createResourceRef(primaryStorageUuid, resourceUuid, resourceType, hostUuid, size, now);
+ }
+
+ private void createResourceRef(String primaryStorageUuid, String resourceUuid,
+ String resourceType, String hostUuid, long size, Timestamp now) {
+ LocalStorageResourceRefVO ref = new LocalStorageResourceRefVO();
+ ref.setPrimaryStorageUuid(primaryStorageUuid);
+ ref.setResourceUuid(resourceUuid);
+ ref.setResourceType(resourceType);
+ ref.setHostUuid(hostUuid);
+ ref.setSize(size);
+ ref.setCreateDate(now);
+ ref.setLastOpDate(now);
+ dbf.persist(ref);
+ }
+}
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 abe9ac152b6..a70c24c3939 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
@@ -36,14 +36,14 @@
import org.zstack.header.storage.backup.BackupStorageVO;
import org.zstack.header.storage.primary.*;
import org.zstack.header.storage.primary.VolumeSnapshotCapability.VolumeSnapshotArrangementType;
-import org.zstack.header.storage.snapshot.DeleteVolumeSnapshotDirection;
import org.zstack.header.storage.snapshot.ShrinkVolumeSnapshotOnPrimaryStorageMsg;
import org.zstack.header.storage.snapshot.VolumeSnapshotConstant;
import org.zstack.header.storage.snapshot.VolumeSnapshotInventory;
+import org.zstack.compute.vm.VmGlobalConfig;
+import org.zstack.header.vm.*;
import org.zstack.header.vm.VmInstanceSpec.ImageSpec;
-import org.zstack.header.vm.VmInstanceState;
-import org.zstack.header.vm.VmInstanceVO;
-import org.zstack.header.vm.VmInstanceVO_;
+import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg;
+import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply;
import org.zstack.header.volume.*;
import org.zstack.kvm.*;
import org.zstack.storage.primary.*;
@@ -51,10 +51,8 @@
import org.zstack.storage.volume.VolumeErrors;
import org.zstack.storage.volume.VolumeSystemTags;
import org.zstack.tag.SystemTagCreator;
-import org.zstack.utils.CollectionUtils;
import org.zstack.utils.DebugUtils;
import org.zstack.utils.Utils;
-import org.zstack.utils.function.Function;
import org.zstack.utils.logging.CLogger;
import javax.persistence.Tuple;
@@ -131,6 +129,8 @@ protected void handleLocalMessage(Message msg) {
handle((CommitVolumeSnapshotOnPrimaryStorageMsg) msg);
} else if (msg instanceof PullVolumeSnapshotOnPrimaryStorageMsg) {
handle((PullVolumeSnapshotOnPrimaryStorageMsg) msg);
+ } else if (msg instanceof RebaseVolumeBackingFileOnPrimaryStorageMsg) {
+ handle((RebaseVolumeBackingFileOnPrimaryStorageMsg) msg);
} else {
super.handleLocalMessage(msg);
}
@@ -1924,4 +1924,170 @@ private String getHostUuidFromVolume(String volumeUuid) {
return hostUuid;
}
+
+ @Override
+ protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) {
+ thdf.chainSubmit(new ChainTask(msg) {
+ @Override
+ public String getSyncSignature() {
+ return "update-metadata-on-ps-" + self.getUuid();
+ }
+
+ @Override
+ public int getSyncLevel() {
+ return VmGlobalConfig.VM_METADATA_PS_MAX_CONCURRENT.value(Integer.class);
+ }
+
+ @Override
+ public void run(SyncTaskChain chain) {
+ UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply();
+
+ String hostUuid = getHostUuidFromVolume(msg.getRootVolumeUuid());
+ if (hostUuid == null || hostUuid.isEmpty()) {
+ reply.setError(operr("no host found for volume[uuid:%s]", msg.getRootVolumeUuid()));
+ bus.reply(msg, reply);
+ chain.next();
+ return;
+ }
+
+ final NfsPrimaryStorageBackend backend = getBackendByHostUuid(hostUuid);
+ backend.handle(msg, hostUuid, new ReturnValueCompletion(msg, chain) {
+ @Override
+ public void success(UpdateVmInstanceMetadataOnPrimaryStorageReply r) {
+ bus.reply(msg, r);
+ chain.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ reply.setError(errorCode);
+ bus.reply(msg, reply);
+ chain.next();
+ }
+ });
+ }
+
+ @Override
+ public String getName() {
+ return "update-metadata-on-ps-" + self.getUuid();
+ }
+ });
+ }
+
+ @Override
+ protected void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg) {
+ GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply();
+
+ List connectedHosts = factory.getConnectedHostForOperation(getSelfInventory());
+ if (connectedHosts.isEmpty()) {
+ reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid()));
+ bus.reply(msg, reply);
+ return;
+ }
+
+ String hostUuid = connectedHosts.get(0).getUuid();
+ final NfsPrimaryStorageBackend backend = getUsableBackend();
+
+ backend.handle(msg, hostUuid, new ReturnValueCompletion(msg) {
+ @Override
+ public void success(GetVmInstanceMetadataFromPrimaryStorageReply r) {
+ bus.reply(msg, r);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ reply.setError(errorCode);
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
+ @Override
+ protected void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg) {
+ ScanVmInstanceMetadataFromPrimaryStorageReply reply = new ScanVmInstanceMetadataFromPrimaryStorageReply();
+
+ List connectedHosts = factory.getConnectedHostForOperation(getSelfInventory());
+ if (connectedHosts.isEmpty()) {
+ reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid()));
+ bus.reply(msg, reply);
+ return;
+ }
+
+ String hostUuid = connectedHosts.get(0).getUuid();
+ final NfsPrimaryStorageBackend backend = getUsableBackend();
+
+ backend.handle(msg, hostUuid, new ReturnValueCompletion(msg) {
+ @Override
+ public void success(ScanVmInstanceMetadataFromPrimaryStorageReply r) {
+ bus.reply(msg, r);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ reply.setError(errorCode);
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
+ @Override
+ protected void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg) {
+ CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply();
+
+ List connectedHosts = factory.getConnectedHostForOperation(getSelfInventory());
+ if (connectedHosts.isEmpty()) {
+ reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid()));
+ bus.reply(msg, reply);
+ return;
+ }
+
+ String hostUuid = connectedHosts.get(0).getUuid();
+ final NfsPrimaryStorageBackend backend = getUsableBackend();
+
+ backend.handle(msg, hostUuid, new ReturnValueCompletion(msg) {
+ @Override
+ public void success(CleanupVmInstanceMetadataOnPrimaryStorageReply r) {
+ bus.reply(msg, r);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ reply.setError(errorCode);
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
+ protected void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg) {
+ RebaseVolumeBackingFileOnPrimaryStorageReply reply = new RebaseVolumeBackingFileOnPrimaryStorageReply();
+
+ NfsPrimaryStorageBackend backend = getUsableBackend();
+ if (backend == null) {
+ reply.setError(operr("the NFS primary storage[uuid:%s, name:%s] cannot find hosts in attached clusters to perform the operation",
+ self.getUuid(), self.getName()));
+ bus.reply(msg, reply);
+ return;
+ }
+
+ List connectedHosts = factory.getConnectedHostForOperation(getSelfInventory());
+ if (connectedHosts.isEmpty()) {
+ reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid()));
+ bus.reply(msg, reply);
+ return;
+ }
+ String hostUuid = connectedHosts.get(0).getUuid();
+
+ backend.handle(msg, hostUuid, new ReturnValueCompletion(msg) {
+ @Override
+ public void success(RebaseVolumeBackingFileOnPrimaryStorageReply r) {
+ bus.reply(msg, r);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ 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 459023d7c17..35a240a6221 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
@@ -7,6 +7,8 @@
import org.zstack.header.image.ImageInventory;
import org.zstack.header.storage.primary.*;
import org.zstack.header.storage.snapshot.VolumeSnapshotInventory;
+import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg;
+import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply;
import org.zstack.header.volume.VolumeStats;
import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageMsg;
import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageReply;
@@ -91,6 +93,16 @@ public interface NfsPrimaryStorageBackend {
void updateMountPoint(PrimaryStorageInventory pinv, String clusterUuid, String oldMountPoint, String newMountPoint, Completion completion);
+ void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion);
+
+ void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion);
+
+ void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion);
+
+ void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion);
+
+ void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion);
+
class BitsInfo {
private String installPath;
private long size;
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 93d3d7aab99..cd3be075aee 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
@@ -21,7 +21,9 @@
import org.zstack.core.timeout.ApiTimeoutManager;
import org.zstack.core.trash.StorageTrash;
import org.zstack.header.core.*;
-import org.zstack.header.core.workflow.*;
+import org.zstack.header.core.workflow.Flow;
+import org.zstack.header.core.workflow.FlowTrigger;
+import org.zstack.header.core.workflow.NoRollbackFlow;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.errorcode.ErrorCodeList;
import org.zstack.header.errorcode.OperationFailureException;
@@ -39,6 +41,8 @@
import org.zstack.header.vm.VmInstanceState;
import org.zstack.header.vm.VmInstanceVO;
import org.zstack.header.vm.VmInstanceVO_;
+import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg;
+import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply;
import org.zstack.header.volume.*;
import org.zstack.identity.AccountManager;
import org.zstack.kvm.*;
@@ -129,7 +133,12 @@ public class NfsPrimaryStorageKVMBackend implements NfsPrimaryStorageBackend,
public static final String GET_DOWNLOAD_BITS_FROM_KVM_HOST_PROGRESS_PATH = "/nfsprimarystorage/kvmhost/download/progress";
public static final String CREATE_VOLUME_FROM_TEMPLATE_PATH = "/nfsprimarystorage/sftp/createvolumefromtemplate";
public static final String GET_QCOW2_HASH_VALUE_PATH = "/nfsprimarystorage/getqcow2hash";
+ public static final String WRITE_VM_METADATA_PATH = "/nfsprimarystorage/vm/metadata/write";
+ public static final String GET_VM_INSTANCE_METADATA_PATH = "/nfsprimarystorage/vm/metadata/get";
+ public static final String SCAN_VM_METADATA_PATH = "/nfsprimarystorage/vm/metadata/scan";
+ public static final String CLEANUP_VM_METADATA_PATH = "/nfsprimarystorage/vm/metadata/cleanup";
+ public static final String NFS_PREFIX_REBASE_BACKING_FILES_PATH = "/nfsprimarystorage/snapshot/prefixrebasebackingfiles";
//////////////// For unit test //////////////////////////
private boolean syncGetCapacity = false;
@@ -2051,4 +2060,166 @@ public void run(MessageReply r) {
}
});
}
+
+ public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) {
+ WriteVmMetadataCmd cmd = new WriteVmMetadataCmd();
+ cmd.metadata = msg.getMetadata();
+ cmd.metadataPath = msg.getMetadataPath();
+ cmd.vmUuid = msg.getVmInstanceUuid();
+ cmd.vmName = msg.getVmInstanceName();
+ cmd.vmCategory = msg.getVmCategory();
+ cmd.architecture = msg.getArchitecture();
+ cmd.schemaVersion = msg.getSchemaVersion();
+
+ KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg();
+ hmsg.setCommand(cmd);
+ hmsg.setPath(WRITE_VM_METADATA_PATH);
+ hmsg.setHostUuid(hostUuid);
+ bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid);
+ bus.send(hmsg, new CloudBusCallBack(completion) {
+ @Override
+ public void run(MessageReply reply) {
+ if (!reply.isSuccess()) {
+ completion.fail(reply.getError());
+ return;
+ }
+
+ WriteVmMetadataRsp rsp = ((KVMHostAsyncHttpCallReply) reply).toResponse(WriteVmMetadataRsp.class);
+ if (!rsp.isSuccess()) {
+ completion.fail(operr("failed to write vm metadata on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError()));
+ return;
+ }
+
+ completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply());
+ }
+ });
+ }
+
+ @Override
+ public void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) {
+ NfsPrimaryStorageKVMBackendCommands.GetVmInstanceMetadataCmd cmd = new NfsPrimaryStorageKVMBackendCommands.GetVmInstanceMetadataCmd();
+ cmd.metadataPath = msg.getMetadataPath();
+
+ KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg();
+ hmsg.setCommand(cmd);
+ hmsg.setPath(GET_VM_INSTANCE_METADATA_PATH);
+ hmsg.setHostUuid(hostUuid);
+ bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid);
+ bus.send(hmsg, new CloudBusCallBack(completion) {
+ @Override
+ public void run(MessageReply reply) {
+ if (!reply.isSuccess()) {
+ completion.fail(reply.getError());
+ return;
+ }
+
+ GetVmInstanceMetadataRsp rsp = ((KVMHostAsyncHttpCallReply) reply).toResponse(GetVmInstanceMetadataRsp.class);
+ if (!rsp.isSuccess()) {
+ completion.fail(operr("failed to get vm instance metadata on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError()));
+ return;
+ }
+
+ GetVmInstanceMetadataFromPrimaryStorageReply r = new GetVmInstanceMetadataFromPrimaryStorageReply();
+ r.setMetadata(rsp.metadata);
+ completion.success(r);
+ }
+ });
+ }
+
+ @Override
+ public void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) {
+ NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataCmd cmd = new NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataCmd();
+ cmd.metadataDir = msg.getMetadataDir();
+
+ KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg();
+ hmsg.setCommand(cmd);
+ hmsg.setPath(SCAN_VM_METADATA_PATH);
+ hmsg.setHostUuid(hostUuid);
+ bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid);
+ bus.send(hmsg, new CloudBusCallBack(completion) {
+ @Override
+ public void run(MessageReply reply) {
+ if (!reply.isSuccess()) {
+ completion.fail(reply.getError());
+ return;
+ }
+
+ NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataRsp rsp = ((KVMHostAsyncHttpCallReply) reply).toResponse(NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataRsp.class);
+ if (!rsp.isSuccess()) {
+ completion.fail(operr("failed to scan vm instance metadata on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError()));
+ return;
+ }
+
+ ScanVmInstanceMetadataFromPrimaryStorageReply r = new ScanVmInstanceMetadataFromPrimaryStorageReply();
+ r.setVmInstanceMetadata(rsp.metadataEntries);
+ completion.success(r);
+ }
+ });
+ }
+
+ @Override
+ public void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) {
+ CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd();
+ cmd.metadataPath = msg.getMetadataPath();
+
+ KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg();
+ hmsg.setCommand(cmd);
+ hmsg.setPath(CLEANUP_VM_METADATA_PATH);
+ hmsg.setHostUuid(hostUuid);
+ bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid);
+ bus.send(hmsg, new CloudBusCallBack(completion) {
+ @Override
+ public void run(MessageReply reply) {
+ if (!reply.isSuccess()) {
+ completion.fail(reply.getError());
+ return;
+ }
+
+ CleanupVmMetadataRsp rsp = ((KVMHostAsyncHttpCallReply) reply).toResponse(CleanupVmMetadataRsp.class);
+ if (!rsp.isSuccess()) {
+ completion.fail(operr("failed to cleanup vm metadata on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError()));
+ return;
+ }
+
+ CleanupVmInstanceMetadataOnPrimaryStorageReply r = new CleanupVmInstanceMetadataOnPrimaryStorageReply();
+ completion.success(r);
+ }
+ });
+ }
+
+ @Override
+ public void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) {
+ NfsPrimaryStorageKVMBackendCommands.PrefixRebaseBackingFilesCmd cmd = new NfsPrimaryStorageKVMBackendCommands.PrefixRebaseBackingFilesCmd();
+ // NFS installPaths are already filesystem paths, no conversion needed
+ cmd.filePaths = msg.getInstallPaths();
+ cmd.oldPrefix = msg.getOldPrefix();
+ cmd.newPrefix = msg.getNewPrefix();
+ cmd.setUuid(msg.getPrimaryStorageUuid());
+
+ KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg();
+ hmsg.setCommand(cmd);
+ hmsg.setPath(NFS_PREFIX_REBASE_BACKING_FILES_PATH);
+ hmsg.setHostUuid(hostUuid);
+ bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid);
+ bus.send(hmsg, new CloudBusCallBack(completion) {
+ @Override
+ public void run(MessageReply reply) {
+ if (!reply.isSuccess()) {
+ completion.fail(reply.getError());
+ return;
+ }
+
+ NfsPrimaryStorageKVMBackendCommands.PrefixRebaseBackingFilesRsp rsp =
+ ((KVMHostAsyncHttpCallReply) reply).toResponse(NfsPrimaryStorageKVMBackendCommands.PrefixRebaseBackingFilesRsp.class);
+ if (!rsp.isSuccess()) {
+ completion.fail(operr("failed to prefix rebase backing files on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError()));
+ return;
+ }
+
+ RebaseVolumeBackingFileOnPrimaryStorageReply r = new RebaseVolumeBackingFileOnPrimaryStorageReply();
+ r.setRebasedCount(rsp.rebasedCount);
+ completion.success(r);
+ }
+ });
+ }
}
diff --git a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java
index d7cebfaa4c6..12c41c054c7 100755
--- a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java
+++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java
@@ -2,6 +2,7 @@
import org.zstack.header.HasThreadContext;
import org.zstack.header.core.validation.Validation;
+import org.zstack.header.storage.primary.VmMetadataScanEntry;
import org.zstack.kvm.KVMAgentCommands;
import org.zstack.kvm.KVMAgentCommands.AgentCommand;
import org.zstack.kvm.KVMAgentCommands.AgentResponse;
@@ -948,4 +949,51 @@ public void setHashValue(String hashValue) {
this.hashValue = hashValue;
}
}
+
+ public static class WriteVmMetadataCmd extends NfsPrimaryStorageAgentCommand {
+ public String metadata;
+ public String metadataPath;
+ public String vmUuid;
+ public String vmName;
+ public String vmCategory;
+ public String architecture;
+ public String schemaVersion;
+ }
+
+ public static class WriteVmMetadataRsp extends NfsPrimaryStorageAgentResponse {
+ }
+
+
+ public static class GetVmInstanceMetadataCmd extends NfsPrimaryStorageAgentCommand {
+ public String metadataPath;
+ }
+
+ public static class GetVmInstanceMetadataRsp extends NfsPrimaryStorageAgentResponse {
+ public String metadata;
+ }
+
+ public static class ScanVmMetadataCmd extends NfsPrimaryStorageAgentCommand {
+ public String metadataDir;
+ }
+
+ public static class ScanVmMetadataRsp extends NfsPrimaryStorageAgentResponse {
+ public List metadataEntries = new ArrayList<>();
+ }
+
+ public static class CleanupVmMetadataCmd extends NfsPrimaryStorageAgentCommand {
+ public String metadataPath;
+ }
+
+ public static class CleanupVmMetadataRsp extends NfsPrimaryStorageAgentResponse {
+ }
+
+ public static class PrefixRebaseBackingFilesCmd extends NfsPrimaryStorageAgentCommand {
+ public List filePaths;
+ public String oldPrefix;
+ public String newPrefix;
+ }
+
+ public static class PrefixRebaseBackingFilesRsp extends NfsPrimaryStorageAgentResponse {
+ public int rebasedCount;
+ }
}
diff --git a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java
new file mode 100644
index 00000000000..beee1a20371
--- /dev/null
+++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java
@@ -0,0 +1,90 @@
+package org.zstack.storage.primary.nfs;
+
+import org.zstack.core.db.Q;
+import org.zstack.header.storage.primary.PrimaryStorageVO;
+import org.zstack.header.storage.primary.PrimaryStorageVO_;
+import org.zstack.header.vm.metadata.VmInstanceMetadataConstants;
+import org.zstack.header.vm.metadata.VmMetadataPathBuildExtensionPoint;
+import org.zstack.header.vm.metadata.VmMetadataPathReplacementExtensionPoint;
+import org.zstack.utils.Utils;
+import org.zstack.utils.logging.CLogger;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class NfsVmMetadataExtension implements VmMetadataPathBuildExtensionPoint, VmMetadataPathReplacementExtensionPoint {
+ private static final CLogger logger = Utils.getLogger(NfsVmMetadataExtension.class);
+
+ @Override
+ public String getPrimaryStorageType() {
+ return NfsPrimaryStorageConstant.NFS_PRIMARY_STORAGE_TYPE;
+ }
+
+ @Override
+ public String buildMetadataDir(String primaryStorageUuid) {
+ String mountPath = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue();
+ return String.format("%s/%s", mountPath, VmInstanceMetadataConstants.METADATA_DIR_NAME);
+ }
+
+ @Override
+ public String buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid) {
+ String mountPath = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue();
+ return String.format("%s/%s/%s.json", mountPath, VmInstanceMetadataConstants.METADATA_DIR_NAME, vmInstanceUuid);
+ }
+
+ @Override
+ public PathReplacementResult calculatePathReplacements(String targetPsUuid, List allOldPaths) {
+ String baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid, targetPsUuid).findValue();
+ if (baseDir == null) {
+ baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, targetPsUuid).findValue();
+ }
+ if (baseDir == null) {
+ logger.warn(String.format("NFS PS[uuid:%s] has no mountPath or url, path replacement disabled", targetPsUuid));
+ PathReplacementResult result = new PathReplacementResult();
+ result.setPathMap(Collections.emptyMap());
+ return result;
+ }
+ String newPrefix = baseDir.endsWith("/") ? baseDir : baseDir + "/";
+
+ // Extract old prefix from the first recognizable path
+ String oldPrefix = null;
+ for (String path : allOldPaths) {
+ oldPrefix = extractOldPrefix(path);
+ if (oldPrefix != null) break;
+ }
+
+ Map pathMap = new LinkedHashMap<>();
+ if (oldPrefix != null) {
+ for (String oldPath : allOldPaths) {
+ if (oldPath != null && oldPath.startsWith(oldPrefix)) {
+ pathMap.put(oldPath, newPrefix + oldPath.substring(oldPrefix.length()));
+ }
+ }
+ } else {
+ logger.warn(String.format("cannot extract old path prefix from any path for NFS PS[uuid:%s], " +
+ "path replacement disabled", targetPsUuid));
+ }
+
+ PathReplacementResult result = new PathReplacementResult();
+ result.setPathMap(pathMap);
+ result.setOldPrefix(oldPrefix);
+ result.setNewPrefix(newPrefix);
+ return result;
+ }
+
+ private String extractOldPrefix(String path) {
+ if (path == null || !path.startsWith("/")) {
+ return null;
+ }
+ String[] markers = {"/rootVolumes/", "/dataVolumes/", "/volumeSnapshots/", "/memory/"};
+ for (String marker : markers) {
+ int idx = path.indexOf(marker);
+ if (idx > 0) {
+ return path.substring(0, idx + 1);
+ }
+ }
+ return null;
+ }
+}
diff --git a/resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java b/resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java
index fd43d66dac3..6bf601508ec 100644
--- a/resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java
+++ b/resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java
@@ -5,11 +5,13 @@
import org.zstack.header.message.APIDeleteMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import org.zstack.header.vo.ResourceVO;
@RestRequest(path = "/resource-configurations/{category}/{name}/{resourceUuid}",
method = HttpMethod.DELETE,
responseClass = APIDeleteResourceConfigEvent.class)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "ResourceBasedVmUuidFromApiResolver")
public class APIDeleteResourceConfigMsg extends APIDeleteMessage implements ResourceConfigMessage {
@APIParam
private String category;
diff --git a/resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java b/resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java
index 1b8437d2404..3f5eb20db52 100644
--- a/resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java
+++ b/resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java
@@ -5,12 +5,14 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
+import org.zstack.header.vm.metadata.MetadataImpact;
import org.zstack.header.vo.ResourceVO;
@RestRequest(path = "/resource-configurations/{category}/{name}/{resourceUuid}/actions",
method = HttpMethod.PUT,
isAction = true,
responseClass = APIUpdateResourceConfigEvent.class)
+@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "ResourceBasedVmUuidFromApiResolver")
public class APIUpdateResourceConfigMsg extends APIMessage implements ResourceConfigMessage {
@APIParam
private String category;
diff --git a/sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java b/sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java
new file mode 100644
index 00000000000..46586cab3c4
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java
@@ -0,0 +1,101 @@
+package org.zstack.sdk;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.zstack.sdk.*;
+
+public class CleanupVmInstanceMetadataAction extends AbstractAction {
+
+ private static final HashMap parameterMap = new HashMap<>();
+
+ private static final HashMap nonAPIParameterMap = new HashMap<>();
+
+ public static class Result {
+ public ErrorCode error;
+ public org.zstack.sdk.CleanupVmInstanceMetadataResult value;
+
+ public Result throwExceptionIfError() {
+ if (error != null) {
+ throw new ApiException(
+ String.format("error[code: %s, description: %s, details: %s]", error.code, error.description, error.details)
+ );
+ }
+
+ return this;
+ }
+ }
+
+ @Param(required = true, nonempty = true, nullElements = false, emptyString = true, noTrim = false)
+ public java.util.List vmUuids;
+
+ @Param(required = false)
+ public java.util.List systemTags;
+
+ @Param(required = false)
+ public java.util.List userTags;
+
+ @Param(required = false)
+ public String sessionId;
+
+ @Param(required = false)
+ public String accessKeyId;
+
+ @Param(required = false)
+ public String accessKeySecret;
+
+ @Param(required = false)
+ public String requestIp;
+
+ @NonAPIParam
+ public long timeout = -1;
+
+ @NonAPIParam
+ public long pollingInterval = -1;
+
+
+ private Result makeResult(ApiResult res) {
+ Result ret = new Result();
+ if (res.error != null) {
+ ret.error = res.error;
+ return ret;
+ }
+
+ org.zstack.sdk.CleanupVmInstanceMetadataResult value = res.getResult(org.zstack.sdk.CleanupVmInstanceMetadataResult.class);
+ ret.value = value == null ? new org.zstack.sdk.CleanupVmInstanceMetadataResult() : value;
+
+ return ret;
+ }
+
+ public Result call() {
+ ApiResult res = ZSClient.call(this);
+ return makeResult(res);
+ }
+
+ public void call(final Completion completion) {
+ ZSClient.call(this, new InternalCompletion() {
+ @Override
+ public void complete(ApiResult res) {
+ completion.complete(makeResult(res));
+ }
+ });
+ }
+
+ protected Map getParameterMap() {
+ return parameterMap;
+ }
+
+ protected Map getNonAPIParameterMap() {
+ return nonAPIParameterMap;
+ }
+
+ protected RestInfo getRestInfo() {
+ RestInfo info = new RestInfo();
+ info.httpMethod = "PUT";
+ info.path = "/vm-instances/metadata/cleanup";
+ info.needSession = true;
+ info.needPoll = true;
+ info.parameterName = "cleanupVmInstanceMetadata";
+ return info;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java b/sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
new file mode 100644
index 00000000000..879a409cc59
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
@@ -0,0 +1,30 @@
+package org.zstack.sdk;
+
+
+
+public class CleanupVmInstanceMetadataResult {
+ public java.lang.Integer totalCleaned;
+ public void setTotalCleaned(java.lang.Integer totalCleaned) {
+ this.totalCleaned = totalCleaned;
+ }
+ public java.lang.Integer getTotalCleaned() {
+ return this.totalCleaned;
+ }
+
+ public java.lang.Integer totalFailed;
+ public void setTotalFailed(java.lang.Integer totalFailed) {
+ this.totalFailed = totalFailed;
+ }
+ public java.lang.Integer getTotalFailed() {
+ return this.totalFailed;
+ }
+
+ public java.util.List failedVmUuids;
+ public void setFailedVmUuids(java.util.List failedVmUuids) {
+ this.failedVmUuids = failedVmUuids;
+ }
+ public java.util.List getFailedVmUuids() {
+ return this.failedVmUuids;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java b/sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java
new file mode 100644
index 00000000000..0471b4b7610
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java
@@ -0,0 +1,95 @@
+package org.zstack.sdk;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.zstack.sdk.*;
+
+public class GetVmInstanceMetadataFromPrimaryStorageAction extends AbstractAction {
+
+ private static final HashMap parameterMap = new HashMap<>();
+
+ private static final HashMap nonAPIParameterMap = new HashMap<>();
+
+ public static class Result {
+ public ErrorCode error;
+ public org.zstack.sdk.GetVmInstanceMetadataFromPrimaryStorageResult value;
+
+ public Result throwExceptionIfError() {
+ if (error != null) {
+ throw new ApiException(
+ String.format("error[code: %s, description: %s, details: %s]", error.code, error.description, error.details)
+ );
+ }
+
+ return this;
+ }
+ }
+
+ @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ public java.lang.String uuid;
+
+ @Param(required = false)
+ public java.util.List systemTags;
+
+ @Param(required = false)
+ public java.util.List userTags;
+
+ @Param(required = false)
+ public String sessionId;
+
+ @Param(required = false)
+ public String accessKeyId;
+
+ @Param(required = false)
+ public String accessKeySecret;
+
+ @Param(required = false)
+ public String requestIp;
+
+
+ private Result makeResult(ApiResult res) {
+ Result ret = new Result();
+ if (res.error != null) {
+ ret.error = res.error;
+ return ret;
+ }
+
+ org.zstack.sdk.GetVmInstanceMetadataFromPrimaryStorageResult value = res.getResult(org.zstack.sdk.GetVmInstanceMetadataFromPrimaryStorageResult.class);
+ ret.value = value == null ? new org.zstack.sdk.GetVmInstanceMetadataFromPrimaryStorageResult() : value;
+
+ return ret;
+ }
+
+ public Result call() {
+ ApiResult res = ZSClient.call(this);
+ return makeResult(res);
+ }
+
+ public void call(final Completion completion) {
+ ZSClient.call(this, new InternalCompletion() {
+ @Override
+ public void complete(ApiResult res) {
+ completion.complete(makeResult(res));
+ }
+ });
+ }
+
+ protected Map getParameterMap() {
+ return parameterMap;
+ }
+
+ protected Map getNonAPIParameterMap() {
+ return nonAPIParameterMap;
+ }
+
+ protected RestInfo getRestInfo() {
+ RestInfo info = new RestInfo();
+ info.httpMethod = "GET";
+ info.path = "/primary-storage/vm-instances/metadata";
+ info.needSession = true;
+ info.needPoll = false;
+ info.parameterName = "";
+ return info;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java b/sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
new file mode 100644
index 00000000000..d22099fae76
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
@@ -0,0 +1,14 @@
+package org.zstack.sdk;
+
+
+
+public class GetVmInstanceMetadataFromPrimaryStorageResult {
+ public java.lang.String metadata;
+ public void setMetadata(java.lang.String metadata) {
+ this.metadata = metadata;
+ }
+ public java.lang.String getMetadata() {
+ return this.metadata;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.java b/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.java
new file mode 100644
index 00000000000..c7acac2ae1f
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.java
@@ -0,0 +1,119 @@
+package org.zstack.sdk;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.zstack.sdk.*;
+
+public class RegisterVmInstanceFromMetadataAction extends AbstractAction {
+
+ private static final HashMap parameterMap = new HashMap<>();
+
+ private static final HashMap nonAPIParameterMap = new HashMap<>();
+
+ public static class Result {
+ public ErrorCode error;
+ public org.zstack.sdk.RegisterVmInstanceFromMetadataResult value;
+
+ public Result throwExceptionIfError() {
+ if (error != null) {
+ throw new ApiException(
+ String.format("error[code: %s, description: %s, details: %s]", error.code, error.description, error.details)
+ );
+ }
+
+ return this;
+ }
+ }
+
+ @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ public java.lang.String metadataPath;
+
+ @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ public java.lang.String primaryStorageUuid;
+
+ @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ public java.lang.String zoneUuid;
+
+ @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ public java.lang.String clusterUuid;
+
+ @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ public java.lang.String hostUuid;
+
+ @Param(required = false, maxLength = 255, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ public java.lang.String name;
+
+ @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ public java.util.List tagUuids;
+
+ @Param(required = false)
+ public java.util.List systemTags;
+
+ @Param(required = false)
+ public java.util.List userTags;
+
+ @Param(required = false)
+ public String sessionId;
+
+ @Param(required = false)
+ public String accessKeyId;
+
+ @Param(required = false)
+ public String accessKeySecret;
+
+ @Param(required = false)
+ public String requestIp;
+
+ @NonAPIParam
+ public long timeout = -1;
+
+ @NonAPIParam
+ public long pollingInterval = -1;
+
+
+ private Result makeResult(ApiResult res) {
+ Result ret = new Result();
+ if (res.error != null) {
+ ret.error = res.error;
+ return ret;
+ }
+
+ org.zstack.sdk.RegisterVmInstanceFromMetadataResult value = res.getResult(org.zstack.sdk.RegisterVmInstanceFromMetadataResult.class);
+ ret.value = value == null ? new org.zstack.sdk.RegisterVmInstanceFromMetadataResult() : value;
+
+ return ret;
+ }
+
+ public Result call() {
+ ApiResult res = ZSClient.call(this);
+ return makeResult(res);
+ }
+
+ public void call(final Completion completion) {
+ ZSClient.call(this, new InternalCompletion() {
+ @Override
+ public void complete(ApiResult res) {
+ completion.complete(makeResult(res));
+ }
+ });
+ }
+
+ protected Map getParameterMap() {
+ return parameterMap;
+ }
+
+ protected Map getNonAPIParameterMap() {
+ return nonAPIParameterMap;
+ }
+
+ protected RestInfo getRestInfo() {
+ RestInfo info = new RestInfo();
+ info.httpMethod = "POST";
+ info.path = "/vm-instances/metadata/register";
+ info.needSession = true;
+ info.needPoll = true;
+ info.parameterName = "params";
+ return info;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java b/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
new file mode 100644
index 00000000000..11634dcf3f1
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
@@ -0,0 +1,14 @@
+package org.zstack.sdk;
+
+import org.zstack.sdk.VmInstanceInventory;
+
+public class RegisterVmInstanceFromMetadataResult {
+ public VmInstanceInventory inventory;
+ public void setInventory(VmInstanceInventory inventory) {
+ this.inventory = inventory;
+ }
+ public VmInstanceInventory getInventory() {
+ return this.inventory;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.java b/sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.java
new file mode 100644
index 00000000000..64984c03149
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.java
@@ -0,0 +1,95 @@
+package org.zstack.sdk;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.zstack.sdk.*;
+
+public class ScanVmInstanceMetadataFromPrimaryStorageAction extends AbstractAction {
+
+ private static final HashMap parameterMap = new HashMap<>();
+
+ private static final HashMap nonAPIParameterMap = new HashMap<>();
+
+ public static class Result {
+ public ErrorCode error;
+ public org.zstack.sdk.ScanVmInstanceMetadataFromPrimaryStorageResult value;
+
+ public Result throwExceptionIfError() {
+ if (error != null) {
+ throw new ApiException(
+ String.format("error[code: %s, description: %s, details: %s]", error.code, error.description, error.details)
+ );
+ }
+
+ return this;
+ }
+ }
+
+ @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ public java.lang.String uuid;
+
+ @Param(required = false)
+ public java.util.List systemTags;
+
+ @Param(required = false)
+ public java.util.List userTags;
+
+ @Param(required = false)
+ public String sessionId;
+
+ @Param(required = false)
+ public String accessKeyId;
+
+ @Param(required = false)
+ public String accessKeySecret;
+
+ @Param(required = false)
+ public String requestIp;
+
+
+ private Result makeResult(ApiResult res) {
+ Result ret = new Result();
+ if (res.error != null) {
+ ret.error = res.error;
+ return ret;
+ }
+
+ org.zstack.sdk.ScanVmInstanceMetadataFromPrimaryStorageResult value = res.getResult(org.zstack.sdk.ScanVmInstanceMetadataFromPrimaryStorageResult.class);
+ ret.value = value == null ? new org.zstack.sdk.ScanVmInstanceMetadataFromPrimaryStorageResult() : value;
+
+ return ret;
+ }
+
+ public Result call() {
+ ApiResult res = ZSClient.call(this);
+ return makeResult(res);
+ }
+
+ public void call(final Completion completion) {
+ ZSClient.call(this, new InternalCompletion() {
+ @Override
+ public void complete(ApiResult res) {
+ completion.complete(makeResult(res));
+ }
+ });
+ }
+
+ protected Map getParameterMap() {
+ return parameterMap;
+ }
+
+ protected Map getNonAPIParameterMap() {
+ return nonAPIParameterMap;
+ }
+
+ protected RestInfo getRestInfo() {
+ RestInfo info = new RestInfo();
+ info.httpMethod = "GET";
+ info.path = "/primary-storage/vm-instances/metadata/scan";
+ info.needSession = true;
+ info.needPoll = false;
+ info.parameterName = "";
+ return info;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java b/sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
new file mode 100644
index 00000000000..4abb18ceae6
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
@@ -0,0 +1,14 @@
+package org.zstack.sdk;
+
+
+
+public class ScanVmInstanceMetadataFromPrimaryStorageResult {
+ public java.util.List vmInstanceMetadata;
+ public void setVmInstanceMetadata(java.util.List vmInstanceMetadata) {
+ this.vmInstanceMetadata = vmInstanceMetadata;
+ }
+ public java.util.List getVmInstanceMetadata() {
+ return this.vmInstanceMetadata;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java b/sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java
new file mode 100644
index 00000000000..bd554a6bcef
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java
@@ -0,0 +1,100 @@
+package org.zstack.sdk;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class UpdateVmInstanceMetadataAction extends AbstractAction {
+
+ private static final HashMap parameterMap = new HashMap<>();
+
+ private static final HashMap nonAPIParameterMap = new HashMap<>();
+
+ public static class Result {
+ public ErrorCode error;
+ public org.zstack.sdk.UpdateVmInstanceMetadataResult value;
+
+ public Result throwExceptionIfError() {
+ if (error != null) {
+ throw new ApiException(
+ String.format("error[code: %s, description: %s, details: %s]", error.code, error.description, error.details)
+ );
+ }
+
+ return this;
+ }
+ }
+
+ @Param(required = true, nonempty = true, nullElements = false, emptyString = true, noTrim = false)
+ public java.util.List vmUuids;
+
+ @Param(required = false)
+ public java.util.List systemTags;
+
+ @Param(required = false)
+ public java.util.List userTags;
+
+ @Param(required = false)
+ public String sessionId;
+
+ @Param(required = false)
+ public String accessKeyId;
+
+ @Param(required = false)
+ public String accessKeySecret;
+
+ @Param(required = false)
+ public String requestIp;
+
+ @NonAPIParam
+ public long timeout = -1;
+
+ @NonAPIParam
+ public long pollingInterval = -1;
+
+
+ private Result makeResult(ApiResult res) {
+ Result ret = new Result();
+ if (res.error != null) {
+ ret.error = res.error;
+ return ret;
+ }
+
+ org.zstack.sdk.UpdateVmInstanceMetadataResult value = res.getResult(org.zstack.sdk.UpdateVmInstanceMetadataResult.class);
+ ret.value = value == null ? new org.zstack.sdk.UpdateVmInstanceMetadataResult() : value;
+
+ return ret;
+ }
+
+ public Result call() {
+ ApiResult res = ZSClient.call(this);
+ return makeResult(res);
+ }
+
+ public void call(final Completion completion) {
+ ZSClient.call(this, new InternalCompletion() {
+ @Override
+ public void complete(ApiResult res) {
+ completion.complete(makeResult(res));
+ }
+ });
+ }
+
+ protected Map getParameterMap() {
+ return parameterMap;
+ }
+
+ protected Map getNonAPIParameterMap() {
+ return nonAPIParameterMap;
+ }
+
+ protected RestInfo getRestInfo() {
+ RestInfo info = new RestInfo();
+ info.httpMethod = "PUT";
+ info.path = "/vm-instances/metadata/actions";
+ info.needSession = true;
+ info.needPoll = true;
+ info.parameterName = "updateVmMetadata";
+ return info;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java b/sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
new file mode 100644
index 00000000000..d09f1fe7bf7
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
@@ -0,0 +1,7 @@
+package org.zstack.sdk;
+
+
+
+public class UpdateVmInstanceMetadataResult {
+
+}
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 b7f8cfbc24d..52d4f78f805 100755
--- a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java
+++ b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java
@@ -6,6 +6,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.transaction.annotation.Transactional;
+import org.zstack.core.Platform;
import org.zstack.core.asyncbatch.While;
import org.zstack.core.cascade.CascadeConstant;
import org.zstack.core.cascade.CascadeFacade;
@@ -50,6 +51,7 @@
import org.zstack.header.storage.primary.PrimaryStorageCanonicalEvent.PrimaryStorageStatusChangedData;
import org.zstack.header.storage.snapshot.*;
import org.zstack.header.vm.*;
+import org.zstack.header.vm.metadata.*;
import org.zstack.header.volume.*;
import org.zstack.storage.volume.VolumeUtils;
import org.zstack.utils.CollectionDSL;
@@ -417,6 +419,16 @@ protected void handleLocalMessage(Message msg) {
handle((DeleteVolumeChainOnPrimaryStorageMsg) msg);
} else if (msg instanceof CleanUpStorageTrashOnPrimaryStorageMsg) {
handle((CleanUpStorageTrashOnPrimaryStorageMsg)msg);
+ } else if (msg instanceof UpdateVmInstanceMetadataOnPrimaryStorageMsg) {
+ handle((UpdateVmInstanceMetadataOnPrimaryStorageMsg) msg);
+ } else if (msg instanceof ScanVmInstanceMetadataFromPrimaryStorageMsg) {
+ handle((ScanVmInstanceMetadataFromPrimaryStorageMsg) msg);
+ } else if (msg instanceof GetVmInstanceMetadataFromPrimaryStorageMsg) {
+ handle((GetVmInstanceMetadataFromPrimaryStorageMsg) msg);
+ } else if (msg instanceof CleanupVmInstanceMetadataOnPrimaryStorageMsg) {
+ handle((CleanupVmInstanceMetadataOnPrimaryStorageMsg) msg);
+ } else if (msg instanceof RebaseVolumeBackingFileOnPrimaryStorageMsg) {
+ handle((RebaseVolumeBackingFileOnPrimaryStorageMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
@@ -935,6 +947,8 @@ protected void handleApiMessage(APIMessage msg) {
handle((APICleanUpStorageTrashOnPrimaryStorageMsg) msg);
} else if (msg instanceof APIAddStorageProtocolMsg) {
handle((APIAddStorageProtocolMsg) msg);
+ } else if (msg instanceof APIScanVmInstanceMetadataFromPrimaryStorageMsg) {
+ handle((APIScanVmInstanceMetadataFromPrimaryStorageMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
@@ -1773,6 +1787,31 @@ protected void handle(UnlinkBitsOnPrimaryStorageMsg msg) {
bus.reply(msg, reply);
};
+ protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) {
+ UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply();
+ bus.reply(msg, reply);
+ }
+
+ protected void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg) {
+ GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply();
+ bus.reply(msg, reply);
+ }
+
+ protected void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg) {
+ ScanVmInstanceMetadataFromPrimaryStorageReply reply = new ScanVmInstanceMetadataFromPrimaryStorageReply();
+ bus.reply(msg, reply);
+ }
+
+ protected void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg) {
+ CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply();
+ bus.reply(msg, reply);
+ }
+
+ protected void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg) {
+ RebaseVolumeBackingFileOnPrimaryStorageReply reply = new RebaseVolumeBackingFileOnPrimaryStorageReply();
+ bus.reply(msg, reply);
+ }
+
// don't attach any cluster
public boolean isUnmounted() {
long count = Q.New(PrimaryStorageClusterRefVO.class)
@@ -1812,4 +1851,38 @@ protected ImageCacheVO createTemporaryImageCacheFromVolumeSnapshot(ImageInventor
private static String getDeduplicateError(String operationName) {
return String.format("an other %s task is running, cancel this operation", operationName);
}
+
+ private void handle(APIScanVmInstanceMetadataFromPrimaryStorageMsg msg) {
+ APIScanVmInstanceMetadataFromPrimaryStorageReply reply = new APIScanVmInstanceMetadataFromPrimaryStorageReply();
+
+ String psType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()).findValue();
+ VmMetadataPathBuildExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class);
+ if (ext == null) {
+ reply.setError(Platform.operr("primary storage type %s does not support metadata", psType));
+ bus.reply(msg, reply);
+ return;
+ }
+ String metadataDir = ext.buildMetadataDir(msg.getPrimaryStorageUuid());
+
+ ScanVmInstanceMetadataFromPrimaryStorageMsg gmsg = new ScanVmInstanceMetadataFromPrimaryStorageMsg();
+ gmsg.setPrimaryStorageUuid(msg.getPrimaryStorageUuid());
+ gmsg.setMetadataDir(metadataDir);
+ bus.makeTargetServiceIdByResourceUuid(gmsg, PrimaryStorageConstant.SERVICE_ID, msg.getPrimaryStorageUuid());
+ bus.send(gmsg, new CloudBusCallBack(msg) {
+ @Override
+ public void run(MessageReply r) {
+ if (!r.isSuccess()) {
+ reply.setError(r.getError());
+ bus.reply(msg, reply);
+ return;
+ }
+ ScanVmInstanceMetadataFromPrimaryStorageReply re = r.castReply();
+ List filtered = re.getVmInstanceMetadata().stream()
+ .filter(e -> !VmMetadataCategory.TEMPLATE_CACHE.toString().equals(e.getVmCategory()))
+ .collect(Collectors.toList());
+ reply.setVmInstanceMetadata(filtered);
+ bus.reply(msg, reply);
+ }
+ });
+ }
}
diff --git a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
index 07c05b73b9e..91aca4c069e 100644
--- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
+++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
@@ -5606,6 +5606,33 @@ abstract class ApiHelper {
}
+ def cleanupVmInstanceMetadata(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CleanupVmInstanceMetadataAction.class) Closure c) {
+ def a = new org.zstack.sdk.CleanupVmInstanceMetadataAction()
+ a.sessionId = Test.currentEnvSpec?.session?.uuid
+ c.resolveStrategy = Closure.OWNER_FIRST
+ c.delegate = a
+ c()
+
+
+ if (System.getProperty("apipath") != null) {
+ if (a.apiId == null) {
+ a.apiId = Platform.uuid
+ }
+
+ def tracker = new ApiPathTracker(a.apiId)
+ def out = errorOut(a.call())
+ def path = tracker.getApiPath()
+ if (!path.isEmpty()) {
+ Test.apiPaths[a.class.name] = path.join(" --->\n")
+ }
+
+ return out
+ } else {
+ return errorOut(a.call())
+ }
+ }
+
+
def cloneVmInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CloneVmInstanceAction.class) Closure c) {
def a = new org.zstack.sdk.CloneVmInstanceAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
@@ -6929,33 +6956,6 @@ abstract class ApiHelper {
}
- def updateHostname(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateHostnameAction.class) Closure c) {
- def a = new org.zstack.sdk.UpdateHostnameAction()
- a.sessionId = Test.currentEnvSpec?.session?.uuid
- c.resolveStrategy = Closure.OWNER_FIRST
- c.delegate = a
- c()
-
-
- if (System.getProperty("apipath") != null) {
- if (a.apiId == null) {
- a.apiId = Platform.uuid
- }
-
- def tracker = new ApiPathTracker(a.apiId)
- def out = errorOut(a.call())
- def path = tracker.getApiPath()
- if (!path.isEmpty()) {
- Test.apiPaths[a.class.name] = path.join(" --->\n")
- }
-
- return out
- } else {
- return errorOut(a.call())
- }
- }
-
-
def createIPsecConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CreateIPsecConnectionAction.class) Closure c) {
def a = new org.zstack.sdk.CreateIPsecConnectionAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
@@ -18566,6 +18566,33 @@ abstract class ApiHelper {
}
+ def getVmInstanceMetadataFromPrimaryStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.GetVmInstanceMetadataFromPrimaryStorageAction.class) Closure c) {
+ def a = new org.zstack.sdk.GetVmInstanceMetadataFromPrimaryStorageAction()
+ a.sessionId = Test.currentEnvSpec?.session?.uuid
+ c.resolveStrategy = Closure.OWNER_FIRST
+ c.delegate = a
+ c()
+
+
+ if (System.getProperty("apipath") != null) {
+ if (a.apiId == null) {
+ a.apiId = Platform.uuid
+ }
+
+ def tracker = new ApiPathTracker(a.apiId)
+ def out = errorOut(a.call())
+ def path = tracker.getApiPath()
+ if (!path.isEmpty()) {
+ Test.apiPaths[a.class.name] = path.join(" --->\n")
+ }
+
+ return out
+ } else {
+ return errorOut(a.call())
+ }
+ }
+
+
def getVmInstanceProtectedRecoveryPoints(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.GetVmInstanceProtectedRecoveryPointsAction.class) Closure c) {
def a = new org.zstack.sdk.GetVmInstanceProtectedRecoveryPointsAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
@@ -27406,6 +27433,33 @@ abstract class ApiHelper {
}
+ def registerVmInstanceFromMetadata(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.RegisterVmInstanceFromMetadataAction.class) Closure c) {
+ def a = new org.zstack.sdk.RegisterVmInstanceFromMetadataAction()
+ a.sessionId = Test.currentEnvSpec?.session?.uuid
+ c.resolveStrategy = Closure.OWNER_FIRST
+ c.delegate = a
+ c()
+
+
+ if (System.getProperty("apipath") != null) {
+ if (a.apiId == null) {
+ a.apiId = Platform.uuid
+ }
+
+ def tracker = new ApiPathTracker(a.apiId)
+ def out = errorOut(a.call())
+ def path = tracker.getApiPath()
+ if (!path.isEmpty()) {
+ Test.apiPaths[a.class.name] = path.join(" --->\n")
+ }
+
+ return out
+ } else {
+ return errorOut(a.call())
+ }
+ }
+
+
def reimageVmInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReimageVmInstanceAction.class) Closure c) {
def a = new org.zstack.sdk.ReimageVmInstanceAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
@@ -28621,6 +28675,33 @@ abstract class ApiHelper {
}
+ def scanVmInstanceMetadataFromPrimaryStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ScanVmInstanceMetadataFromPrimaryStorageAction.class) Closure c) {
+ def a = new org.zstack.sdk.ScanVmInstanceMetadataFromPrimaryStorageAction()
+ a.sessionId = Test.currentEnvSpec?.session?.uuid
+ c.resolveStrategy = Closure.OWNER_FIRST
+ c.delegate = a
+ c()
+
+
+ if (System.getProperty("apipath") != null) {
+ if (a.apiId == null) {
+ a.apiId = Platform.uuid
+ }
+
+ def tracker = new ApiPathTracker(a.apiId)
+ def out = errorOut(a.call())
+ def path = tracker.getApiPath()
+ if (!path.isEmpty()) {
+ Test.apiPaths[a.class.name] = path.join(" --->\n")
+ }
+
+ return out
+ } else {
+ return errorOut(a.call())
+ }
+ }
+
+
def securityMachineDetectSync(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SecurityMachineDetectSyncAction.class) Closure c) {
def a = new org.zstack.sdk.SecurityMachineDetectSyncAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
@@ -32212,6 +32293,33 @@ abstract class ApiHelper {
}
+ def updateHostname(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateHostnameAction.class) Closure c) {
+ def a = new org.zstack.sdk.UpdateHostnameAction()
+ a.sessionId = Test.currentEnvSpec?.session?.uuid
+ c.resolveStrategy = Closure.OWNER_FIRST
+ c.delegate = a
+ c()
+
+
+ if (System.getProperty("apipath") != null) {
+ if (a.apiId == null) {
+ a.apiId = Platform.uuid
+ }
+
+ def tracker = new ApiPathTracker(a.apiId)
+ def out = errorOut(a.call())
+ def path = tracker.getApiPath()
+ if (!path.isEmpty()) {
+ Test.apiPaths[a.class.name] = path.join(" --->\n")
+ }
+
+ return out
+ } else {
+ return errorOut(a.call())
+ }
+ }
+
+
def updateIPsecConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateIPsecConnectionAction.class) Closure c) {
def a = new org.zstack.sdk.UpdateIPsecConnectionAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
@@ -34183,6 +34291,33 @@ abstract class ApiHelper {
}
+ def updateVmInstanceMetadata(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmInstanceMetadataAction.class) Closure c) {
+ def a = new org.zstack.sdk.UpdateVmInstanceMetadataAction()
+ a.sessionId = Test.currentEnvSpec?.session?.uuid
+ c.resolveStrategy = Closure.OWNER_FIRST
+ c.delegate = a
+ c()
+
+
+ if (System.getProperty("apipath") != null) {
+ if (a.apiId == null) {
+ a.apiId = Platform.uuid
+ }
+
+ def tracker = new ApiPathTracker(a.apiId)
+ def out = errorOut(a.call())
+ def path = tracker.getApiPath()
+ if (!path.isEmpty()) {
+ Test.apiPaths[a.class.name] = path.join(" --->\n")
+ }
+
+ return out
+ } else {
+ return errorOut(a.call())
+ }
+ }
+
+
def updateVmNicDriver(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmNicDriverAction.class) Closure c) {
def a = new org.zstack.sdk.UpdateVmNicDriverAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
@@ -38395,8 +38530,8 @@ abstract class ApiHelper {
}
- def addZBox(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.AddZBoxAction.class) Closure c) {
- def a = new org.zstack.sdk.zbox.AddZBoxAction()
+ def cleanSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.CleanSoftwarePackageAction.class) Closure c) {
+ def a = new org.zstack.sdk.softwarePackage.header.CleanSoftwarePackageAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
c.resolveStrategy = Closure.OWNER_FIRST
c.delegate = a
@@ -38422,8 +38557,8 @@ abstract class ApiHelper {
}
- def createZBoxBackup(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.CreateZBoxBackupAction.class) Closure c) {
- def a = new org.zstack.sdk.zbox.CreateZBoxBackupAction()
+ def getDirectoryUsage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.GetDirectoryUsageAction.class) Closure c) {
+ def a = new org.zstack.sdk.softwarePackage.header.GetDirectoryUsageAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
c.resolveStrategy = Closure.OWNER_FIRST
c.delegate = a
@@ -38449,8 +38584,8 @@ abstract class ApiHelper {
}
- def ejectZBox(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.EjectZBoxAction.class) Closure c) {
- def a = new org.zstack.sdk.zbox.EjectZBoxAction()
+ def getUploadSoftwarePackageJobDetails(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.GetUploadSoftwarePackageJobDetailsAction.class) Closure c) {
+ def a = new org.zstack.sdk.softwarePackage.header.GetUploadSoftwarePackageJobDetailsAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
c.resolveStrategy = Closure.OWNER_FIRST
c.delegate = a
@@ -38476,8 +38611,8 @@ abstract class ApiHelper {
}
- def getZBoxBackupDetails(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.GetZBoxBackupDetailsAction.class) Closure c) {
- def a = new org.zstack.sdk.zbox.GetZBoxBackupDetailsAction()
+ def installSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.InstallSoftwarePackageAction.class) Closure c) {
+ def a = new org.zstack.sdk.softwarePackage.header.InstallSoftwarePackageAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
c.resolveStrategy = Closure.OWNER_FIRST
c.delegate = a
@@ -38503,8 +38638,8 @@ abstract class ApiHelper {
}
- def queryZBox(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.QueryZBoxAction.class) Closure c) {
- def a = new org.zstack.sdk.zbox.QueryZBoxAction()
+ def querySoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.QuerySoftwarePackageAction.class) Closure c) {
+ def a = new org.zstack.sdk.softwarePackage.header.QuerySoftwarePackageAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
c.resolveStrategy = Closure.OWNER_FIRST
c.delegate = a
@@ -38532,15 +38667,13 @@ abstract class ApiHelper {
}
- def queryZBoxBackup(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.QueryZBoxBackupAction.class) Closure c) {
- def a = new org.zstack.sdk.zbox.QueryZBoxBackupAction()
+ def uninstallSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.UninstallSoftwarePackageAction.class) Closure c) {
+ def a = new org.zstack.sdk.softwarePackage.header.UninstallSoftwarePackageAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
c.resolveStrategy = Closure.OWNER_FIRST
c.delegate = a
c()
- a.conditions = a.conditions.collect { it.toString() }
-
if (System.getProperty("apipath") != null) {
if (a.apiId == null) {
@@ -38561,8 +38694,8 @@ abstract class ApiHelper {
}
- def syncZBoxCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.SyncZBoxCapacityAction.class) Closure c) {
- def a = new org.zstack.sdk.zbox.SyncZBoxCapacityAction()
+ def uploadSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.UploadSoftwarePackageAction.class) Closure c) {
+ def a = new org.zstack.sdk.softwarePackage.header.UploadSoftwarePackageAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
c.resolveStrategy = Closure.OWNER_FIRST
c.delegate = a
@@ -38588,25 +38721,26 @@ abstract class ApiHelper {
}
- def cleanSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.CleanSoftwarePackageAction.class) Closure c) {
- def a = new org.zstack.sdk.softwarePackage.header.CleanSoftwarePackageAction()
+ def addZBox(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.AddZBoxAction.class) Closure c) {
+ def a = new org.zstack.sdk.zbox.AddZBoxAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
c.resolveStrategy = Closure.OWNER_FIRST
c.delegate = a
c()
+
if (System.getProperty("apipath") != null) {
if (a.apiId == null) {
a.apiId = Platform.uuid
}
-
+
def tracker = new ApiPathTracker(a.apiId)
def out = errorOut(a.call())
def path = tracker.getApiPath()
if (!path.isEmpty()) {
Test.apiPaths[a.class.name] = path.join(" --->\n")
}
-
+
return out
} else {
return errorOut(a.call())
@@ -38614,25 +38748,26 @@ abstract class ApiHelper {
}
- def getDirectoryUsage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.GetDirectoryUsageAction.class) Closure c) {
- def a = new org.zstack.sdk.softwarePackage.header.GetDirectoryUsageAction()
+ def createZBoxBackup(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.CreateZBoxBackupAction.class) Closure c) {
+ def a = new org.zstack.sdk.zbox.CreateZBoxBackupAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
c.resolveStrategy = Closure.OWNER_FIRST
c.delegate = a
c()
+
if (System.getProperty("apipath") != null) {
if (a.apiId == null) {
a.apiId = Platform.uuid
}
-
+
def tracker = new ApiPathTracker(a.apiId)
def out = errorOut(a.call())
def path = tracker.getApiPath()
if (!path.isEmpty()) {
Test.apiPaths[a.class.name] = path.join(" --->\n")
}
-
+
return out
} else {
return errorOut(a.call())
@@ -38640,25 +38775,26 @@ abstract class ApiHelper {
}
- def getUploadSoftwarePackageJobDetails(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.GetUploadSoftwarePackageJobDetailsAction.class) Closure c) {
- def a = new org.zstack.sdk.softwarePackage.header.GetUploadSoftwarePackageJobDetailsAction()
+ def ejectZBox(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.EjectZBoxAction.class) Closure c) {
+ def a = new org.zstack.sdk.zbox.EjectZBoxAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
c.resolveStrategy = Closure.OWNER_FIRST
c.delegate = a
c()
+
if (System.getProperty("apipath") != null) {
if (a.apiId == null) {
a.apiId = Platform.uuid
}
-
+
def tracker = new ApiPathTracker(a.apiId)
def out = errorOut(a.call())
def path = tracker.getApiPath()
if (!path.isEmpty()) {
Test.apiPaths[a.class.name] = path.join(" --->\n")
}
-
+
return out
} else {
return errorOut(a.call())
@@ -38666,22 +38802,26 @@ abstract class ApiHelper {
}
- def installSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.InstallSoftwarePackageAction.class) Closure c) {
- def a = new org.zstack.sdk.softwarePackage.header.InstallSoftwarePackageAction()
+ def getZBoxBackupDetails(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.GetZBoxBackupDetailsAction.class) Closure c) {
+ def a = new org.zstack.sdk.zbox.GetZBoxBackupDetailsAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
c.resolveStrategy = Closure.OWNER_FIRST
c.delegate = a
c()
+
+
if (System.getProperty("apipath") != null) {
if (a.apiId == null) {
a.apiId = Platform.uuid
}
+
def tracker = new ApiPathTracker(a.apiId)
def out = errorOut(a.call())
def path = tracker.getApiPath()
if (!path.isEmpty()) {
Test.apiPaths[a.class.name] = path.join(" --->\n")
}
+
return out
} else {
return errorOut(a.call())
@@ -38689,13 +38829,13 @@ abstract class ApiHelper {
}
- def querySoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.QuerySoftwarePackageAction.class) Closure c) {
- def a = new org.zstack.sdk.softwarePackage.header.QuerySoftwarePackageAction()
+ def queryZBox(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.QueryZBoxAction.class) Closure c) {
+ def a = new org.zstack.sdk.zbox.QueryZBoxAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
c.resolveStrategy = Closure.OWNER_FIRST
c.delegate = a
c()
-
+
a.conditions = a.conditions.collect { it.toString() }
@@ -38703,14 +38843,14 @@ abstract class ApiHelper {
if (a.apiId == null) {
a.apiId = Platform.uuid
}
-
+
def tracker = new ApiPathTracker(a.apiId)
def out = errorOut(a.call())
def path = tracker.getApiPath()
if (!path.isEmpty()) {
Test.apiPaths[a.class.name] = path.join(" --->\n")
}
-
+
return out
} else {
return errorOut(a.call())
@@ -38718,26 +38858,28 @@ abstract class ApiHelper {
}
- def uninstallSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.UninstallSoftwarePackageAction.class) Closure c) {
- def a = new org.zstack.sdk.softwarePackage.header.UninstallSoftwarePackageAction()
+ def queryZBoxBackup(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.QueryZBoxBackupAction.class) Closure c) {
+ def a = new org.zstack.sdk.zbox.QueryZBoxBackupAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
c.resolveStrategy = Closure.OWNER_FIRST
c.delegate = a
c()
+
+ a.conditions = a.conditions.collect { it.toString() }
if (System.getProperty("apipath") != null) {
if (a.apiId == null) {
a.apiId = Platform.uuid
}
-
+
def tracker = new ApiPathTracker(a.apiId)
def out = errorOut(a.call())
def path = tracker.getApiPath()
if (!path.isEmpty()) {
Test.apiPaths[a.class.name] = path.join(" --->\n")
}
-
+
return out
} else {
return errorOut(a.call())
@@ -38745,25 +38887,26 @@ abstract class ApiHelper {
}
- def uploadSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.UploadSoftwarePackageAction.class) Closure c) {
- def a = new org.zstack.sdk.softwarePackage.header.UploadSoftwarePackageAction()
+ def syncZBoxCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.SyncZBoxCapacityAction.class) Closure c) {
+ def a = new org.zstack.sdk.zbox.SyncZBoxCapacityAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
c.resolveStrategy = Closure.OWNER_FIRST
c.delegate = a
c()
+
if (System.getProperty("apipath") != null) {
if (a.apiId == null) {
a.apiId = Platform.uuid
}
-
+
def tracker = new ApiPathTracker(a.apiId)
def out = errorOut(a.call())
def path = tracker.getApiPath()
if (!path.isEmpty()) {
Test.apiPaths[a.class.name] = path.join(" --->\n")
}
-
+
return out
} else {
return errorOut(a.call())
diff --git a/testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy b/testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy
index 747ca4388b1..ceee209296c 100755
--- a/testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy
+++ b/testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy
@@ -600,6 +600,24 @@ class LocalStorageSpec extends PrimaryStorageSpec {
rsp.hashValue = cmd.installPath
return rsp
}
+
+ simulator(LocalStorageKvmBackend.WRITE_VM_METADATA_PATH) {
+ return new LocalStorageKvmBackend.WriteVmMetadataRsp()
+ }
+
+ simulator(LocalStorageKvmBackend.GET_VM_INSTANCE_METADATA_PATH) {
+ def rsp = new LocalStorageKvmBackend.GetVmInstanceMetadataRsp()
+ rsp.metadata = "{\"vmInstanceVO\":\"{\\\"vmNics\\\":[{\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"l3NetworkUuid\\\":\\\"28d3a9c8e54c48f290ab4f9e52bbb006\\\",\\\"mac\\\":\\\"fa:81:16:b2:32:00\\\",\\\"hypervisorType\\\":\\\"KVM\\\",\\\"deviceId\\\":0,\\\"internalName\\\":\\\"vnic1.0\\\",\\\"driverType\\\":\\\"virtio\\\",\\\"type\\\":\\\"VNIC\\\",\\\"state\\\":\\\"enable\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"usedIps\\\":[],\\\"uuid\\\":\\\"a77234a5a45a4a7caca46d01d746f41f\\\",\\\"resourceType\\\":\\\"VmNicVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.vm.VmNicVO\\\"}],\\\"allVolumes\\\":[{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/7832daf63d9b41d68bd1460c20ed0e0a\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":2,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/7832daf63d9b41d68bd1460c20ed0e0a\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":2,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"resourceName\\\":\\\"volumeName1null\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"},{\\\"name\\\":\\\"ROOT-for-vmName\\\",\\\"description\\\":\\\"Root volume for VM[uuid:77bc3074f5f4438c836ce6c56bc5a4aa]\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"rootImageUuid\\\":\\\"575591e021b446e4b465e981da3a8d1b\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/bc5ab54cb3d04635923a2a9d0b5fc73f\\\",\\\"type\\\":\\\"Root\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":0,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"ROOT-for-vmName\\\",\\\"description\\\":\\\"Root volume for VM[uuid:77bc3074f5f4438c836ce6c56bc5a4aa]\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"rootImageUuid\\\":\\\"575591e021b446e4b465e981da3a8d1b\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/bc5ab54cb3d04635923a2a9d0b5fc73f\\\",\\\"type\\\":\\\"Root\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":0,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"resourceName\\\":\\\"ROOT-for-vmName\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"},{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/43436624dc714282913e0a141246629e\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":1,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/43436624dc714282913e0a141246629e\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":1,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"resourceName\\\":\\\"volumeName1null\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"},{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b711f22ad5c045b6ad1d770d4f301d05\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":3,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b711f22ad5c045b6ad1d770d4f301d05\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":3,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"resourceName\\\":\\\"volumeName1null\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"}],\\\"vmCdRoms\\\":[{\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"deviceId\\\":0,\\\"name\\\":\\\"vm-77bc3074f5f4438c836ce6c56bc5a4aa-cdRom\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"uuid\\\":\\\"e8a57f5b8c834573b4da822b672740e4\\\",\\\"resourceName\\\":\\\"vm-77bc3074f5f4438c836ce6c56bc5a4aa-cdRom\\\",\\\"resourceType\\\":\\\"VmCdRomVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.vm.cdrom.VmCdRomVO\\\"}],\\\"name\\\":\\\"vmName\\\",\\\"zoneUuid\\\":\\\"d71de3f6981d46c9a2be43e5fcf31021\\\",\\\"clusterUuid\\\":\\\"29f13acb820d4f7f8cd3593b79b742e5\\\",\\\"imageUuid\\\":\\\"575591e021b446e4b465e981da3a8d1b\\\",\\\"hostUuid\\\":\\\"e99debc09c5845fb8ed682320117f4ce\\\",\\\"internalId\\\":1,\\\"lastHostUuid\\\":\\\"e99debc09c5845fb8ed682320117f4ce\\\",\\\"rootVolumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"defaultL3NetworkUuid\\\":\\\"28d3a9c8e54c48f290ab4f9e52bbb006\\\",\\\"type\\\":\\\"UserVm\\\",\\\"hypervisorType\\\":\\\"KVM\\\",\\\"cpuNum\\\":1,\\\"cpuSpeed\\\":0,\\\"memorySize\\\":1073741824,\\\"reservedMemorySize\\\":0,\\\"platform\\\":\\\"Linux\\\",\\\"architecture\\\":\\\"x86_64\\\",\\\"guestOsType\\\":\\\"CentOS\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:45 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\",\\\"state\\\":\\\"Running\\\",\\\"uuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"resourceName\\\":\\\"vmName\\\",\\\"resourceType\\\":\\\"VmInstanceVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.vm.VmInstanceVO\\\"}\",\"vmSystemTags\":[\"{\\\"inherent\\\":false,\\\"uuid\\\":\\\"38a9b4bd1b8b3dfa829d582aafb2ec25\\\",\\\"resourceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"resourceType\\\":\\\"VmInstanceVO\\\",\\\"tag\\\":\\\"syncPorts::77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:45 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:45 AM\\\"}\",\"{\\\"inherent\\\":true,\\\"uuid\\\":\\\"3e984cdb5edb47559a3f907e1d49bfcc\\\",\\\"resourceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"resourceType\\\":\\\"VmInstanceVO\\\",\\\"tag\\\":\\\"additionalQmp\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\"}\",\"{\\\"inherent\\\":false,\\\"uuid\\\":\\\"85237d3a06133523bd84669349040ec5\\\",\\\"resourceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"resourceType\\\":\\\"VmInstanceVO\\\",\\\"tag\\\":\\\"vmPriority::Normal\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\"}\",\"{\\\"inherent\\\":true,\\\"uuid\\\":\\\"b7c5d5e94ba13159ab2c8c65c1d7bc29\\\",\\\"resourceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"resourceType\\\":\\\"VmInstanceVO\\\",\\\"tag\\\":\\\"vmSystemSerialNumber::8ed14f00-50bb-4e9e-9448-e92c0f67e1e1\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\"}\",\"{\\\"inherent\\\":false,\\\"uuid\\\":\\\"d5019730aeba3e57b2f1a3e8d74d0cbc\\\",\\\"resourceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"resourceType\\\":\\\"VmInstanceVO\\\",\\\"tag\\\":\\\"ha::None\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\"}\"],\"vmResourceConfigs\":[\"{\\\"uuid\\\":\\\"8d2f9937a28846aba03fded826c10c73\\\",\\\"resourceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"resourceType\\\":\\\"VmInstanceVO\\\",\\\"name\\\":\\\"nicMultiQueueNum\\\",\\\"description\\\":\\\"default num of queues on virtio nic\\\",\\\"category\\\":\\\"vm\\\",\\\"value\\\":\\\"1\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\"}\"],\"volumeVOs\":[\"{\\\"name\\\":\\\"ROOT-for-vmName\\\",\\\"description\\\":\\\"Root volume for VM[uuid:77bc3074f5f4438c836ce6c56bc5a4aa]\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"rootImageUuid\\\":\\\"575591e021b446e4b465e981da3a8d1b\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/bc5ab54cb3d04635923a2a9d0b5fc73f\\\",\\\"type\\\":\\\"Root\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":0,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"ROOT-for-vmName\\\",\\\"description\\\":\\\"Root volume for VM[uuid:77bc3074f5f4438c836ce6c56bc5a4aa]\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"rootImageUuid\\\":\\\"575591e021b446e4b465e981da3a8d1b\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/bc5ab54cb3d04635923a2a9d0b5fc73f\\\",\\\"type\\\":\\\"Root\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":0,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"resourceName\\\":\\\"ROOT-for-vmName\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"}\",\"{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b711f22ad5c045b6ad1d770d4f301d05\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":3,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b711f22ad5c045b6ad1d770d4f301d05\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":3,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"resourceName\\\":\\\"volumeName1null\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"}\",\"{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/7832daf63d9b41d68bd1460c20ed0e0a\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":2,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/7832daf63d9b41d68bd1460c20ed0e0a\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":2,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"resourceName\\\":\\\"volumeName1null\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"}\",\"{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/43436624dc714282913e0a141246629e\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":1,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/43436624dc714282913e0a141246629e\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":1,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"resourceName\\\":\\\"volumeName1null\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"}\"],\"volumeSystemTags\":{\"b7290c15276b4700af2c1b108b2b62e1\":[\"{\\\"inherent\\\":true,\\\"uuid\\\":\\\"b9874ec02b583538a5603e7eec8c5b69\\\",\\\"resourceUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"tag\\\":\\\"kvm::volume::0x000f59f934d14a68\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\"],\"8d1e76eca52647f5a4544b9ff2d370de\":[\"{\\\"inherent\\\":true,\\\"uuid\\\":\\\"96cb4b006708387b8318f0fd6ae6ab8b\\\",\\\"resourceUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"tag\\\":\\\"kvm::volume::0x000faad0c9ca4231\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\"}\"],\"ae9f28cb5055498e8661793d204208ba\":[\"{\\\"inherent\\\":true,\\\"uuid\\\":\\\"5ceacd06bf753b0c8abe5bcef9b5a894\\\",\\\"resourceUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"tag\\\":\\\"kvm::volume::0x000fc4ffeaab6e71\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\"],\"db8251e870b14d60ace863a7598cce8b\":[\"{\\\"inherent\\\":true,\\\"uuid\\\":\\\"d53865baa675373a9bf07a6f501eab41\\\",\\\"resourceUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"tag\\\":\\\"kvm::volume::0x000fad154165d205\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"}\"]},\"volumeResourceConfigs\":{\"b7290c15276b4700af2c1b108b2b62e1\":[],\"8d1e76eca52647f5a4544b9ff2d370de\":[],\"ae9f28cb5055498e8661793d204208ba\":[],\"db8251e870b14d60ace863a7598cce8b\":[]},\"vmNicVOs\":[\"{\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"l3NetworkUuid\\\":\\\"28d3a9c8e54c48f290ab4f9e52bbb006\\\",\\\"mac\\\":\\\"fa:81:16:b2:32:00\\\",\\\"hypervisorType\\\":\\\"KVM\\\",\\\"deviceId\\\":0,\\\"internalName\\\":\\\"vnic1.0\\\",\\\"driverType\\\":\\\"virtio\\\",\\\"type\\\":\\\"VNIC\\\",\\\"state\\\":\\\"enable\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"usedIps\\\":[],\\\"uuid\\\":\\\"a77234a5a45a4a7caca46d01d746f41f\\\",\\\"resourceType\\\":\\\"VmNicVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.vm.VmNicVO\\\"}\"],\"vmNicSystemTags\":{\"a77234a5a45a4a7caca46d01d746f41f\":[]},\"vmNicResourceConfigs\":{\"a77234a5a45a4a7caca46d01d746f41f\":[]},\"volumeSnapshots\":{\"b7290c15276b4700af2c1b108b2b62e1\":[\"{\\\"uuid\\\":\\\"7832daf63d9b41d68bd1460c20ed0e0a\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"treeUuid\\\":\\\"f8042fb57bb04ebcb0f01bab2abeb5dd\\\",\\\"parentUuid\\\":\\\"a31c3de68ce246538e982e0e5c7d2d73\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a31c3de68ce246538e982e0e5c7d2d73\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":true,\\\"size\\\":1,\\\"distance\\\":4,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\"}\",\"{\\\"uuid\\\":\\\"a31c3de68ce246538e982e0e5c7d2d73\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"treeUuid\\\":\\\"f8042fb57bb04ebcb0f01bab2abeb5dd\\\",\\\"parentUuid\\\":\\\"b5d771aa83584c9c88d9b84147dfc9ad\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b5d771aa83584c9c88d9b84147dfc9ad\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":3,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\"}\",\"{\\\"uuid\\\":\\\"b5d771aa83584c9c88d9b84147dfc9ad\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"treeUuid\\\":\\\"f8042fb57bb04ebcb0f01bab2abeb5dd\\\",\\\"parentUuid\\\":\\\"bcc3ed8070984cd691c62f421aeaa44d\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/bcc3ed8070984cd691c62f421aeaa44d\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":2,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\"}\",\"{\\\"uuid\\\":\\\"bcc3ed8070984cd691c62f421aeaa44d\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"treeUuid\\\":\\\"f8042fb57bb04ebcb0f01bab2abeb5dd\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":1,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:51 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\"}\"],\"8d1e76eca52647f5a4544b9ff2d370de\":[\"{\\\"uuid\\\":\\\"04ef6d31675f4ba5816b104920dc3e2c\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"treeUuid\\\":\\\"d4a030087ed3407894c393ee81f0bc3b\\\",\\\"parentUuid\\\":\\\"79caace79a1048d58ea7c0b38815bbd0\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/79caace79a1048d58ea7c0b38815bbd0\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":0,\\\"distance\\\":3,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\"}\",\"{\\\"uuid\\\":\\\"61e2ada0170142bb8b303910a27690aa\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"treeUuid\\\":\\\"d4a030087ed3407894c393ee81f0bc3b\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":0,\\\"distance\\\":1,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:51 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\"}\",\"{\\\"uuid\\\":\\\"79caace79a1048d58ea7c0b38815bbd0\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"treeUuid\\\":\\\"d4a030087ed3407894c393ee81f0bc3b\\\",\\\"parentUuid\\\":\\\"61e2ada0170142bb8b303910a27690aa\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/61e2ada0170142bb8b303910a27690aa\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":0,\\\"distance\\\":2,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\"}\",\"{\\\"uuid\\\":\\\"bc5ab54cb3d04635923a2a9d0b5fc73f\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"treeUuid\\\":\\\"d4a030087ed3407894c393ee81f0bc3b\\\",\\\"parentUuid\\\":\\\"04ef6d31675f4ba5816b104920dc3e2c\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/04ef6d31675f4ba5816b104920dc3e2c\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":true,\\\"size\\\":0,\\\"distance\\\":4,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\"}\"],\"ae9f28cb5055498e8661793d204208ba\":[\"{\\\"uuid\\\":\\\"1aaa92b2d1eb4c36bb8951b8e1521b34\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"treeUuid\\\":\\\"055b80b0727e4117b246b1b29f2d58b6\\\",\\\"parentUuid\\\":\\\"aefbe47465c047d1b118321c34425869\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/aefbe47465c047d1b118321c34425869\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":3,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\"}\",\"{\\\"uuid\\\":\\\"69e85ac72fea4263a55cbcd21785006e\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"treeUuid\\\":\\\"055b80b0727e4117b246b1b29f2d58b6\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":1,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:51 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\"}\",\"{\\\"uuid\\\":\\\"aefbe47465c047d1b118321c34425869\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"treeUuid\\\":\\\"055b80b0727e4117b246b1b29f2d58b6\\\",\\\"parentUuid\\\":\\\"69e85ac72fea4263a55cbcd21785006e\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/69e85ac72fea4263a55cbcd21785006e\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":2,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\"}\",\"{\\\"uuid\\\":\\\"b711f22ad5c045b6ad1d770d4f301d05\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"treeUuid\\\":\\\"055b80b0727e4117b246b1b29f2d58b6\\\",\\\"parentUuid\\\":\\\"1aaa92b2d1eb4c36bb8951b8e1521b34\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/1aaa92b2d1eb4c36bb8951b8e1521b34\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":true,\\\"size\\\":1,\\\"distance\\\":4,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\"}\"],\"db8251e870b14d60ace863a7598cce8b\":[\"{\\\"uuid\\\":\\\"43436624dc714282913e0a141246629e\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"treeUuid\\\":\\\"1c0773fa98f4465b8e535ba3c00dc039\\\",\\\"parentUuid\\\":\\\"a70bb2be871644b6ad12ac8d6e9524d0\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a70bb2be871644b6ad12ac8d6e9524d0\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":true,\\\"size\\\":1,\\\"distance\\\":4,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\"}\",\"{\\\"uuid\\\":\\\"791f523bc99a4f08bd70b5a59d8ed5c8\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"treeUuid\\\":\\\"1c0773fa98f4465b8e535ba3c00dc039\\\",\\\"parentUuid\\\":\\\"a35b7ae1616a4974b2f80654c5527fbb\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a35b7ae1616a4974b2f80654c5527fbb\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":2,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\"}\",\"{\\\"uuid\\\":\\\"a35b7ae1616a4974b2f80654c5527fbb\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"treeUuid\\\":\\\"1c0773fa98f4465b8e535ba3c00dc039\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":1,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:51 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\"}\",\"{\\\"uuid\\\":\\\"a70bb2be871644b6ad12ac8d6e9524d0\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"treeUuid\\\":\\\"1c0773fa98f4465b8e535ba3c00dc039\\\",\\\"parentUuid\\\":\\\"791f523bc99a4f08bd70b5a59d8ed5c8\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/791f523bc99a4f08bd70b5a59d8ed5c8\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":3,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\"}\"]},\"volumeSnapshotGroupVO\":[\"{\\\"snapshotCount\\\":4,\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeSnapshotRefs\\\":[{\\\"volumeSnapshotUuid\\\":\\\"a35b7ae1616a4974b2f80654c5527fbb\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"69e85ac72fea4263a55cbcd21785006e\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"61e2ada0170142bb8b303910a27690aa\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"bcc3ed8070984cd691c62f421aeaa44d\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}],\\\"uuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"resourceName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"resourceType\\\":\\\"VolumeSnapshotGroupVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO\\\"}\",\"{\\\"snapshotCount\\\":4,\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeSnapshotRefs\\\":[{\\\"volumeSnapshotUuid\\\":\\\"b5d771aa83584c9c88d9b84147dfc9ad\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/bcc3ed8070984cd691c62f421aeaa44d\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"79caace79a1048d58ea7c0b38815bbd0\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/61e2ada0170142bb8b303910a27690aa\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"aefbe47465c047d1b118321c34425869\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/69e85ac72fea4263a55cbcd21785006e\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"791f523bc99a4f08bd70b5a59d8ed5c8\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a35b7ae1616a4974b2f80654c5527fbb\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"}],\\\"uuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"resourceName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"resourceType\\\":\\\"VolumeSnapshotGroupVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO\\\"}\",\"{\\\"snapshotCount\\\":4,\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeSnapshotRefs\\\":[{\\\"volumeSnapshotUuid\\\":\\\"1aaa92b2d1eb4c36bb8951b8e1521b34\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/aefbe47465c047d1b118321c34425869\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"a70bb2be871644b6ad12ac8d6e9524d0\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/791f523bc99a4f08bd70b5a59d8ed5c8\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"04ef6d31675f4ba5816b104920dc3e2c\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/79caace79a1048d58ea7c0b38815bbd0\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"a31c3de68ce246538e982e0e5c7d2d73\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b5d771aa83584c9c88d9b84147dfc9ad\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}],\\\"uuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"resourceName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"resourceType\\\":\\\"VolumeSnapshotGroupVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO\\\"}\",\"{\\\"snapshotCount\\\":4,\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeSnapshotRefs\\\":[{\\\"volumeSnapshotUuid\\\":\\\"43436624dc714282913e0a141246629e\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a70bb2be871644b6ad12ac8d6e9524d0\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"bc5ab54cb3d04635923a2a9d0b5fc73f\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/04ef6d31675f4ba5816b104920dc3e2c\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"b711f22ad5c045b6ad1d770d4f301d05\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/1aaa92b2d1eb4c36bb8951b8e1521b34\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"7832daf63d9b41d68bd1460c20ed0e0a\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a31c3de68ce246538e982e0e5c7d2d73\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}],\\\"uuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"resourceName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"resourceType\\\":\\\"VolumeSnapshotGroupVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO\\\"}\"],\"volumeSnapshotGroupRefVO\":[\"{\\\"volumeSnapshotUuid\\\":\\\"04ef6d31675f4ba5816b104920dc3e2c\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/79caace79a1048d58ea7c0b38815bbd0\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"1aaa92b2d1eb4c36bb8951b8e1521b34\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/aefbe47465c047d1b118321c34425869\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"43436624dc714282913e0a141246629e\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a70bb2be871644b6ad12ac8d6e9524d0\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"61e2ada0170142bb8b303910a27690aa\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"69e85ac72fea4263a55cbcd21785006e\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"7832daf63d9b41d68bd1460c20ed0e0a\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a31c3de68ce246538e982e0e5c7d2d73\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"791f523bc99a4f08bd70b5a59d8ed5c8\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a35b7ae1616a4974b2f80654c5527fbb\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"79caace79a1048d58ea7c0b38815bbd0\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/61e2ada0170142bb8b303910a27690aa\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"a31c3de68ce246538e982e0e5c7d2d73\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b5d771aa83584c9c88d9b84147dfc9ad\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"a35b7ae1616a4974b2f80654c5527fbb\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"a70bb2be871644b6ad12ac8d6e9524d0\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/791f523bc99a4f08bd70b5a59d8ed5c8\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"aefbe47465c047d1b118321c34425869\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/69e85ac72fea4263a55cbcd21785006e\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"b5d771aa83584c9c88d9b84147dfc9ad\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/bcc3ed8070984cd691c62f421aeaa44d\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"b711f22ad5c045b6ad1d770d4f301d05\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/1aaa92b2d1eb4c36bb8951b8e1521b34\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"bc5ab54cb3d04635923a2a9d0b5fc73f\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/04ef6d31675f4ba5816b104920dc3e2c\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"bcc3ed8070984cd691c62f421aeaa44d\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\"],\"volumeSnapshotReferenceVO\":{},\"volumeSnapshotReferenceTreeVO\":{},\"EncryptedResourceKeyRefVO\":{}}"
+ return new LocalStorageKvmBackend.GetVmInstanceMetadataRsp()
+ }
+
+ simulator(LocalStorageKvmBackend.SCAN_VM_METADATA_PATH) {
+ return new LocalStorageKvmBackend.ScanVmMetadataRsp()
+ }
+
+ simulator(LocalStorageKvmBackend.CLEANUP_VM_METADATA_PATH) {
+ return new LocalStorageKvmBackend.CleanupVmMetadataRsp()
+ }
}
}
diff --git a/testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy b/testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
index 0dbe59bfc01..09344d42a5c 100755
--- a/testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
+++ b/testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
@@ -522,6 +522,22 @@ class NfsPrimaryStorageSpec extends PrimaryStorageSpec {
return rsp
}
+ simulator(NfsPrimaryStorageKVMBackend.WRITE_VM_METADATA_PATH) {
+ return new NfsPrimaryStorageKVMBackendCommands.WriteVmMetadataRsp()
+ }
+
+ simulator(NfsPrimaryStorageKVMBackend.GET_VM_INSTANCE_METADATA_PATH) {
+ return new NfsPrimaryStorageKVMBackendCommands.GetVmInstanceMetadataRsp()
+ }
+
+ simulator(NfsPrimaryStorageKVMBackend.SCAN_VM_METADATA_PATH) {
+ return new NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataRsp()
+ }
+
+ simulator(NfsPrimaryStorageKVMBackend.CLEANUP_VM_METADATA_PATH) {
+ return new NfsPrimaryStorageKVMBackendCommands.CleanupVmMetadataRsp()
+ }
+
VFS.vfsHook(NfsPrimaryStorageKVMBackend.NFS_REBASE_VOLUME_BACKING_FILE_PATH, xspec) { rsp, HttpEntity e, EnvSpec spec ->
def cmd = JSONObjectUtil.toObject(e.body, NfsPrimaryStorageKVMBackendCommands.NfsRebaseVolumeBackingFileCmd.class)