diff --git a/conf/serviceConfig/primaryStorage.xml b/conf/serviceConfig/primaryStorage.xml
index 337ce4eaac3..c928d2f6d8c 100755
--- a/conf/serviceConfig/primaryStorage.xml
+++ b/conf/serviceConfig/primaryStorage.xml
@@ -81,7 +81,16 @@
org.zstack.header.storage.primary.APICleanUpStorageTrashOnPrimaryStorageMsg
+
org.zstack.header.storage.primary.APIAddStorageProtocolMsg
+
+
+ org.zstack.header.storage.primary.APICheckPrimaryStorageConsistencyMsg
+
+
+
+ org.zstack.header.storage.primary.APITakeoverPrimaryStorageMsg
+
diff --git a/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyMsg.java b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyMsg.java
new file mode 100644
index 00000000000..e80bd4f9efc
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyMsg.java
@@ -0,0 +1,35 @@
+package org.zstack.header.storage.primary;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APISyncCallMessage;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.rest.RestRequest;
+
+@RestRequest(
+ path = "/primary-storage/{uuid}/consistency",
+ responseClass = APICheckPrimaryStorageConsistencyReply.class,
+ method = HttpMethod.GET
+)
+public class APICheckPrimaryStorageConsistencyMsg extends APISyncCallMessage implements PrimaryStorageMessage {
+ @APIParam(resourceType = PrimaryStorageVO.class)
+ private String uuid;
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ return uuid;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public static APICheckPrimaryStorageConsistencyMsg __example__() {
+ APICheckPrimaryStorageConsistencyMsg msg = new APICheckPrimaryStorageConsistencyMsg();
+ msg.setUuid(uuid(PrimaryStorageVO.class));
+ return msg;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..e8a409b9718
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyMsgDoc_zh_cn.groovy
@@ -0,0 +1,58 @@
+package org.zstack.header.storage.primary
+
+import org.zstack.header.storage.primary.APICheckPrimaryStorageConsistencyReply
+
+doc {
+ title "CheckPrimaryStorageConsistency"
+
+ category "storage.primary"
+
+ desc """检查存储一致性"""
+
+ rest {
+ request {
+ url "GET /v1/primary-storage/{uuid}/consistency"
+
+ header (Authorization: 'OAuth the-session-uuid')
+
+ clz APICheckPrimaryStorageConsistencyMsg.class
+
+ desc """检查指定主存储的一致性状态"""
+
+ params {
+
+ column {
+ name "uuid"
+ enclosedIn ""
+ desc "主存储的UUID"
+ location "url"
+ 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 APICheckPrimaryStorageConsistencyReply.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyReply.java b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyReply.java
new file mode 100644
index 00000000000..b5b0ef91642
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyReply.java
@@ -0,0 +1,42 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.APIReply;
+import org.zstack.header.rest.RestResponse;
+
+@RestResponse(fieldsTo = {"all"})
+public class APICheckPrimaryStorageConsistencyReply extends APIReply {
+ private boolean consistent;
+ private ConsistencyCheckReason reason;
+ private String candidateVgUuid;
+
+ public boolean isConsistent() {
+ return consistent;
+ }
+
+ public void setConsistent(boolean consistent) {
+ this.consistent = consistent;
+ }
+
+ public ConsistencyCheckReason getReason() {
+ return reason;
+ }
+
+ public void setReason(ConsistencyCheckReason reason) {
+ this.reason = reason;
+ }
+
+ public String getCandidateVgUuid() {
+ return candidateVgUuid;
+ }
+
+ public void setCandidateVgUuid(String candidateVgUuid) {
+ this.candidateVgUuid = candidateVgUuid;
+ }
+
+ public static APICheckPrimaryStorageConsistencyReply __example__() {
+ APICheckPrimaryStorageConsistencyReply reply = new APICheckPrimaryStorageConsistencyReply();
+ reply.setConsistent(true);
+ reply.setReason(ConsistencyCheckReason.CONSISTENT);
+ return reply;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyReplyDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyReplyDoc_zh_cn.groovy
new file mode 100644
index 00000000000..bb223a0308b
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyReplyDoc_zh_cn.groovy
@@ -0,0 +1,41 @@
+package org.zstack.header.storage.primary
+
+import org.zstack.header.errorcode.ErrorCode
+
+doc {
+
+ title "检查存储一致性返回"
+
+ field {
+ name "consistent"
+ desc "是否一致"
+ type "boolean"
+ since "5.0.0"
+ }
+ field {
+ name "reason"
+ desc "一致性检查结果原因: CONSISTENT(VG 存在且 UUID 一致)/ UUID_MISMATCH(VG 存在但 UUID 不一致,可执行接管)/ VG_NOT_FOUND(未找到 WWID 匹配的 VG)"
+ type "ConsistencyCheckReason"
+ since "5.0.0"
+ }
+ field {
+ name "candidateVgUuid"
+ desc "reason 为 UUID_MISMATCH 时,存储上实际找到的 VG UUID(即接管候选);其他情况为 null"
+ type "String"
+ since "5.0.0"
+ }
+ field {
+ name "success"
+ desc "操作是否成功"
+ type "boolean"
+ since "5.0.0"
+ }
+ ref {
+ name "error"
+ path "org.zstack.header.storage.primary.APICheckPrimaryStorageConsistencyReply.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/APITakeoverPrimaryStorageEvent.java b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageEvent.java
new file mode 100644
index 00000000000..47b76fc5e21
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageEvent.java
@@ -0,0 +1,60 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
+
+import java.util.Collections;
+
+@RestResponse(fieldsTo = {"all"})
+public class APITakeoverPrimaryStorageEvent extends APIEvent {
+ private PrimaryStorageInventory inventory;
+
+ private ReconnectResult reconnectResult;
+
+ private String reconnectError;
+
+ public APITakeoverPrimaryStorageEvent() {
+ }
+
+ public APITakeoverPrimaryStorageEvent(String apiId) {
+ super(apiId);
+ }
+
+ public PrimaryStorageInventory getInventory() {
+ return inventory;
+ }
+
+ public void setInventory(PrimaryStorageInventory inventory) {
+ this.inventory = inventory;
+ }
+
+ public ReconnectResult getReconnectResult() {
+ return reconnectResult;
+ }
+
+ public void setReconnectResult(ReconnectResult reconnectResult) {
+ this.reconnectResult = reconnectResult;
+ }
+
+ public String getReconnectError() {
+ return reconnectError;
+ }
+
+ public void setReconnectError(String reconnectError) {
+ this.reconnectError = reconnectError;
+ }
+
+ public static APITakeoverPrimaryStorageEvent __example__() {
+ APITakeoverPrimaryStorageEvent event = new APITakeoverPrimaryStorageEvent();
+
+ PrimaryStorageInventory ps = new PrimaryStorageInventory();
+ ps.setName("PS1");
+ ps.setUrl("/zstack_ps");
+ ps.setType("SharedBlock");
+ ps.setAttachedClusterUuids(Collections.singletonList(uuid()));
+
+ event.setInventory(ps);
+ event.setReconnectResult(ReconnectResult.SUCCESS);
+ return event;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageEventDoc_zh_cn.groovy
new file mode 100644
index 00000000000..a40aad2cd59
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageEventDoc_zh_cn.groovy
@@ -0,0 +1,44 @@
+package org.zstack.header.storage.primary
+
+import org.zstack.header.storage.primary.PrimaryStorageInventory
+import org.zstack.header.errorcode.ErrorCode
+
+doc {
+
+ title "接管主存储返回"
+
+ ref {
+ name "inventory"
+ path "org.zstack.header.storage.primary.APITakeoverPrimaryStorageEvent.inventory"
+ desc "主存储信息"
+ type "PrimaryStorageInventory"
+ since "5.0.0"
+ clz PrimaryStorageInventory.class
+ }
+ field {
+ name "success"
+ desc "操作是否成功"
+ type "boolean"
+ since "5.0.0"
+ }
+ ref {
+ name "error"
+ path "org.zstack.header.storage.primary.APITakeoverPrimaryStorageEvent.error"
+ desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null"
+ type "ErrorCode"
+ since "5.0.0"
+ clz ErrorCode.class
+ }
+ field {
+ name "reconnectResult"
+ desc "接管后重连结果,取值参见 ReconnectResult 枚举: SUCCESS(重连成功)/ FAILED(重连失败,但接管已完成且不可逆)/ NOT_ATTEMPTED(未尝试重连)"
+ type "ReconnectResult"
+ since "5.0.0"
+ }
+ field {
+ name "reconnectError"
+ desc "重连失败时的错误信息"
+ type "String"
+ since "5.0.0"
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsg.java
new file mode 100644
index 00000000000..4fbc9e5e5c3
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsg.java
@@ -0,0 +1,40 @@
+package org.zstack.header.storage.primary;
+
+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 java.util.concurrent.TimeUnit;
+
+@RestRequest(
+ path = "/primary-storage/{uuid}/takeover",
+ responseClass = APITakeoverPrimaryStorageEvent.class,
+ method = HttpMethod.PUT,
+ isAction = true
+)
+@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 1)
+public class APITakeoverPrimaryStorageMsg extends APIMessage implements PrimaryStorageMessage {
+ @APIParam(resourceType = PrimaryStorageVO.class)
+ private String uuid;
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ return uuid;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public static APITakeoverPrimaryStorageMsg __example__() {
+ APITakeoverPrimaryStorageMsg msg = new APITakeoverPrimaryStorageMsg();
+ msg.setUuid(uuid(PrimaryStorageVO.class));
+ return msg;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..802d0902dc6
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsgDoc_zh_cn.groovy
@@ -0,0 +1,61 @@
+package org.zstack.header.storage.primary
+
+import org.zstack.header.storage.primary.APITakeoverPrimaryStorageEvent
+
+doc {
+ title "TakeoverPrimaryStorage"
+
+ category "storage.primary"
+
+ desc """接管主存储。将其他ZStack平台的共享块主存储接管到当前平台。
+前置条件:主存储必须通过APICheckPrimaryStorageConsistencyMsg检查且consistent=false。
+接管操作不可逆(agent侧会执行VG rename、PV UUID reset、sanlock lockspace reset)。
+接管完成后自动触发reconnect,通过返回的reconnectResult/reconnectError获取重连状态。"""
+
+ rest {
+ request {
+ url "PUT /v1/primary-storage/{uuid}/takeover"
+
+ header (Authorization: 'OAuth the-session-uuid')
+
+ clz APITakeoverPrimaryStorageMsg.class
+
+ desc """接管指定主存储"""
+
+ params {
+
+ column {
+ name "uuid"
+ enclosedIn "takeoverPrimaryStorage"
+ desc "主存储的UUID"
+ location "url"
+ type "String"
+ 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 APITakeoverPrimaryStorageEvent.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/header/src/main/java/org/zstack/header/storage/primary/ConsistencyCheckReason.java b/header/src/main/java/org/zstack/header/storage/primary/ConsistencyCheckReason.java
new file mode 100644
index 00000000000..5ee7e5aeb8e
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/ConsistencyCheckReason.java
@@ -0,0 +1,10 @@
+package org.zstack.header.storage.primary;
+
+public enum ConsistencyCheckReason {
+ /** VG found by WWID match, UUID matches the database — fully consistent */
+ CONSISTENT,
+ /** VG found by WWID match, but its UUID differs from the database — takeover candidate */
+ UUID_MISMATCH,
+ /** Hosts returned complete VG data, but no VG has a matching WWID set */
+ VG_NOT_FOUND
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/ReconnectResult.java b/header/src/main/java/org/zstack/header/storage/primary/ReconnectResult.java
new file mode 100644
index 00000000000..b4d7bb5241b
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/ReconnectResult.java
@@ -0,0 +1,7 @@
+package org.zstack.header.storage.primary;
+
+public enum ReconnectResult {
+ SUCCESS,
+ FAILED,
+ NOT_ATTEMPTED
+}
diff --git a/sdk/src/main/java/SourceClassMap.java b/sdk/src/main/java/SourceClassMap.java
index 146b132c459..1f4807fcfeb 100644
--- a/sdk/src/main/java/SourceClassMap.java
+++ b/sdk/src/main/java/SourceClassMap.java
@@ -243,9 +243,11 @@ public class SourceClassMap {
put("org.zstack.header.storage.database.backup.DatabaseBackupStorageRefInventory", "org.zstack.sdk.databasebackup.DatabaseBackupStorageRefInventory");
put("org.zstack.header.storage.database.backup.DatabaseBackupStruct", "org.zstack.sdk.databasebackup.DatabaseBackupStruct");
put("org.zstack.header.storage.database.backup.DatabaseType", "org.zstack.sdk.databasebackup.DatabaseType");
+ put("org.zstack.header.storage.primary.ConsistencyCheckReason", "org.zstack.sdk.ConsistencyCheckReason");
put("org.zstack.header.storage.primary.ImageCacheInventory", "org.zstack.sdk.ImageCacheInventory");
put("org.zstack.header.storage.primary.PrimaryStorageHostStatus", "org.zstack.sdk.PrimaryStorageHostStatus");
put("org.zstack.header.storage.primary.PrimaryStorageInventory", "org.zstack.sdk.PrimaryStorageInventory");
+ put("org.zstack.header.storage.primary.ReconnectResult", "org.zstack.sdk.ReconnectResult");
put("org.zstack.header.storage.primary.UsageReport", "org.zstack.sdk.UsageReport");
put("org.zstack.header.storage.snapshot.BatchDeleteVolumeSnapshotStruct", "org.zstack.sdk.BatchDeleteVolumeSnapshotStruct");
put("org.zstack.header.storage.snapshot.ShrinkResult", "org.zstack.sdk.ShrinkResult");
@@ -555,6 +557,7 @@ public class SourceClassMap {
put("org.zstack.storage.primary.sharedblock.SharedBlockGroupPrimaryStorageHostRefInventory", "org.zstack.sdk.SharedBlockGroupPrimaryStorageHostRefInventory");
put("org.zstack.storage.primary.sharedblock.SharedBlockGroupPrimaryStorageInventory", "org.zstack.sdk.SharedBlockGroupPrimaryStorageInventory");
put("org.zstack.storage.primary.sharedblock.SharedBlockGroupType", "org.zstack.sdk.SharedBlockGroupType");
+ put("org.zstack.storage.primary.sharedblock.SharedBlockGroupVgInfo", "org.zstack.sdk.SharedBlockGroupVgInfo");
put("org.zstack.storage.primary.sharedblock.SharedBlockInventory", "org.zstack.sdk.SharedBlockInventory");
put("org.zstack.storage.primary.sharedblock.SharedBlockState", "org.zstack.sdk.SharedBlockState");
put("org.zstack.storage.primary.sharedblock.SharedBlockStatus", "org.zstack.sdk.SharedBlockStatus");
@@ -765,6 +768,7 @@ public class SourceClassMap {
put("org.zstack.sdk.CloudFormationStackEventInventory", "org.zstack.header.cloudformation.CloudFormationStackEventInventory");
put("org.zstack.sdk.ClusterDRSInventory", "org.zstack.drs.entity.ClusterDRSInventory");
put("org.zstack.sdk.ClusterInventory", "org.zstack.header.cluster.ClusterInventory");
+ put("org.zstack.sdk.ConsistencyCheckReason", "org.zstack.header.storage.primary.ConsistencyCheckReason");
put("org.zstack.sdk.ConsoleInventory", "org.zstack.header.console.ConsoleInventory");
put("org.zstack.sdk.ConsoleProxyAgentInventory", "org.zstack.header.console.ConsoleProxyAgentInventory");
put("org.zstack.sdk.ControlStrategy", "org.zstack.loginControl.entity.ControlStrategy");
@@ -1025,6 +1029,7 @@ public class SourceClassMap {
put("org.zstack.sdk.QuotaUsage", "org.zstack.header.identity.Quota$QuotaUsage");
put("org.zstack.sdk.RaidControllerInventory", "org.zstack.storage.device.localRaid.RaidControllerInventory");
put("org.zstack.sdk.RaidPhysicalDriveInventory", "org.zstack.storage.device.localRaid.RaidPhysicalDriveInventory");
+ put("org.zstack.sdk.ReconnectResult", "org.zstack.header.storage.primary.ReconnectResult");
put("org.zstack.sdk.RedirectUrlTemplate", "org.zstack.sso.header.RedirectUrlTemplate");
put("org.zstack.sdk.RemoteVtepInventory", "org.zstack.network.l2.vxlan.vtep.RemoteVtepInventory");
put("org.zstack.sdk.RemovalInstanceRuleInventory", "org.zstack.autoscaling.group.rule.RemovalInstanceRuleInventory");
@@ -1078,6 +1083,7 @@ public class SourceClassMap {
put("org.zstack.sdk.SharedBlockGroupPrimaryStorageHostRefInventory", "org.zstack.storage.primary.sharedblock.SharedBlockGroupPrimaryStorageHostRefInventory");
put("org.zstack.sdk.SharedBlockGroupPrimaryStorageInventory", "org.zstack.storage.primary.sharedblock.SharedBlockGroupPrimaryStorageInventory");
put("org.zstack.sdk.SharedBlockGroupType", "org.zstack.storage.primary.sharedblock.SharedBlockGroupType");
+ put("org.zstack.sdk.SharedBlockGroupVgInfo", "org.zstack.storage.primary.sharedblock.SharedBlockGroupVgInfo");
put("org.zstack.sdk.SharedBlockInventory", "org.zstack.storage.primary.sharedblock.SharedBlockInventory");
put("org.zstack.sdk.SharedBlockState", "org.zstack.storage.primary.sharedblock.SharedBlockState");
put("org.zstack.sdk.SharedBlockStatus", "org.zstack.storage.primary.sharedblock.SharedBlockStatus");
diff --git a/sdk/src/main/java/org/zstack/sdk/CheckPrimaryStorageConsistencyAction.java b/sdk/src/main/java/org/zstack/sdk/CheckPrimaryStorageConsistencyAction.java
new file mode 100644
index 00000000000..5c86726b2d3
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/CheckPrimaryStorageConsistencyAction.java
@@ -0,0 +1,95 @@
+package org.zstack.sdk;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.zstack.sdk.*;
+
+public class CheckPrimaryStorageConsistencyAction 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.CheckPrimaryStorageConsistencyResult 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.CheckPrimaryStorageConsistencyResult value = res.getResult(org.zstack.sdk.CheckPrimaryStorageConsistencyResult.class);
+ ret.value = value == null ? new org.zstack.sdk.CheckPrimaryStorageConsistencyResult() : 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/{uuid}/consistency";
+ info.needSession = true;
+ info.needPoll = false;
+ info.parameterName = "";
+ return info;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/CheckPrimaryStorageConsistencyResult.java b/sdk/src/main/java/org/zstack/sdk/CheckPrimaryStorageConsistencyResult.java
new file mode 100644
index 00000000000..6cd3cdac968
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/CheckPrimaryStorageConsistencyResult.java
@@ -0,0 +1,30 @@
+package org.zstack.sdk;
+
+import org.zstack.sdk.ConsistencyCheckReason;
+
+public class CheckPrimaryStorageConsistencyResult {
+ public boolean consistent;
+ public void setConsistent(boolean consistent) {
+ this.consistent = consistent;
+ }
+ public boolean getConsistent() {
+ return this.consistent;
+ }
+
+ public ConsistencyCheckReason reason;
+ public void setReason(ConsistencyCheckReason reason) {
+ this.reason = reason;
+ }
+ public ConsistencyCheckReason getReason() {
+ return this.reason;
+ }
+
+ public java.lang.String candidateVgUuid;
+ public void setCandidateVgUuid(java.lang.String candidateVgUuid) {
+ this.candidateVgUuid = candidateVgUuid;
+ }
+ public java.lang.String getCandidateVgUuid() {
+ return this.candidateVgUuid;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/ConsistencyCheckReason.java b/sdk/src/main/java/org/zstack/sdk/ConsistencyCheckReason.java
new file mode 100644
index 00000000000..92344dc8921
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/ConsistencyCheckReason.java
@@ -0,0 +1,7 @@
+package org.zstack.sdk;
+
+public enum ConsistencyCheckReason {
+ CONSISTENT,
+ UUID_MISMATCH,
+ VG_NOT_FOUND,
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/DiscoverSharedBlockGroupVgsAction.java b/sdk/src/main/java/org/zstack/sdk/DiscoverSharedBlockGroupVgsAction.java
new file mode 100644
index 00000000000..e88feb2199e
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/DiscoverSharedBlockGroupVgsAction.java
@@ -0,0 +1,95 @@
+package org.zstack.sdk;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.zstack.sdk.*;
+
+public class DiscoverSharedBlockGroupVgsAction 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.DiscoverSharedBlockGroupVgsResult 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 clusterUuid;
+
+ @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.DiscoverSharedBlockGroupVgsResult value = res.getResult(org.zstack.sdk.DiscoverSharedBlockGroupVgsResult.class);
+ ret.value = value == null ? new org.zstack.sdk.DiscoverSharedBlockGroupVgsResult() : 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/sharedblockgroup/vgs";
+ info.needSession = true;
+ info.needPoll = false;
+ info.parameterName = "";
+ return info;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/DiscoverSharedBlockGroupVgsResult.java b/sdk/src/main/java/org/zstack/sdk/DiscoverSharedBlockGroupVgsResult.java
new file mode 100644
index 00000000000..9d23a4446e7
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/DiscoverSharedBlockGroupVgsResult.java
@@ -0,0 +1,14 @@
+package org.zstack.sdk;
+
+
+
+public class DiscoverSharedBlockGroupVgsResult {
+ public java.util.Map vgInfos;
+ public void setVgInfos(java.util.Map vgInfos) {
+ this.vgInfos = vgInfos;
+ }
+ public java.util.Map getVgInfos() {
+ return this.vgInfos;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/ReconnectResult.java b/sdk/src/main/java/org/zstack/sdk/ReconnectResult.java
new file mode 100644
index 00000000000..30eadb24630
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/ReconnectResult.java
@@ -0,0 +1,7 @@
+package org.zstack.sdk;
+
+public enum ReconnectResult {
+ SUCCESS,
+ FAILED,
+ NOT_ATTEMPTED,
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/SharedBlockGroupVgInfo.java b/sdk/src/main/java/org/zstack/sdk/SharedBlockGroupVgInfo.java
new file mode 100644
index 00000000000..3bd931409ed
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/SharedBlockGroupVgInfo.java
@@ -0,0 +1,31 @@
+package org.zstack.sdk;
+
+
+
+public class SharedBlockGroupVgInfo {
+
+ public java.util.List candidateLuns;
+ public void setCandidateLuns(java.util.List candidateLuns) {
+ this.candidateLuns = candidateLuns;
+ }
+ public java.util.List getCandidateLuns() {
+ return this.candidateLuns;
+ }
+
+ public boolean sharedGroupComplete;
+ public void setSharedGroupComplete(boolean sharedGroupComplete) {
+ this.sharedGroupComplete = sharedGroupComplete;
+ }
+ public boolean getSharedGroupComplete() {
+ return this.sharedGroupComplete;
+ }
+
+ public java.util.Map existLunWwidsByHost;
+ public void setExistLunWwidsByHost(java.util.Map existLunWwidsByHost) {
+ this.existLunWwidsByHost = existLunWwidsByHost;
+ }
+ public java.util.Map getExistLunWwidsByHost() {
+ return this.existLunWwidsByHost;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageAction.java b/sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageAction.java
new file mode 100644
index 00000000000..f168002ef64
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageAction.java
@@ -0,0 +1,101 @@
+package org.zstack.sdk;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.zstack.sdk.*;
+
+public class TakeoverPrimaryStorageAction 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.TakeoverPrimaryStorageResult 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;
+
+ @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.TakeoverPrimaryStorageResult value = res.getResult(org.zstack.sdk.TakeoverPrimaryStorageResult.class);
+ ret.value = value == null ? new org.zstack.sdk.TakeoverPrimaryStorageResult() : 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 = "/primary-storage/{uuid}/takeover";
+ info.needSession = true;
+ info.needPoll = true;
+ info.parameterName = "takeoverPrimaryStorage";
+ return info;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageResult.java b/sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageResult.java
new file mode 100644
index 00000000000..6b76df3b5bf
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageResult.java
@@ -0,0 +1,31 @@
+package org.zstack.sdk;
+
+import org.zstack.sdk.PrimaryStorageInventory;
+import org.zstack.sdk.ReconnectResult;
+
+public class TakeoverPrimaryStorageResult {
+ public PrimaryStorageInventory inventory;
+ public void setInventory(PrimaryStorageInventory inventory) {
+ this.inventory = inventory;
+ }
+ public PrimaryStorageInventory getInventory() {
+ return this.inventory;
+ }
+
+ public ReconnectResult reconnectResult;
+ public void setReconnectResult(ReconnectResult reconnectResult) {
+ this.reconnectResult = reconnectResult;
+ }
+ public ReconnectResult getReconnectResult() {
+ return this.reconnectResult;
+ }
+
+ public java.lang.String reconnectError;
+ public void setReconnectError(java.lang.String reconnectError) {
+ this.reconnectError = reconnectError;
+ }
+ public java.lang.String getReconnectError() {
+ return this.reconnectError;
+ }
+
+}
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..5798d7c6951 100755
--- a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java
+++ b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java
@@ -935,11 +935,27 @@ protected void handleApiMessage(APIMessage msg) {
handle((APICleanUpStorageTrashOnPrimaryStorageMsg) msg);
} else if (msg instanceof APIAddStorageProtocolMsg) {
handle((APIAddStorageProtocolMsg) msg);
+ } else if (msg instanceof APITakeoverPrimaryStorageMsg) {
+ handle((APITakeoverPrimaryStorageMsg) msg);
+ } else if (msg instanceof APICheckPrimaryStorageConsistencyMsg) {
+ handle((APICheckPrimaryStorageConsistencyMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
}
+ protected void handle(APITakeoverPrimaryStorageMsg msg) {
+ APITakeoverPrimaryStorageEvent event = new APITakeoverPrimaryStorageEvent(msg.getId());
+ event.setError(operr("takeover not supported for primary storage type[%s]", self.getType()));
+ bus.publish(event);
+ }
+
+ protected void handle(APICheckPrimaryStorageConsistencyMsg msg) {
+ APICheckPrimaryStorageConsistencyReply reply = new APICheckPrimaryStorageConsistencyReply();
+ reply.setError(operr("consistency check not supported for primary storage type[%s]", self.getType()));
+ bus.reply(msg, reply);
+ }
+
private void handle(APIAddStorageProtocolMsg msg) {
APIAddStorageProtocolEvent evt = new APIAddStorageProtocolEvent(msg.getId());
addStorageProtocol(msg, new Completion(msg) {
diff --git a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
index 07c05b73b9e..ed92dddf6bb 100644
--- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
+++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
@@ -5282,6 +5282,33 @@ abstract class ApiHelper {
}
+ def checkPrimaryStorageConsistency(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CheckPrimaryStorageConsistencyAction.class) Closure c) {
+ def a = new org.zstack.sdk.CheckPrimaryStorageConsistencyAction()
+ 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 checkScsiLunClusterStatus(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CheckScsiLunClusterStatusAction.class) Closure c) {
def a = new org.zstack.sdk.CheckScsiLunClusterStatusAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
@@ -17648,6 +17675,33 @@ abstract class ApiHelper {
}
+ def discoverSharedBlockGroupVgs(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DiscoverSharedBlockGroupVgsAction.class) Closure c) {
+ def a = new org.zstack.sdk.DiscoverSharedBlockGroupVgsAction()
+ 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 getSignatureServerEncryptPublicKey(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.GetSignatureServerEncryptPublicKeyAction.class) Closure c) {
def a = new org.zstack.sdk.GetSignatureServerEncryptPublicKeyAction()
@@ -30484,6 +30538,33 @@ abstract class ApiHelper {
}
+ def takeoverPrimaryStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.TakeoverPrimaryStorageAction.class) Closure c) {
+ def a = new org.zstack.sdk.TakeoverPrimaryStorageAction()
+ 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 triggerGCJob(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.TriggerGCJobAction.class) Closure c) {
def a = new org.zstack.sdk.TriggerGCJobAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid