feat(monitor): common alert notify by role scope

This commit is contained in:
Zexi Li
2022-07-12 17:04:46 +08:00
parent 0928e67f11
commit ed2a5ea586
7 changed files with 164 additions and 35 deletions
+6 -1
View File
@@ -58,12 +58,16 @@ type CommonAlertCreateInput struct {
Recipients []string `json:"recipients"`
RobotIds []string `json:"robot_ids"`
// 角色 id 或者 name
Roles []string `json:"roles"`
// 静默期
SilentPeriod string `json:"silent_period"`
// 报警类型
AlertType string `json:"alert_type"`
//scope Resource
// Scope Resource
Scope string `json:"scope"`
DomainId string `json:"domain_id"`
ProjectId string `json:"project_id"`
@@ -136,6 +140,7 @@ type CommonAlertDetails struct {
Channel []string `json:"channel"`
Recipients []string `json:"recipients"`
RobotIds []string `json:"robot_ids"`
RoleIds []string `json:"role_ids"`
// 静默期
SilentPeriod string `json:"silent_period"`
Status string `json:"status"`
+1
View File
@@ -82,6 +82,7 @@ type NotificationSettingOneCloud struct {
Channel string `json:"channel"`
UserIds []string `json:"user_ids"`
RobotIds []string `json:"robot_ids"`
RoleIds []string `json:"role_ids"`
}
type SendWebhookSync struct {
+86 -13
View File
@@ -25,8 +25,10 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/sets"
"yunion.io/x/onecloud/pkg/apis/monitor"
notiapi "yunion.io/x/onecloud/pkg/apis/notify"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
"yunion.io/x/onecloud/pkg/hostman/hostinfo/hostconsts"
@@ -40,6 +42,7 @@ import (
"yunion.io/x/onecloud/pkg/monitor/alerting/notifiers/templates"
"yunion.io/x/onecloud/pkg/monitor/models"
"yunion.io/x/onecloud/pkg/monitor/options"
"yunion.io/x/onecloud/pkg/util/rbacutils"
)
const (
@@ -104,10 +107,14 @@ func newOneCloudNotifier(config alerting.NotificationConfig) (alerting.Notifier,
return &OneCloudNotifier{
NotifierBase: NewNotifierBase(config),
Setting: setting,
session: auth.GetAdminSession(context.Background(), options.Options.Region, ""),
session: getAdminSession(),
}, nil
}
func getAdminSession() *mcclient.ClientSession {
return auth.GetAdminSession(context.Background(), options.Options.Region, "")
}
func GetNotifyTemplateConfig(ctx *alerting.EvalContext) monitor.NotificationTemplateConfig {
priority := notify.NotifyPriorityNormal
level := "普通"
@@ -175,20 +182,32 @@ func GetNotifyTemplateConfigOfEN(ctx *alerting.EvalContext) monitor.Notification
// Notify sends the alert notification.
func (oc *OneCloudNotifier) Notify(ctx *alerting.EvalContext, _ jsonutils.JSONObject) error {
log.Infof("Sending alert notification %s to onecloud", ctx.GetRuleTitle())
langIdsMap, err := GetUserLangIdsMap(oc.Setting.UserIds)
if err != nil {
return errors.Wrapf(err, "OneCloudNotifier getIds:%s userLang err", oc.Setting.UserIds)
userIds := oc.Setting.UserIds
if len(oc.Setting.RoleIds) != 0 {
alert, err := models.CommonAlertManager.GetAlert(ctx.Rule.Id)
if err != nil {
return errors.Wrapf(err, "Get alert by %s", ctx.Rule.Id)
}
scope := alert.GetResourceScope()
scopeId := ""
switch scope {
case rbacutils.ScopeDomain:
scopeId = alert.GetDomainId()
case rbacutils.ScopeProject:
scopeId = alert.GetProjectId()
}
roleUserIds, err := getUsersByRoles(oc.Setting.RoleIds, string(scope), scopeId)
if err != nil {
return errors.Wrapf(err, "getUsersByRoles with %v:%s:%s", oc.Setting.RoleIds, scope, scopeId)
}
userIds = append(userIds, roleUserIds...)
userIds = sets.NewString(userIds...).List()
}
langNotifyGroup, _ := errgroup.WithContext(ctx.Ctx)
for lang, _ := range langIdsMap {
ids := langIdsMap[lang]
langTag, _ := language.Parse(lang)
langStr := i18nTable.LookupByLang(langTag, SUFFIX)
langContext := i18n.WithLangTag(context.Background(), getLangBystr(langStr))
langNotifyGroup.Go(func() error {
return oc.notifyByContextLang(langContext, ctx, ids)
})
if err := oc.notifyByUserIds(ctx, userIds, langNotifyGroup); err != nil {
return errors.Wrapf(err, "notifyByUserIds with %v", userIds)
}
if len(oc.Setting.RobotIds) != 0 {
withLangTag := i18n.WithLangTag(context.Background(), language.English)
langNotifyGroup.Go(func() error {
@@ -198,6 +217,60 @@ func (oc *OneCloudNotifier) Notify(ctx *alerting.EvalContext, _ jsonutils.JSONOb
return langNotifyGroup.Wait()
}
func (oc *OneCloudNotifier) notifyByUserIds(ctx *alerting.EvalContext, userIds []string, errGrp *errgroup.Group) error {
langIdsMap, err := GetUserLangIdsMap(userIds)
if err != nil {
return errors.Wrapf(err, "GetUserLangIdsMap: %v", userIds)
}
for lang, _ := range langIdsMap {
ids := langIdsMap[lang]
langTag, _ := language.Parse(lang)
langStr := i18nTable.LookupByLang(langTag, SUFFIX)
langContext := i18n.WithLangTag(context.Background(), getLangBystr(langStr))
errGrp.Go(func() error {
return oc.notifyByContextLang(langContext, ctx, ids)
})
}
return nil
}
func getUsersByRoles(roleIds []string, roleScope string, scopeId string) ([]string, error) {
query := jsonutils.NewDict()
query.Set("roles", jsonutils.Marshal(roleIds))
query.Set("effective", jsonutils.JSONTrue)
switch roleScope {
case notiapi.SUBSCRIBER_SCOPE_SYSTEM:
case notiapi.SUBSCRIBER_SCOPE_DOMAIN:
if scopeId == "" {
return nil, errors.Errorf("need projectDomainId")
}
query.Set("project_domain_id", jsonutils.NewString(scopeId))
case notiapi.SUBSCRIBER_SCOPE_PROJECT:
if scopeId == "" {
return nil, errors.Errorf("need projectId")
}
query.Add(jsonutils.NewString(scopeId), "scope", "project", "id")
}
s := getAdminSession()
ret, err := modules.RoleAssignments.List(s, query)
if err != nil {
return nil, errors.Wrapf(err, "list RoleAssignments with query %s", query.String())
}
users := make([]string, 0)
for i := range ret.Data {
ras := ret.Data[i]
user, err := ras.Get("user")
if err == nil {
id, err := user.GetString("id")
if err != nil {
return nil, errors.Wrap(err, "unable to get user.id from result of RoleAssignments.List")
}
users = append(users, id)
}
}
return users, nil
}
func getLangBystr(str string) language.Tag {
for lang, val := range i18nEnTry {
if val == str {
@@ -303,7 +376,7 @@ func GetUserLangIdsMap(ids []string) (map[string][]string, error) {
if len(ids) == 0 {
return map[string][]string{}, nil
}
session := auth.GetAdminSession(context.Background(), "", "")
session := getAdminSession()
langIdsMap := make(map[string][]string)
params := jsonutils.NewDict()
params.Set("filter", jsonutils.NewString(fmt.Sprintf("id.in(%s)", strings.Join(ids, ","))))
+63 -13
View File
@@ -32,6 +32,7 @@ import (
"yunion.io/x/onecloud/pkg/apis"
"yunion.io/x/onecloud/pkg/apis/monitor"
notiapi "yunion.io/x/onecloud/pkg/apis/notify"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
@@ -146,6 +147,18 @@ func (man *SCommonAlertManager) UpdateAlertsResType(ctx context.Context, userCre
return errors.NewAggregate(errs)
}
func (man *SCommonAlertManager) validateRoles(ctx context.Context, roles []string) ([]string, error) {
ids := []string{}
for _, role := range roles {
roleCache, err := db.RoleCacheManager.FetchRoleByIdOrName(ctx, role)
if err != nil {
return nil, errors.Wrapf(err, "fetch role by id or name: %q", role)
}
ids = append(ids, roleCache.GetId())
}
return ids, nil
}
func (man *SCommonAlertManager) ValidateCreateData(
ctx context.Context, userCred mcclient.TokenCredential,
ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject,
@@ -168,6 +181,7 @@ func (man *SCommonAlertManager) ValidateCreateData(
//else {
// data.Channel = append(data.Channel, monitor.DEFAULT_SEND_NOTIFY_CHANNEL)
//}
if !utils.IsInStringArray(data.Level, monitor.CommonAlertLevels) {
return data, httperrors.NewInputParameterError("Invalid level format: %s", data.Level)
}
@@ -217,6 +231,22 @@ func (man *SCommonAlertManager) ValidateCreateData(
return data, errors.Wrap(err, "metric query error")
}
// validate role
if len(data.Roles) != 0 {
roleIds, err := man.validateRoles(ctx, data.Roles)
if err != nil {
return data, errors.Wrap(err, "validateRole")
}
data.Roles = roleIds
if !utils.IsInStringArray(data.Scope, []string{
notiapi.SUBSCRIBER_SCOPE_SYSTEM,
notiapi.SUBSCRIBER_SCOPE_DOMAIN,
notiapi.SUBSCRIBER_SCOPE_PROJECT,
}) {
return data, httperrors.NewInputParameterError("unsupport scope %s", data.Scope)
}
}
name, err := man.genName(ctx, ownerId, data.Name)
if err != nil {
return data, err
@@ -320,30 +350,47 @@ func (alert *SCommonAlert) customizeCreateNotis(ctx context.Context, userCred mc
if err := data.Unmarshal(input); err != nil {
return err
}
//user_by 弃用
// used_by 弃用
if input.AlertType == monitor.CommonAlertSystemAlertType {
return alert.createAlertNoti(ctx, userCred, input.Name, "webconsole", []string{}, nil, input.SilentPeriod, true)
s := &monitor.NotificationSettingOneCloud{
Channel: "webconsole",
}
return alert.createAlertNoti(ctx, userCred, input.Name, s, input.SilentPeriod, true)
}
for _, channel := range input.Channel {
err := alert.createAlertNoti(ctx, userCred, input.Name, channel, input.Recipients, nil, input.SilentPeriod, false)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("create notify[channel is %s]error", channel))
s := &monitor.NotificationSettingOneCloud{
Channel: channel,
UserIds: input.Recipients,
}
if err := alert.createAlertNoti(ctx, userCred, input.Name, s, input.SilentPeriod, false); err != nil {
return errors.Wrap(err, fmt.Sprintf("create notify[channel is %s] error", channel))
}
}
if len(input.RobotIds) != 0 {
err := alert.createAlertNoti(ctx, userCred, input.Name, string(notify.NotifyByRobot), []string{},
input.RobotIds,
input.SilentPeriod, false)
if err != nil {
return errors.Wrap(err, "create notify channel is robot error")
s := &monitor.NotificationSettingOneCloud{
Channel: string(notify.NotifyByRobot),
RobotIds: input.RobotIds,
}
if err := alert.createAlertNoti(ctx, userCred, input.Name, s, input.SilentPeriod, false); err != nil {
return errors.Wrapf(err, "create alert notification by robot %v", input.RobotIds)
}
}
if len(input.Roles) != 0 {
s := &monitor.NotificationSettingOneCloud{
RoleIds: input.Roles,
}
if err := alert.createAlertNoti(ctx, userCred, input.Name, s, input.SilentPeriod, false); err != nil {
return errors.Wrapf(err, "create alert notify by role %v, scope %q", input.Roles, input.Scope)
}
}
return nil
}
func (alert *SCommonAlert) createAlertNoti(ctx context.Context, userCred mcclient.TokenCredential, notiName, channel string, userIds []string, robotIds []string, silentPeriod string, isSysNoti bool) error {
noti, err := NotificationManager.CreateOneCloudNotification(ctx, userCred, notiName, channel, userIds, robotIds,
silentPeriod)
func (alert *SCommonAlert) createAlertNoti(ctx context.Context, userCred mcclient.TokenCredential, notiName string, settings *monitor.NotificationSettingOneCloud, silentPeriod string, isSysNoti bool) error {
noti, err := NotificationManager.CreateOneCloudNotification(ctx, userCred, notiName, settings, silentPeriod)
if err != nil {
return errors.Wrap(err, "create notification")
}
@@ -650,6 +697,9 @@ func (alert *SCommonAlert) GetMoreDetails(ctx context.Context, out monitor.Commo
if len(settings.RobotIds) != 0 {
out.RobotIds = settings.RobotIds
}
if len(settings.RoleIds) != 0 {
out.RoleIds = settings.RoleIds
}
}
out.Channel = channel.List()
out.Status = alert.GetStatus()
+5 -1
View File
@@ -84,7 +84,11 @@ func (v1man *SV1AlertManager) CreateNotification(
channel string,
recipients string) (*SNotification, error) {
userIds := strings.Split(recipients, ",")
return NotificationManager.CreateOneCloudNotification(ctx, userCred, alertName, channel, userIds, nil, "")
s := &monitor.NotificationSettingOneCloud{
Channel: channel,
UserIds: userIds,
}
return NotificationManager.CreateOneCloudNotification(ctx, userCred, alertName, s, "")
}
func (man *SNodeAlertManager) ValidateCreateData(
+1 -7
View File
@@ -160,13 +160,7 @@ func (man *SNotificationManager) ListItemFilter(ctx context.Context, q *sqlchemy
return q, err
}
func (man *SNotificationManager) CreateOneCloudNotification(ctx context.Context, userCred mcclient.TokenCredential, alertName string, channel string, userIds []string, robotIds []string, silentPeriod string) (*SNotification, error) {
settings := &monitor.NotificationSettingOneCloud{
Channel: channel,
UserIds: userIds,
RobotIds: robotIds,
}
func (man *SNotificationManager) CreateOneCloudNotification(ctx context.Context, userCred mcclient.TokenCredential, alertName string, settings *monitor.NotificationSettingOneCloud, silentPeriod string) (*SNotification, error) {
newName, err := db.GenerateName(ctx, man, userCred, alertName)
if err != nil {
return nil, errors.Wrapf(err, "generate name: %s", alertName)
+2
View File
@@ -38,7 +38,9 @@ func InitHandlers(app *appsrv.Application) {
db.RegisterModelManager(db.TenantCacheManager)
db.RegisterModelManager(db.UserCacheManager)
db.RegisterModelManager(db.RoleCacheManager)
db.RegistUserCredCacheUpdater()
for _, manager := range []db.IModelManager{
taskman.TaskManager,
taskman.SubTaskManager,