diff --git a/conf/serviceConfig/primaryStorage.xml b/conf/serviceConfig/primaryStorage.xml
index 337ce4eaac3..c7b2a7d558a 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.APITakeoverPrimaryStorageMsg
+
+
+
+ org.zstack.header.storage.primary.APIDiscoverStrangePrimaryStorageMsg
+
diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIDiscoverStrangePrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/APIDiscoverStrangePrimaryStorageMsg.java
new file mode 100644
index 00000000000..f1d4e5d0c6b
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APIDiscoverStrangePrimaryStorageMsg.java
@@ -0,0 +1,31 @@
+package org.zstack.header.storage.primary;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.cluster.ClusterVO;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.message.APISyncCallMessage;
+import org.zstack.header.rest.RestRequest;
+
+@RestRequest(
+ path = "/primary-storage/stranger",
+ method = HttpMethod.GET,
+ responseClass = APIDiscoverStrangePrimaryStorageReply.class
+)
+public class APIDiscoverStrangePrimaryStorageMsg extends APISyncCallMessage {
+ @APIParam(resourceType = ClusterVO.class)
+ private String clusterUuid;
+
+ public String getClusterUuid() {
+ return clusterUuid;
+ }
+
+ public void setClusterUuid(String clusterUuid) {
+ this.clusterUuid = clusterUuid;
+ }
+
+ public static APIDiscoverStrangePrimaryStorageMsg __example__() {
+ APIDiscoverStrangePrimaryStorageMsg msg = new APIDiscoverStrangePrimaryStorageMsg();
+ msg.setClusterUuid(uuid());
+ return msg;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIDiscoverStrangePrimaryStorageMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APIDiscoverStrangePrimaryStorageMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..043467cd721
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APIDiscoverStrangePrimaryStorageMsgDoc_zh_cn.groovy
@@ -0,0 +1,58 @@
+package org.zstack.header.storage.primary
+
+import org.zstack.header.storage.primary.APIDiscoverStrangePrimaryStorageReply
+
+doc {
+ title "DiscoverStrangePrimaryStorage"
+
+ category "storage.primary"
+
+ desc """发现集群物理机上未受当前平台管控的主存储"""
+
+ rest {
+ request {
+ url "GET /v1/primary-storage/stranger"
+
+ header (Authorization: 'OAuth the-session-uuid')
+
+ clz APIDiscoverStrangePrimaryStorageMsg.class
+
+ desc """"""
+
+ params {
+
+ column {
+ name "clusterUuid"
+ 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 APIDiscoverStrangePrimaryStorageReply.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIDiscoverStrangePrimaryStorageReply.java b/header/src/main/java/org/zstack/header/storage/primary/APIDiscoverStrangePrimaryStorageReply.java
new file mode 100644
index 00000000000..704ee1f5de2
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APIDiscoverStrangePrimaryStorageReply.java
@@ -0,0 +1,34 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.APIReply;
+import org.zstack.header.rest.RestResponse;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@RestResponse(fieldsTo = {"all"})
+public class APIDiscoverStrangePrimaryStorageReply extends APIReply {
+ private List inventories = new ArrayList<>();
+
+ public List getInventories() {
+ return inventories;
+ }
+
+ public void setInventories(List inventories) {
+ this.inventories = inventories;
+ }
+
+ public static APIDiscoverStrangePrimaryStorageReply __example__() {
+ APIDiscoverStrangePrimaryStorageReply reply = new APIDiscoverStrangePrimaryStorageReply();
+
+ PrimaryStorageInventory ps = new PrimaryStorageInventory();
+ ps.setName("SharedBlockGroup-1");
+ ps.setUrl("/dev/vg_uuid");
+ ps.setType("SharedBlock");
+ ps.setAttachedClusterUuids(Collections.singletonList(uuid()));
+ reply.setInventories(Collections.singletonList(ps));
+
+ return reply;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIDiscoverStrangePrimaryStorageReplyDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APIDiscoverStrangePrimaryStorageReplyDoc_zh_cn.groovy
new file mode 100644
index 00000000000..8c39eaa987c
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APIDiscoverStrangePrimaryStorageReplyDoc_zh_cn.groovy
@@ -0,0 +1,29 @@
+package org.zstack.header.storage.primary
+
+import org.zstack.header.errorcode.ErrorCode
+
+doc {
+
+ title "发现集群物理机上未受当前平台管控的主存储返回"
+
+ field {
+ name "inventories"
+ desc "发现的主存储清单列表"
+ type "List"
+ since "5.0.0"
+ }
+ field {
+ name "success"
+ desc "操作是否成功"
+ type "boolean"
+ since "5.0.0"
+ }
+ ref {
+ name "error"
+ path "org.zstack.header.storage.primary.APIDiscoverStrangePrimaryStorageReply.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..81e7bc13a0a
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageEvent.java
@@ -0,0 +1,39 @@
+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;
+
+ public APITakeoverPrimaryStorageEvent() {
+ }
+
+ public APITakeoverPrimaryStorageEvent(String apiId) {
+ super(apiId);
+ }
+
+ public PrimaryStorageInventory getInventory() {
+ return inventory;
+ }
+
+ public void setInventory(PrimaryStorageInventory inventory) {
+ this.inventory = inventory;
+ }
+
+ 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);
+ 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..ad6515183af
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageEventDoc_zh_cn.groovy
@@ -0,0 +1,32 @@
+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
+ }
+}
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..5f5cc4e3345
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsg.java
@@ -0,0 +1,52 @@
+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;
+
+ @APIParam(required = false)
+ private boolean dryRun;
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ return uuid;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public boolean isDryRun() {
+ return dryRun;
+ }
+
+ public void setDryRun(boolean dryRun) {
+ this.dryRun = dryRun;
+ }
+
+ public static APITakeoverPrimaryStorageMsg __example__() {
+ APITakeoverPrimaryStorageMsg msg = new APITakeoverPrimaryStorageMsg();
+ msg.setUuid(uuid(PrimaryStorageVO.class));
+ msg.setDryRun(false);
+ 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..7f46140e45f
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsgDoc_zh_cn.groovy
@@ -0,0 +1,70 @@
+package org.zstack.header.storage.primary
+
+import org.zstack.header.storage.primary.APITakeoverPrimaryStorageEvent
+
+doc {
+ title "TakeoverPrimaryStorage"
+
+ category "storage.primary"
+
+ desc """接管主存储。将其他ZStack平台的共享块主存储接管到当前平台。
+dryRun=true时仅执行一致性预检:若一致(无需接管)则返回错误,若不一致(可接管)则返回成功。
+接管操作不可逆(agent侧会执行VG rename、PV UUID reset、sanlock lockspace reset)。
+接管完成后自动触发reconnect,通过返回的inventory.status获取重连状态。"""
+
+ 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 "dryRun"
+ enclosedIn "takeoverPrimaryStorage"
+ desc "预检模式:成功表示可以执行接管,失败表示无需或无法接管"
+ location "body"
+ type "boolean"
+ 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 APITakeoverPrimaryStorageEvent.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageDiscoverExtensionPoint.java b/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageDiscoverExtensionPoint.java
new file mode 100644
index 00000000000..2790c047ece
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageDiscoverExtensionPoint.java
@@ -0,0 +1,14 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.core.ReturnValueCompletion;
+
+import java.util.List;
+
+/**
+ * Extension point for discovering unmanaged (strange) primary storage.
+ * Each primary storage type can implement this to discover its own PS instances
+ * that exist on hosts but are not managed by the platform.
+ */
+public interface PrimaryStorageDiscoverExtensionPoint {
+ void discoverStrangePrimaryStorage(String clusterUuid, ReturnValueCompletion> completion);
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/DiscoverStrangePrimaryStorageAction.java b/sdk/src/main/java/org/zstack/sdk/DiscoverStrangePrimaryStorageAction.java
new file mode 100644
index 00000000000..3adabc73102
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/DiscoverStrangePrimaryStorageAction.java
@@ -0,0 +1,95 @@
+package org.zstack.sdk;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.zstack.sdk.*;
+
+public class DiscoverStrangePrimaryStorageAction 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.DiscoverStrangePrimaryStorageResult 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.DiscoverStrangePrimaryStorageResult value = res.getResult(org.zstack.sdk.DiscoverStrangePrimaryStorageResult.class);
+ ret.value = value == null ? new org.zstack.sdk.DiscoverStrangePrimaryStorageResult() : 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/stranger";
+ info.needSession = true;
+ info.needPoll = false;
+ info.parameterName = "";
+ return info;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/DiscoverStrangePrimaryStorageResult.java b/sdk/src/main/java/org/zstack/sdk/DiscoverStrangePrimaryStorageResult.java
new file mode 100644
index 00000000000..714607ff063
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/DiscoverStrangePrimaryStorageResult.java
@@ -0,0 +1,14 @@
+package org.zstack.sdk;
+
+
+
+public class DiscoverStrangePrimaryStorageResult {
+ public java.util.List inventories;
+ public void setInventories(java.util.List inventories) {
+ this.inventories = inventories;
+ }
+ public java.util.List getInventories() {
+ return this.inventories;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/SharedBlockInventory.java b/sdk/src/main/java/org/zstack/sdk/SharedBlockInventory.java
index 2f7e294b9af..eac9ff43c44 100644
--- a/sdk/src/main/java/org/zstack/sdk/SharedBlockInventory.java
+++ b/sdk/src/main/java/org/zstack/sdk/SharedBlockInventory.java
@@ -102,4 +102,12 @@ public java.lang.Long getAvailableCapacity() {
return this.availableCapacity;
}
+ public java.lang.String vendor;
+ public void setVendor(java.lang.String vendor) {
+ this.vendor = vendor;
+ }
+ public java.lang.String getVendor() {
+ return this.vendor;
+ }
+
}
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..0be12e75a7a
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageAction.java
@@ -0,0 +1,104 @@
+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, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ public boolean dryRun = false;
+
+ @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..f6fc1d6b138
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageResult.java
@@ -0,0 +1,14 @@
+package org.zstack.sdk;
+
+import org.zstack.sdk.PrimaryStorageInventory;
+
+public class TakeoverPrimaryStorageResult {
+ public PrimaryStorageInventory inventory;
+ public void setInventory(PrimaryStorageInventory inventory) {
+ this.inventory = inventory;
+ }
+ public PrimaryStorageInventory getInventory() {
+ return this.inventory;
+ }
+
+}
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..87e1f2c821d 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,19 @@ 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 {
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);
+ }
+
private void handle(APIAddStorageProtocolMsg msg) {
APIAddStorageProtocolEvent evt = new APIAddStorageProtocolEvent(msg.getId());
addStorageProtocol(msg, new Completion(msg) {
diff --git a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageManagerImpl.java b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageManagerImpl.java
index a84ed42f7be..a1139d84db2 100755
--- a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageManagerImpl.java
+++ b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageManagerImpl.java
@@ -9,6 +9,7 @@
import org.zstack.configuration.InstanceOfferingSystemTags;
import org.zstack.configuration.OfferingUserConfigUtils;
import org.zstack.core.Platform;
+import org.zstack.core.asyncbatch.While;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.CloudBusCallBack;
import org.zstack.core.cloudbus.MessageSafe;
@@ -35,9 +36,12 @@
import org.zstack.header.configuration.userconfig.InstanceOfferingUserConfig;
import org.zstack.header.configuration.userconfig.InstanceOfferingUserConfigValidator;
import org.zstack.header.core.NoErrorCompletion;
+import org.zstack.header.core.ReturnValueCompletion;
+import org.zstack.header.core.WhileDoneCompletion;
import org.zstack.header.core.trash.InstallPathRecycleVO;
import org.zstack.header.core.trash.InstallPathRecycleVO_;
import org.zstack.header.errorcode.ErrorCode;
+import org.zstack.header.errorcode.ErrorCodeList;
import org.zstack.header.errorcode.OperationFailureException;
import org.zstack.header.errorcode.SysErrors;
import org.zstack.header.exception.CloudRuntimeException;
@@ -149,6 +153,8 @@ private void handleApiMessage(APIMessage msg) {
handle((APIGetPrimaryStorageLicenseInfoMsg) msg);
} else if (msg instanceof APIGetPrimaryStorageUsageReportMsg) {
handle((APIGetPrimaryStorageUsageReportMsg) msg);
+ } else if (msg instanceof APIDiscoverStrangePrimaryStorageMsg) {
+ handle((APIDiscoverStrangePrimaryStorageMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
@@ -290,6 +296,42 @@ public void run(MessageReply r) {
});
}
+ private void handle(APIDiscoverStrangePrimaryStorageMsg msg) {
+ APIDiscoverStrangePrimaryStorageReply reply = new APIDiscoverStrangePrimaryStorageReply();
+
+ List exts = pluginRgty.getExtensionList(PrimaryStorageDiscoverExtensionPoint.class);
+ if (exts.isEmpty()) {
+ bus.reply(msg, reply);
+ return;
+ }
+
+ new While<>(exts).all((ext, whileCompletion) -> {
+ ext.discoverStrangePrimaryStorage(msg.getClusterUuid(), new ReturnValueCompletion>(whileCompletion) {
+ @Override
+ public void success(List inventories) {
+ if (inventories != null) {
+ synchronized (reply) {
+ reply.getInventories().addAll(inventories);
+ }
+ }
+ whileCompletion.done();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ logger.warn(String.format("failed to discover strange primary storage from extension[%s]: %s",
+ ext.getClass().getSimpleName(), errorCode));
+ whileCompletion.done();
+ }
+ });
+ }).run(new WhileDoneCompletion(msg) {
+ @Override
+ public void done(ErrorCodeList errorCodeList) {
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
private void passThrough(PrimaryStorageMessage pmsg) {
PrimaryStorageVO vo = dbf.findByUuid(pmsg.getPrimaryStorageUuid(), PrimaryStorageVO.class);
if (vo == null && allowedMessageAfterSoftDeletion.contains(pmsg.getClass())) {
diff --git a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
index 515c1f6ce99..8a7b1396de6 100644
--- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
+++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
@@ -13490,6 +13490,33 @@ abstract class ApiHelper {
}
+ def discoverStrangePrimaryStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DiscoverStrangePrimaryStorageAction.class) Closure c) {
+ def a = new org.zstack.sdk.DiscoverStrangePrimaryStorageAction()
+ 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 enableCbtTask(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.EnableCbtTaskAction.class) Closure c) {
def a = new org.zstack.sdk.EnableCbtTaskAction()
a.sessionId = Test.currentEnvSpec?.session?.uuid
@@ -30484,6 +30511,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