monitor_resource_alert.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package models
  15. import (
  16. "context"
  17. "fmt"
  18. "strings"
  19. "time"
  20. "yunion.io/x/jsonutils"
  21. "yunion.io/x/log"
  22. "yunion.io/x/pkg/errors"
  23. "yunion.io/x/pkg/util/rbacscope"
  24. "yunion.io/x/sqlchemy"
  25. "yunion.io/x/onecloud/pkg/apis/monitor"
  26. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  27. "yunion.io/x/onecloud/pkg/httperrors"
  28. "yunion.io/x/onecloud/pkg/mcclient"
  29. "yunion.io/x/onecloud/pkg/util/stringutils2"
  30. )
  31. var (
  32. MonitorResourceAlertManager *SMonitorResourceAlertManager
  33. )
  34. func init() {
  35. MonitorResourceAlertManager = &SMonitorResourceAlertManager{
  36. SJointResourceBaseManager: db.NewJointResourceBaseManager(
  37. SMonitorResourceAlert{},
  38. "monitor_resource_alert_tbl",
  39. "monitorresourcealert",
  40. "monitorresourcealerts",
  41. MonitorResourceManager, CommonAlertManager),
  42. }
  43. MonitorResourceAlertManager.SetVirtualObject(MonitorResourceAlertManager)
  44. }
  45. // +onecloud:swagger-gen-model-singular=monitorresourcealert
  46. // +onecloud:swagger-gen-model-plural=monitorresourcealerts
  47. type SMonitorResourceAlertManager struct {
  48. db.SJointResourceBaseManager
  49. SMonitorScopedResourceManager
  50. }
  51. type SMonitorResourceAlert struct {
  52. db.SJointResourceBase
  53. MonitorResourceId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true" json:"monitor_resource_id"`
  54. AlertId string `width:"36" charset:"ascii" list:"user" create:"required" index:"true"`
  55. AlertRecordId string `width:"36" charset:"ascii" list:"user" update:"user"`
  56. ResType string `width:"36" charset:"ascii" list:"user" update:"user" json:"res_type"`
  57. Metric string `width:"36" charset:"ascii" list:"user" create:"required" json:"metric"`
  58. AlertState string `width:"18" charset:"ascii" default:"init" list:"user" update:"user"`
  59. SendState string `width:"18" charset:"ascii" default:"ok" list:"user" update:"user"`
  60. TriggerTime time.Time `list:"user" update:"user" json:"trigger_time"`
  61. Data jsonutils.JSONObject `list:"user" update:"user"`
  62. }
  63. func (manager *SMonitorResourceAlertManager) GetMasterFieldName() string {
  64. return "monitor_resource_id"
  65. }
  66. func (manager *SMonitorResourceAlertManager) GetSlaveFieldName() string {
  67. return "alert_id"
  68. }
  69. func (manager *SMonitorResourceAlertManager) DetachJoint(ctx context.Context, userCred mcclient.TokenCredential,
  70. input monitor.MonitorResourceJointListInput) error {
  71. joints, err := manager.GetJoinsByListInput(input)
  72. if err != nil {
  73. return errors.Wrapf(err, "SMonitorResourceAlertManager DetachJoint when GetJoinsByListInput err,input:%v", input)
  74. }
  75. errs := make([]error, 0)
  76. for _, joint := range joints {
  77. err := joint.Delete(ctx, nil)
  78. if err != nil {
  79. errs = append(errs, errors.Wrapf(err, "joint %s:%s ,%s:%s", manager.GetMasterFieldName(),
  80. joint.MonitorResourceId, manager.GetSlaveFieldName(), joint.AlertId))
  81. continue
  82. }
  83. resources, err := MonitorResourceManager.GetMonitorResources(monitor.MonitorResourceListInput{ResId: []string{joint.
  84. MonitorResourceId}})
  85. if err != nil {
  86. errs = append(errs, err)
  87. }
  88. for _, res := range resources {
  89. err := res.UpdateAlertState()
  90. if err != nil {
  91. errs = append(errs, err)
  92. }
  93. }
  94. }
  95. return errors.NewAggregate(errs)
  96. }
  97. func (obj *SMonitorResourceAlert) Detach(ctx context.Context, userCred mcclient.TokenCredential) error {
  98. return db.DetachJoint(ctx, userCred, obj)
  99. }
  100. func (obj *SMonitorResourceAlert) GetAlert() (*SCommonAlert, error) {
  101. sObj, err := CommonAlertManager.FetchById(obj.AlertId)
  102. if err != nil {
  103. return nil, err
  104. }
  105. return sObj.(*SCommonAlert), nil
  106. }
  107. func (manager *SMonitorResourceAlertManager) GetJoinsByListInput(input monitor.MonitorResourceJointListInput) ([]SMonitorResourceAlert, error) {
  108. joints := make([]SMonitorResourceAlert, 0)
  109. query := manager.Query()
  110. if len(input.MonitorResourceId) != 0 {
  111. query.Equals(manager.GetMasterFieldName(), input.MonitorResourceId)
  112. }
  113. if len(input.AlertId) != 0 {
  114. query.Equals(manager.GetSlaveFieldName(), input.AlertId)
  115. }
  116. if len(input.Metric) > 0 {
  117. query.Equals("metric", input.Metric)
  118. }
  119. if len(input.JointId) != 0 {
  120. query.In("row_id", input.JointId)
  121. }
  122. if len(input.AlertState) > 0 {
  123. query = query.Equals("alert_state", input.AlertState)
  124. }
  125. err := db.FetchModelObjects(manager, query, &joints)
  126. if err != nil {
  127. return nil, errors.Wrapf(err, "FetchModelObjects by GetJoinsByMasterId:%s err", input.MonitorResourceId)
  128. }
  129. return joints, nil
  130. }
  131. func (obj *SMonitorResourceAlert) UpdateAlertRecordData(ctx context.Context, userCred mcclient.TokenCredential, input *UpdateMonitorResourceAlertInput, match *monitor.EvalMatch) error {
  132. sendState := input.SendState
  133. if _, ok := match.Tags[monitor.ALERT_RESOURCE_RECORD_SHIELD_KEY]; ok {
  134. sendState = monitor.SEND_STATE_SHIELD
  135. }
  136. if _, err := db.Update(obj, func() error {
  137. if input.AlertRecordId != "" {
  138. obj.AlertRecordId = input.AlertRecordId
  139. }
  140. obj.ResType = input.ResType
  141. obj.AlertState = input.AlertState
  142. obj.SendState = sendState
  143. obj.TriggerTime = input.TriggerTime
  144. obj.Metric = match.Metric
  145. obj.Data = jsonutils.Marshal(match)
  146. return nil
  147. }); err != nil {
  148. return errors.Wrap(err, "db.Update")
  149. }
  150. if err := obj.GetModelManager().GetExtraHook().AfterPostUpdate(ctx, userCred, obj, jsonutils.NewDict(), jsonutils.NewDict()); err != nil {
  151. log.Warningf("UpdateAlertRecordData after post update hook error: %v", err)
  152. }
  153. return nil
  154. }
  155. func (obj *SMonitorResourceAlert) GetData() (*monitor.EvalMatch, error) {
  156. if obj.Data == nil {
  157. return nil, errors.Errorf("data is nil")
  158. }
  159. match := new(monitor.EvalMatch)
  160. if err := obj.Data.Unmarshal(match); err != nil {
  161. return nil, errors.Wrap(err, "unmarshal to monitor.EvalMatch")
  162. }
  163. return match, nil
  164. }
  165. func (m *SMonitorResourceAlertManager) GetNowAlertingAlerts(ctx context.Context, userCred mcclient.TokenCredential, input *monitor.AlertRecordListInput) ([]SMonitorResourceAlert, error) {
  166. ownerId, err := m.FetchOwnerId(ctx, jsonutils.Marshal(&input))
  167. if err != nil {
  168. return nil, errors.Wrapf(err, "FetchOwnerId by input: %s", jsonutils.Marshal(input))
  169. }
  170. if ownerId == nil {
  171. ownerId = userCred
  172. }
  173. alertsQuery := CommonAlertManager.Query("id").Equals("state", monitor.AlertStateAlerting).IsTrue("enabled").
  174. IsNull("used_by")
  175. alertsQuery = CommonAlertManager.FilterByOwner(ctx, alertsQuery, CommonAlertManager, userCred, ownerId, rbacscope.String2Scope(input.Scope))
  176. alertRess := make([]SMonitorResourceAlert, 0)
  177. q := m.Query()
  178. q = q.Equals("alert_state", monitor.AlertStateAlerting)
  179. q = q.In("alert_id", alertsQuery)
  180. if err := db.FetchModelObjects(m, q, &alertRess); err != nil {
  181. return nil, errors.Wrapf(err, "FetchModelObjects by GetNowAlertingAlerts err")
  182. }
  183. return alertRess, nil
  184. }
  185. func (m *SMonitorResourceAlertManager) FilterByParams(q *sqlchemy.SQuery, params jsonutils.JSONObject) *sqlchemy.SQuery {
  186. input := &monitor.MonitorResourceJointListInput{}
  187. if err := params.Unmarshal(input); err != nil {
  188. log.Errorf("FilterByParams unmarshal params error: %v", err)
  189. return q
  190. }
  191. if input.Metric != "" {
  192. q = q.Equals("metric", input.Metric)
  193. }
  194. return q
  195. }
  196. func (m *SMonitorResourceAlertManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input *monitor.MonitorResourceJointListInput) (*sqlchemy.SQuery, error) {
  197. // 如果指定了时间段、top 和 alert_id 参数,执行特殊的 top 查询
  198. if input.Top != nil {
  199. // 加上 top 的参数校验
  200. if input.Top == nil || *input.Top <= 0 {
  201. return nil, httperrors.NewInputParameterError("top must be specified and greater than 0")
  202. }
  203. if input.StartTime.IsZero() || input.EndTime.IsZero() {
  204. return nil, httperrors.NewInputParameterError("start_time and end_time must be specified")
  205. }
  206. if input.StartTime.After(input.EndTime) {
  207. return nil, httperrors.NewInputParameterError("start_time must be before end_time")
  208. }
  209. return m.getTopResourcesByMetricAndAlertCount(ctx, q, userCred, input)
  210. }
  211. var err error
  212. q, err = m.SJointResourceBaseManager.ListItemFilter(ctx, q, userCred, input.JointResourceBaseListInput)
  213. if err != nil {
  214. return q, errors.Wrap(err, "SJointResourceBaseManager ListItemFilter err")
  215. }
  216. alertSq := AlertManager.Query("id").SubQuery()
  217. q = q.In("alert_id", alertSq)
  218. if len(input.AlertState) > 0 {
  219. q = q.Equals("alert_state", input.AlertState)
  220. }
  221. if input.Alerting {
  222. q = q.Equals("alert_state", monitor.AlertStateAlerting)
  223. resQ := MonitorResourceManager.Query("res_id")
  224. resQ, err = MonitorResourceManager.ListItemFilter(ctx, resQ, userCred, monitor.MonitorResourceListInput{})
  225. if err != nil {
  226. return q, errors.Wrap(err, "Get monitor in Query err")
  227. }
  228. resQ = m.SMonitorScopedResourceManager.FilterByOwner(ctx, resQ, m, userCred, userCred, rbacscope.TRbacScope(input.Scope))
  229. q = q.Filter(sqlchemy.In(q.Field("monitor_resource_id"), resQ.SubQuery()))
  230. }
  231. if len(input.SendState) != 0 {
  232. q = q.Equals("send_state", input.SendState)
  233. }
  234. if len(input.ResType) != 0 {
  235. q = q.Equals("res_type", input.ResType)
  236. }
  237. if len(input.ResName) != 0 {
  238. resQ := MonitorResourceManager.Query("res_id")
  239. resQ, err = MonitorResourceManager.ListItemFilter(ctx, resQ, userCred,
  240. monitor.MonitorResourceListInput{ResName: input.ResName})
  241. if err != nil {
  242. return q, errors.Wrap(err, "Get monitor in Query err")
  243. }
  244. q = q.Filter(sqlchemy.In(q.Field("monitor_resource_id"), resQ.SubQuery()))
  245. }
  246. alertQuery := CommonAlertManager.Query("id")
  247. alertQuery = m.SMonitorScopedResourceManager.FilterByOwner(ctx, alertQuery, m, userCred, userCred, rbacscope.TRbacScope(input.Scope))
  248. if len(input.AlertName) != 0 {
  249. CommonAlertManager.FieldListFilter(alertQuery, monitor.CommonAlertListInput{Name: input.AlertName})
  250. q = q.Filter(sqlchemy.In(q.Field(m.GetSlaveFieldName()), alertQuery.SubQuery()))
  251. }
  252. if len(input.Level) != 0 {
  253. CommonAlertManager.FieldListFilter(alertQuery, monitor.CommonAlertListInput{Level: input.Level})
  254. q = q.Filter(sqlchemy.In(q.Field(m.GetSlaveFieldName()), alertQuery.SubQuery()))
  255. }
  256. if len(input.MonitorResourceId) != 0 {
  257. q = q.Filter(sqlchemy.Equals(q.Field("monitor_resource_id"), input.MonitorResourceId))
  258. }
  259. if !input.AllState {
  260. q = q.Filter(sqlchemy.In(q.Field(m.GetSlaveFieldName()), alertQuery.SubQuery()))
  261. q = q.Filter(sqlchemy.In(q.Field("alert_record_id"), AlertRecordManager.Query("id").SubQuery()))
  262. }
  263. return q, nil
  264. }
  265. func (m *SMonitorResourceAlertManager) CustomizeFilterList(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*db.CustomizeListFilters, error) {
  266. filters := db.NewCustomizeListFilters()
  267. if query.Contains("ip") {
  268. ip, _ := query.GetString("ip")
  269. ipF := func(obj jsonutils.JSONObject) (bool, error) {
  270. match := new(monitor.EvalMatch)
  271. if err := obj.Unmarshal(match, "data"); err != nil {
  272. log.Warningf("unmarshal data of object %s: %v", obj, err)
  273. return false, nil
  274. }
  275. if tagIp, ok := match.Tags["ip"]; ok {
  276. if strings.Contains(tagIp, ip) {
  277. return true, nil
  278. }
  279. }
  280. return false, nil
  281. }
  282. filters.Append(ipF)
  283. }
  284. return filters, nil
  285. }
  286. func (m *SMonitorResourceAlertManager) GetMonitorResourceAlert(resId, alertId, metric string) (*SMonitorResourceAlert, error) {
  287. joint, err := db.FetchJointByIds(m, resId, alertId, jsonutils.Marshal(&monitor.MonitorResourceJointListInput{
  288. Metric: metric,
  289. }))
  290. if err != nil {
  291. return nil, errors.Wrapf(err, "FetchJointByIds with master id %s and slave id %s and metric %s", resId, alertId, metric)
  292. }
  293. return joint.(*SMonitorResourceAlert), nil
  294. }
  295. // getTopResourcesByMetricAndAlertCount 查询指定时间段内,某个监控策略下各监控指标报警资源最多的 top N 资源
  296. func (m *SMonitorResourceAlertManager) getTopResourcesByMetricAndAlertCount(
  297. ctx context.Context,
  298. q *sqlchemy.SQuery,
  299. userCred mcclient.TokenCredential,
  300. input *monitor.MonitorResourceJointListInput,
  301. ) (*sqlchemy.SQuery, error) {
  302. // 验证时间段和 top 参数
  303. startTime, endTime, top, err := validateTopQueryInput(input.TopQueryInput)
  304. if err != nil {
  305. return nil, err
  306. }
  307. // 查询指定时间段内的 AlertRecord,获取 alert_id、res_ids 和 alert_rule(用于解析 metric)
  308. recordQuery := AlertRecordManager.Query("id", "alert_id", "res_ids", "res_type", "alert_rule")
  309. if len(input.AlertId) > 0 {
  310. recordQuery = recordQuery.Equals("alert_id", input.AlertId)
  311. }
  312. recordQuery = recordQuery.GE("created_at", startTime).LE("created_at", endTime)
  313. recordQuery = recordQuery.IsNotEmpty("res_ids")
  314. // 如果指定了 ResType,添加过滤条件
  315. if len(input.ResType) > 0 {
  316. recordQuery = recordQuery.Equals("res_type", input.ResType)
  317. }
  318. // 执行查询获取所有记录
  319. type RecordRow struct {
  320. Id string
  321. AlertId string
  322. ResIds string
  323. ResType string
  324. AlertRule jsonutils.JSONObject
  325. }
  326. rows := make([]RecordRow, 0)
  327. err = recordQuery.All(&rows)
  328. if err != nil {
  329. return nil, errors.Wrap(err, "query alert records")
  330. }
  331. // 按照 resId、alert_id 和 metric 三个维度统计告警数量
  332. // resourceAlertMetricCount[resId][alertId][metric] = count
  333. type ResourceAlertMetricKey struct {
  334. ResId string
  335. AlertId string
  336. Metric string
  337. }
  338. resourceAlertMetricCount := make(map[ResourceAlertMetricKey]int)
  339. for _, row := range rows {
  340. if len(row.ResIds) == 0 {
  341. continue
  342. }
  343. // 从 AlertRule 中解析 metric
  344. var alertRules []*monitor.AlertRecordRule
  345. if row.AlertRule != nil {
  346. if err := row.AlertRule.Unmarshal(&alertRules); err != nil {
  347. log.Warningf("unmarshal alert_rule error: %v", err)
  348. continue
  349. }
  350. }
  351. if len(alertRules) == 0 {
  352. continue
  353. }
  354. // 解析 res_ids(逗号分隔)
  355. resIds := strings.Split(row.ResIds, ",")
  356. for _, resId := range resIds {
  357. resId = strings.TrimSpace(resId)
  358. if len(resId) == 0 {
  359. continue
  360. }
  361. // 如果指定了 ResType,需要匹配 res_type
  362. if len(input.ResType) > 0 && row.ResType != input.ResType {
  363. continue
  364. }
  365. // 对于每个 metric,统计 (resId, alertId, metric) 组合的数量
  366. for _, rule := range alertRules {
  367. if len(rule.Metric) == 0 {
  368. continue
  369. }
  370. key := ResourceAlertMetricKey{
  371. ResId: resId,
  372. AlertId: row.AlertId,
  373. Metric: rule.Metric,
  374. }
  375. resourceAlertMetricCount[key]++
  376. }
  377. }
  378. }
  379. // 转换为切片并按报警数量排序
  380. type ResourceAlertMetricCount struct {
  381. ResId string
  382. AlertId string
  383. Metric string
  384. Count int
  385. }
  386. counts := make([]ResourceAlertMetricCount, 0, len(resourceAlertMetricCount))
  387. for key, count := range resourceAlertMetricCount {
  388. counts = append(counts, ResourceAlertMetricCount{
  389. ResId: key.ResId,
  390. AlertId: key.AlertId,
  391. Metric: key.Metric,
  392. Count: count,
  393. })
  394. }
  395. // 按报警数量降序排序
  396. for i := 0; i < len(counts)-1; i++ {
  397. for j := i + 1; j < len(counts); j++ {
  398. if counts[i].Count < counts[j].Count {
  399. counts[i], counts[j] = counts[j], counts[i]
  400. }
  401. }
  402. }
  403. // 获取全局 top N 的 (resId, alertId, metric) 组合
  404. topN := min(top, len(counts))
  405. topCombinations := make([]ResourceAlertMetricCount, 0, topN)
  406. for i := 0; i < topN; i++ {
  407. topCombinations = append(topCombinations, counts[i])
  408. }
  409. log.Infof("top %d combinations: %v", top, topCombinations)
  410. if len(topCombinations) == 0 {
  411. // 如果没有找到任何记录,返回空查询
  412. return q.FilterByFalse(), nil
  413. }
  414. // 构建查询条件:匹配 top N 的 (resId, alertId, metric) 组合
  415. q = m.Query()
  416. conditions := make([]sqlchemy.ICondition, 0, len(topCombinations))
  417. for _, combo := range topCombinations {
  418. cond := sqlchemy.AND(
  419. sqlchemy.Equals(q.Field("monitor_resource_id"), combo.ResId),
  420. sqlchemy.Equals(q.Field("alert_id"), combo.AlertId),
  421. sqlchemy.Equals(q.Field("metric"), combo.Metric),
  422. )
  423. conditions = append(conditions, cond)
  424. }
  425. q = q.Filter(sqlchemy.OR(conditions...))
  426. // 应用其他过滤条件
  427. if len(input.AlertState) > 0 {
  428. q = q.Equals("alert_state", input.AlertState)
  429. }
  430. if input.Alerting {
  431. q = q.Equals("alert_state", monitor.AlertStateAlerting)
  432. resQ := MonitorResourceManager.Query("res_id")
  433. resQ, err = MonitorResourceManager.ListItemFilter(ctx, resQ, userCred, monitor.MonitorResourceListInput{})
  434. if err != nil {
  435. return q, errors.Wrap(err, "Get monitor in Query err")
  436. }
  437. resQ = m.SMonitorScopedResourceManager.FilterByOwner(ctx, resQ, m, userCred, userCred, rbacscope.TRbacScope(input.Scope))
  438. q = q.Filter(sqlchemy.In(q.Field("monitor_resource_id"), resQ.SubQuery()))
  439. }
  440. if len(input.SendState) != 0 {
  441. q = q.Equals("send_state", input.SendState)
  442. }
  443. if len(input.ResType) != 0 {
  444. q = q.Equals("res_type", input.ResType)
  445. }
  446. if len(input.Metric) != 0 {
  447. q = q.Equals("metric", input.Metric)
  448. }
  449. return q, nil
  450. }
  451. func min(a, b int) int {
  452. if a < b {
  453. return a
  454. }
  455. return b
  456. }
  457. type SAlertRecordCount struct {
  458. Count int
  459. ResIds string
  460. AlertId string
  461. }
  462. func (man *SMonitorResourceAlertManager) FetchCustomizeColumns(
  463. ctx context.Context,
  464. userCred mcclient.TokenCredential,
  465. query jsonutils.JSONObject,
  466. objs []interface{},
  467. fields stringutils2.SSortedStrings,
  468. isList bool,
  469. ) []monitor.MonitorResourceJointDetails {
  470. input := &monitor.MonitorResourceJointListInput{}
  471. query.Unmarshal(input)
  472. rows := make([]monitor.MonitorResourceJointDetails, len(objs))
  473. alertRecordIds := make([]string, len(objs))
  474. alertIds := make([]string, len(objs))
  475. resourceIds := make([]string, len(objs))
  476. for i := range rows {
  477. obj := objs[i].(*SMonitorResourceAlert)
  478. alertRecordIds[i] = obj.AlertRecordId
  479. alertIds[i] = obj.AlertId
  480. resourceIds[i] = obj.MonitorResourceId
  481. }
  482. records := map[string]SAlertRecord{}
  483. err := db.FetchModelObjectsByIds(AlertRecordManager, "id", alertRecordIds, &records)
  484. if err != nil {
  485. log.Errorf("fetch alert records error: %v", err)
  486. return rows
  487. }
  488. alerts := map[string]SCommonAlert{}
  489. err = db.FetchModelObjectsByIds(CommonAlertManager, "id", alertIds, &alerts)
  490. if err != nil {
  491. log.Errorf("fetch alerts error: %v", err)
  492. return rows
  493. }
  494. resources := map[string]SMonitorResource{}
  495. err = db.FetchModelObjectsByIds(MonitorResourceManager, "res_id", resourceIds, &resources)
  496. if err != nil {
  497. log.Errorf("fetch monitor resources error: %v", err)
  498. return rows
  499. }
  500. shields := make([]SAlertRecordShield, 0)
  501. err = AlertRecordShieldManager.Query().GE("end_time", time.Now()).In("res_id", resourceIds).In("alert_id", alertIds).All(&shields)
  502. if err != nil {
  503. log.Errorf("fetch alert record shields error: %v", err)
  504. return rows
  505. }
  506. shieldsMap := map[string]bool{}
  507. for _, shield := range shields {
  508. shieldsMap[fmt.Sprintf("%s-%s", shield.ResId, shield.AlertId)] = true
  509. }
  510. recordCountMap := map[string][]SAlertRecordCount{}
  511. if !input.StartTime.IsZero() && !input.EndTime.IsZero() {
  512. sq := AlertRecordManager.Query().GE("created_at", input.StartTime).LE("created_at", input.EndTime).In("alert_id", alertIds).SubQuery()
  513. q := sq.Query(
  514. sqlchemy.COUNT("count", sq.Field("id")),
  515. sq.Field("alert_id"),
  516. sq.Field("res_ids"),
  517. ).GroupBy(sq.Field("alert_id"), sq.Field("res_ids"))
  518. recordCount := []SAlertRecordCount{}
  519. err = q.All(&recordCount)
  520. if err != nil {
  521. log.Errorf("fetch alert records error: %v", err)
  522. return rows
  523. }
  524. for i := range recordCount {
  525. _, ok := recordCountMap[recordCount[i].AlertId]
  526. if !ok {
  527. recordCountMap[recordCount[i].AlertId] = make([]SAlertRecordCount, 0)
  528. }
  529. recordCountMap[recordCount[i].AlertId] = append(recordCountMap[recordCount[i].AlertId], recordCount[i])
  530. }
  531. }
  532. for i := range rows {
  533. rows[i] = monitor.MonitorResourceJointDetails{}
  534. obj := objs[i].(*SMonitorResourceAlert)
  535. rows[i].ResId = obj.MonitorResourceId
  536. rows[i].ResType = obj.ResType
  537. if record, ok := records[obj.AlertRecordId]; ok {
  538. rows[i].SendState = record.SendState
  539. rows[i].State = record.State
  540. }
  541. if alert, ok := alerts[obj.AlertId]; ok {
  542. rows[i].AlertName = alert.Name
  543. rows[i].Level = alert.Level
  544. silentPeriod, _ := alert.GetSilentPeriod()
  545. rule, _ := alert.GetAlertRules(silentPeriod)
  546. rows[i].AlertRule = jsonutils.Marshal(rule)
  547. }
  548. if res, ok := resources[obj.MonitorResourceId]; ok {
  549. rows[i].ResName = res.Name
  550. rows[i].ResType = res.ResType
  551. rows[i].MonitorResourceObjectId = res.GetId()
  552. }
  553. if _, ok := shieldsMap[fmt.Sprintf("%s-%s", obj.MonitorResourceId, obj.AlertId)]; ok {
  554. rows[i].IsSetShield = true
  555. }
  556. if recordCount, ok := recordCountMap[obj.AlertId]; ok {
  557. rows[i].AlertCount = 0
  558. for _, record := range recordCount {
  559. if strings.Contains(record.ResIds, obj.MonitorResourceId) {
  560. rows[i].AlertCount += record.Count
  561. }
  562. }
  563. }
  564. }
  565. return rows
  566. }
  567. func (manager *SMonitorResourceAlertManager) ResourceScope() rbacscope.TRbacScope {
  568. return manager.SScopedResourceBaseManager.ResourceScope()
  569. }
  570. func (manager *SMonitorResourceAlertManager) ListItemExportKeys(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, keys stringutils2.SSortedStrings) (*sqlchemy.SQuery, error) {
  571. q, err := manager.SScopedResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
  572. if err != nil {
  573. return nil, errors.Wrap(err, "SScopedResourceBaseManager.ListItemExportKeys")
  574. }
  575. return q, nil
  576. }
  577. func (m *SMonitorResourceAlertManager) FilterByOwner(ctx context.Context, q *sqlchemy.SQuery, man db.FilterByOwnerProvider, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
  578. return q
  579. }
  580. func (m *SMonitorResourceAlertManager) GetResourceAlert(alertId string, resourceId string, metric string) (*SMonitorResourceAlert, error) {
  581. q := m.Query().Equals("alert_id", alertId).Equals("monitor_resource_id", resourceId).Equals("metric", metric)
  582. obj := new(SMonitorResourceAlert)
  583. if err := q.First(obj); err != nil {
  584. return nil, errors.Wrapf(err, "query resource alert by alert_id: %q, resource_id: %q, metric: %q", alertId, resourceId, metric)
  585. }
  586. return obj, nil
  587. }
  588. // GetAlertRecords 通过 MonitorResourceAlert 查询历史的 AlertRecord
  589. // 查询条件:相同的 AlertId 且 ResIds 包含该 MonitorResourceId 的所有记录
  590. func (obj *SMonitorResourceAlert) GetAlertRecords() ([]SAlertRecord, error) {
  591. records := make([]SAlertRecord, 0)
  592. q := AlertRecordManager.Query()
  593. q = q.Equals("alert_id", obj.AlertId)
  594. // ResIds 字段存储的是逗号分隔的资源ID列表,使用 ContainsAny 查询
  595. q = q.Filter(sqlchemy.ContainsAny(q.Field("res_ids"), []string{obj.MonitorResourceId}))
  596. q = q.Desc("created_at") // 按创建时间倒序,最新的在前
  597. err := db.FetchModelObjects(AlertRecordManager, q, &records)
  598. if err != nil {
  599. return nil, errors.Wrapf(err, "get historical alert records by alert_id: %q, monitor_resource_id: %q", obj.AlertId, obj.MonitorResourceId)
  600. }
  601. return records, nil
  602. }