alertrecord.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  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. "database/sql"
  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/pkg/util/sets"
  25. "yunion.io/x/pkg/util/timeutils"
  26. "yunion.io/x/sqlchemy"
  27. "yunion.io/x/onecloud/pkg/apis/monitor"
  28. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  29. "yunion.io/x/onecloud/pkg/httperrors"
  30. "yunion.io/x/onecloud/pkg/mcclient"
  31. "yunion.io/x/onecloud/pkg/util/filterclause"
  32. "yunion.io/x/onecloud/pkg/util/stringutils2"
  33. )
  34. var (
  35. AlertRecordManager *SAlertRecordManager
  36. )
  37. // +onecloud:swagger-gen-model-singular=alertrecord
  38. // +onecloud:swagger-gen-model-plural=alertrecords
  39. type SAlertRecordManager struct {
  40. db.SEnabledResourceBaseManager
  41. db.SStandaloneAnonResourceBaseManager
  42. SMonitorScopedResourceManager
  43. }
  44. type SAlertRecord struct {
  45. //db.SVirtualResourceBase
  46. db.SEnabledResourceBase
  47. db.SStandaloneAnonResourceBase
  48. SMonitorScopedResource
  49. AlertId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"`
  50. Level string `charset:"ascii" width:"36" nullable:"false" default:"normal" list:"user" update:"user"`
  51. State string `width:"36" charset:"ascii" nullable:"false" default:"unknown" list:"user" update:"user"`
  52. SendState string `width:"36" charset:"ascii" default:"ok" list:"user" update:"user"`
  53. EvalData jsonutils.JSONObject `list:"user" update:"user" length:"medium"`
  54. AlertRule jsonutils.JSONObject `list:"user" update:"user" length:"medium"`
  55. ResType string `width:"36" list:"user" update:"user"`
  56. ResIds string `length:"medium" list:"user"`
  57. }
  58. func init() {
  59. AlertRecordManager = &SAlertRecordManager{
  60. SStandaloneAnonResourceBaseManager: db.NewStandaloneAnonResourceBaseManager(
  61. SAlertRecord{},
  62. "alertrecord_tbl",
  63. "alertrecord",
  64. "alertrecords",
  65. ),
  66. }
  67. AlertRecordManager.SetVirtualObject(AlertRecordManager)
  68. }
  69. func (manager *SAlertRecordManager) NamespaceScope() rbacscope.TRbacScope {
  70. return rbacscope.ScopeSystem
  71. }
  72. func (manager *SAlertRecordManager) ListItemExportKeys(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, keys stringutils2.SSortedStrings) (*sqlchemy.SQuery, error) {
  73. q, err := manager.SStandaloneAnonResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
  74. if err != nil {
  75. return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.ListItemExportKeys")
  76. }
  77. q, err = manager.SScopedResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
  78. if err != nil {
  79. return nil, errors.Wrap(err, "SScopedResourceBaseManager.ListItemExportKeys")
  80. }
  81. return q, nil
  82. }
  83. func (m *SAlertRecordManager) FilterByOwner(ctx context.Context, q *sqlchemy.SQuery, man db.FilterByOwnerProvider, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
  84. return m.SMonitorScopedResourceManager.FilterByOwner(ctx, q, man, userCred, ownerId, scope)
  85. }
  86. func (manager *SAlertRecordManager) ListItemFilter(
  87. ctx context.Context, q *sqlchemy.SQuery,
  88. userCred mcclient.TokenCredential,
  89. query monitor.AlertRecordListInput,
  90. ) (*sqlchemy.SQuery, error) {
  91. var err error
  92. q, err = manager.SStandaloneAnonResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StandaloneAnonResourceListInput)
  93. if err != nil {
  94. return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.ListItemFilter")
  95. }
  96. q, err = manager.SEnabledResourceBaseManager.ListItemFilter(ctx, q, userCred,
  97. query.EnabledResourceBaseListInput)
  98. if err != nil {
  99. return nil, errors.Wrap(err, "SEnabledResourceBaseManager.ListItemFilter")
  100. }
  101. q, err = manager.SScopedResourceBaseManager.ListItemFilter(ctx, q, userCred,
  102. query.ScopedResourceBaseListInput)
  103. if err != nil {
  104. return nil, errors.Wrap(err, "SScopedResourceBaseManager.ListItemFilter")
  105. }
  106. if query.Alerting {
  107. alertingQuery := manager.getAlertingRecordQuery().SubQuery()
  108. q.Join(alertingQuery, sqlchemy.Equals(q.Field("alert_id"), alertingQuery.Field("alert_id"))).Filter(
  109. sqlchemy.Equals(q.Field("created_at"), alertingQuery.Field("max_created_at")))
  110. }
  111. if len(query.Level) != 0 {
  112. q.Filter(sqlchemy.Equals(q.Field("level"), query.Level))
  113. }
  114. if len(query.State) != 0 {
  115. q.Filter(sqlchemy.Equals(q.Field("state"), query.State))
  116. }
  117. if len(query.AlertId) != 0 {
  118. q.Filter(sqlchemy.Equals(q.Field("alert_id"), query.AlertId))
  119. } else {
  120. q = q.IsNotEmpty("res_type").IsNotNull("res_type")
  121. }
  122. if len(query.AlertName) != 0 {
  123. alertQ := GetCommonAlertManager().Query("id").Contains("name", query.AlertName).SubQuery()
  124. q = q.In("alert_id", alertQ)
  125. }
  126. if len(query.ResType) != 0 {
  127. q.Filter(sqlchemy.Equals(q.Field("res_type"), query.ResType))
  128. }
  129. if len(query.ResId) != 0 {
  130. q.Filter(sqlchemy.ContainsAny(q.Field("res_ids"), []string{query.ResId}))
  131. }
  132. if len(query.Filter) != 0 {
  133. for i, _ := range query.Filter {
  134. if strings.Contains(query.Filter[i], "trigger_time") {
  135. timeFilter := strings.ReplaceAll(query.Filter[i], "trigger_time", "created_at")
  136. filterClause := filterclause.ParseFilterClause(timeFilter)
  137. q.Filter(filterClause.QueryCondition(q))
  138. }
  139. }
  140. }
  141. return q, nil
  142. }
  143. func (man *SAlertRecordManager) getAlertingRecordQuery() *sqlchemy.SQuery {
  144. q := CommonAlertManager.Query("id").IsTrue("enabled").IsNull("used_by")
  145. q = q.Filter(sqlchemy.OR(sqlchemy.Equals(q.Field("state"), monitor.AlertStateAlerting),
  146. sqlchemy.Equals(q.Field("state"), monitor.AlertStatePending)))
  147. alertsQuery := q.SubQuery()
  148. recordSub := man.Query().SubQuery()
  149. recordQuery := recordSub.Query(recordSub.Field("alert_id"), sqlchemy.MAX("max_created_at", recordSub.Field("created_at")))
  150. recordQuery.Equals("state", monitor.AlertStateAlerting)
  151. recordQuery.In("alert_id", alertsQuery)
  152. recordQuery.IsNotNull("res_type").IsNotEmpty("res_type")
  153. recordQuery.GroupBy("alert_id")
  154. return recordQuery
  155. }
  156. func (man *SAlertRecordManager) CustomizeFilterList(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*db.CustomizeListFilters, error) {
  157. filters := db.NewCustomizeListFilters()
  158. input := new(monitor.AlertRecordListInput)
  159. if err := query.Unmarshal(input); err != nil {
  160. return nil, errors.Wrap(err, "AlertRecordManager.CustomizeFilterList.Unmarshal")
  161. }
  162. if input.ResName != "" {
  163. filters.Append(func(item jsonutils.JSONObject) (bool, error) {
  164. evalMatchs := make([]monitor.EvalMatch, 0)
  165. if item.Contains("eval_data") {
  166. err := item.Unmarshal(&evalMatchs, "eval_data")
  167. if err != nil {
  168. return false, errors.Wrap(err, "record Unmarshal evalMatchs error")
  169. }
  170. for _, match := range evalMatchs {
  171. for k, v := range match.Tags {
  172. if strings.Contains(k, "name") && strings.Contains(v, input.ResName) {
  173. return true, nil
  174. }
  175. }
  176. }
  177. }
  178. return false, nil
  179. })
  180. }
  181. return filters, nil
  182. }
  183. func (man *SAlertRecordManager) GetAlertRecord(id string) (*SAlertRecord, error) {
  184. obj, err := man.FetchById(id)
  185. if err != nil {
  186. return nil, err
  187. }
  188. return obj.(*SAlertRecord), nil
  189. }
  190. func (man *SAlertRecordManager) GetAlertRecordsByAlertId(id string) ([]SAlertRecord, error) {
  191. records := make([]SAlertRecord, 0)
  192. query := man.Query()
  193. query = query.Equals("alert_id", id)
  194. err := db.FetchModelObjects(man, query, &records)
  195. if err != nil {
  196. return nil, err
  197. }
  198. return records, nil
  199. }
  200. func (manager *SAlertRecordManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
  201. var err error
  202. if field == "res_type" {
  203. resTypeQuery := MetricMeasurementManager.Query("res_type").Distinct()
  204. return resTypeQuery, nil
  205. }
  206. q, err = manager.SStandaloneAnonResourceBaseManager.QueryDistinctExtraField(q, field)
  207. if err == nil {
  208. return q, nil
  209. }
  210. return q, httperrors.ErrNotFound
  211. }
  212. func (man *SAlertRecordManager) OrderByExtraFields(
  213. ctx context.Context,
  214. q *sqlchemy.SQuery,
  215. userCred mcclient.TokenCredential,
  216. input monitor.AlertRecordListInput,
  217. ) (*sqlchemy.SQuery, error) {
  218. /*var err error
  219. q, err = man.SStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, input.StatusStandaloneResourceListInput)
  220. if err != nil {
  221. return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.OrderByExtraFields")
  222. }
  223. /*q, err = man.SScopedResourceBaseManager.OrderByExtraFields(ctx, q, userCred, input.ScopedResourceBaseListInput)
  224. if err != nil {
  225. return nil, errors.Wrap(err, "SScopedResourceBaseManager.OrderByExtraFields")
  226. }*/
  227. return q, nil
  228. }
  229. func (man *SAlertRecordManager) FetchCustomizeColumns(
  230. ctx context.Context,
  231. userCred mcclient.TokenCredential,
  232. query jsonutils.JSONObject,
  233. objs []interface{},
  234. fields stringutils2.SSortedStrings,
  235. isList bool,
  236. ) []monitor.AlertRecordDetails {
  237. rows := make([]monitor.AlertRecordDetails, len(objs))
  238. stdRows := man.SStandaloneAnonResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  239. scopedRows := man.SScopedResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  240. alertIds := sets.NewString()
  241. records := make([]*SAlertRecord, len(objs))
  242. for i := range rows {
  243. rows[i] = monitor.AlertRecordDetails{
  244. StandaloneAnonResourceDetails: stdRows[i],
  245. ScopedResourceBaseInfo: scopedRows[i],
  246. }
  247. record := objs[i].(*SAlertRecord)
  248. alertIds.Insert(record.AlertId)
  249. records[i] = record
  250. }
  251. alerts := map[string]SCommonAlert{}
  252. err := db.FetchModelObjectsByIds(CommonAlertManager, "id", alertIds.List(), &alerts)
  253. if err != nil {
  254. log.Errorf("FetchModelObjectsByIds common alert error: %v", err)
  255. return rows
  256. }
  257. for i := range rows {
  258. if alert, ok := alerts[records[i].AlertId]; ok {
  259. rows[i].AlertName = alert.Name
  260. rows[i].TriggerTime = records[i].CreatedAt
  261. }
  262. evalMatches, err := records[i].GetEvalData()
  263. if err != nil {
  264. continue
  265. }
  266. for j := range evalMatches {
  267. evalMatches[j] = records[i].filterTags(evalMatches[j])
  268. }
  269. rows[i].ResNum = int64(len(evalMatches))
  270. }
  271. return rows
  272. }
  273. func (record *SAlertRecord) filterTags(match monitor.EvalMatch) monitor.EvalMatch {
  274. for key, _ := range match.Tags {
  275. if strings.HasSuffix(key, "_id") {
  276. delete(match.Tags, key)
  277. }
  278. }
  279. return match
  280. }
  281. func (man *SAlertRecordManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, _ jsonutils.JSONObject, data monitor.AlertRecordCreateInput) (monitor.AlertRecordCreateInput, error) {
  282. return data, nil
  283. }
  284. func (record *SAlertRecord) GetEvalData() ([]monitor.EvalMatch, error) {
  285. ret := make([]monitor.EvalMatch, 0)
  286. if record.EvalData == nil {
  287. return ret, nil
  288. }
  289. if err := record.EvalData.Unmarshal(&ret); err != nil {
  290. return nil, errors.Wrap(err, "unmarshal evalMatchs error")
  291. }
  292. return ret, nil
  293. }
  294. func (record *SAlertRecord) CustomizeCreate(
  295. ctx context.Context, userCred mcclient.TokenCredential,
  296. ownerId mcclient.IIdentityProvider,
  297. query jsonutils.JSONObject,
  298. data jsonutils.JSONObject,
  299. ) error {
  300. /*err := record.SMonitorScopedResource.CustomizeCreate(ctx, userCred, ownerId, query, data)
  301. if err != nil {
  302. return err
  303. }*/
  304. alert, err := AlertManager.GetAlert(record.AlertId)
  305. if err != nil {
  306. return errors.Wrapf(err, "GetAlert %s", record.AlertId)
  307. }
  308. record.DomainId = alert.GetDomainId()
  309. record.ProjectId = alert.GetProjectId()
  310. obj, err := db.NewModelObject(AlertRecordManager)
  311. if err != nil {
  312. return errors.Wrapf(err, "NewModelObject %s", AlertRecordManager.Keyword())
  313. }
  314. q := AlertRecordManager.Query().Equals("alert_id", record.AlertId).Desc("created_at")
  315. if err := q.First(obj); err != nil {
  316. if errors.Cause(err) == sql.ErrNoRows {
  317. return nil
  318. } else {
  319. return errors.Wrapf(err, "Get latest alertrecord error by alertId:%s", record.AlertId)
  320. }
  321. }
  322. latestRecord := obj.(*SAlertRecord)
  323. if latestRecord.GetState() == monitor.AlertStateAlerting && record.GetState() == monitor.AlertStateOK {
  324. err := record.unionEvalMatch(latestRecord)
  325. if err != nil {
  326. return errors.Wrap(err, "unionEvalMatch error")
  327. }
  328. }
  329. // fill res_ids
  330. matches, err := record.GetEvalData()
  331. if err != nil {
  332. return errors.Wrap(err, "GetEvalData error")
  333. }
  334. resIds := sets.NewString()
  335. for _, match := range matches {
  336. for k, v := range match.Tags {
  337. switch k {
  338. case "vm_id":
  339. if record.ResType == monitor.METRIC_RES_TYPE_AGENT || record.ResType == monitor.METRIC_RES_TYPE_GUEST {
  340. resIds.Insert(v)
  341. }
  342. case "host_id":
  343. if record.ResType == monitor.METRIC_RES_TYPE_HOST {
  344. resIds.Insert(v)
  345. }
  346. case "cloudaccount_id":
  347. if record.ResType == monitor.METRIC_RES_TYPE_CLOUDACCOUNT {
  348. resIds.Insert(v)
  349. }
  350. case "id":
  351. resIds.Insert(v)
  352. }
  353. }
  354. }
  355. record.ResIds = strings.Join(resIds.List(), ",")
  356. return nil
  357. }
  358. func (record *SAlertRecord) unionEvalMatch(alertingRecord *SAlertRecord) error {
  359. matches, err := record.GetEvalData()
  360. if err != nil {
  361. return err
  362. }
  363. alertingMatches, err := alertingRecord.GetEvalData()
  364. if err != nil {
  365. return err
  366. }
  367. newEvalMatchs := make([]monitor.EvalMatch, 0)
  368. getNewMatchTag:
  369. for i, _ := range matches {
  370. for j, _ := range alertingMatches {
  371. if matches[i].Tags["name"] == alertingMatches[j].Tags["name"] {
  372. newEvalMatchs = append(newEvalMatchs, matches[i])
  373. continue getNewMatchTag
  374. }
  375. }
  376. }
  377. record.EvalData = jsonutils.Marshal(&newEvalMatchs)
  378. return nil
  379. }
  380. func (record *SAlertRecord) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
  381. record.SStandaloneAnonResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
  382. err := MonitorResourceManager.UpdateMonitorResourceAttachJointByRecord(ctx, userCred, record)
  383. if err != nil {
  384. log.Errorf("UpdateMonitorResourceAttachJointByRecord error: %v", err)
  385. }
  386. if err := GetAlertResourceManager().ReconcileFromRecord(ctx, userCred, ownerId, record); err != nil {
  387. log.Errorf("Reconcile from alert record error: %v", err)
  388. return
  389. }
  390. // err = GetAlertResourceManager().NotifyAlertResourceCount(ctx)
  391. // if err != nil {
  392. // log.Errorf("NotifyAlertResourceCount error: %v", err)
  393. // return
  394. // }
  395. }
  396. func (record *SAlertRecord) GetState() monitor.AlertStateType {
  397. return monitor.AlertStateType(record.State)
  398. }
  399. func (manager *SAlertRecordManager) DeleteRecordsOfThirtyDaysAgo(ctx context.Context, userCred mcclient.TokenCredential,
  400. isStart bool) {
  401. records := make([]SAlertRecord, 0)
  402. query := manager.Query()
  403. query = query.LE("created_at", timeutils.MysqlTime(time.Now().Add(-time.Hour*24*30)))
  404. err := db.FetchModelObjects(manager, query, &records)
  405. if err != nil {
  406. log.Errorf("fetch records ofthirty days ago err:%v", err)
  407. return
  408. }
  409. for i, _ := range records {
  410. err := db.DeleteModel(ctx, userCred, &records[i])
  411. if err != nil {
  412. log.Errorf("delete expire record: %s err: %v", records[i].GetId(), err)
  413. }
  414. }
  415. }
  416. func (manager *SAlertRecordManager) GetPropertyTotalAlert(ctx context.Context, userCred mcclient.TokenCredential,
  417. query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  418. input := new(monitor.AlertRecordListInput)
  419. err := query.Unmarshal(input)
  420. if err != nil {
  421. return nil, errors.Wrap(err, "Unmarshal AlertRecordListInput error")
  422. }
  423. alertRess, err := MonitorResourceAlertManager.GetNowAlertingAlerts(ctx, userCred, input)
  424. if err != nil {
  425. return nil, errors.Wrap(err, "getNowAlertingRecord error")
  426. }
  427. alertCountMap := jsonutils.NewDict()
  428. for _, res := range alertRess {
  429. var count int64 = 1
  430. if alertCountMap.Contains(res.ResType) {
  431. resTypeCount, _ := alertCountMap.Int(res.ResType)
  432. count = count + resTypeCount
  433. }
  434. alertCountMap.Set(res.ResType, jsonutils.NewInt(count))
  435. }
  436. return alertCountMap, nil
  437. }
  438. // 获取过去一天的报警历史分布
  439. func (manager *SAlertRecordManager) GetPropertyHistoryAlert(
  440. ctx context.Context,
  441. userCred mcclient.TokenCredential,
  442. query jsonutils.JSONObject,
  443. ) (*monitor.AlertRecordHistoryAlert, error) {
  444. q := manager.Query().GE("created_at", time.Now().Add(-time.Hour*24)).IsNotEmpty("eval_data").NotEquals("state", monitor.AlertStateOK)
  445. alerts := []SAlertRecord{}
  446. err := q.All(&alerts)
  447. if err != nil {
  448. return nil, errors.Wrap(err, "q.All")
  449. }
  450. ret := map[string]map[string]map[string]int64{}
  451. domainIds, projectIds := []string{}, []string{}
  452. domainMap := map[string]string{}
  453. projectMap := map[string]string{}
  454. for _, alert := range alerts {
  455. if _, ok := ret[alert.DomainId]; !ok {
  456. ret[alert.DomainId] = map[string]map[string]int64{}
  457. domainIds = append(domainIds, alert.DomainId)
  458. }
  459. if _, ok := ret[alert.DomainId][alert.ProjectId]; !ok {
  460. ret[alert.DomainId][alert.ProjectId] = map[string]int64{}
  461. projectIds = append(projectIds, alert.ProjectId)
  462. }
  463. if _, ok := ret[alert.DomainId][alert.ProjectId][alert.ResType]; !ok {
  464. ret[alert.DomainId][alert.ProjectId][alert.ResType] = 0
  465. }
  466. eval := make([]monitor.EvalMatch, 0)
  467. err := alert.EvalData.Unmarshal(&eval)
  468. if err != nil {
  469. continue
  470. }
  471. ret[alert.DomainId][alert.ProjectId][alert.ResType] += int64(len(eval))
  472. }
  473. domains := []db.STenant{}
  474. err = db.TenantCacheManager.GetDomainQuery().In("id", domainIds).All(&domains)
  475. if err != nil {
  476. return nil, errors.Wrap(err, "GetDomainQuery.In.All")
  477. }
  478. for _, domain := range domains {
  479. domainMap[domain.Id] = domain.Name
  480. }
  481. projects := []db.STenant{}
  482. err = db.TenantCacheManager.GetTenantQuery().In("id", projectIds).All(&projects)
  483. if err != nil {
  484. return nil, errors.Wrap(err, "GetTenantQuery.In.All")
  485. }
  486. for _, project := range projects {
  487. projectMap[project.Id] = project.Name
  488. }
  489. result := &monitor.AlertRecordHistoryAlert{}
  490. for domainId, projects := range ret {
  491. for projectId, resTypes := range projects {
  492. for resType, alert := range resTypes {
  493. result.Data = append(result.Data, monitor.AlertRecordHistoryAlertData{
  494. ProjectId: projectId,
  495. Project: projectMap[projectId],
  496. DomainId: domainId,
  497. Domain: domainMap[domainId],
  498. ResType: resType,
  499. ResNum: alert,
  500. })
  501. }
  502. }
  503. }
  504. return result, nil
  505. }
  506. // GetPropertyProjectAlertResourceCount 获取指定时间段内各项目下的报警资源数量
  507. func (manager *SAlertRecordManager) GetPropertyProjectAlertResourceCount(
  508. ctx context.Context,
  509. userCred mcclient.TokenCredential,
  510. input monitor.ProjectAlertResourceCountInput,
  511. ) (*monitor.ProjectAlertResourceCount, error) {
  512. // 验证时间段参数
  513. if input.StartTime.IsZero() || input.EndTime.IsZero() {
  514. return nil, httperrors.NewInputParameterError("start_time and end_time must be specified")
  515. }
  516. if input.StartTime.After(input.EndTime) {
  517. return nil, httperrors.NewInputParameterError("start_time must be before end_time")
  518. }
  519. // 构建查询
  520. q := manager.Query()
  521. q = q.GE("created_at", input.StartTime).LE("created_at", input.EndTime)
  522. q = q.IsNotEmpty("res_ids")
  523. // 应用权限过滤
  524. scope := rbacscope.ScopeSystem
  525. if input.Scope != "" {
  526. scope = rbacscope.TRbacScope(input.Scope)
  527. }
  528. q = manager.SMonitorScopedResourceManager.FilterByOwner(ctx, q, manager, userCred, userCred, scope)
  529. // 如果指定了 ResType,添加过滤条件
  530. if input.ResType != "" {
  531. q = q.Equals("res_type", input.ResType)
  532. }
  533. // 如果指定了 AlertId,添加过滤条件
  534. if input.AlertId != "" {
  535. q = q.Equals("alert_id", input.AlertId)
  536. }
  537. // 执行查询获取所有记录
  538. alerts := make([]SAlertRecord, 0)
  539. err := q.All(&alerts)
  540. if err != nil {
  541. return nil, errors.Wrap(err, "query alert records")
  542. }
  543. // 按 scope 分组统计唯一资源数量
  544. // systemResourceSet = set of resource IDs (system scope)
  545. // domainResourceSet[domainId] = set of resource IDs (domain scope)
  546. // projectResourceSet[domainId][projectId] = set of resource IDs (project scope)
  547. systemResourceSet := sets.NewString()
  548. domainResourceSet := make(map[string]sets.String)
  549. projectResourceSet := make(map[string]map[string]sets.String)
  550. domainIds := sets.NewString()
  551. projectIds := sets.NewString()
  552. for _, alert := range alerts {
  553. if len(alert.ResIds) == 0 {
  554. continue
  555. }
  556. domainId := alert.DomainId
  557. projectId := alert.ProjectId
  558. // 解析 res_ids(逗号分隔)
  559. resIds := strings.Split(alert.ResIds, ",")
  560. for _, resId := range resIds {
  561. resId = strings.TrimSpace(resId)
  562. if len(resId) == 0 {
  563. continue
  564. }
  565. // 根据 domainId 和 projectId 判断 scope
  566. if domainId == "" && projectId == "" {
  567. // system scope
  568. systemResourceSet.Insert(resId)
  569. } else if domainId != "" && projectId == "" {
  570. // domain scope
  571. domainIds.Insert(domainId)
  572. if domainResourceSet[domainId] == nil {
  573. domainResourceSet[domainId] = sets.NewString()
  574. }
  575. domainResourceSet[domainId].Insert(resId)
  576. } else if domainId != "" && projectId != "" {
  577. // project scope
  578. domainIds.Insert(domainId)
  579. projectIds.Insert(projectId)
  580. if projectResourceSet[domainId] == nil {
  581. projectResourceSet[domainId] = make(map[string]sets.String)
  582. }
  583. if projectResourceSet[domainId][projectId] == nil {
  584. projectResourceSet[domainId][projectId] = sets.NewString()
  585. }
  586. projectResourceSet[domainId][projectId].Insert(resId)
  587. }
  588. }
  589. }
  590. // 获取项目和域的名称
  591. domainMap := make(map[string]string)
  592. if domainIds.Len() > 0 {
  593. domains := []db.STenant{}
  594. err = db.TenantCacheManager.GetDomainQuery().In("id", domainIds.List()).All(&domains)
  595. if err != nil {
  596. return nil, errors.Wrap(err, "GetDomainQuery.In.All")
  597. }
  598. for _, domain := range domains {
  599. domainMap[domain.Id] = domain.Name
  600. }
  601. }
  602. projectMap := make(map[string]string)
  603. if projectIds.Len() > 0 {
  604. projects := []db.STenant{}
  605. err = db.TenantCacheManager.GetTenantQuery().In("id", projectIds.List()).All(&projects)
  606. if err != nil {
  607. return nil, errors.Wrap(err, "GetTenantQuery.In.All")
  608. }
  609. for _, project := range projects {
  610. projectMap[project.Id] = project.Name
  611. }
  612. }
  613. // 构建返回结果
  614. result := &monitor.ProjectAlertResourceCount{
  615. Data: make([]monitor.ProjectAlertResourceCountData, 0),
  616. }
  617. // system scope
  618. if systemResourceSet.Len() > 0 {
  619. result.Data = append(result.Data, monitor.ProjectAlertResourceCountData{
  620. Scope: string(rbacscope.ScopeSystem),
  621. ResCount: int64(systemResourceSet.Len()),
  622. })
  623. }
  624. // domain scope
  625. for domainId, resourceSet := range domainResourceSet {
  626. if resourceSet.Len() > 0 {
  627. result.Data = append(result.Data, monitor.ProjectAlertResourceCountData{
  628. Scope: string(rbacscope.ScopeDomain),
  629. DomainId: domainId,
  630. Domain: domainMap[domainId],
  631. ResCount: int64(resourceSet.Len()),
  632. })
  633. }
  634. }
  635. // project scope
  636. for domainId, projects := range projectResourceSet {
  637. for projectId, resourceSet := range projects {
  638. if resourceSet.Len() > 0 {
  639. result.Data = append(result.Data, monitor.ProjectAlertResourceCountData{
  640. Scope: string(rbacscope.ScopeProject),
  641. DomainId: domainId,
  642. Domain: domainMap[domainId],
  643. ProjectId: projectId,
  644. Project: projectMap[projectId],
  645. ResCount: int64(resourceSet.Len()),
  646. })
  647. }
  648. }
  649. }
  650. return result, nil
  651. }