notify.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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 notifyclient
  15. import (
  16. "context"
  17. "fmt"
  18. "html/template"
  19. "sync"
  20. "time"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/log"
  23. api "yunion.io/x/onecloud/pkg/apis/notify"
  24. "yunion.io/x/onecloud/pkg/appsrv"
  25. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  26. "yunion.io/x/onecloud/pkg/mcclient"
  27. "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
  28. npk "yunion.io/x/onecloud/pkg/mcclient/modules/notify"
  29. "yunion.io/x/onecloud/pkg/util/stringutils2"
  30. )
  31. var (
  32. notifyClientWorkerMan *appsrv.SWorkerManager
  33. notifyAdminUsers []string
  34. notifyAdminGroups []string
  35. notifyDBHookResources sync.Map
  36. )
  37. func init() {
  38. notifyClientWorkerMan = appsrv.NewWorkerManager("NotifyClientWorkerManager", 1, 1024, false)
  39. // set db notify hook
  40. db.SetUpdateNotifyHook(func(ctx context.Context, userCred mcclient.TokenCredential, obj db.IModel) {
  41. _, ok := notifyDBHookResources.Load(obj.KeywordPlural())
  42. if !ok {
  43. return
  44. }
  45. EventNotify(ctx, userCred, SEventNotifyParam{
  46. Obj: obj,
  47. Action: ActionUpdate,
  48. })
  49. })
  50. db.SetCustomizeNotifyHook(func(ctx context.Context, userCred mcclient.TokenCredential, action string, obj db.IModel, moreDetails jsonutils.JSONObject) {
  51. _, ok := notifyDBHookResources.Load(obj.KeywordPlural())
  52. if !ok {
  53. return
  54. }
  55. EventNotify(ctx, userCred, SEventNotifyParam{
  56. Obj: obj,
  57. Action: api.SAction(action),
  58. ObjDetailsDecorator: func(ctx context.Context, details *jsonutils.JSONDict) {
  59. if moreDetails != nil {
  60. details.Set("customize_details", moreDetails)
  61. }
  62. },
  63. })
  64. })
  65. db.SetStatusChangedNotifyHook(func(ctx context.Context, userCred mcclient.TokenCredential, oldStatus, newStatus string, obj db.IModel) {
  66. _, ok := notifyDBHookResources.Load(obj.KeywordPlural())
  67. if !ok {
  68. return
  69. }
  70. EventNotify(ctx, userCred, SEventNotifyParam{
  71. Obj: obj,
  72. Action: api.ActionStatusChanged,
  73. ObjDetailsDecorator: func(ctx context.Context, details *jsonutils.JSONDict) {
  74. details.Set("old_status", jsonutils.NewString(oldStatus))
  75. details.Set("new_status", jsonutils.NewString(newStatus))
  76. },
  77. })
  78. })
  79. }
  80. func AddNotifyDBHookResources(keywordPlurals ...string) {
  81. for _, kp := range keywordPlurals {
  82. notifyDBHookResources.Store(kp, true)
  83. }
  84. }
  85. func NotifyWithCtx(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
  86. notify(ctx, recipientId, isGroup, priority, event, data)
  87. }
  88. func Notify(recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
  89. notify(context.Background(), recipientId, isGroup, priority, event, data)
  90. }
  91. func NotifyWithTag(ctx context.Context, params SNotifyParams) {
  92. p := newSNotifyParams(params.Event, params.Data).
  93. withRecipientId(params.RecipientId).
  94. withIsGroup(params.IsGroup).
  95. withPriority(params.Priority).
  96. withTag(params.Tag).
  97. withMetadata(params.Metadata).
  98. withIgnoreNonexistentReceiver(params.IgnoreNonexistentReceiver)
  99. notifyWithChannel(ctx, p,
  100. npk.NotifyByEmail,
  101. npk.NotifyByMobile,
  102. npk.NotifyByDingTalk,
  103. npk.NotifyByFeishu,
  104. npk.NotifyByWorkwx,
  105. npk.NotifyByWebConsole,
  106. )
  107. }
  108. type SNotifyParams struct {
  109. RecipientId []string
  110. IsGroup bool
  111. Priority npk.TNotifyPriority
  112. Event string
  113. Data jsonutils.JSONObject
  114. Tag string
  115. Metadata map[string]interface{}
  116. IgnoreNonexistentReceiver bool
  117. }
  118. func NotifyWithContact(ctx context.Context, contacts []string, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
  119. p := newSNotifyParams(event, data).
  120. withContactChannelAndPriority(contacts, channel, priority)
  121. rawNotify(ctx, p)
  122. }
  123. func NotifyNormal(recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
  124. notifyNormal(context.Background(), recipientId, isGroup, event, data)
  125. }
  126. func NotifyNormalWithCtx(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
  127. notifyNormal(ctx, recipientId, isGroup, event, data)
  128. }
  129. func NotifyImportant(recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
  130. notifyImportant(context.Background(), recipientId, isGroup, event, data)
  131. }
  132. func NotifyImportantWithCtx(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
  133. notifyImportant(ctx, recipientId, isGroup, event, data)
  134. }
  135. func NotifyCritical(recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
  136. notifyCritical(context.Background(), recipientId, isGroup, event, data)
  137. }
  138. func NotifyCriticalWithCtx(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
  139. notifyCritical(ctx, recipientId, isGroup, event, data)
  140. }
  141. // NotifyAllWithoutRobot will send messages via all contacnt type from exclude robot contact type such as dingtalk-robot.
  142. func NotifyAllWithoutRobot(recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error {
  143. return notifyAll(context.Background(), recipientId, isGroup, priority, event, data)
  144. }
  145. // NotifyAllWithoutRobot will send messages via all contacnt type from exclude robot contact type such as dingtalk-robot.
  146. func NotifyAllWithoutRobotWithCtx(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error {
  147. return NotifyAllWithoutRobotWithCtxAndTemplateFuncs(ctx, recipientId, isGroup, priority, event, data, nil)
  148. }
  149. // NotifyAllWithoutRobotWithCtxAndTemplateFuncs 发送通知给所有用户(不包括机器人),支持自定义模板函数
  150. func NotifyAllWithoutRobotWithCtxAndTemplateFuncs(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, templateFuncs template.FuncMap) error {
  151. return notifyAllWithTemplateFuncs(ctx, recipientId, isGroup, priority, event, data, templateFuncs)
  152. }
  153. // NotifyRobot will send messages via all robot contact type such as dingtalk-robot.
  154. func NotifyRobot(robotIds []string, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error {
  155. return NotifyRobotWithCtx(context.Background(), robotIds, priority, event, data)
  156. }
  157. // NotifyRobot will send messages via all robot contact type such as dingtalk-robot.
  158. func NotifyRobotWithCtx(ctx context.Context, robotIds []string, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error {
  159. return NotifyRobotWithCtxAndTemplateFuncs(ctx, robotIds, priority, event, data, nil)
  160. }
  161. // NotifyRobotWithCtxAndTemplateFuncs 发送通知给机器人,支持自定义模板函数
  162. func NotifyRobotWithCtxAndTemplateFuncs(ctx context.Context, robotIds []string, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, templateFuncs template.FuncMap) error {
  163. p := newSNotifyParams(event, data).
  164. withRobotChannelAndPriority(robotIds, npk.NotifyByRobot, priority, templateFuncs)
  165. rawNotify(ctx, p)
  166. return nil
  167. }
  168. func SystemNotify(priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
  169. systemNotify(context.Background(), priority, event, data)
  170. }
  171. func SystemNotifyWithCtx(ctx context.Context, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
  172. SystemNotifyWithCtxAndTemplateFuncs(ctx, priority, event, data, nil)
  173. }
  174. // SystemNotifyWithCtxAndTemplateFuncs 发送系统通知,支持自定义模板函数
  175. func SystemNotifyWithCtxAndTemplateFuncs(ctx context.Context, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, templateFuncs template.FuncMap) {
  176. systemNotifyWithTemplateFuncs(ctx, priority, event, data, templateFuncs)
  177. }
  178. func NotifyGeneralSystemError(data jsonutils.JSONObject) {
  179. notifyGeneralSystemError(context.Background(), data)
  180. }
  181. func NotifyGeneralSystemErrorWithCtx(ctx context.Context, data jsonutils.JSONObject) {
  182. notifyGeneralSystemError(ctx, data)
  183. }
  184. type SSystemEventMsg struct {
  185. Id string
  186. Name string
  187. Event string
  188. Reason string
  189. Created time.Time
  190. }
  191. func NotifySystemError(idstr string, name string, event string, reason string) {
  192. notifySystemError(context.Background(), idstr, name, event, reason)
  193. }
  194. func NotifySystemErrorWithCtx(ctx context.Context, idstr string, name string, event string, reason string) {
  195. notifySystemError(ctx, idstr, name, event, reason)
  196. }
  197. func NotifyError(ctx context.Context, userCred mcclient.TokenCredential, idstr, name, event, reason string) {
  198. msg := SSystemEventMsg{
  199. Id: idstr,
  200. Name: name,
  201. Event: event,
  202. Reason: reason,
  203. Created: time.Now(),
  204. }
  205. notify(ctx, []string{userCred.GetUserId()}, false, npk.NotifyPriorityCritical, SYSTEM_ERROR, jsonutils.Marshal(msg))
  206. }
  207. func NotifySystemWarning(idstr string, name string, event string, reason string) {
  208. notifySystemWarning(context.Background(), idstr, name, event, reason)
  209. }
  210. func NotifySystemWarningWithCtx(ctx context.Context, idstr string, name string, event string, reason string) {
  211. notifySystemWarning(ctx, idstr, name, event, reason)
  212. }
  213. func NotifyWebhook(ctx context.Context, userCred mcclient.TokenCredential, obj db.IModel, action api.SAction) {
  214. ret, err := db.FetchCustomizeColumns(obj.GetModelManager(), ctx, userCred, jsonutils.NewDict(), []interface{}{obj}, stringutils2.SSortedStrings{}, false)
  215. if err != nil {
  216. log.Errorf("unable to NotifyWebhook: %v", err)
  217. return
  218. }
  219. if len(ret) == 0 {
  220. log.Errorf("unable to NotifyWebhook: details of model %q is empty", obj.GetId())
  221. return
  222. }
  223. event := Event.WithAction(action).WithResourceType(obj.GetModelManager())
  224. msg := jsonutils.NewDict()
  225. msg.Set("resource_type", jsonutils.NewString(event.ResourceType()))
  226. msg.Set("action", jsonutils.NewString(event.Action()))
  227. msg.Set("resource_details", ret[0])
  228. RawNotifyWithCtx(ctx, []string{}, false, npk.NotifyByWebhook, npk.NotifyPriorityNormal, event.String(), msg)
  229. }
  230. type SEventMessage struct {
  231. ResourceType string `json:"resource_type"`
  232. Action string `json:"action"`
  233. ResourceDetails *jsonutils.JSONDict `json:"resource_details"`
  234. }
  235. type SEventNotifyParam struct {
  236. Obj db.IModel
  237. ResourceType string
  238. Action api.SAction
  239. IsFail bool
  240. ObjDetailsDecorator func(context.Context, *jsonutils.JSONDict)
  241. AdvanceDays int
  242. }
  243. type eventTask struct {
  244. params api.NotificationManagerEventNotifyInput
  245. }
  246. func (t *eventTask) Dump() string {
  247. return fmt.Sprintf("eventTask params: %v", t.params)
  248. }
  249. func (t *eventTask) Run() {
  250. s, err := AdminSessionGenerator(context.Background(), "")
  251. if err != nil {
  252. log.Errorf("unable to get admin session: %v", err)
  253. return
  254. }
  255. _, err = npk.Notification.PerformClassAction(s, "event-notify", jsonutils.Marshal(t.params))
  256. if err != nil {
  257. log.Errorf("unable to EventNotify: %s", err)
  258. }
  259. }
  260. func EventNotify(ctx context.Context, userCred mcclient.TokenCredential, ep SEventNotifyParam) {
  261. var objDetails *jsonutils.JSONDict
  262. if ep.Action == ActionDelete || ep.Action == ActionSyncDelete {
  263. objDetails = jsonutils.Marshal(ep.Obj).(*jsonutils.JSONDict)
  264. } else {
  265. ret, err := db.FetchCustomizeColumns(ep.Obj.GetModelManager(), ctx, userCred, jsonutils.NewDict(), []interface{}{ep.Obj}, nil, false)
  266. if err != nil {
  267. log.Errorf("unable to FetchCustomizeColumns: %v", err)
  268. return
  269. }
  270. if len(ret) == 0 {
  271. log.Errorf("unable to FetchCustomizeColumns: details of model %q is empty", ep.Obj.GetId())
  272. return
  273. }
  274. objDetails = ret[0]
  275. }
  276. if ep.ObjDetailsDecorator != nil {
  277. ep.ObjDetailsDecorator(ctx, objDetails)
  278. }
  279. rt := ep.ResourceType
  280. if len(rt) == 0 {
  281. rt = ep.Obj.GetModelManager().Keyword()
  282. }
  283. event := api.Event.WithAction(ep.Action).WithResourceType(rt)
  284. if ep.IsFail {
  285. event = event.WithResult(api.ResultFailed)
  286. }
  287. var (
  288. projectId string
  289. projectDomainId string
  290. )
  291. ownerId := ep.Obj.GetOwnerId()
  292. if ownerId != nil {
  293. projectId = ownerId.GetProjectId()
  294. projectDomainId = ownerId.GetProjectDomainId()
  295. }
  296. params := api.NotificationManagerEventNotifyInput{
  297. ReceiverIds: []string{userCred.GetUserId()},
  298. ResourceDetails: objDetails,
  299. Event: event.String(),
  300. AdvanceDays: ep.AdvanceDays,
  301. Priority: string(npk.NotifyPriorityNormal),
  302. ProjectId: projectId,
  303. ProjectDomainId: projectDomainId,
  304. ResourceType: ep.ResourceType,
  305. Action: ep.Action,
  306. }
  307. EventNotify2(params)
  308. }
  309. func EventNotify2(params api.NotificationManagerEventNotifyInput) {
  310. t := eventTask{
  311. params: params,
  312. }
  313. notifyClientWorkerMan.Run(&t, nil, nil)
  314. }
  315. func EventNotifyServiceAbnormal(ctx context.Context, userCred mcclient.TokenCredential, service, method, path string, body jsonutils.JSONObject, err error) {
  316. event := api.Event.WithAction(api.ActionServiceAbnormal).WithResourceType(api.TOPIC_RESOURCE_SERVICE)
  317. obj := jsonutils.NewDict()
  318. if body != nil {
  319. obj.Set("body", jsonutils.NewString(body.PrettyString()))
  320. }
  321. obj.Set("method", jsonutils.NewString(method))
  322. obj.Set("path", jsonutils.NewString(path))
  323. obj.Set("error", jsonutils.NewString(err.Error()))
  324. obj.Set("service_name", jsonutils.NewString(service))
  325. params := api.NotificationManagerEventNotifyInput{
  326. ReceiverIds: []string{userCred.GetUserId()},
  327. ResourceDetails: obj,
  328. Event: event.String(),
  329. AdvanceDays: 0,
  330. Priority: string(npk.NotifyPriorityNormal),
  331. ResourceType: api.TOPIC_RESOURCE_SERVICE,
  332. Action: api.ActionServiceAbnormal,
  333. }
  334. EventNotify2(params)
  335. }
  336. func systemEventNotify(ctx context.Context, action api.SAction, resType string, result api.SResult, priority string, obj *jsonutils.JSONDict) {
  337. event := api.Event.WithAction(action).WithResourceType(resType).WithResult(result)
  338. params := api.NotificationManagerEventNotifyInput{
  339. ReceiverIds: []string{},
  340. ResourceDetails: obj,
  341. Event: event.String(),
  342. Priority: priority,
  343. }
  344. EventNotify2(params)
  345. }
  346. func SystemEventNotify(ctx context.Context, action api.SAction, resType string, obj *jsonutils.JSONDict) {
  347. systemEventNotify(ctx, action, resType, api.ResultSucceed, string(npk.NotifyPriorityNormal), obj)
  348. }
  349. func SystemExceptionNotify(ctx context.Context, action api.SAction, resType string, obj *jsonutils.JSONDict) {
  350. systemEventNotify(ctx, action, resType, api.ResultSucceed, string(npk.NotifyPriorityCritical), obj)
  351. }
  352. func SystemExceptionNotifyWithResult(ctx context.Context, action api.SAction, resType string, result api.SResult, obj *jsonutils.JSONDict) {
  353. systemEventNotify(ctx, action, resType, result, string(npk.NotifyPriorityCritical), obj)
  354. }
  355. func RawNotifyWithCtx(ctx context.Context, recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
  356. RawNotifyWithCtxAndTemplateFuncs(ctx, recipientId, isGroup, channel, priority, event, data, nil)
  357. }
  358. // RawNotifyWithCtxAndTemplateFuncs 发送通知,支持自定义模板函数
  359. func RawNotifyWithCtxAndTemplateFuncs(ctx context.Context, recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, templateFuncs template.FuncMap) {
  360. p := newSNotifyParams(event, data).
  361. withRecipientChannelAndPriority(recipientId, isGroup, channel, priority, templateFuncs)
  362. rawNotify(ctx, p)
  363. }
  364. func RawNotify(recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
  365. p := newSNotifyParams(event, data).
  366. withRecipientChannelAndPriority(recipientId, isGroup, channel, priority, nil)
  367. rawNotify(context.Background(), p)
  368. }
  369. // IntelliNotify try to create receiver nonexistent if createReceiver is set to true
  370. func IntelliNotify(ctx context.Context, recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, createReceiver bool) {
  371. p := newSNotifyParams(event, data).
  372. withRecipientChannelAndPriority(recipientId, isGroup, channel, priority, nil).
  373. withCreateReceiver(createReceiver)
  374. intelliNotify(ctx, p)
  375. }
  376. func FetchNotifyAdminRecipients(ctx context.Context, region string, users []string, groups []string) {
  377. s, err := AdminSessionGenerator(ctx, region)
  378. if err != nil {
  379. log.Errorf("unable to get admin session: %v", err)
  380. }
  381. notifyAdminUsers = make([]string, 0)
  382. for _, u := range users {
  383. uId, err := getIdentityId(s, u, &identity.UsersV3)
  384. if err != nil {
  385. log.Warningf("fetch user %s fail: %s", u, err)
  386. } else {
  387. notifyAdminUsers = append(notifyAdminUsers, uId)
  388. }
  389. }
  390. notifyAdminGroups = make([]string, 0)
  391. for _, g := range groups {
  392. gId, err := getIdentityId(s, g, &identity.Groups)
  393. if err != nil {
  394. log.Warningf("fetch group %s fail: %s", g, err)
  395. } else {
  396. notifyAdminGroups = append(notifyAdminGroups, gId)
  397. }
  398. }
  399. }
  400. func NotifyVmIntegrity(ctx context.Context, name string) {
  401. data := jsonutils.NewDict()
  402. data.Add(jsonutils.NewString(name), "name")
  403. SystemExceptionNotifyWithResult(ctx, api.ActionChecksumTest, api.TOPIC_RESOURCE_VM_INTEGRITY_CHECK, api.ResultFailed, data)
  404. }