Merge pull request #5340 from zexi/feature/monitor-service

alert notification use joint model
This commit is contained in:
yunion-ci-robot
2020-03-04 19:58:08 +08:00
committed by GitHub
26 changed files with 763 additions and 384 deletions
+14
View File
@@ -46,6 +46,20 @@ func init() {
return nil
})
R(&options.AlertUpdateOptions{}, aN("update"), "Update a alert rule",
func(s *mcclient.ClientSession, args *options.AlertUpdateOptions) error {
params, err := args.Params()
if err != nil {
return err
}
ret, err := monitor.Alerts.Update(s, args.ID, params.JSON(params))
if err != nil {
return err
}
printObject(ret)
return nil
})
R(&options.AlertDeleteOptions{}, aN("delete"), "Delete alerts",
func(s *mcclient.ClientSession, args *options.AlertDeleteOptions) error {
ret := monitor.Alerts.BatchDelete(s, args.ID, nil)
+10 -13
View File
@@ -28,11 +28,11 @@ func init() {
if err != nil {
return err
}
ret, err := monitor.AlertNotifications.List(s, params)
ret, err := monitor.Notifications.List(s, params)
if err != nil {
return err
}
printList(ret, monitor.AlertNotifications.GetColumns(s))
printList(ret, monitor.Notifications.GetColumns(s))
return nil
})
@@ -43,7 +43,7 @@ func init() {
if err != nil {
return err
}
ret, err := monitor.AlertNotifications.Create(s, params.JSON(params))
ret, err := monitor.Notifications.Create(s, params.JSON(params))
if err != nil {
return err
}
@@ -58,7 +58,7 @@ func init() {
if err != nil {
return err
}
ret, err := monitor.AlertNotifications.Create(s, params.JSON(params))
ret, err := monitor.Notifications.Create(s, params.JSON(params))
if err != nil {
return err
}
@@ -68,7 +68,7 @@ func init() {
R(&options.NotificationShowOptions{}, nN("show"), "Show alert notification",
func(s *mcclient.ClientSession, args *options.NotificationShowOptions) error {
ret, err := monitor.AlertNotifications.Get(s, args.ID, nil)
ret, err := monitor.Notifications.Get(s, args.ID, nil)
if err != nil {
return err
}
@@ -82,7 +82,7 @@ func init() {
if err != nil {
return err
}
ret, err := monitor.AlertNotifications.Update(s, args.ID, params.JSON(params))
ret, err := monitor.Notifications.Update(s, args.ID, params.JSON(params))
if err != nil {
return err
}
@@ -90,13 +90,10 @@ func init() {
return nil
})
R(&options.NotificationShowOptions{}, nN("delete"), "Show delete notification",
func(s *mcclient.ClientSession, args *options.NotificationShowOptions) error {
ret, err := monitor.AlertNotifications.Delete(s, args.ID, nil)
if err != nil {
return err
}
printObject(ret)
R(&options.NotificationDeleteOptions{}, nN("delete"), "Show delete notification",
func(s *mcclient.ClientSession, args *options.NotificationDeleteOptions) error {
ret := monitor.Notifications.BatchDelete(s, args.ID, nil)
printBatchResults(ret, monitor.Notifications.GetColumns(s))
return nil
})
}
+20 -17
View File
@@ -78,9 +78,7 @@ func (s ExecutionErrorOption) ToAlertState() AlertStateType {
// AlertSettings contains alert conditions
type AlertSetting struct {
Conditions []AlertCondition `json:"conditions"`
Notifications []string `json:"notifications"`
Level string `json:"level"`
Conditions []AlertCondition `json:"conditions"`
}
type AlertCondition struct {
@@ -101,31 +99,36 @@ type AlertQuery struct {
type AlertCreateInput struct {
apis.Meta
Name string `json:"name"`
Frequency int64 `json:"frequency"`
Settings AlertSetting `json:"settings"`
Enabled *bool `json:"enabled"`
// 报警名称
Name string `json:"name"`
// 报警执行频率
Frequency int64 `json:"frequency"`
// 报警设置
Settings AlertSetting `json:"settings"`
// 启用报警
Enabled *bool `json:"enabled"`
// 报警级别
Level string `json:"level"`
}
type AlertUpdateInput struct {
apis.Meta
Name *string `json:"name"`
Frequency *int64 `json:"frequency"`
Settings *AlertSetting `json:"settings"`
ResourceId *string `json:"resource_id"`
ResourceType *string `json:"resource_type"`
Message *string `json:"message"`
Enabled *bool `json:"enabled"`
// 报警名称
Name *string `json:"name"`
// 报警执行频率
Frequency *int64 `json:"frequency"`
// 报警设置
Settings *AlertSetting `json:"settings"`
// 启用报警
Enabled *bool `json:"enabled"`
Message *string `json:"message"`
}
type AlertListInput struct {
apis.VirtualResourceListInput
apis.EnabledResourceBaseListInput
// 监控指标名称
Metric string `json:"metric"`
// 以报警是否启用/禁用过滤列表
// Enabled *bool `json:"enabled"`
}
+14 -1
View File
@@ -46,7 +46,7 @@ type MeterAlertListInput struct {
Provider string `json:"provider"`
// 云账号 Id
AccountId string `json:"account_id"`
// 项目 Id string
// 项目 Id
ProjectId string `json:"project_id"`
}
@@ -58,3 +58,16 @@ type MeterAlertDetails struct {
AccountId string `json:"account_id"`
Provider string `json:"provider"`
}
type MeterAlertUpdateInput struct {
// 比较运算符, 比如: >, <, >=, <=
Comparator *string `json:"comparator"`
// 报警阀值
Threshold *float64 `json:"threshold"`
// 通知接受者
Recipients *string `json:"recipients"`
// 项目 Id
ProjectId *string `json:"project_id"`
// 通知方式, 比如: email, mobile
Channel *string `json:"channel"`
}
+3 -8
View File
@@ -26,7 +26,7 @@ const (
)
type ResourceAlertV1CreateInput struct {
*AlertCreateInput
AlertCreateInput
// 查询指标周期
Period string `json:"period"`
@@ -36,8 +36,6 @@ type ResourceAlertV1CreateInput struct {
Comparator string `json:"comparator"`
// 报警阀值
Threshold float64 `json:"threshold"`
// 报警级别
Level string `json:"level"`
// 通知方式, 比如: email, mobile
Channel string `json:"channel"`
// 通知接受者
@@ -61,15 +59,13 @@ func (input NodeAlertCreateInput) ToAlertCreateInput(
name string,
field string,
measurement string,
db string,
notifications []string) AlertCreateInput {
db string) AlertCreateInput {
freq, _ := time.ParseDuration(input.Window)
ret := AlertCreateInput{
Name: name,
Frequency: int64(freq / time.Second),
Level: input.Level,
Settings: AlertSetting{
Level: input.Level,
Notifications: notifications,
Conditions: []AlertCondition{
{
Type: "query",
@@ -157,7 +153,6 @@ type NodeAlertListInput struct {
func (input NodeAlertListInput) ToAlertListInput() AlertListInput {
return AlertListInput{
VirtualResourceListInput: input.VirtualResourceListInput,
Metric: input.Metric,
}
}
+2 -11
View File
@@ -36,7 +36,7 @@ const (
AlertNotificationTypeFeishu = "feishu"
)
type AlertNotificationCreateInput struct {
type NotificationCreateInput struct {
apis.Meta
// 报警通知名称
@@ -55,7 +55,7 @@ type AlertNotificationCreateInput struct {
Settings jsonutils.JSONObject `json:"settings"`
}
type AlertNotificationUpdateInput struct {
type NotificationUpdateInput struct {
apis.Meta
// 报警通知名称
@@ -95,12 +95,3 @@ type NotificationSettingFeishu struct {
AppId string `json:"app_id"`
AppSecret string `json:"app_secret"`
}
type AlertNotificationStateCreateInput struct {
apis.Meta
Name string `json:"name"`
AlertId string `json:"alert_id"`
NotifierId string `json:"notifier_id"`
State AlertNotificationStateType `json:"state"`
}
+1
View File
@@ -24,6 +24,7 @@ type NotificationTemplateConfig struct {
Matches []EvalMatch `json:"matches"`
// PrevAlertState AlertStateType `json:"prev_alert_state"`
// State AlertStateType `json:"state"`
NoDataFound bool `json:"no_data"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
Description string `json:"description"`
+5 -5
View File
@@ -20,21 +20,21 @@ import (
)
var (
Alerts modulebase.ResourceManager
AlertNotifications modulebase.ResourceManager
Alerts modulebase.ResourceManager
Notifications modulebase.ResourceManager
)
func init() {
Alerts = modules.NewMonitorV2Manager("alert", "alerts",
[]string{"id", "name", "settings"},
[]string{"id", "name", "state", "frequency", "enabled", "settings"},
[]string{})
AlertNotifications = modules.NewMonitorV2Manager(
Notifications = modules.NewMonitorV2Manager(
"alert_notification", "alert_notifications",
[]string{"id", "name", "type", "is_default", "disable_resolve_message", "send_reminder", "settings"},
[]string{})
for _, m := range []modulebase.ResourceManager{
Alerts,
AlertNotifications,
Notifications,
} {
modules.Register(&m)
}
+57
View File
@@ -0,0 +1,57 @@
// 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 monitor
import (
"fmt"
"time"
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
type AlertListOptions struct {
options.BaseListOptions
}
type AlertShowOptions struct {
ID string `help:"ID or name of the alert" json:"-"`
}
type AlertDeleteOptions struct {
ID []string `help:"ID of alert to delete"`
}
type AlertUpdateOptions struct {
ID string `help:"ID or name of the alert"`
Name string `help:"Update alert name"`
Frequency string `help:"Alert execute frequency, e.g. '5m', '1h'"`
}
func (opt AlertUpdateOptions) Params() (*monitor.AlertUpdateInput, error) {
input := new(monitor.AlertUpdateInput)
if opt.Name != "" {
input.Name = &opt.Name
}
if opt.Frequency != "" {
freq, err := time.ParseDuration(opt.Frequency)
if err != nil {
return nil, fmt.Errorf("Invalid frequency time format %s: %v", opt.Frequency, err)
}
f := int64(freq / time.Second)
input.Frequency = &f
}
return input, nil
}
+10 -18
View File
@@ -41,6 +41,10 @@ type NotificationShowOptions struct {
ID string `help:"ID or name of the alert notification config" json:"-"`
}
type NotificationDeleteOptions struct {
ID []string `help:"ID or name of the alert notification config" json:"-"`
}
type NotificationFields struct {
Frequency string `help:"notify frequency, e.g. 5m, 1h"`
IsDefault *bool `help:"set as default notification"`
@@ -53,8 +57,8 @@ type NotificationCreateOptions struct {
NotificationFields
}
func (opt NotificationCreateOptions) Params() (*monitor.AlertNotificationCreateInput, error) {
ret := &monitor.AlertNotificationCreateInput{
func (opt NotificationCreateOptions) Params() (*monitor.NotificationCreateInput, error) {
ret := &monitor.NotificationCreateInput{
Name: opt.NAME,
SendReminder: opt.SendReminder,
DisableResolveMessage: opt.DisableResolveMessage,
@@ -71,7 +75,7 @@ type NotificationDingDingCreateOptions struct {
MsgType string `help:"message type" choices:"markdown|actionCard" default:"markdown"`
}
func (opt NotificationDingDingCreateOptions) Params() (*monitor.AlertNotificationCreateInput, error) {
func (opt NotificationDingDingCreateOptions) Params() (*monitor.NotificationCreateInput, error) {
out, err := opt.NotificationCreateOptions.Params()
if err != nil {
return nil, err
@@ -90,7 +94,7 @@ type NotificationFeishuCreateOptions struct {
APPSECRET string `help:"feishu robt appSecret"`
}
func (opt NotificationFeishuCreateOptions) Params() (*monitor.AlertNotificationCreateInput, error) {
func (opt NotificationFeishuCreateOptions) Params() (*monitor.NotificationCreateInput, error) {
out, err := opt.NotificationCreateOptions.Params()
if err != nil {
return nil, err
@@ -112,7 +116,7 @@ type NotificationUpdateOptions struct {
DisableSendReminder *bool `help:"disable send reminder" json:"-"`
}
func (opt NotificationUpdateOptions) Params() (*monitor.AlertNotificationUpdateInput, error) {
func (opt NotificationUpdateOptions) Params() (*monitor.NotificationUpdateInput, error) {
if opt.DisableDefault != nil && *opt.DisableDefault {
tmp := false
opt.IsDefault = &tmp
@@ -125,22 +129,10 @@ func (opt NotificationUpdateOptions) Params() (*monitor.AlertNotificationUpdateI
tmp := false
opt.SendReminder = &tmp
}
ret := &monitor.AlertNotificationUpdateInput{
ret := &monitor.NotificationUpdateInput{
IsDefault: opt.IsDefault,
DisableResolveMessage: opt.DisableResolveMessage,
SendReminder: opt.SendReminder,
}
return ret, nil
}
type AlertListOptions struct {
options.BaseListOptions
}
type AlertShowOptions struct {
ID string `help:"ID or name of the alert" json:"-"`
}
type AlertDeleteOptions struct {
ID []string `help:"ID of alert to delete"`
}
+3 -1
View File
@@ -16,6 +16,7 @@ package alerting
import (
"context"
"fmt"
"time"
"yunion.io/x/log"
@@ -83,7 +84,7 @@ func (c *EvalContext) GetStateModel() *StateDescription {
Text: "Unknown",
}
default:
panic("Unknown rule state for alert " + c.Rule.State)
panic(fmt.Sprintf("Unknown rule state %q for alert %s", c.Rule.State, c.Rule.Name))
}
}
@@ -177,6 +178,7 @@ func (c *EvalContext) GetNotificationTemplateConfig() monitor.NotificationTempla
EndTime: c.EndTime.Format(time.RFC3339),
Description: desc,
Level: c.Rule.Level,
NoDataFound: c.NoDataFound,
}
}
+1 -1
View File
@@ -50,5 +50,5 @@ type Notifier interface {
Notify(evalContext *EvalContext) error
// ShouldNotify checks this evaluation should send an alert notification
ShouldNotify(ctx context.Context, evalContext *EvalContext, notificationState *models.SAlertNotificationState) bool
ShouldNotify(ctx context.Context, evalContext *EvalContext, notificationState *models.SAlertnotification) bool
}
+16 -7
View File
@@ -15,6 +15,7 @@
package alerting
import (
"database/sql"
"time"
"yunion.io/x/log"
@@ -48,7 +49,7 @@ func (n *notificationService) SendIfNeeded(evalCtx *EvalContext) error {
type notifierState struct {
notifier Notifier
state *models.SAlertNotificationState
state *models.SAlertnotification
}
type notifierStateSlice []*notifierState
@@ -92,7 +93,7 @@ func (n *notificationService) sendNotifications(evalCtx *EvalContext, states not
}
func (n *notificationService) getNeededNotifiers(nIds []string, evalCtx *EvalContext) (notifierStateSlice, error) {
notis, err := models.AlertNotificationManager.GetNotificationsWithDefault(nIds)
notis, err := models.NotificationManager.GetNotificationsWithDefault(nIds)
if err != nil {
return nil, err
}
@@ -109,13 +110,21 @@ func (n *notificationService) getNeededNotifiers(nIds []string, evalCtx *EvalCon
Settings: obj.Settings,
})
if err != nil {
log.Errorf("Could not creat enotifier %s, error: %v", obj.GetId(), err)
log.Errorf("Could not create notifier %s, error: %v", obj.GetId(), err)
continue
}
state, err := models.AlertNotificationStateManager.GetOrCreateState(evalCtx.Ctx, evalCtx.UserCred, evalCtx.Rule.Id, obj.GetId())
state, err := models.AlertNotificationManager.Get(evalCtx.Rule.Id, obj.GetId())
if err != nil {
log.Errorf("Get alert state: %v, alertId %s, notifierId: %s", err, evalCtx.Rule.Id, obj.GetId())
continue
if errors.Cause(err) == sql.ErrNoRows {
state, err = obj.AttachToAlert(evalCtx.Ctx, evalCtx.UserCred, evalCtx.Rule.Id)
if err != nil {
log.Errorf("Attach notification %s to alert %s error: %v", obj.GetName(), evalCtx.Rule.Id, err)
continue
}
} else {
log.Errorf("Get alert state: %v, alertId %s, notifierId: %s", err, evalCtx.Rule.Id, obj.GetId())
continue
}
}
if not.ShouldNotify(evalCtx.Ctx, evalCtx, state) {
@@ -132,7 +141,7 @@ func (n *notificationService) getNeededNotifiers(nIds []string, evalCtx *EvalCon
type NotifierPlugin struct {
Type string
Factory NotifierFactory
ValidateCreateData func(cred mcclient.IIdentityProvider, input monitor.AlertNotificationCreateInput) (monitor.AlertNotificationCreateInput, error)
ValidateCreateData func(cred mcclient.IIdentityProvider, input monitor.NotificationCreateInput) (monitor.NotificationCreateInput, error)
}
type NotificationConfig notifydrivers.NotificationConfig
+1 -1
View File
@@ -48,7 +48,7 @@ func NewNotifierBase(config alerting.NotificationConfig) NotifierBase {
}
// ShouldNotify checks this evaluation should send an alert notification
func (n *NotifierBase) ShouldNotify(_ context.Context, evalCtx *alerting.EvalContext, state *models.SAlertNotificationState) bool {
func (n *NotifierBase) ShouldNotify(_ context.Context, evalCtx *alerting.EvalContext, state *models.SAlertnotification) bool {
prevState := evalCtx.PrevAlertState
newState := evalCtx.Rule.State
+1 -1
View File
@@ -41,7 +41,7 @@ func init() {
alerting.RegisterNotifier(&alerting.NotifierPlugin{
Type: monitor.AlertNotificationTypeDingding,
Factory: newDingdingNotifier,
ValidateCreateData: func(cred mcclient.IIdentityProvider, input monitor.AlertNotificationCreateInput) (monitor.AlertNotificationCreateInput, error) {
ValidateCreateData: func(cred mcclient.IIdentityProvider, input monitor.NotificationCreateInput) (monitor.NotificationCreateInput, error) {
settings := new(monitor.NotificationSettingDingding)
if err := input.Settings.Unmarshal(settings); err != nil {
return input, errors.Wrap(err, "unmarshal setting")
+1 -1
View File
@@ -34,7 +34,7 @@ func init() {
alerting.RegisterNotifier(&alerting.NotifierPlugin{
Type: monitor.AlertNotificationTypeFeishu,
Factory: newFeishuNotifier,
ValidateCreateData: func(cred mcclient.IIdentityProvider, input monitor.AlertNotificationCreateInput) (monitor.AlertNotificationCreateInput, error) {
ValidateCreateData: func(cred mcclient.IIdentityProvider, input monitor.NotificationCreateInput) (monitor.NotificationCreateInput, error) {
settings := new(monitor.NotificationSettingFeishu)
if err := input.Settings.Unmarshal(settings); err != nil {
return input, errors.Wrap(err, "unmarshal setting")
+3 -2
View File
@@ -37,7 +37,7 @@ func init() {
alerting.RegisterNotifier(&alerting.NotifierPlugin{
Type: monitor.AlertNotificationTypeOneCloud,
Factory: newOneCloudNotifier,
ValidateCreateData: func(cred mcclient.IIdentityProvider, input monitor.AlertNotificationCreateInput) (monitor.AlertNotificationCreateInput, error) {
ValidateCreateData: func(cred mcclient.IIdentityProvider, input monitor.NotificationCreateInput) (monitor.NotificationCreateInput, error) {
settings := new(monitor.NotificationSettingOneCloud)
if err := input.Settings.Unmarshal(settings); err != nil {
return input, errors.Wrap(err, "unmarshal setting")
@@ -99,6 +99,8 @@ func GetNotifyTemplateConfig(ctx *alerting.EvalContext) monitor.NotificationTemp
if ctx.Rule.State == monitor.AlertStateOK {
isRecovery = true
topic = fmt.Sprintf("%s %s 告警已恢复", topic, ctx.GetRuleTitle())
} else if ctx.NoDataFound {
topic = fmt.Sprintf("%s %s 暂无数据", topic, ctx.GetRuleTitle())
} else {
topic = fmt.Sprintf("%s %s 发生告警", topic, ctx.GetRuleTitle())
}
@@ -128,7 +130,6 @@ func (oc *OneCloudNotifier) Notify(ctx *alerting.EvalContext) error {
Msg: content,
}
log.Errorf("---send msg: %s", jsonutils.Marshal(msg))
return notify.Notifications.Send(oc.session, msg)
}
+10 -2
View File
@@ -121,8 +121,16 @@ func NewRuleFromDBAlert(ruleDef *models.SAlert) (*Rule, error) {
return nil, err
}
model.Level = settings.Level
model.Notifications = settings.Notifications
model.Level = ruleDef.Level
nIds := []string{}
notis, err := ruleDef.GetNotifications()
if err != nil {
return nil, err
}
for _, n := range notis {
nIds = append(nIds, n.NotificationId)
}
model.Notifications = nIds
// model.AlertRuleTags = ruleDef.GetTagsFromSettings()
for index, condition := range settings.Conditions {
+150 -30
View File
@@ -21,12 +21,14 @@ import (
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/apis"
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/monitor/validators"
@@ -77,20 +79,49 @@ type SAlert struct {
Frequency int64 `nullable:"false" list:"user" create:"required" update:"user"`
Settings jsonutils.JSONObject `nullable:"false" list:"user" create:"required" update:"user"`
// Enabled bool `nullable:"false" default:"false" list:"user" create:"optional"`
Level string `charset:"ascii" width:"36"nullable:"false" default:"normal" list:"user"`
Message string `charset:"utf8" list:"user" update:"user"`
UsedBy string `charset:"ascii" list:"user"`
Message string `charset:"utf8" list:"user" update:"user"`
State string `width:"36" charset:"ascii" list:"user"`
// Silenced bool
ExecutionError string `charset:"utf8" list:"user"`
For int64 `nullable:"false" list:"user"`
ExecutionError string `charset:"utf8" list:"user"`
For int64 `nullable:"false" list:"user"`
EvalData jsonutils.JSONObject `list:"user" list:"user"`
State string `width:"36" charset:"ascii" nullable:"false" default:"unknown" list:"user"`
NoDataState string `width:"36" charset:"ascii" nullable:"false" default:"pending" list:"user"`
ExecutionErrorState string `width:"36" charset:"ascii" nullable:"false" default:"alerting" list:"user"`
LastStateChange time.Time `list:"user"`
StateChanges int `default:"0" nullable:"false" list:"user"`
}
EvalData jsonutils.JSONObject `list:"user"`
LastStateChange time.Time `json:"last_state_change" list:"user"`
StateChanges int `default:"0" nullable:"false" list:"user" json:"state_changes"`
func (alert *SAlert) IsEnable() bool {
return alert.Enabled.Bool()
}
NoDataState string `charset:"utf8" list:"user"`
ExecutionErrorState string `charset:"utf8" list:"user"`
func (alert *SAlert) SetEnable() error {
_, err := db.Update(alert, func() error {
alert.SetEnabled(true)
alert.State = string(monitor.AlertStatePending)
return nil
})
return err
}
func (alert *SAlert) SetDisable() error {
_, err := db.Update(alert, func() error {
alert.SEnabledResourceBase.SetEnabled(false)
alert.State = string(monitor.AlertStatePaused)
return nil
})
return err
}
func (alert *SAlert) SetUsedBy(usedBy string) error {
_, err := db.Update(alert, func() error {
alert.UsedBy = usedBy
return nil
})
return err
}
func (alert *SAlert) SetTitle(ctx context.Context, t string) error {
@@ -251,22 +282,6 @@ func (alert *SAlert) PerformDisable(ctx context.Context, userCred mcclient.Token
return nil, nil
}
func (alert *SAlert) GetNotifications() ([]SAlertNotification, error) {
settings, err := alert.GetSettings()
if err != nil {
return nil, errors.Wrap(err, "get settings")
}
nIds := settings.Notifications
notis, err := AlertNotificationManager.GetNotifications(nIds)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
return notis, nil
}
const (
ErrAlertChannotChangeStateOnPaused = errors.Error("Cannot change state on pause alert")
)
@@ -296,10 +311,115 @@ func (alert *SAlert) SetState(input AlertSetStateInput) error {
}
func (alert *SAlert) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input monitor.AlertUpdateInput) (*jsonutils.JSONDict, error) {
if input.Enabled == nil {
enable := true
input.Enabled = &enable
if input.Settings != nil {
if err := jsonutils.Update(alert.Settings, jsonutils.Marshal(input.Settings)); err != nil {
return nil, err
}
if err := jsonutils.Update(input.Settings, alert.Settings); err != nil {
return nil, err
}
}
input.Settings = setAlertDefaultSetting(input.Settings, "")
return alert.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, input.JSON(input))
}
func (alert *SAlert) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
input := new(monitor.AlertUpdateInput)
if err := data.Unmarshal(input); err != nil {
log.Errorf("update unmarshal error: %v", err)
return
}
if _, err := db.Update(alert, func() error {
alert.Settings = jsonutils.Marshal(input)
return nil
}); err != nil {
log.Errorf("update setting error: %v", err)
}
}
func (alert *SAlert) IsAttachNotification(noti *SNotification) (bool, error) {
q := AlertNotificationManager.Query().Equals("notification_id", noti.GetId()).Equals("alert_id", alert.GetId())
cnt, err := q.CountWithError()
if err != nil {
return false, err
}
return cnt > 0, nil
}
func (alert *SAlert) GetNotificationsQuery() *sqlchemy.SQuery {
return AlertNotificationManager.Query().Equals("alert_id", alert.GetId())
}
func (alert *SAlert) GetNotifications() ([]SAlertnotification, error) {
notis := make([]SAlertnotification, 0)
q := alert.GetNotificationsQuery().Asc("index")
if err := db.FetchModelObjects(AlertNotificationManager, q, &notis); err != nil {
return nil, err
}
return notis, nil
}
func (alert *SAlert) getNotificationIndex() (int8, error) {
notis, err := alert.GetNotifications()
if err != nil {
return -1, err
}
var max uint
for i := 0; i < len(notis); i++ {
if uint(notis[i].Index) > max {
max = uint(notis[i].Index)
}
}
idxs := make([]int, max+1)
for i := 0; i < len(notis); i++ {
idxs[notis[i].Index] = 1
}
// find first idx not set
for i := 0; i < len(idxs); i++ {
if idxs[i] != 1 {
return int8(i), nil
}
}
return int8(max + 1), nil
}
func (alert *SAlert) AttachNotification(
ctx context.Context,
userCred mcclient.TokenCredential,
noti *SNotification,
state monitor.AlertNotificationStateType,
usedBy string) (*SAlertnotification, error) {
attached, err := alert.IsAttachNotification(noti)
if err != nil {
return nil, err
}
if attached {
return nil, httperrors.NewNotAcceptableError("alert already attached to notification")
}
defer lockman.ReleaseObject(ctx, alert)
lockman.LockObject(ctx, alert)
alertNoti := new(SAlertnotification)
alertNoti.AlertId = alert.GetId()
alertNoti.Index, err = alert.getNotificationIndex()
alertNoti.NotificationId = noti.GetId()
if err != nil {
return nil, err
}
alertNoti.State = string(state)
alertNoti.UsedBy = usedBy
if err := alertNoti.DoSave(ctx, userCred); err != nil {
return nil, err
}
return alertNoti, nil
}
func (alert *SAlert) SetFor(forTime time.Duration) error {
_, err := db.Update(alert, func() error {
alert.For = int64(forTime)
return nil
})
return err
}
+46
View File
@@ -0,0 +1,46 @@
// 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 models
import "yunion.io/x/onecloud/pkg/cloudcommon/db"
type SAlertJointsManager struct {
db.SVirtualJointResourceBaseManager
}
func NewAlertJointsManager(
dt interface{}, tableName string,
keyword string, keywordPlural string,
slave db.IVirtualModelManager) SAlertJointsManager {
return SAlertJointsManager{
db.NewVirtualJointResourceBaseManager(
dt, tableName, keyword, keywordPlural, AlertManager, slave),
}
}
type SAlertJointsBase struct {
db.SVirtualJointResourceBase
AlertId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true"`
}
func (b *SAlertJointsBase) getAlert() *SAlert {
alert, _ := AlertManager.GetAlert(b.AlertId)
return alert
}
func (man *SAlertJointsManager) GetMasterFieldName() string {
return "alert_id"
}
+135
View File
@@ -0,0 +1,135 @@
// 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 models
import (
"context"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient"
)
const (
AlertNotificationUsedByMeterAlert = "meter_alert"
AlertNotificationUsedByNodeAlert = "node_alert"
)
type SAlertNotificationManager struct {
SAlertJointsManager
}
var AlertNotificationManager *SAlertNotificationManager
func init() {
db.InitManager(func() {
AlertNotificationManager = &SAlertNotificationManager{
SAlertJointsManager: NewAlertJointsManager(
SAlertnotification{},
"alertnotifications_tbl",
"alertnotification",
"alertnotifications",
NotificationManager),
}
AlertNotificationManager.SetVirtualObject(AlertNotificationManager)
AlertNotificationManager.TableSpec().AddIndex(true, "notification_id", "alert_id")
})
}
type SAlertnotification struct {
SAlertJointsBase
NotificationId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"`
State string `nullable:"false" list:"user" create:"required"`
Index int8 `nullable:"false" default:"0" list:"user" list:"user" update:"user"`
UsedBy string `width:"36" charset:"ascii" nullable:"true" list:"user"`
}
func (man *SAlertNotificationManager) GetSlaveFieldName() string {
return "notification_id"
}
func (man *SAlertNotificationManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return false
}
func (man *SAlertNotificationManager) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return false
}
func (man *SAlertNotificationManager) Get(alertId string, notiId string) (*SAlertnotification, error) {
q := man.Query().Equals("alert_id", alertId).Equals("notification_id", notiId)
obj := new(SAlertnotification)
err := q.First(obj)
obj.SetModelManager(man, obj)
return obj, err
}
func (joint *SAlertnotification) DoSave(ctx context.Context, userCred mcclient.TokenCredential) error {
if err := AlertNotificationManager.TableSpec().Insert(joint); err != nil {
return err
}
joint.SetModelManager(AlertNotificationManager, joint)
return nil
}
func (joint *SAlertnotification) Master() db.IStandaloneModel {
return db.JointMaster(joint)
}
func (joint *SAlertnotification) Slave() db.IStandaloneModel {
return db.JointSlave(joint)
}
func (joint *SAlertnotification) GetNotification() (*SNotification, error) {
noti, err := NotificationManager.GetNotification(joint.NotificationId)
if err != nil {
return nil, err
}
return noti, nil
}
func (joint *SAlertnotification) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
return db.DeleteModel(ctx, userCred, joint)
}
func (joint *SAlertnotification) Detach(ctx context.Context, userCred mcclient.TokenCredential) error {
return db.DetachJoint(ctx, userCred, joint)
}
func (joint *SAlertnotification) GetUsedBy() string {
return joint.UsedBy
}
func (state *SAlertnotification) SetToPending() error {
return state.setState(monitor.AlertNotificationStatePending)
}
func (state *SAlertnotification) SetToCompleted() error {
return state.setState(monitor.AlertNotificationStateCompleted)
}
func (state *SAlertnotification) setState(changeState monitor.AlertNotificationStateType) error {
_, err := db.Update(state, func() error {
state.State = string(changeState)
return nil
})
return err
}
func (state *SAlertnotification) GetState() monitor.AlertNotificationStateType {
return monitor.AlertNotificationStateType(state.State)
}
+128 -29
View File
@@ -48,7 +48,8 @@ func init() {
type IMeterAlertDriver interface {
GetType() string
GetName() string
ToAlertCreateInput(input monitor.MeterAlertCreateInput, notificatoins []string, allAccountIds []string) monitor.AlertCreateInput
GetFor() time.Duration
ToAlertCreateInput(input monitor.MeterAlertCreateInput, dsId string, allAccountIds []string, level string) monitor.AlertCreateInput
}
type SMeterAlertManager struct {
@@ -139,18 +140,6 @@ func (man *SMeterAlertManager) ValidateCreateData(
if _, err := time.ParseDuration(data.Period); err != nil {
return nil, httperrors.NewInputParameterError("Invalid period format: %s", data.Period)
}
if data.Recipients == "" {
return nil, httperrors.NewInputParameterError("recipients is empty")
}
notification, err := man.CreateNotification(ctx, userCred, data.Type, data.Channel, data.Recipients)
if err != nil {
return nil, errors.Wrap(err, "create notification")
}
if data.ProjectId == "" {
return nil, httperrors.NewInputParameterError("project_id is empty")
}
drv := man.GetDriver(data.Type)
if drv == nil {
return nil, httperrors.NewInputParameterError("not support type %q", data.Type)
@@ -166,15 +155,22 @@ func (man *SMeterAlertManager) ValidateCreateData(
return nil, err
}
}
alertInput := drv.ToAlertCreateInput(
data, []string{notification.GetId()},
allAccountIds)
if data.Recipients == "" {
return nil, httperrors.NewInputParameterError("recipients is empty")
}
ds, err := DataSourceManager.GetDefaultSource()
if err != nil {
return nil, err
}
alertInput := drv.ToAlertCreateInput(data, ds.GetId(), allAccountIds, data.Level)
alertInput, err = AlertManager.ValidateCreateData(ctx, userCred, ownerId, query, alertInput)
if err != nil {
return nil, err
}
data.Name = name
data.AlertCreateInput = &alertInput
data.AlertCreateInput = alertInput
return &data, nil
}
@@ -188,16 +184,23 @@ func (_ *sMeterDailyFee) GetName() string {
return "日消费"
}
func (_ *sMeterDailyFee) GetFor() time.Duration {
return 12 * time.Hour
}
func (f *sMeterDailyFee) ToAlertCreateInput(
input monitor.MeterAlertCreateInput,
notifications []string,
dsId string,
allAccountIds []string,
level string,
) monitor.AlertCreateInput {
freq, _ := time.ParseDuration(input.Window)
ret := monitor.AlertCreateInput{
Name: f.GetName(),
Level: level,
Frequency: int64(freq / time.Second),
Settings: GetMeterAlertSetting(input, notifications,
Settings: GetMeterAlertSetting(input,
dsId,
"account_daily_resfee",
"meter_db", allAccountIds, "sumDate"),
}
@@ -214,16 +217,23 @@ func (_ *sMeterMonthFee) GetName() string {
return "月消费"
}
func (_ *sMeterMonthFee) GetFor() time.Duration {
return 24 * time.Hour
}
func (f *sMeterMonthFee) ToAlertCreateInput(
input monitor.MeterAlertCreateInput,
notifications []string,
dsId string,
allAccountIds []string,
level string,
) monitor.AlertCreateInput {
freq, _ := time.ParseDuration(input.Window)
ret := monitor.AlertCreateInput{
Name: f.GetName(),
Level: level,
Frequency: int64(freq / time.Second),
Settings: GetMeterAlertSetting(input, notifications,
Settings: GetMeterAlertSetting(input,
dsId,
"account_month_resfee",
"meter_db", allAccountIds, "sumMonth"),
}
@@ -232,7 +242,7 @@ func (f *sMeterMonthFee) ToAlertCreateInput(
func GetMeterAlertSetting(
input monitor.MeterAlertCreateInput,
ns []string,
dsId string,
measurement string,
db string,
accountIds []string,
@@ -240,16 +250,15 @@ func GetMeterAlertSetting(
) monitor.AlertSetting {
q, reducer, eval := GetMeterAlertQuery(input, measurement, db, accountIds, groupByStr)
return monitor.AlertSetting{
Level: input.Level,
Notifications: ns,
Conditions: []monitor.AlertCondition{
{
Type: "query",
Operator: "and",
Query: monitor.AlertQuery{
Model: q,
From: input.Period,
To: "now",
Model: q,
From: input.Period,
To: "now",
DataSourceId: dsId,
},
Reducer: reducer,
Evaluator: eval,
@@ -306,7 +315,7 @@ func GetMeterAlertQuery(
})
}
log.Debugf("==alertType: %s", alertType)
log.Debugf("meteralert alertType: %s", alertType)
if input.ProjectId != "" {
filters = append(filters, monitor.MetricQueryTag{
@@ -344,6 +353,18 @@ func (man *SMeterAlertManager) GetAlert(id string) (*SMeterAlert, error) {
return obj.(*SMeterAlert), nil
}
func (man *SMeterAlertManager) ListItemFilter(
ctx context.Context, q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
query monitor.MeterAlertListInput) (*sqlchemy.SQuery, error) {
q, err := AlertManager.ListItemFilter(ctx, q, userCred, monitor.AlertListInput{})
if err != nil {
return nil, err
}
q.Equals("used_by", AlertNotificationUsedByMeterAlert)
return q, nil
}
func (man *SMeterAlertManager) CustomizeFilterList(
ctx context.Context, q *sqlchemy.SQuery,
userCred mcclient.TokenCredential, query jsonutils.JSONObject) (
@@ -397,6 +418,17 @@ func (man *SMeterAlertManager) CustomizeFilterList(
return filters, nil
}
func (alert *SMeterAlert) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
if err := alert.SVirtualResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data); err != nil {
return err
}
input := new(monitor.MeterAlertCreateInput)
if err := data.Unmarshal(input); err != nil {
return err
}
return alert.SV1Alert.CustomizeCreate(ctx, userCred, input.Type, input.Channel, input.Recipients, AlertNotificationUsedByMeterAlert)
}
func (alert *SMeterAlert) setType(ctx context.Context, userCred mcclient.TokenCredential, t string) error {
return alert.SetMetadata(ctx, MeterAlertMetadataType, t, userCred)
}
@@ -458,12 +490,16 @@ func (alert *SMeterAlert) PostCreate(ctx context.Context,
log.Errorf("set project_id: %v", err)
}
}
forTime := MeterAlertManager.GetDriver(alert.getType()).GetFor()
if err := alert.SetFor(forTime); err != nil {
log.Errorf("set for error: %v", err)
}
}
func (alert *SMeterAlert) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, isList bool) (monitor.MeterAlertDetails, error) {
var err error
out := monitor.MeterAlertDetails{}
commonDetails, err := alert.SV1Alert.GetExtraDetails(ctx, userCred, query, isList)
commonDetails, err := alert.SV1Alert.GetExtraDetails(ctx, userCred, query, isList, AlertNotificationUsedByMeterAlert)
if err != nil {
return out, err
}
@@ -476,3 +512,66 @@ func (alert *SMeterAlert) GetExtraDetails(ctx context.Context, userCred mcclient
return out, nil
}
func (alert *SMeterAlert) ValidateUpdateData(
ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, input monitor.MeterAlertUpdateInput) (*jsonutils.JSONDict, error) {
ret := new(monitor.AlertUpdateInput)
details, err := alert.GetExtraDetails(ctx, userCred, query, false)
if err != nil {
return nil, err
}
if input.Threshold != nil && *input.Threshold != details.Threshold {
details.Threshold = *input.Threshold
}
if input.Comparator != nil && *input.Comparator != details.Comparator {
details.Comparator = *input.Comparator
}
ds, err := DataSourceManager.GetDefaultSource()
if err != nil {
return nil, errors.Wrap(err, "get default data source")
}
// hack: update notification here
if err := alert.UpdateNotification(AlertNotificationUsedByMeterAlert, input.Channel, input.Recipients); err != nil {
return nil, errors.Wrap(err, "update notification")
}
allAccountIds := []string{}
if details.AccountId == "" {
allAccountIds, err = MeterAlertManager.getAllBillAccountIds(ctx)
if err != nil {
return nil, err
}
}
tmpS := alert.getUpdateSetting(details, ds.GetId(), allAccountIds)
ret.Settings = &tmpS
return alert.SAlert.ValidateUpdateData(ctx, userCred, query, *ret)
}
func (alert *SMeterAlert) getUpdateSetting(details monitor.MeterAlertDetails, dsId string, accountIds []string) monitor.AlertSetting {
drv := MeterAlertManager.GetDriver(alert.getType())
input := monitor.MeterAlertCreateInput{
ResourceAlertV1CreateInput: monitor.ResourceAlertV1CreateInput{
Period: details.Period,
Window: details.Window,
Comparator: details.Comparator,
Threshold: details.Threshold,
Channel: details.Channel,
Recipients: details.Recipients,
},
Type: details.Type,
Provider: details.Provider,
ProjectId: details.ProjectId,
AccountId: details.AccountId,
}
input.Level = details.Level
out := drv.ToAlertCreateInput(input, dsId, accountIds, details.Level)
return out.Settings
}
func (alert *SMeterAlert) CustomizeDelete(
ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject) error {
return alert.SV1Alert.CustomizeDelete(ctx, userCred, query, data)
}
+96 -73
View File
@@ -78,9 +78,9 @@ func (v1man *SV1AlertManager) CreateNotification(
userCred mcclient.TokenCredential,
alertName string,
channel string,
recipients string) (*SAlertNotification, error) {
recipients string) (*SNotification, error) {
userIds := strings.Split(recipients, ",")
return AlertNotificationManager.CreateOneCloudNotification(ctx, userCred, alertName, channel, userIds)
return NotificationManager.CreateOneCloudNotification(ctx, userCred, alertName, channel, userIds)
}
func (man *SNodeAlertManager) ValidateCreateData(
@@ -107,10 +107,6 @@ func (man *SNodeAlertManager) ValidateCreateData(
if data.Recipients == "" {
return nil, httperrors.NewInputParameterError("recipients is empty")
}
notification, err := man.CreateNotification(ctx, userCred, data.Metric, data.Channel, data.Recipients)
if err != nil {
return nil, errors.Wrap(err, "create notification")
}
if data.NodeId == "" {
return nil, httperrors.NewInputParameterError("node_id is empty")
}
@@ -123,12 +119,12 @@ func (man *SNodeAlertManager) ValidateCreateData(
if err != nil {
return nil, err
}
alertInput := data.ToAlertCreateInput(name, field, measurement, "telegraf", []string{notification.GetId()})
alertInput := data.ToAlertCreateInput(name, field, measurement, "telegraf")
alertInput, err = AlertManager.ValidateCreateData(ctx, userCred, ownerId, query, alertInput)
if err != nil {
return nil, err
}
data.AlertCreateInput = &alertInput
data.AlertCreateInput = alertInput
return &data, nil
}
@@ -187,11 +183,16 @@ func (man *SNodeAlertManager) ValidateListConditions(ctx context.Context, userCr
return query, nil
}
func (man *SV1AlertManager) ListItemFilter(
func (man *SNodeAlertManager) ListItemFilter(
ctx context.Context, q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
query monitor.NodeAlertListInput) (*sqlchemy.SQuery, error) {
return AlertManager.ListItemFilter(ctx, q, userCred, query.ToAlertListInput())
q, err := AlertManager.ListItemFilter(ctx, q, userCred, query.ToAlertListInput())
if err != nil {
return nil, err
}
q.Equals("used_by", AlertNotificationUsedByNodeAlert)
return q, nil
}
func (man *SNodeAlertManager) GetAlert(id string) (*SNodeAlert, error) {
@@ -236,7 +237,7 @@ func (man *SNodeAlertManager) CustomizeFilterList(
}
mF := func(obj *SNodeAlert) (bool, error) {
settings := new(monitor.AlertSetting)
if err := obj.Settings.Unmarshal(settings, "settings"); err != nil {
if err := obj.Settings.Unmarshal(settings); err != nil {
return false, errors.Wrapf(err, "alert %s unmarshal", obj.GetId())
}
for _, s := range settings.Conditions {
@@ -273,6 +274,38 @@ func (man *SNodeAlertManager) CustomizeFilterList(
return filters, nil
}
func (alert *SV1Alert) CustomizeCreate(
ctx context.Context, userCred mcclient.TokenCredential,
notiName, channel, recipients, usedBy string) error {
noti, err := NodeAlertManager.CreateNotification(ctx, userCred, notiName, channel, recipients)
if err != nil {
return errors.Wrap(err, "create notification")
}
if alert.Id == "" {
alert.Id = db.DefaultUUIDGenerator()
}
alert.UsedBy = usedBy
_, err = alert.AttachNotification(
ctx, userCred, noti,
monitor.AlertNotificationStateUnknown,
usedBy)
return err
}
func (alert *SNodeAlert) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
if err := alert.SVirtualResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data); err != nil {
return err
}
if err := alert.SVirtualResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data); err != nil {
return err
}
input := new(monitor.NodeAlertCreateInput)
if err := data.Unmarshal(input); err != nil {
return err
}
return alert.SV1Alert.CustomizeCreate(ctx, userCred, input.Metric, input.Channel, input.Recipients, AlertNotificationUsedByNodeAlert)
}
func (alert *SNodeAlert) getNodeId() string {
return alert.GetMetadata(NodeAlertMetadataNodeId, nil)
}
@@ -320,7 +353,13 @@ func (alert *SNodeAlert) PostCreate(ctx context.Context,
}
}
func (alert *SV1Alert) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, isList bool) (monitor.AlertV1Details, error) {
func (alert *SV1Alert) GetExtraDetails(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
isList bool,
usedBy string,
) (monitor.AlertV1Details, error) {
var err error
out := monitor.AlertV1Details{}
out.VirtualResourceDetails, err = alert.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query, isList)
@@ -349,15 +388,23 @@ func (alert *SV1Alert) GetExtraDetails(ctx context.Context, userCred mcclient.To
case "lt":
cmp = "<="
}
out.Level = setting.Level
out.Level = alert.Level
out.Comparator = cmp
out.Threshold = cond.Evaluator.Params[0]
out.Period = cond.Query.From
notification := alert.GetNotificationBySetting(setting)
if notification != nil {
out.Recipients = strings.Join(notification.UserIds, ",")
out.Channel = notification.Channel
noti, err := alert.GetNotification(usedBy)
if err != nil {
return out, err
}
if noti != nil {
out.NotifierId = noti.GetId()
settings := new(monitor.NotificationSettingOneCloud)
if err := noti.Settings.Unmarshal(settings); err != nil {
return out, err
}
out.Recipients = strings.Join(settings.UserIds, ",")
out.Channel = settings.Channel
}
q := cond.Query
@@ -367,20 +414,13 @@ func (alert *SV1Alert) GetExtraDetails(ctx context.Context, userCred mcclient.To
out.Measurement = measurement
out.Field = field
out.DB = db
noti, err := alert.GetNotification()
if err != nil {
return out, err
}
if noti != nil {
out.NotifierId = noti.GetId()
}
return out, nil
}
func (alert *SNodeAlert) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, isList bool) (monitor.NodeAlertDetails, error) {
var err error
out := monitor.NodeAlertDetails{}
commonDetails, err := alert.SV1Alert.GetExtraDetails(ctx, userCred, query, isList)
commonDetails, err := alert.SV1Alert.GetExtraDetails(ctx, userCred, query, isList, AlertNotificationUsedByNodeAlert)
if err != nil {
return out, err
}
@@ -402,26 +442,24 @@ func (alert *SNodeAlert) GetExtraDetails(ctx context.Context, userCred mcclient.
return out, nil
}
func (alert *SV1Alert) GetNotification() (*SAlertNotification, error) {
setting, err := alert.GetSettings()
func (alert *SV1Alert) GetNotification(usedby string) (*SNotification, error) {
alertNotis, err := alert.GetNotifications()
if err != nil {
return nil, err
}
nIds := setting.Notifications
if len(nIds) == 0 {
if len(alertNotis) == 0 {
return nil, nil
}
// only get first notification setting
nId := nIds[0]
obj, err := AlertNotificationManager.GetNotification(nId)
if err != nil {
return nil, errors.Wrapf(err, "Get notificatoin %s", nId)
for _, an := range alertNotis {
if an.GetUsedBy() == usedby {
return an.GetNotification()
}
}
return obj, nil
return nil, httperrors.NewNotFoundError("not found alert notification used by %s", usedby)
}
func (alert *SV1Alert) UpdateNotification(channel, recipients *string) error {
obj, err := alert.GetNotification()
func (alert *SV1Alert) UpdateNotification(usedBy string, channel, recipients *string) error {
obj, err := alert.GetNotification(usedBy)
if err != nil {
return errors.Wrap(err, "Get notification when update")
}
@@ -445,30 +483,7 @@ func (alert *SV1Alert) UpdateNotification(channel, recipients *string) error {
return err
}
func (alert *SV1Alert) GetNotificationBySetting(setting *monitor.AlertSetting) *monitor.NotificationSettingOneCloud {
nIds := setting.Notifications
if len(nIds) == 0 {
return nil
}
// only get first notification setting
nId := nIds[0]
obj, err := AlertNotificationManager.GetNotification(nId)
if err != nil {
log.Errorf("Get notification by %s: %v", nId, err)
return nil
}
if obj == nil {
return nil
}
ocSetting := new(monitor.NotificationSettingOneCloud)
if err := obj.Settings.Unmarshal(ocSetting); err != nil {
log.Errorf("Unmarshal notification %s setting: %v", nId, err)
return nil
}
return ocSetting
}
func (alert *SNodeAlert) CustomizeDelete(
func (alert *SV1Alert) CustomizeDelete(
ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject) error {
notis, err := alert.GetNotifications()
@@ -476,10 +491,19 @@ func (alert *SNodeAlert) CustomizeDelete(
return err
}
for _, noti := range notis {
if err := noti.CustomizeDelete(ctx, userCred, query, data); err != nil {
conf, err := noti.GetNotification()
if err != nil {
return err
}
if err := noti.Delete(ctx, userCred); err != nil {
if !conf.IsDefault {
if err := conf.CustomizeDelete(ctx, userCred, query, data); err != nil {
return err
}
if err := conf.Delete(ctx, userCred); err != nil {
return err
}
}
if err := noti.Detach(ctx, userCred); err != nil {
return err
}
}
@@ -498,7 +522,6 @@ func (alert *SNodeAlert) ValidateUpdateData(
nameChange := false
if input.NodeId != nil && *input.NodeId != details.NodeId {
nameChange = true
ret.ResourceId = input.NodeId
details.NodeId = *input.NodeId
if err := alert.setNodeId(ctx, userCred, details.NodeId); err != nil {
return nil, err
@@ -506,7 +529,6 @@ func (alert *SNodeAlert) ValidateUpdateData(
}
if input.Type != nil && *input.Type != details.Type {
nameChange = true
ret.ResourceType = input.Type
details.Type = *input.Type
if err := alert.setType(ctx, userCred, details.Type); err != nil {
return nil, err
@@ -573,15 +595,10 @@ func (alert *SNodeAlert) ValidateUpdateData(
return nil, errors.Wrap(err, "get default data source")
}
// hack: update notification here
if err := alert.UpdateNotification(input.Channel, input.Recipients); err != nil {
if err := alert.UpdateNotification(AlertNotificationUsedByNodeAlert, input.Channel, input.Recipients); err != nil {
return nil, errors.Wrap(err, "update notification")
}
tmpS := alert.getUpdateSetting(name, details, ds.GetId())
os, err := alert.GetSettings()
if err != nil {
return nil, errors.Wrap(err, "get origin setting")
}
tmpS.Notifications = os.Notifications
ret.Settings = &tmpS
return alert.SAlert.ValidateUpdateData(ctx, userCred, query, ret)
}
@@ -597,7 +614,6 @@ func (alert *SNodeAlert) getUpdateSetting(
Window: details.Window,
Comparator: details.Comparator,
Threshold: details.Threshold,
Level: details.Level,
Channel: details.Channel,
Recipients: details.Recipients,
},
@@ -605,7 +621,14 @@ func (alert *SNodeAlert) getUpdateSetting(
Type: details.Type,
NodeId: details.NodeId,
}
out := data.ToAlertCreateInput(name, details.Field, details.Measurement, details.DB, []string{details.NotifierId})
data.Level = details.Level
out := data.ToAlertCreateInput(name, details.Field, details.Measurement, details.DB)
out.Settings = *setAlertDefaultSetting(&out.Settings, dsId)
return out.Settings
}
func (alert *SNodeAlert) CustomizeDelete(
ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject) error {
return alert.SV1Alert.CustomizeDelete(ctx, userCred, query, data)
}
+24 -159
View File
@@ -17,7 +17,6 @@ package models
import (
"context"
"database/sql"
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
@@ -31,16 +30,14 @@ import (
)
var (
AlertNotificationManager *SAlertNotificationManager
AlertNotificationStateManager *SAlertNotificationStateManager
NotificationManager *SNotificationManager
)
func init() {
AlertNotificationManager = NewAlertNotificationManager()
AlertNotificationStateManager = NewAlertNotificationStateManager()
NotificationManager = NewNotificationManager()
}
type SAlertNotificationManager struct {
type SNotificationManager struct {
db.SVirtualResourceBaseManager
}
@@ -48,11 +45,11 @@ type SAlertNotificationStateManager struct {
db.SStandaloneResourceBaseManager
}
func NewAlertNotificationManager() *SAlertNotificationManager {
man := &SAlertNotificationManager{
func NewNotificationManager() *SNotificationManager {
man := &SNotificationManager{
SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
SAlertNotification{},
"alert_notifications_tbl",
SNotification{},
"notifications_tbl",
"alert_notification",
"alert_notifications",
),
@@ -61,20 +58,7 @@ func NewAlertNotificationManager() *SAlertNotificationManager {
return man
}
func NewAlertNotificationStateManager() *SAlertNotificationStateManager {
man := &SAlertNotificationStateManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
SAlertNotificationState{},
"alert_notification_states_tbl",
"alert_notification_state",
"alert_notification_states",
),
}
man.SetVirtualObject(man)
return man
}
type SAlertNotification struct {
type SNotification struct {
db.SVirtualResourceBase
Type string `nullable:"false" list:"user" create:"required"`
@@ -85,15 +69,7 @@ type SAlertNotification struct {
Settings jsonutils.JSONObject `nullable:"false" list:"user" create:"required" update:"user"`
}
type SAlertNotificationState struct {
db.SStandaloneResourceBase
AlertId string `nullable:"false" list:"user" create:"required"`
NotifierId string `nullable:"false" list:"user" create:"required"`
State string `nullable:"false" list:"user" create:"required"`
}
func (man *SAlertNotificationManager) GetPlugin(typ string) (*notifydrivers.NotifierPlugin, error) {
func (man *SNotificationManager) GetPlugin(typ string) (*notifydrivers.NotifierPlugin, error) {
drv, err := notifydrivers.GetPlugin(typ)
if err != nil {
if errors.Cause(err) == notifydrivers.ErrUnsupportedNotificationType {
@@ -105,7 +81,7 @@ func (man *SAlertNotificationManager) GetPlugin(typ string) (*notifydrivers.Noti
return drv, nil
}
func (man *SAlertNotificationManager) GetNotification(id string) (*SAlertNotification, error) {
func (man *SNotificationManager) GetNotification(id string) (*SNotification, error) {
obj, err := man.FetchById(id)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
@@ -113,11 +89,11 @@ func (man *SAlertNotificationManager) GetNotification(id string) (*SAlertNotific
}
return nil, err
}
return obj.(*SAlertNotification), nil
return obj.(*SNotification), nil
}
func (man *SAlertNotificationManager) GetNotifications(ids []string) ([]SAlertNotification, error) {
objs := make([]SAlertNotification, 0)
func (man *SNotificationManager) GetNotifications(ids []string) ([]SNotification, error) {
objs := make([]SNotification, 0)
notis := man.Query().SubQuery()
q := notis.Query().Filter(sqlchemy.In(notis.Field("id"), ids))
if err := db.FetchModelObjects(man, q, &objs); err != nil {
@@ -129,8 +105,8 @@ func (man *SAlertNotificationManager) GetNotifications(ids []string) ([]SAlertNo
return objs, nil
}
func (man *SAlertNotificationManager) GetNotificationsWithDefault(ids []string) ([]SAlertNotification, error) {
objs := make([]SAlertNotification, 0)
func (man *SNotificationManager) GetNotificationsWithDefault(ids []string) ([]SNotification, error) {
objs := make([]SNotification, 0)
notis := man.Query().SubQuery()
q := notis.Query().Filter(
sqlchemy.OR(
@@ -145,7 +121,7 @@ func (man *SAlertNotificationManager) GetNotificationsWithDefault(ids []string)
return objs, nil
}
func (man *SAlertNotificationManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, _ jsonutils.JSONObject, input monitor.AlertNotificationCreateInput) (monitor.AlertNotificationCreateInput, error) {
func (man *SNotificationManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, _ jsonutils.JSONObject, input monitor.NotificationCreateInput) (monitor.NotificationCreateInput, error) {
if input.Type == "" {
return input, httperrors.NewInputParameterError("notification type is empty")
}
@@ -164,12 +140,12 @@ func (man *SAlertNotificationManager) ValidateCreateData(ctx context.Context, us
return plug.ValidateCreateData(userCred, input)
}
func (man *SAlertNotificationManager) CreateOneCloudNotification(
func (man *SNotificationManager) CreateOneCloudNotification(
ctx context.Context,
userCred mcclient.TokenCredential,
alertName string,
channel string,
userIds []string) (*SAlertNotification, error) {
userIds []string) (*SNotification, error) {
settings := &monitor.NotificationSettingOneCloud{
Channel: channel,
UserIds: userIds,
@@ -178,7 +154,7 @@ func (man *SAlertNotificationManager) CreateOneCloudNotification(
if err != nil {
return nil, errors.Wrapf(err, "generate name: %s", alertName)
}
input := &monitor.AlertNotificationCreateInput{
input := &monitor.NotificationCreateInput{
Name: newName,
Type: monitor.AlertNotificationTypeOneCloud,
Settings: jsonutils.Marshal(settings),
@@ -187,127 +163,16 @@ func (man *SAlertNotificationManager) CreateOneCloudNotification(
if err != nil {
return nil, errors.Wrapf(err, "create notification input: %s", input.JSON(input))
}
return obj.(*SAlertNotification), nil
return obj.(*SNotification), nil
}
func (n *SAlertNotification) GetStates() ([]SAlertNotificationState, error) {
states := AlertNotificationStateManager.Query().SubQuery()
q := states.Query().Filter(sqlchemy.Equals(states.Field("notifier_id"), n.GetId()))
objs := make([]SAlertNotificationState, 0)
if err := db.FetchModelObjects(AlertNotificationStateManager, q, &objs); err != nil {
return nil, err
}
return objs, nil
}
func (n *SAlertNotification) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
stats, err := n.GetStates()
if err != nil {
return err
}
for _, stat := range stats {
if err := stat.Delete(ctx, userCred); err != nil {
return err
}
}
return nil
}
func (man *SAlertNotificationStateManager) ValidateCreateData(
func (n *SNotification) AttachToAlert(
ctx context.Context,
userCred mcclient.TokenCredential,
ownerId mcclient.IIdentityProvider,
_ jsonutils.JSONObject,
input monitor.AlertNotificationStateCreateInput) (monitor.AlertNotificationStateCreateInput, error) {
if input.AlertId == "" {
return input, httperrors.NewNotEmptyError("alert_id is empty")
}
if input.NotifierId == "" {
return input, httperrors.NewNotEmptyError("notifier_id is empty")
}
var name string
if obj, err := AlertManager.FetchById(input.AlertId); err != nil {
return input, err
} else {
name = obj.GetName()
}
if obj, err := AlertNotificationManager.FetchById(input.NotifierId); err != nil {
return input, err
} else {
name = fmt.Sprintf("%s_%s", name, obj.GetName())
}
name, err := db.GenerateName(man, ownerId, name)
if err != nil {
return input, err
}
input.Name = name
return input, nil
}
func (man *SAlertNotificationStateManager) CreateState(
ctx context.Context,
userCred mcclient.TokenCredential,
input monitor.AlertNotificationStateCreateInput) (*SAlertNotificationState, error) {
obj, err := db.DoCreate(man, ctx, userCred, nil, input.JSON(input), userCred)
if err != nil {
return nil, errors.Wrapf(err, "create notification state: %s", input.JSON(input))
}
return obj.(*SAlertNotificationState), nil
}
func (man *SAlertNotificationStateManager) GetState(alertId, notifierId string) (*SAlertNotificationState, error) {
state := man.Query().SubQuery()
q := state.Query().Filter(sqlchemy.AND(
sqlchemy.Equals(state.Field("alert_id"), alertId),
sqlchemy.Equals(state.Field("notifier_id"), notifierId)))
obj := new(SAlertNotificationState)
err := q.First(obj)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, nil
} else {
return nil, err
}
}
return obj, nil
}
func (man *SAlertNotificationStateManager) GetOrCreateState(
ctx context.Context,
userCred mcclient.TokenCredential,
alertId string,
notifierId string) (*SAlertNotificationState, error) {
state, err := man.GetState(alertId, notifierId)
alertId string) (*SAlertnotification, error) {
alert, err := AlertManager.GetAlert(alertId)
if err != nil {
return nil, err
}
if state == nil {
return man.CreateState(ctx, userCred, monitor.AlertNotificationStateCreateInput{
AlertId: alertId,
NotifierId: notifierId,
State: monitor.AlertNotificationStateUnknown,
})
}
state.SetModelManager(man, state)
return state, nil
}
func (state *SAlertNotificationState) SetToPending() error {
return state.setState(monitor.AlertNotificationStatePending)
}
func (state *SAlertNotificationState) SetToCompleted() error {
return state.setState(monitor.AlertNotificationStateCompleted)
}
func (state *SAlertNotificationState) setState(changeState monitor.AlertNotificationStateType) error {
_, err := db.Update(state, func() error {
state.State = string(changeState)
return nil
})
return err
}
func (state *SAlertNotificationState) GetState() monitor.AlertNotificationStateType {
return monitor.AlertNotificationStateType(state.State)
return alert.AttachNotification(ctx, userCred, n, monitor.AlertNotificationStateUnknown, "")
}
+1 -1
View File
@@ -56,7 +56,7 @@ var notifierFactories = make(map[string]*NotifierPlugin)
type NotifierPlugin struct {
Type string
Factory NotifierFactory
ValidateCreateData func(cred mcclient.IIdentityProvider, input monitor.AlertNotificationCreateInput) (monitor.AlertNotificationCreateInput, error)
ValidateCreateData func(cred mcclient.IIdentityProvider, input monitor.NotificationCreateInput) (monitor.NotificationCreateInput, error)
}
func RegisterNotifier(plugin *NotifierPlugin) {
+11 -3
View File
@@ -24,8 +24,9 @@ import (
func InitHandlers(app *appsrv.Application) {
db.InitAllManagers()
db.RegisterModelManager(db.UserCacheManager)
db.RegisterModelManager(db.TenantCacheManager)
db.RegisterModelManager(db.UserCacheManager)
db.RegistUserCredCacheUpdater()
for _, manager := range []db.IModelManager{
db.OpsLog,
db.Metadata,
@@ -33,11 +34,18 @@ func InitHandlers(app *appsrv.Application) {
models.AlertManager,
models.NodeAlertManager,
models.MeterAlertManager,
models.AlertNotificationManager,
models.AlertNotificationStateManager,
models.NotificationManager,
} {
db.RegisterModelManager(manager)
handler := db.NewModelHandler(manager)
dispatcher.AddModelDispatcher("", app, handler)
}
for _, manager := range []db.IJointModelManager{
models.AlertNotificationManager,
} {
db.RegisterModelManager(manager)
handler := db.NewJointModelHandler(manager)
dispatcher.AddJointModelDispatcher("", app, handler)
}
}