alertresource.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  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. "sync"
  19. "yunion.io/x/jsonutils"
  20. "yunion.io/x/log"
  21. "yunion.io/x/pkg/errors"
  22. "yunion.io/x/sqlchemy"
  23. "yunion.io/x/onecloud/pkg/apis"
  24. "yunion.io/x/onecloud/pkg/apis/monitor"
  25. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  26. "yunion.io/x/onecloud/pkg/mcclient"
  27. "yunion.io/x/onecloud/pkg/mcclient/auth"
  28. "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
  29. "yunion.io/x/onecloud/pkg/util/stringutils2"
  30. )
  31. var (
  32. alertResourceManager *SAlertResourceManager
  33. adminUsers *sync.Map
  34. )
  35. func init() {
  36. // init singleton alertResourceManager
  37. GetAlertResourceManager()
  38. }
  39. func GetAlertResourceManager() *SAlertResourceManager {
  40. if alertResourceManager == nil {
  41. alertResourceManager = &SAlertResourceManager{
  42. SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
  43. SAlertResource{},
  44. "alertresources_tbl",
  45. "alertresource",
  46. "alertresources",
  47. ),
  48. }
  49. alertResourceManager.SetVirtualObject(alertResourceManager)
  50. }
  51. return alertResourceManager
  52. }
  53. // +onecloud:swagger-gen-model-singular=alertresource
  54. // +onecloud:swagger-gen-model-plural=alertresources
  55. type SAlertResourceManager struct {
  56. db.SStandaloneResourceBaseManager
  57. }
  58. // SAlertResource records alerting single resource, one resource has multi alerts
  59. type SAlertResource struct {
  60. db.SStandaloneResourceBase
  61. Type string `charset:"ascii" width:"36" nullable:"false" create:"required" list:"user"`
  62. }
  63. func (m *SAlertResourceManager) ListItemFilter(
  64. ctx context.Context,
  65. q *sqlchemy.SQuery,
  66. userCred mcclient.TokenCredential,
  67. input monitor.AlertResourceListInput,
  68. ) (*sqlchemy.SQuery, error) {
  69. q, err := m.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StandaloneResourceListInput)
  70. if err != nil {
  71. return nil, err
  72. }
  73. if input.Type != "" {
  74. q = q.Equals("type", input.Type)
  75. }
  76. return q, nil
  77. }
  78. func (man *SAlertResourceManager) FetchCustomizeColumns(
  79. ctx context.Context,
  80. userCred mcclient.TokenCredential,
  81. query jsonutils.JSONObject,
  82. objs []interface{},
  83. fields stringutils2.SSortedStrings,
  84. isList bool,
  85. ) []monitor.AlertResourceDetails {
  86. rows := make([]monitor.AlertResourceDetails, len(objs))
  87. stdRows := man.SStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  88. for i := range rows {
  89. rows[i] = monitor.AlertResourceDetails{
  90. StandaloneResourceDetails: stdRows[i],
  91. }
  92. rows[i] = objs[i].(*SAlertResource).getDetails(rows[i])
  93. }
  94. return rows
  95. }
  96. func (m *SAlertResourceManager) ReconcileFromRecord(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, record *SAlertRecord) error {
  97. matches, err := record.GetEvalData()
  98. if err != nil {
  99. return errors.Wrapf(err, "Get record %s eval data", record.GetId())
  100. }
  101. oldResources, err := m.getResourceFromAlertId(record.AlertId)
  102. if err != nil {
  103. return errors.Wrap(err, "ReconcileFromRecord getResourceFromAlertId error")
  104. }
  105. errs := make([]error, 0)
  106. for _, match := range matches {
  107. if err := m.reconcileFromRecordMatch(ctx, userCred, ownerId, record, match); err != nil {
  108. errs = append(errs, err)
  109. }
  110. }
  111. if len(errs) == 0 {
  112. delErrs := m.deleteOldResource(ctx, userCred, record, oldResources)
  113. if len(delErrs) != 0 {
  114. errs = append(errs, delErrs...)
  115. }
  116. }
  117. return errors.NewAggregate(errs)
  118. }
  119. func (m *SAlertResourceManager) deleteOldResource(ctx context.Context, userCred mcclient.TokenCredential,
  120. record *SAlertRecord, oldResources []SAlertResource) (errs []error) {
  121. matches, _ := record.GetEvalData()
  122. needDelResources := make([]SAlertResource, 0)
  123. LoopRes:
  124. for _, oldResource := range oldResources {
  125. for _, match := range matches {
  126. resourceD, _ := GetAlertResourceDriver(match)
  127. if oldResource.Name == resourceD.GetUniqCond().Name {
  128. continue LoopRes
  129. }
  130. }
  131. needDelResources = append(needDelResources, oldResource)
  132. }
  133. for i, _ := range needDelResources {
  134. if err := needDelResources[i].DetachAlert(ctx, userCred, record.AlertId); err != nil {
  135. errs = append(errs, errors.Wrapf(err, "deleteOldResource remove resource %s alert %s",
  136. needDelResources[i].GetName(),
  137. record.AlertId))
  138. }
  139. }
  140. return
  141. }
  142. type AlertResourceUniqCond struct {
  143. Type monitor.AlertResourceType
  144. Name string
  145. }
  146. func (m *SAlertResourceManager) getResourceFromMatch(ctx context.Context, userCred mcclient.TokenCredential, drv IAlertResourceDriver, match monitor.EvalMatch) (*SAlertResource, error) {
  147. uniqCond := drv.GetUniqCond()
  148. if uniqCond == nil {
  149. return nil, errors.Errorf("alert resource driver %s get %#v uniq match condition is nil", drv.GetType(), match)
  150. }
  151. q := m.Query().Equals("type", uniqCond.Type).Equals("name", uniqCond.Name)
  152. objs := make([]SAlertResource, 0)
  153. if err := db.FetchModelObjects(m, q, &objs); err != nil {
  154. return nil, errors.Wrapf(err, "fetch alert resource by condition: %v", uniqCond)
  155. }
  156. if len(objs) == 0 {
  157. return nil, nil
  158. }
  159. if len(objs) > 2 {
  160. return nil, errors.Wrapf(sqlchemy.ErrDuplicateEntry, "duplicate resource match by %#v", uniqCond)
  161. }
  162. return &objs[0], nil
  163. }
  164. func (m *SAlertResourceManager) getResourceFromAlertId(alertId string) ([]SAlertResource, error) {
  165. searchResourceIdQuery := GetAlertResourceAlertManager().Query(GetAlertResourceAlertManager().GetMasterFieldName())
  166. searchResourceIdQuery = searchResourceIdQuery.Equals(GetAlertResourceAlertManager().GetSlaveFieldName(), alertId)
  167. query := m.Query().In("id", searchResourceIdQuery.SubQuery())
  168. objs := make([]SAlertResource, 0)
  169. if err := db.FetchModelObjects(m, query, &objs); err != nil {
  170. return nil, errors.Wrapf(err, "getResourceFromAlertId:%s error", alertId)
  171. }
  172. return objs, nil
  173. }
  174. func (m *SAlertResourceManager) reconcileFromRecordMatch(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, record *SAlertRecord, match monitor.EvalMatch) error {
  175. drv, err := GetAlertResourceDriver(match)
  176. if err != nil {
  177. return errors.Wrap(err, "get resource driver by match")
  178. }
  179. res, err := m.getResourceFromMatch(ctx, userCred, drv, match)
  180. if err != nil {
  181. return errors.Wrap(err, "getResourceFromMatch")
  182. }
  183. recordState := record.GetState()
  184. if recordState == monitor.AlertStateOK {
  185. // remove matched resource's related alert record
  186. if res == nil {
  187. return nil
  188. }
  189. if err := res.DetachAlert(ctx, userCred, record.AlertId); err != nil {
  190. return errors.Wrapf(err, "remove resource %s alert %s", res.GetName(), record.AlertId)
  191. }
  192. } else if recordState == monitor.AlertStateAlerting {
  193. // create or update resource's related alert record
  194. if err := m.createOrUpdateFromRecord(ctx, userCred, ownerId, drv, record, match, res); err != nil {
  195. return errors.Wrap(err, "create or update record resource")
  196. }
  197. }
  198. return nil
  199. }
  200. func (m *SAlertResourceManager) createOrUpdateFromRecord(
  201. ctx context.Context,
  202. userCred mcclient.TokenCredential,
  203. ownerId mcclient.IIdentityProvider,
  204. drv IAlertResourceDriver,
  205. record *SAlertRecord,
  206. match monitor.EvalMatch,
  207. res *SAlertResource,
  208. ) error {
  209. if res == nil {
  210. return m.createFromRecord(ctx, userCred, ownerId, drv, record, match)
  211. } else {
  212. return res.updateFromRecord(ctx, userCred, drv, record, match)
  213. }
  214. }
  215. func (m *SAlertResourceManager) createFromRecord(
  216. ctx context.Context,
  217. userCred mcclient.TokenCredential,
  218. ownerId mcclient.IIdentityProvider,
  219. drv IAlertResourceDriver,
  220. record *SAlertRecord,
  221. match monitor.EvalMatch,
  222. ) error {
  223. uniqCond := drv.GetUniqCond()
  224. input := &monitor.AlertResourceCreateInput{
  225. StandaloneResourceCreateInput: apis.StandaloneResourceCreateInput{
  226. Name: uniqCond.Name,
  227. },
  228. Type: uniqCond.Type,
  229. }
  230. obj, err := db.DoCreate(m, ctx, userCred, nil, input.JSON(input), ownerId)
  231. if err != nil {
  232. return errors.Wrapf(err, "create alert resource by data %s", input.JSON(input))
  233. }
  234. if err := obj.(*SAlertResource).attachAlert(ctx, record, match); err != nil {
  235. return errors.Wrapf(err, "resource %s attch alert by matches %v", obj.GetName(), match)
  236. }
  237. return nil
  238. }
  239. func (res *SAlertResource) GetJointAlert(alertId string) (*SAlertResourceAlert, error) {
  240. jm := GetAlertResourceAlertManager()
  241. jObj, err := jm.GetJointAlert(res, alertId)
  242. if err != nil {
  243. return nil, errors.Wrapf(err, "get joint alert by alertId %s", alertId)
  244. }
  245. return jObj, nil
  246. }
  247. func (res *SAlertResource) isAttach2Alert(alertId string) (*SAlertResourceAlert, bool, error) {
  248. jobj, err := res.GetJointAlert(alertId)
  249. if err != nil {
  250. return nil, false, err
  251. }
  252. if jobj == nil {
  253. return nil, false, nil
  254. }
  255. return jobj, true, nil
  256. }
  257. func (res *SAlertResource) getJointAlerts() ([]SAlertResourceAlert, error) {
  258. jm := GetAlertResourceAlertManager()
  259. jObjs := make([]SAlertResourceAlert, 0)
  260. q := jm.Query().Equals(jm.GetMasterFieldName(), res.GetId())
  261. if err := db.FetchModelObjects(jm, q, &jObjs); err != nil {
  262. return nil, errors.Wrapf(err, "get resource %s joint alerts", res.GetName())
  263. }
  264. return jObjs, nil
  265. }
  266. func (res *SAlertResource) getAttachedAlerts() ([]SCommonAlert, error) {
  267. jm := GetAlertResourceAlertManager()
  268. alerts := make([]SCommonAlert, 0)
  269. q := CommonAlertManager.Query()
  270. sq := jm.Query("alert_id").Equals(jm.GetMasterFieldName(), res.GetId()).SubQuery()
  271. q = q.In("id", sq)
  272. if err := db.FetchModelObjects(CommonAlertManager, q, &alerts); err != nil {
  273. return nil, errors.Wrapf(err, "get resource %s attached alerts", res.GetName())
  274. }
  275. return alerts, nil
  276. }
  277. func (res *SAlertResource) updateFromRecord(ctx context.Context, userCred mcclient.TokenCredential,
  278. drv IAlertResourceDriver, record *SAlertRecord, match monitor.EvalMatch) error {
  279. jObj, err := res.GetJointAlert(record.AlertId)
  280. if err != nil {
  281. return errors.Wrapf(err, "get joint alert by id %s", record.AlertId)
  282. }
  283. if jObj == nil {
  284. if err := res.attachAlert(ctx, record, match); err != nil {
  285. return errors.Wrapf(err, "resource %s update from record %s attch alert by matches %v", res.GetName(), record.AlertId, match)
  286. }
  287. } else {
  288. if err := jObj.UpdateData(record, &match); err != nil {
  289. return errors.Wrapf(err, "update joint object by matches %v", match)
  290. }
  291. if _, err := db.Update(res, func() error {
  292. res.Type = string(drv.GetType())
  293. return nil
  294. }); err != nil {
  295. return err
  296. }
  297. }
  298. return nil
  299. }
  300. func (res *SAlertResource) attachAlert(ctx context.Context, record *SAlertRecord, match monitor.EvalMatch) error {
  301. _, attached, err := res.isAttach2Alert(record.AlertId)
  302. if err != nil {
  303. return errors.Wrap(err, "check isAttach2Alert")
  304. }
  305. if attached {
  306. return errors.Errorf("%s resource has attached to alert %s", res.GetName(), record.AlertId)
  307. }
  308. // create resource joint alert record into db
  309. jm := GetAlertResourceAlertManager()
  310. jObj, err := db.NewModelObject(jm)
  311. input := &monitor.AlertResourceAttachInput{
  312. AlertResourceId: res.GetId(),
  313. AlertId: record.AlertId,
  314. AlertRecordId: record.GetId(),
  315. Data: match,
  316. }
  317. data := input.JSON(input)
  318. if err := data.Unmarshal(jObj); err != nil {
  319. return errors.Wrap(err, "unmarshal to resource joint alert")
  320. }
  321. jObj.(*SAlertResourceAlert).TriggerTime = record.CreatedAt
  322. if err := jm.TableSpec().Insert(ctx, jObj); err != nil {
  323. return errors.Wrap(err, "insert joint model")
  324. }
  325. return nil
  326. }
  327. func (res *SAlertResource) LogPrefix() string {
  328. return fmt.Sprintf("%s/%s/%s", res.GetId(), res.GetName(), res.Type)
  329. }
  330. func (res *SAlertResource) DetachAlert(ctx context.Context, userCred mcclient.TokenCredential, alertId string) error {
  331. log.Errorf("=====resource %s detach alert %s", res.GetName(), alertId)
  332. jObj, attached, err := res.isAttach2Alert(alertId)
  333. if err != nil {
  334. return errors.Wrap(err, "check isAttach2Alert")
  335. }
  336. if !attached {
  337. return nil
  338. }
  339. if err := jObj.Detach(ctx, userCred); err != nil {
  340. return errors.Wrap(err, "detach joint alert")
  341. }
  342. count, err := res.getAttachedAlertCount()
  343. if err != nil {
  344. return errors.Wrap(err, "getAttachedAlertCount when detachAlert")
  345. }
  346. if count == 0 {
  347. if err := res.SStandaloneResourceBase.Delete(ctx, userCred); err != nil {
  348. return errors.Wrapf(err, "delete alert resource %s when no alert attached", res.LogPrefix())
  349. }
  350. log.Infof("alert resource %s deleted when no alert attached", res.LogPrefix())
  351. }
  352. return nil
  353. }
  354. func (res *SAlertResource) GetType() monitor.AlertResourceType {
  355. return monitor.AlertResourceType(res.Type)
  356. }
  357. func (res *SAlertResource) getAttachedAlertCount() (int, error) {
  358. alerts, err := res.getAttachedAlerts()
  359. if err != nil {
  360. return 0, errors.Wrap(err, "getAttachedAlerts")
  361. }
  362. return len(alerts), nil
  363. }
  364. func (res *SAlertResource) getDetails(input monitor.AlertResourceDetails) monitor.AlertResourceDetails {
  365. jointAlerts, err := res.getJointAlerts()
  366. if err != nil {
  367. log.Errorf("get %s resource joint alerts error: %v", res.GetName(), err)
  368. } else {
  369. input.Count = len(jointAlerts)
  370. }
  371. tags := make(map[string]string, 0)
  372. for _, jObj := range jointAlerts {
  373. data, err := jObj.GetData()
  374. if err != nil {
  375. log.Errorf("get resource %s joint object data error: %v", res.LogPrefix(), err)
  376. continue
  377. }
  378. for k, v := range data.Tags {
  379. tags[k] = v
  380. }
  381. }
  382. input.Tags = tags
  383. return input
  384. }
  385. func (res *SAlertResource) CustomizeDelete(
  386. ctx context.Context, userCred mcclient.TokenCredential,
  387. query jsonutils.JSONObject, data jsonutils.JSONObject) error {
  388. if err := res.SStandaloneResourceBase.CustomizeDelete(ctx, userCred, query, data); err != nil {
  389. return errors.Wrap(err, "SStandaloneResourceBase.CustomizeDelete")
  390. }
  391. alerts, err := res.getJointAlerts()
  392. if err != nil {
  393. return errors.Wrap(err, "getJointAlerts when customize delete")
  394. }
  395. for _, alert := range alerts {
  396. if err := alert.Detach(ctx, userCred); err != nil {
  397. return errors.Wrap(err, "detach joint alert")
  398. }
  399. }
  400. return nil
  401. }
  402. /*func (manager *SAlertResourceManager) NotifyAlertResourceCount(ctx context.Context) error {
  403. log.Errorln("exec NotifyAlertResourceCount func")
  404. cn, err := manager.getResourceCount()
  405. if err != nil {
  406. return err
  407. }
  408. alertResourceCount := resourceCount{
  409. AlertResourceCount: cn,
  410. }
  411. if adminUsers == nil {
  412. manager.GetAdminRoleUsers(ctx, nil, true)
  413. }
  414. adminUsersTmp := *adminUsers
  415. ids := make([]string, 0)
  416. adminUsersTmp.Range(func(key, value interface{}) bool {
  417. ids = append(ids, key.(string))
  418. return true
  419. })
  420. if len(ids) == 0 {
  421. return fmt.Errorf("no find users in receivers has admin role")
  422. }
  423. //if len(ids) != 0 {
  424. // notifyclient.RawNotifyWithCtx(ctx, ids, false, npk.NotifyByWebConsole, npk.NotifyPriorityCritical,
  425. // "alertResourceCount", jsonutils.Marshal(&alertResourceCount))
  426. // return nil
  427. //} else {
  428. // return fmt.Errorf("no find users in receivers has admin role")
  429. //}
  430. // manager.sendWebsocketInfo(ids, alertResourceCount)
  431. return nil
  432. }*/
  433. type resourceCount struct {
  434. AlertResourceCount int `json:"alert_resource_count"`
  435. }
  436. func (manager *SAlertResourceManager) getResourceCount() (int, error) {
  437. query := manager.Query("id")
  438. cn, err := query.CountWithError()
  439. if err != nil {
  440. return cn, errors.Wrap(err, "SAlertResourceManager get resource count error")
  441. }
  442. return cn, nil
  443. }
  444. func (manager *SAlertResourceManager) GetAdminRoleUsers(ctx context.Context, userCred mcclient.TokenCredential,
  445. isStart bool) {
  446. if adminUsers == nil {
  447. adminUsers = new(sync.Map)
  448. }
  449. offset := 0
  450. query := jsonutils.NewDict()
  451. session := auth.GetAdminSession(ctx, "")
  452. rid, err := identity.RolesV3.GetId(session, "admin", jsonutils.NewDict())
  453. if err != nil {
  454. errors.Errorf("get role admin id error: %v", err)
  455. return
  456. }
  457. query.Add(jsonutils.NewString(rid), "role", "id")
  458. for {
  459. query.Set("offset", jsonutils.NewInt(int64(offset)))
  460. result, err := identity.RoleAssignments.List(session, query)
  461. if err != nil {
  462. errors.Errorf("get admin role list by query: %s, error: %v", query, err)
  463. return
  464. }
  465. for _, roleAssign := range result.Data {
  466. userId, err := roleAssign.GetString("user", "id")
  467. if err != nil {
  468. log.Errorf("get user.id from roleAssign %s: %v", roleAssign, err)
  469. continue
  470. }
  471. //_, err = .NotifyReceiver.GetById(session, userId, jsonutils.NewDict())
  472. //if err != nil {
  473. // log.Errorf("Recipients GetById err:%v", err)
  474. // continue
  475. //}
  476. adminUsers.Store(userId, roleAssign)
  477. }
  478. offset = result.Offset + len(result.Data)
  479. if offset >= result.Total {
  480. break
  481. }
  482. }
  483. }
  484. /*
  485. func (manager *SAlertResourceManager) sendWebsocketInfo(uids []string, alertResourceCount resourceCount) {
  486. session := auth.GetAdminSession(context.Background(), "", "")
  487. params := jsonutils.NewDict()
  488. params.Set("obj_type", jsonutils.NewString("monitor"))
  489. params.Set("obj_id", jsonutils.NewString(""))
  490. params.Set("obj_name", jsonutils.NewString(""))
  491. params.Set("success", jsonutils.JSONTrue)
  492. params.Set("action", jsonutils.NewString("alertResourceCount"))
  493. params.Set("ignore_alert", jsonutils.JSONTrue)
  494. params.Set("notes", jsonutils.NewString(fmt.Sprintf("priority=%s; content=%s", string(notify.NotifyPriorityCritical),
  495. jsonutils.Marshal(&alertResourceCount).String())))
  496. for _, uid := range uids {
  497. params.Set("user_id", jsonutils.NewString(uid))
  498. params.Set("user", jsonutils.NewString(uid))
  499. _, err := websocket.Websockets.Create(session, params)
  500. if err != nil {
  501. log.Errorf("websocket send info err:%v", err)
  502. }
  503. }
  504. }*/