Files
cloudpods/pkg/cloudcommon/notifyclient/notify.go
2023-10-12 15:30:00 +08:00

464 lines
16 KiB
Go

// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notifyclient
import (
"context"
"fmt"
"sync"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
api "yunion.io/x/onecloud/pkg/apis/notify"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modules/identity"
npk "yunion.io/x/onecloud/pkg/mcclient/modules/notify"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
var (
notifyClientWorkerMan *appsrv.SWorkerManager
notifyAdminUsers []string
notifyAdminGroups []string
notifyDBHookResources sync.Map
)
func init() {
notifyClientWorkerMan = appsrv.NewWorkerManager("NotifyClientWorkerManager", 1, 50, false)
// set db notify hook
db.SetUpdateNotifyHook(func(ctx context.Context, userCred mcclient.TokenCredential, obj db.IModel) {
_, ok := notifyDBHookResources.Load(obj.KeywordPlural())
if !ok {
return
}
EventNotify(ctx, userCred, SEventNotifyParam{
Obj: obj,
Action: ActionUpdate,
})
})
db.SetCreateNotifyHook(func(ctx context.Context, userCred mcclient.TokenCredential, obj db.IModel) {
_, ok := notifyDBHookResources.Load(obj.KeywordPlural())
if !ok {
return
}
EventNotify(ctx, userCred, SEventNotifyParam{
Obj: obj,
Action: ActionCreate,
})
})
db.SetDeleteNotifyHook(func(ctx context.Context, userCred mcclient.TokenCredential, obj db.IModel) {
_, ok := notifyDBHookResources.Load(obj.KeywordPlural())
if !ok {
return
}
EventNotify(ctx, userCred, SEventNotifyParam{
Obj: obj,
Action: ActionDelete,
})
})
}
func AddNotifyDBHookResources(keywordPlurals ...string) {
for _, kp := range keywordPlurals {
notifyDBHookResources.Store(kp, true)
}
}
func NotifyWithCtx(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
notify(ctx, recipientId, isGroup, priority, event, data)
}
func Notify(recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
notify(context.Background(), recipientId, isGroup, priority, event, data)
}
func NotifyWithTag(ctx context.Context, params SNotifyParams) {
p := sNotifyParams{
recipientId: params.RecipientId,
isGroup: params.IsGroup,
event: params.Event,
data: params.Data,
priority: params.Priority,
tag: params.Tag,
metadata: params.Metadata,
ignoreNonexistentReceiver: params.IgnoreNonexistentReceiver,
}
notifyWithChannel(ctx, p,
npk.NotifyByEmail,
npk.NotifyByMobile,
npk.NotifyByDingTalk,
npk.NotifyByFeishu,
npk.NotifyByWorkwx,
npk.NotifyByWebConsole,
)
}
type SNotifyParams struct {
RecipientId []string
IsGroup bool
Priority npk.TNotifyPriority
Event string
Data jsonutils.JSONObject
Tag string
Metadata map[string]interface{}
IgnoreNonexistentReceiver bool
}
func NotifyWithContact(ctx context.Context, contacts []string, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
p := sNotifyParams{
contacts: contacts,
priority: priority,
channel: channel,
event: event,
data: data,
}
rawNotify(ctx, p)
}
func NotifyNormal(recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
notifyNormal(context.Background(), recipientId, isGroup, event, data)
}
func NotifyNormalWithCtx(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
notifyNormal(ctx, recipientId, isGroup, event, data)
}
func NotifyImportant(recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
notifyImportant(context.Background(), recipientId, isGroup, event, data)
}
func NotifyImportantWithCtx(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
notifyImportant(ctx, recipientId, isGroup, event, data)
}
func NotifyCritical(recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
notifyCritical(context.Background(), recipientId, isGroup, event, data)
}
func NotifyCriticalWithCtx(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
notifyCritical(ctx, recipientId, isGroup, event, data)
}
// NotifyAllWithoutRobot will send messages via all contacnt type from exclude robot contact type such as dingtalk-robot.
func NotifyAllWithoutRobot(recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error {
return notifyAll(context.Background(), recipientId, isGroup, priority, event, data)
}
// NotifyAllWithoutRobot will send messages via all contacnt type from exclude robot contact type such as dingtalk-robot.
func NotifyAllWithoutRobotWithCtx(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error {
return notifyAll(ctx, recipientId, isGroup, priority, event, data)
}
// NotifyRobot will send messages via all robot contact type such as dingtalk-robot.
func NotifyRobot(robotIds []string, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error {
return NotifyRobotWithCtx(context.Background(), robotIds, priority, event, data)
}
// NotifyRobot will send messages via all robot contact type such as dingtalk-robot.
func NotifyRobotWithCtx(ctx context.Context, robotIds []string, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error {
rawNotify(ctx, sNotifyParams{
robots: robotIds,
channel: npk.NotifyByRobot,
priority: priority,
event: event,
data: data,
})
return nil
}
func SystemNotify(priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
systemNotify(context.Background(), priority, event, data)
}
func SystemNotifyWithCtx(ctx context.Context, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
systemNotify(ctx, priority, event, data)
}
func NotifyGeneralSystemError(data jsonutils.JSONObject) {
notifyGeneralSystemError(context.Background(), data)
}
func NotifyGeneralSystemErrorWithCtx(ctx context.Context, data jsonutils.JSONObject) {
notifyGeneralSystemError(ctx, data)
}
type SSystemEventMsg struct {
Id string
Name string
Event string
Reason string
Created time.Time
}
func NotifySystemError(idstr string, name string, event string, reason string) {
notifySystemError(context.Background(), idstr, name, event, reason)
}
func NotifySystemErrorWithCtx(ctx context.Context, idstr string, name string, event string, reason string) {
notifySystemError(ctx, idstr, name, event, reason)
}
func NotifyError(ctx context.Context, userCred mcclient.TokenCredential, idstr, name, event, reason string) {
msg := SSystemEventMsg{
Id: idstr,
Name: name,
Event: event,
Reason: reason,
Created: time.Now(),
}
notify(ctx, []string{userCred.GetUserId()}, false, npk.NotifyPriorityCritical, SYSTEM_ERROR, jsonutils.Marshal(msg))
}
func NotifySystemWarning(idstr string, name string, event string, reason string) {
notifySystemWarning(context.Background(), idstr, name, event, reason)
}
func NotifySystemWarningWithCtx(ctx context.Context, idstr string, name string, event string, reason string) {
notifySystemWarning(ctx, idstr, name, event, reason)
}
func NotifyWebhook(ctx context.Context, userCred mcclient.TokenCredential, obj db.IModel, action api.SAction) {
ret, err := db.FetchCustomizeColumns(obj.GetModelManager(), ctx, userCred, jsonutils.NewDict(), []interface{}{obj}, stringutils2.SSortedStrings{}, false)
if err != nil {
log.Errorf("unable to NotifyWebhook: %v", err)
return
}
if len(ret) == 0 {
log.Errorf("unable to NotifyWebhook: details of model %q is empty", obj.GetId())
return
}
event := Event.WithAction(action).WithResourceType(obj.GetModelManager())
msg := jsonutils.NewDict()
msg.Set("resource_type", jsonutils.NewString(event.ResourceType()))
msg.Set("action", jsonutils.NewString(event.Action()))
msg.Set("resource_details", ret[0])
RawNotifyWithCtx(ctx, []string{}, false, npk.NotifyByWebhook, npk.NotifyPriorityNormal, event.String(), msg)
}
type SEventMessage struct {
ResourceType string `json:"resource_type"`
Action string `json:"action"`
ResourceDetails *jsonutils.JSONDict `json:"resource_details"`
}
type SEventNotifyParam struct {
Obj db.IModel
ResourceType string
Action api.SAction
IsFail bool
ObjDetailsDecorator func(context.Context, *jsonutils.JSONDict)
AdvanceDays int
}
type eventTask struct {
params api.NotificationManagerEventNotifyInput
}
func (t *eventTask) Dump() string {
return fmt.Sprintf("eventTask params: %v", t.params)
}
func (t *eventTask) Run() {
s, err := AdminSessionGenerator(context.Background(), "")
if err != nil {
log.Errorf("unable to get admin session: %v", err)
return
}
_, err = npk.Notification.PerformClassAction(s, "event-notify", jsonutils.Marshal(t.params))
if err != nil {
log.Errorf("unable to EventNotify: %s", err)
}
}
func EventNotify(ctx context.Context, userCred mcclient.TokenCredential, ep SEventNotifyParam) {
var objDetails *jsonutils.JSONDict
if ep.Action == ActionDelete || ep.Action == ActionSyncDelete {
objDetails = jsonutils.Marshal(ep.Obj).(*jsonutils.JSONDict)
} else {
ret, err := db.FetchCustomizeColumns(ep.Obj.GetModelManager(), ctx, userCred, jsonutils.NewDict(), []interface{}{ep.Obj}, nil, false)
if err != nil {
log.Errorf("unable to FetchCustomizeColumns: %v", err)
return
}
if len(ret) == 0 {
log.Errorf("unable to FetchCustomizeColumns: details of model %q is empty", ep.Obj.GetId())
return
}
objDetails = ret[0]
}
if ep.ObjDetailsDecorator != nil {
ep.ObjDetailsDecorator(ctx, objDetails)
}
rt := ep.ResourceType
if len(rt) == 0 {
rt = ep.Obj.GetModelManager().Keyword()
}
event := api.Event.WithAction(ep.Action).WithResourceType(rt)
if ep.IsFail {
event = event.WithResult(api.ResultFailed)
}
var (
projectId string
projectDomainId string
)
ownerId := ep.Obj.GetOwnerId()
if ownerId != nil {
projectId = ownerId.GetProjectId()
projectDomainId = ownerId.GetProjectDomainId()
}
params := api.NotificationManagerEventNotifyInput{
ReceiverIds: []string{userCred.GetUserId()},
ResourceDetails: objDetails,
Event: event.String(),
AdvanceDays: ep.AdvanceDays,
Priority: string(npk.NotifyPriorityNormal),
ProjectId: projectId,
ProjectDomainId: projectDomainId,
ResourceType: ep.ResourceType,
Action: ep.Action,
}
t := eventTask{
params: params,
}
notifyClientWorkerMan.Run(&t, nil, nil)
}
func EventNotifyServiceAbnormal(ctx context.Context, userCred mcclient.TokenCredential, service, method, path string, body jsonutils.JSONObject, err error) {
event := api.Event.WithAction(api.ActionServiceAbnormal).WithResourceType(api.TOPIC_RESOURCE_SERVICE)
obj := jsonutils.NewDict()
if body != nil {
obj.Set("body", jsonutils.NewString(body.PrettyString()))
}
obj.Set("method", jsonutils.NewString(method))
obj.Set("path", jsonutils.NewString(path))
obj.Set("error", jsonutils.NewString(err.Error()))
obj.Set("service_name", jsonutils.NewString(service))
params := api.NotificationManagerEventNotifyInput{
ReceiverIds: []string{userCred.GetUserId()},
ResourceDetails: obj,
Event: event.String(),
AdvanceDays: 0,
Priority: string(npk.NotifyPriorityNormal),
ResourceType: api.TOPIC_RESOURCE_SERVICE,
Action: api.ActionServiceAbnormal,
}
t := eventTask{
params: params,
}
notifyClientWorkerMan.Run(&t, nil, nil)
}
func systemEventNotify(ctx context.Context, action api.SAction, resType string, result api.SResult, priority string, obj *jsonutils.JSONDict) {
event := api.Event.WithAction(action).WithResourceType(resType).WithResult(result)
params := api.NotificationManagerEventNotifyInput{
ReceiverIds: []string{},
ResourceDetails: obj,
Event: event.String(),
Priority: priority,
}
t := eventTask{
params: params,
}
notifyClientWorkerMan.Run(&t, nil, nil)
}
func SystemEventNotify(ctx context.Context, action api.SAction, resType string, obj *jsonutils.JSONDict) {
systemEventNotify(ctx, action, resType, api.ResultSucceed, string(npk.NotifyPriorityNormal), obj)
}
func SystemExceptionNotify(ctx context.Context, action api.SAction, resType string, obj *jsonutils.JSONDict) {
systemEventNotify(ctx, action, resType, api.ResultSucceed, string(npk.NotifyPriorityCritical), obj)
}
func SystemExceptionNotifyWithResult(ctx context.Context, action api.SAction, resType string, result api.SResult, obj *jsonutils.JSONDict) {
systemEventNotify(ctx, action, resType, result, string(npk.NotifyPriorityCritical), obj)
}
func RawNotifyWithCtx(ctx context.Context, recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
rawNotify(ctx, sNotifyParams{
recipientId: recipientId,
isGroup: isGroup,
channel: channel,
priority: priority,
event: event,
data: data,
})
}
func RawNotify(recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
rawNotify(context.Background(), sNotifyParams{
recipientId: recipientId,
isGroup: isGroup,
channel: channel,
priority: priority,
event: event,
data: data,
})
}
// IntelliNotify try to create receiver nonexistent if createReceiver is set to true
func IntelliNotify(ctx context.Context, recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, createReceiver bool) {
intelliNotify(ctx, sNotifyParams{
recipientId: recipientId,
isGroup: isGroup,
channel: channel,
priority: priority,
event: event,
data: data,
createReceiver: createReceiver,
})
}
func FetchNotifyAdminRecipients(ctx context.Context, region string, users []string, groups []string) {
s, err := AdminSessionGenerator(ctx, region)
if err != nil {
log.Errorf("unable to get admin session: %v", err)
}
notifyAdminUsers = make([]string, 0)
for _, u := range users {
uId, err := getIdentityId(s, u, &identity.UsersV3)
if err != nil {
log.Warningf("fetch user %s fail: %s", u, err)
} else {
notifyAdminUsers = append(notifyAdminUsers, uId)
}
}
notifyAdminGroups = make([]string, 0)
for _, g := range groups {
gId, err := getIdentityId(s, g, &identity.Groups)
if err != nil {
log.Warningf("fetch group %s fail: %s", g, err)
} else {
notifyAdminGroups = append(notifyAdminGroups, gId)
}
}
}
func NotifyVmIntegrity(ctx context.Context, name string) {
data := jsonutils.NewDict()
data.Add(jsonutils.NewString(name), "name")
SystemExceptionNotifyWithResult(ctx, api.ActionChecksumTest, api.TOPIC_RESOURCE_VM_INTEGRITY_CHECK, api.ResultFailed, data)
}