云端事件:修复实例通知并移出 LaunchInstance 回传

This commit is contained in:
2026-07-17 16:52:49 +08:00
parent 882eeade1e
commit 6cf9465fea
6 changed files with 131 additions and 13 deletions
+16
View File
@@ -554,6 +554,8 @@ func envActor(env onsEnvelope) string {
// envOutcome 判读事件成败与补充说明:登录事件解析 auditEventMapValue;其余
// Audit 事件看 message 后缀,补充说明取 stateChange.current.description(策略描述)。
// 计算类失败形如 "LaunchInstance failed with response 'NotAuthorizedOrNotFound'",
// 判失败并以引号内错误码作补充说明(无其他说明时)。
func envOutcome(env onsEnvelope) (outcome, detail string) {
if env.StateChange != nil {
detail = env.StateChange.Current.Description
@@ -566,10 +568,24 @@ func envOutcome(env onsEnvelope) (outcome, detail string) {
outcome = "成功"
case strings.HasSuffix(env.Message, " failed"):
outcome = "失败"
case strings.Contains(env.Message, " failed with response "):
outcome = "失败"
if detail == "" {
detail = failedResponse(env.Message)
}
}
return outcome, detail
}
// failedResponse 提取 "X failed with response 'Err'" 中引号内的错误码。
func failedResponse(msg string) string {
_, after, ok := strings.Cut(msg, " failed with response ")
if !ok {
return ""
}
return strings.Trim(strings.TrimSpace(after), "'")
}
// ssoOutcome 解析 IDCS 审计负载(JSON 字符串):eventId 含 success/failure 定成败,
// 失败时以其 message 作为原因说明。
func ssoOutcome(raw, detail string) (string, string) {
+8
View File
@@ -308,6 +308,14 @@ func TestParseLogEvent(t *testing.T) {
wantType: "x",
wantOutcome: "失败",
},
{
name: "failed with response 判失败并提取错误码",
payload: `{"type":"com.oraclecloud.computeApi.LaunchInstance.begin","data":{
"message":"LaunchInstance failed with response 'NotAuthorizedOrNotFound'"}}`,
wantType: "com.oraclecloud.computeApi.LaunchInstance.begin",
wantOutcome: "失败",
wantDetail: "NotAuthorizedOrNotFound",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+17 -8
View File
@@ -1,6 +1,7 @@
package service
import (
"cmp"
"context"
"errors"
"fmt"
@@ -23,9 +24,11 @@ const (
)
// RelayCriticalEvents 是回传的关键事件清单(Connector Log Filter 与 P2 告警共用):
// 实例生命周期、用户与凭据、区域订阅、策略变更、控制台登录。
// 实例终止与电源操作、用户与凭据、区域订阅、策略变更、控制台登录。
// 不含 LaunchInstance:创建以面板自身(手动/抢机反复尝试)发起为主,回传即噪声
// (10 个 1min 抢机任务一天即可刷掉 2 万条上限),终止与电源操作才是外部风险信号。
var RelayCriticalEvents = []string{
"LaunchInstance", "TerminateInstance", "InstanceAction",
"TerminateInstance", "InstanceAction",
"CreateUser", "DeleteUser", "UpdateUser",
"CreateApiKey", "DeleteApiKey", "UpdateUserCapabilities",
"CreateRegionSubscription",
@@ -46,7 +49,7 @@ var relayCriticalSet = func() map[string]bool {
// 通知管理「云端事件」按此细分开关。
func relayEventClass(name string) string {
switch name {
case "LaunchInstance", "TerminateInstance", "InstanceAction":
case "TerminateInstance", "InstanceAction":
return "instance"
case "CreateUser", "DeleteUser", "UpdateUser",
"CreateApiKey", "DeleteApiKey", "UpdateUserCapabilities":
@@ -313,24 +316,30 @@ func (s *LogEventService) teardownResources(ctx context.Context, cred oci.Creden
// ---- P2 告警联动:解析出关键事件后经 Notifier 推送 ----
// relayEventShortName 取 CloudEvents type 末段(com.oraclecloud.ComputeApi.LaunchInstance → LaunchInstance)。
// relayEventShortName 取 CloudEvents type 中的事件短名:Audit v2 计算类事件带
// .begin/.end 阶段后缀(com.oraclecloud.ComputeApi.LaunchInstance.end),先剥阶段再取末段。
func relayEventShortName(eventType string) string {
if i := strings.LastIndex(eventType, "."); i >= 0 {
return eventType[i+1:]
t := strings.TrimSuffix(strings.TrimSuffix(eventType, ".begin"), ".end")
if i := strings.LastIndex(t, "."); i >= 0 {
return t[i+1:]
}
return eventType
return t
}
// criticalEventVars 判定关键事件并生成模板变量;非关键事件 ok 为 false。
// 成对事件只推 .begin(携带操作者/IP/成败),.end 无操作者且信息重复,不再告警;
// actor/ip/outcome/resource 缺失时兜底 —,detail 包装为独立行(空则不占行)。
func criticalEventVars(alias string, p parsedEvent) (map[string]string, bool) {
if strings.HasSuffix(p.EventType, ".end") {
return nil, false
}
name := relayEventShortName(p.EventType)
if name == "" || !relayCriticalSet[name] {
return nil, false
}
return map[string]string{
"tenant": alias, "event": name,
"resource": orDash(p.ResourceName), "actor": orDash(p.Actor),
"resource": orDash(cmp.Or(p.ResourceName, p.Source)), "actor": orDash(p.Actor),
"ip": orDash(p.SourceIP), "outcome": orDash(p.Outcome),
"detail": detailLine(p.Detail),
}, true
+15 -3
View File
@@ -339,10 +339,22 @@ func TestCriticalEventText(t *testing.T) {
wantOK: true,
want: map[string]string{"event": "CreatePolicy", "detail": "\n允许发布到 ONS Topic"},
},
{
name: "begin 阶段剥后缀命中并回退 source 作资源",
event: parsedEvent{EventType: "com.oraclecloud.computeApi.TerminateInstance.begin",
Source: "instance-20260717-1445", Actor: "IT Team", SourceIP: "137.131.7.136", Outcome: "成功"},
wantOK: true,
want: map[string]string{"event": "TerminateInstance", "resource": "instance-20260717-1445",
"actor": "IT Team", "ip": "137.131.7.136", "outcome": "成功"},
},
{name: "end 阶段不重复告警",
event: parsedEvent{EventType: "com.oraclecloud.ComputeApi.TerminateInstance.end"}, wantOK: false},
{name: "LaunchInstance 已移出清单不告警",
event: parsedEvent{EventType: "com.oraclecloud.computeApi.LaunchInstance.begin"}, wantOK: false},
{name: "List 噪声不推", event: parsedEvent{EventType: "com.oraclecloud.ComputeApi.ListInstances"}, wantOK: false},
{name: "空类型不推", event: parsedEvent{}, wantOK: false},
{name: "短名直接命中", event: parsedEvent{EventType: "LaunchInstance"}, wantOK: true,
want: map[string]string{"event": "LaunchInstance"}},
{name: "短名直接命中", event: parsedEvent{EventType: "InstanceAction"}, wantOK: true,
want: map[string]string{"event": "InstanceAction"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -370,7 +382,7 @@ func TestRelayEventClass(t *testing.T) {
name string
class string
}{
{"LaunchInstance", "instance"},
{"LaunchInstance", ""}, // 已移出回传清单,不归类
{"TerminateInstance", "instance"},
{"InstanceAction", "instance"},
{"CreateUser", "identity"},