From 306f5514fb203cbd33b2a93b9b743aa22111841b Mon Sep 17 00:00:00 2001 From: "tao.gan" Date: Thu, 15 Jan 2026 14:06:14 +0800 Subject: [PATCH] [storage]: register and take over sblk APIImpact Resolves: ZSV-10000 Change-Id: I70637377776e777070676c6a6c616e74786b6667 --- conf/serviceConfig/primaryStorage.xml | 9 ++ .../APICheckPrimaryStorageConsistencyMsg.java | 35 ++++++ ...imaryStorageConsistencyMsgDoc_zh_cn.groovy | 58 ++++++++++ ...PICheckPrimaryStorageConsistencyReply.java | 42 ++++++++ ...aryStorageConsistencyReplyDoc_zh_cn.groovy | 41 +++++++ .../APITakeoverPrimaryStorageEvent.java | 60 +++++++++++ ...akeoverPrimaryStorageEventDoc_zh_cn.groovy | 44 ++++++++ .../primary/APITakeoverPrimaryStorageMsg.java | 40 +++++++ ...ITakeoverPrimaryStorageMsgDoc_zh_cn.groovy | 61 +++++++++++ .../primary/ConsistencyCheckReason.java | 10 ++ .../storage/primary/ReconnectResult.java | 7 ++ sdk/src/main/java/SourceClassMap.java | 6 ++ .../CheckPrimaryStorageConsistencyAction.java | 95 ++++++++++++++++ .../CheckPrimaryStorageConsistencyResult.java | 30 ++++++ .../zstack/sdk/ConsistencyCheckReason.java | 7 ++ .../DiscoverSharedBlockGroupVgsAction.java | 95 ++++++++++++++++ .../DiscoverSharedBlockGroupVgsResult.java | 14 +++ .../java/org/zstack/sdk/ReconnectResult.java | 7 ++ .../zstack/sdk/SharedBlockGroupVgInfo.java | 31 ++++++ .../sdk/TakeoverPrimaryStorageAction.java | 101 ++++++++++++++++++ .../sdk/TakeoverPrimaryStorageResult.java | 31 ++++++ .../storage/primary/PrimaryStorageBase.java | 16 +++ .../java/org/zstack/testlib/ApiHelper.groovy | 81 ++++++++++++++ 23 files changed, 921 insertions(+) create mode 100644 header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyMsg.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyMsgDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyReply.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyReplyDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageEvent.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageEventDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsg.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsgDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/storage/primary/ConsistencyCheckReason.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/ReconnectResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/CheckPrimaryStorageConsistencyAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/CheckPrimaryStorageConsistencyResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/ConsistencyCheckReason.java create mode 100644 sdk/src/main/java/org/zstack/sdk/DiscoverSharedBlockGroupVgsAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/DiscoverSharedBlockGroupVgsResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/ReconnectResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/SharedBlockGroupVgInfo.java create mode 100644 sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageResult.java 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