-
Notifications
You must be signed in to change notification settings - Fork 0
<feature>[vm]: support DELETE /vm-instances/metadata bulk cleanup #3882
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: zsv_5.0.0
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package org.zstack.header.storage.primary; | ||
|
|
||
| import org.zstack.header.message.NeedReplyMessage; | ||
|
|
||
| public class CleanupAllVmMetadataOnPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage { | ||
| private String primaryStorageUuid; | ||
| private String metadataDir; | ||
|
|
||
| @Override | ||
| public String getPrimaryStorageUuid() { | ||
| return primaryStorageUuid; | ||
| } | ||
|
|
||
| public void setPrimaryStorageUuid(String primaryStorageUuid) { | ||
| this.primaryStorageUuid = primaryStorageUuid; | ||
| } | ||
|
|
||
| public String getMetadataDir() { | ||
| return metadataDir; | ||
| } | ||
|
|
||
| public void setMetadataDir(String metadataDir) { | ||
| this.metadataDir = metadataDir; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package org.zstack.header.storage.primary; | ||
|
|
||
| import org.zstack.header.message.MessageReply; | ||
|
|
||
| public class CleanupAllVmMetadataOnPrimaryStorageReply extends MessageReply { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package org.zstack.header.vm; | ||
|
|
||
| import org.zstack.header.message.APIEvent; | ||
| import org.zstack.header.rest.RestResponse; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @RestResponse(fieldsTo = {"all"}) | ||
| public class APICleanupAllVmInstanceMetadataEvent extends APIEvent { | ||
| private List<String> failedPrimaryStorageUuids; | ||
|
|
||
| public APICleanupAllVmInstanceMetadataEvent() { | ||
| super(null); | ||
| } | ||
|
|
||
| public APICleanupAllVmInstanceMetadataEvent(String apiId) { | ||
| super(apiId); | ||
| } | ||
|
|
||
| public List<String> getFailedPrimaryStorageUuids() { | ||
| return failedPrimaryStorageUuids; | ||
| } | ||
|
|
||
| public void setFailedPrimaryStorageUuids(List<String> failedPrimaryStorageUuids) { | ||
| this.failedPrimaryStorageUuids = failedPrimaryStorageUuids; | ||
| } | ||
|
|
||
| public static APICleanupAllVmInstanceMetadataEvent __example__() { | ||
| APICleanupAllVmInstanceMetadataEvent evt = new APICleanupAllVmInstanceMetadataEvent(); | ||
| evt.failedPrimaryStorageUuids = java.util.Collections.emptyList(); | ||
| return evt; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package org.zstack.header.vm | ||
|
|
||
| import org.zstack.header.errorcode.ErrorCode | ||
|
|
||
| doc { | ||
|
|
||
| title "清理全部云主机元数据返回" | ||
|
|
||
| field { | ||
| name "failedPrimaryStorageUuids" | ||
| desc "清理失败的主存储UUID列表;具体失败原因汇总见 error 字段,逐条详情见 mn / agent 日志" | ||
| type "List" | ||
| since "5.0.0" | ||
| } | ||
| field { | ||
| name "success" | ||
| desc "操作是否成功;任一主存储清理失败则为 false" | ||
| type "boolean" | ||
| since "5.0.0" | ||
| } | ||
| ref { | ||
| name "error" | ||
| path "org.zstack.header.vm.APICleanupAllVmInstanceMetadataEvent.error" | ||
| desc "错误码;success=false 时聚合所有失败主存储的失败原因" | ||
| type "ErrorCode" | ||
| since "5.0.0" | ||
| clz ErrorCode.class | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package org.zstack.header.vm; | ||
|
|
||
| import org.springframework.http.HttpMethod; | ||
| import org.zstack.header.message.APIMessage; | ||
| import org.zstack.header.message.APIParam; | ||
| import org.zstack.header.rest.RestRequest; | ||
| import org.zstack.header.storage.primary.PrimaryStorageVO; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @RestRequest( | ||
| path = "/vm-instances/metadata", | ||
| method = HttpMethod.DELETE, | ||
| responseClass = APICleanupAllVmInstanceMetadataEvent.class, | ||
| isAction = true | ||
| ) | ||
| public class APICleanupAllVmInstanceMetadataMsg extends APIMessage { | ||
| @APIParam(resourceType = PrimaryStorageVO.class, required = false) | ||
| private List<String> primaryStorageUuids; | ||
|
|
||
| public List<String> getPrimaryStorageUuids() { | ||
| return primaryStorageUuids; | ||
| } | ||
|
|
||
| public void setPrimaryStorageUuids(List<String> primaryStorageUuids) { | ||
| this.primaryStorageUuids = primaryStorageUuids; | ||
| } | ||
|
|
||
| public static APICleanupAllVmInstanceMetadataMsg __example__() { | ||
| APICleanupAllVmInstanceMetadataMsg msg = new APICleanupAllVmInstanceMetadataMsg(); | ||
| msg.primaryStorageUuids = java.util.Arrays.asList(uuid(), uuid()); | ||
| return msg; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package org.zstack.header.vm | ||
|
|
||
| import org.zstack.header.vm.APICleanupAllVmInstanceMetadataEvent | ||
|
|
||
| doc { | ||
| title "清理全部云主机元数据" | ||
|
|
||
| category "云主机" | ||
|
|
||
| desc """清理一个或多个主存储上保存的全部云主机元数据文件,仅管理员可调用。当 primaryStorageUuids 为空(未传或传空列表)时,将清理系统中所有 Enabled+Connected 且支持云主机元数据的主存储;否则仅清理列表中指定的主存储。""" | ||
|
|
||
| rest { | ||
| request { | ||
| url "DELETE /v1/vm-instances/metadata" | ||
|
|
||
| header (Authorization: 'OAuth the-session-uuid') | ||
|
|
||
| clz APICleanupAllVmInstanceMetadataMsg.class | ||
|
|
||
| desc """""" | ||
|
|
||
| params { | ||
|
|
||
| column { | ||
| name "primaryStorageUuids" | ||
| enclosedIn "cleanupAllVmInstanceMetadata" | ||
| desc "需要清理云主机元数据的主存储UUID列表;为空时清理所有 Enabled+Connected 主存储上的元数据" | ||
| location "body" | ||
| type "List" | ||
| optional true | ||
| 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 APICleanupAllVmInstanceMetadataEvent.class | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -950,6 +950,13 @@ public static class CleanupVmMetadataCmd extends AgentCommand { | |
| public static class CleanupVmMetadataRsp extends AgentResponse { | ||
| } | ||
|
|
||
| public static class CleanupAllVmMetadataCmd extends AgentCommand { | ||
| public String metadataDir; | ||
| } | ||
|
|
||
| public static class CleanupAllVmMetadataRsp extends AgentResponse { | ||
| } | ||
|
Comment on lines
+953
to
+958
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cleanup-all 响应未承载并回传统计字段,结果可观测性不足。
🔧 建议修复 public static class CleanupAllVmMetadataRsp extends AgentResponse {
+ public int cleanedCount;
+ public int failedCount;
} public void success(CleanupAllVmMetadataRsp rsp) {
CleanupAllVmMetadataOnPrimaryStorageReply reply = new CleanupAllVmMetadataOnPrimaryStorageReply();
+ reply.setCleanedCount(rsp.cleanedCount);
+ reply.setFailedCount(rsp.failedCount);
completion.success(reply);
}Also applies to: 3966-3983 🤖 Prompt for AI Agents |
||
|
|
||
| public static class PrefixRebaseBackingFilesCmd extends LocalStorageKvmBackend.AgentCommand { | ||
| public List<String> filePaths; | ||
| public String oldPrefix; | ||
|
|
@@ -996,6 +1003,7 @@ public static class PrefixRebaseBackingFilesRsp extends LocalStorageKvmBackend.A | |
| public static final String GET_VM_INSTANCE_METADATA_PATH = "/localstorage/vm/metadata/get"; | ||
| public static final String SCAN_VM_METADATA_PATH = "/localstorage/vm/metadata/scan"; | ||
| public static final String CLEANUP_VM_METADATA_PATH = "/localstorage/vm/metadata/cleanup"; | ||
| public static final String CLEANUP_ALL_VM_METADATA_PATH = "/localstorage/vm/metadata/cleanup-all"; | ||
| public static final String PREFIX_REBASE_BACKING_FILES_PATH = "/localstorage/snapshot/prefixrebasebackingfiles"; | ||
|
|
||
| public LocalStorageKvmBackend() { | ||
|
|
@@ -3955,6 +3963,25 @@ public void fail(ErrorCode errorCode) { | |
| }); | ||
| } | ||
|
|
||
| @Override | ||
| void handle(CleanupAllVmMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupAllVmMetadataOnPrimaryStorageReply> completion) { | ||
| CleanupAllVmMetadataCmd cmd = new CleanupAllVmMetadataCmd(); | ||
| cmd.metadataDir = msg.getMetadataDir(); | ||
|
|
||
| httpCall(CLEANUP_ALL_VM_METADATA_PATH, hostUuid, cmd, CleanupAllVmMetadataRsp.class, new ReturnValueCompletion<CleanupAllVmMetadataRsp>(completion) { | ||
| @Override | ||
| public void success(CleanupAllVmMetadataRsp rsp) { | ||
| CleanupAllVmMetadataOnPrimaryStorageReply reply = new CleanupAllVmMetadataOnPrimaryStorageReply(); | ||
| completion.success(reply); | ||
| } | ||
|
|
||
| @Override | ||
| public void fail(ErrorCode errorCode) { | ||
| completion.fail(errorCode); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| @Override | ||
| void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<RebaseVolumeBackingFileOnPrimaryStorageReply> completion) { | ||
| PrefixRebaseBackingFilesCmd cmd = new PrefixRebaseBackingFilesCmd(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -136,6 +136,8 @@ protected void handleLocalMessage(Message msg) { | |
| handle((PullVolumeSnapshotOnPrimaryStorageMsg) msg); | ||
| } else if (msg instanceof RebaseVolumeBackingFileOnPrimaryStorageMsg) { | ||
| handle((RebaseVolumeBackingFileOnPrimaryStorageMsg) msg); | ||
| } else if (msg instanceof CleanupAllVmMetadataOnPrimaryStorageMsg) { | ||
| handle((CleanupAllVmMetadataOnPrimaryStorageMsg) msg); | ||
| } else { | ||
| super.handleLocalMessage(msg); | ||
| } | ||
|
|
@@ -2089,4 +2091,42 @@ public void fail(ErrorCode errorCode) { | |
| } | ||
| }); | ||
| } | ||
|
|
||
| @Override | ||
| protected void handle(CleanupAllVmMetadataOnPrimaryStorageMsg msg) { | ||
| CleanupAllVmMetadataOnPrimaryStorageReply reply = new CleanupAllVmMetadataOnPrimaryStorageReply(); | ||
| List<HostInventory> connectedHosts = factory.getConnectedHostForOperation(getSelfInventory()); | ||
| if (connectedHosts.isEmpty()) { | ||
| reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid())); | ||
| bus.reply(msg, reply); | ||
| return; | ||
| } | ||
| cleanupAllOnHostWithFallback(msg, reply, connectedHosts, 0); | ||
| } | ||
|
|
||
| private void cleanupAllOnHostWithFallback(CleanupAllVmMetadataOnPrimaryStorageMsg msg, | ||
| CleanupAllVmMetadataOnPrimaryStorageReply reply, | ||
| List<HostInventory> connectedHosts, int idx) { | ||
| if (idx >= connectedHosts.size()) { | ||
| reply.setError(operr("failed to cleanup all vm metadata on NFS primary storage[uuid:%s] after trying %d connected host(s)", | ||
| self.getUuid(), connectedHosts.size())); | ||
| bus.reply(msg, reply); | ||
|
Comment on lines
+2110
to
+2113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 最终失败错误缺少根因信息,接口可观测性偏弱。 当前重试全部失败后仅返回通用错误,调用方拿不到最后一次失败的具体原因。建议在最终 🔧 建议修改- cleanupAllOnHostWithFallback(msg, reply, connectedHosts, 0);
+ cleanupAllOnHostWithFallback(msg, reply, connectedHosts, 0, null);
}
private void cleanupAllOnHostWithFallback(CleanupAllVmMetadataOnPrimaryStorageMsg msg,
CleanupAllVmMetadataOnPrimaryStorageReply reply,
- List<HostInventory> connectedHosts, int idx) {
+ List<HostInventory> connectedHosts, int idx,
+ ErrorCode lastError) {
if (idx >= connectedHosts.size()) {
- reply.setError(operr("failed to cleanup all vm metadata on NFS primary storage[uuid:%s] after trying %d connected host(s)",
- self.getUuid(), connectedHosts.size()));
+ reply.setError(lastError == null
+ ? operr("failed to cleanup all vm metadata on NFS primary storage[uuid:%s] after trying %d connected host(s)",
+ self.getUuid(), connectedHosts.size())
+ : operr("failed to cleanup all vm metadata on NFS primary storage[uuid:%s] after trying %d connected host(s), last error: %s",
+ self.getUuid(), connectedHosts.size(), lastError.getDetails()));
bus.reply(msg, reply);
return;
}
@@
public void fail(ErrorCode errorCode) {
logger.warn(String.format("[MetadataCleanup] cleanAll: NFS ps[uuid:%s] failed on host[uuid:%s]: %s; trying next connected host",
self.getUuid(), hostUuid, errorCode));
- cleanupAllOnHostWithFallback(msg, reply, connectedHosts, idx + 1);
+ cleanupAllOnHostWithFallback(msg, reply, connectedHosts, idx + 1, errorCode);
}
});
}Also applies to: 2125-2129 🤖 Prompt for AI Agents |
||
| return; | ||
| } | ||
| String hostUuid = connectedHosts.get(idx).getUuid(); | ||
| final NfsPrimaryStorageBackend backend = getBackendByHostUuid(hostUuid); | ||
| backend.handle(msg, hostUuid, new ReturnValueCompletion<CleanupAllVmMetadataOnPrimaryStorageReply>(msg) { | ||
| @Override | ||
| public void success(CleanupAllVmMetadataOnPrimaryStorageReply r) { | ||
| bus.reply(msg, r); | ||
| } | ||
|
|
||
| @Override | ||
| public void fail(ErrorCode errorCode) { | ||
| logger.warn(String.format("[MetadataCleanup] cleanAll: NFS ps[uuid:%s] failed on host[uuid:%s]: %s; trying next connected host", | ||
| self.getUuid(), hostUuid, errorCode)); | ||
| cleanupAllOnHostWithFallback(msg, reply, connectedHosts, idx + 1); | ||
| } | ||
| }); | ||
|
Comment on lines
+2095
to
+2130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 不要把批量清理硬性绑定到在线 host。 当前实现要求 🛠️ 建议的调整 List<HostInventory> connectedHosts = factory.getConnectedHostForOperation(getSelfInventory());
if (connectedHosts.isEmpty()) {
- reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid()));
+ logger.warn(String.format(
+ "no connected host found for NFS primary storage[uuid:%s] when cleaning VM metadata",
+ self.getUuid()));
+ reply.setCleanedCount(0);
+ reply.setFailedCount(0);
bus.reply(msg, reply);
return;
}
String hostUuid = connectedHosts.get(0).getUuid();🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cleanup-all未覆盖断连主机,失败统计会被低估。当前仅以 Connected 主机作为清理集合;当存在 Disconnected 主机时,
failedCount和failedHostUuids会遗漏,Line 3638在“无连接主机”场景也会返回 0 失败,和“全量清理”语义不一致。建议修复(示例)
Also applies to: 3699-3708
🤖 Prompt for AI Agents