mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Merge pull request #5630 from zexi/automated-cherry-pick-of-#5629-upstream-release-3.1
Automated cherry pick of #5629: monitor: add alert config helper
This commit is contained in:
@@ -15,6 +15,9 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
monitorapi "yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules/monitor"
|
||||
options "yunion.io/x/onecloud/pkg/mcclient/options/monitor"
|
||||
@@ -36,6 +39,20 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&options.AlertCreateOptions{}, aN("create"), "Create alert rule",
|
||||
func(s *mcclient.ClientSession, args *options.AlertCreateOptions) error {
|
||||
params, err := args.Params()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ret, err := monitor.Alerts.DoCreate(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&options.AlertShowOptions{}, aN("show"), "Show details of a alert rule",
|
||||
func(s *mcclient.ClientSession, args *options.AlertShowOptions) error {
|
||||
ret, err := monitor.Alerts.Get(s, args.ID, nil)
|
||||
@@ -66,4 +83,18 @@ func init() {
|
||||
printBatchResults(ret, monitor.Alerts.GetColumns(s))
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&options.AlertTestRunOptions{}, aN("test-run"), "Test run alert",
|
||||
func(s *mcclient.ClientSession, args *options.AlertTestRunOptions) error {
|
||||
data := new(monitorapi.AlertTestRunInput)
|
||||
if args.Debug {
|
||||
data.IsDebug = true
|
||||
}
|
||||
ret, err := monitor.Alerts.DoTestRun(s, args.ID, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(ret.JSON(ret).YAMLString())
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
monitorapi "yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules/monitor"
|
||||
options "yunion.io/x/onecloud/pkg/mcclient/options/monitor"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initAlertNotification()
|
||||
}
|
||||
|
||||
func initAlertNotification() {
|
||||
aN := cmdN("alert-notification")
|
||||
R(&options.AlertNotificationListOptions{}, aN("list"), "List alert notification pairs",
|
||||
func(s *mcclient.ClientSession, args *options.AlertNotificationListOptions) error {
|
||||
params, err := args.Params()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var result *modulebase.ListResult
|
||||
if len(args.Alert) > 0 {
|
||||
result, err = monitor.Alertnotification.ListDescendent(s, args.Alert, params)
|
||||
} else if len(args.Notification) > 0 {
|
||||
result, err = monitor.Alertnotification.ListDescendent2(s, args.Notification, params)
|
||||
} else {
|
||||
result, err = monitor.Alertnotification.List(s, params)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printList(result, monitor.Alertnotification.GetColumns(s))
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&options.AlertNotificationAttachOptions{}, aN("attach"), "Attach a notification to a alert",
|
||||
func(s *mcclient.ClientSession, args *options.AlertNotificationAttachOptions) error {
|
||||
input := &monitorapi.AlertnotificationCreateInput{
|
||||
UsedBy: args.UsedBy,
|
||||
}
|
||||
ret, err := monitor.Alertnotification.Attach(s, args.ALERT, args.NOTIFICATION, input.JSON(input))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&options.AlertNotificationAttachOptions{}, aN("detach"), "Detach a notification to a alert",
|
||||
func(s *mcclient.ClientSession, args *options.AlertNotificationAttachOptions) error {
|
||||
ret, err := monitor.Alertnotification.Detach(s, args.ALERT, args.NOTIFICATION, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
@@ -135,3 +137,49 @@ type AlertListInput struct {
|
||||
// 以报警是否启用/禁用过滤列表
|
||||
Enabled *bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type AlertTestRunInput struct {
|
||||
apis.Meta
|
||||
|
||||
IsDebug bool `json:"is_debug"`
|
||||
}
|
||||
|
||||
// ResultLogEntry represents log data for the alert evaluation.
|
||||
type ResultLogEntry struct {
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// EvalMatch represents the series violating the threshold.
|
||||
type EvalMatch struct {
|
||||
Condition string `json:"condition"`
|
||||
Value *float64 `json:"value"`
|
||||
Metric string `json:"metric"`
|
||||
Tags map[string]string `json:"tags"`
|
||||
}
|
||||
|
||||
type AlertTestRunOutput struct {
|
||||
apis.Meta
|
||||
|
||||
Firing bool `json:"firing"`
|
||||
EvalMatches []*EvalMatch `json:"eval_matches"`
|
||||
Logs []*ResultLogEntry `json:"logs"`
|
||||
Error error `json:"error"`
|
||||
ConditionEvals string `json:"condition_evals"`
|
||||
StartTime time.Time `json:"start_time"`
|
||||
EndTime time.Time `json:"end_time"`
|
||||
}
|
||||
|
||||
type AlertAttachNotificationInput struct {
|
||||
apis.Meta
|
||||
|
||||
NotificationId string `json:"notification_id"`
|
||||
UsedBy string `json:"used_by"`
|
||||
}
|
||||
|
||||
type AlertAttachNotificationOutput struct {
|
||||
apis.Meta
|
||||
|
||||
NotificationId string `json:"notification_id"`
|
||||
UsedBy string `json:"used_by"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package monitor
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/apis"
|
||||
|
||||
type AlertJointResourceBaseDetails struct {
|
||||
SAlertJointsBase
|
||||
Alert string `json:"alert"`
|
||||
}
|
||||
|
||||
type AlertnotificationDetails struct {
|
||||
AlertJointResourceBaseDetails
|
||||
Notification string `json:"notification"`
|
||||
}
|
||||
|
||||
type AlertJointCreateInput struct {
|
||||
apis.Meta
|
||||
|
||||
AlertId string `json:"alert_id"`
|
||||
}
|
||||
|
||||
type AlertnotificationCreateInput struct {
|
||||
AlertJointCreateInput
|
||||
|
||||
NotificationId string `json:"notification_id"`
|
||||
UsedBy string `json:"used_by"`
|
||||
}
|
||||
@@ -32,11 +32,3 @@ type NotificationTemplateConfig struct {
|
||||
Level string `json:"level"`
|
||||
IsRecovery bool `json:"is_recovery"`
|
||||
}
|
||||
|
||||
// EvalMatch represents the series violating the threshold.
|
||||
type EvalMatch struct {
|
||||
Condition string `json:"condition"`
|
||||
Value *float64 `json:"value"`
|
||||
Metric string `json:"metric"`
|
||||
Tags map[string]string `json:"tags"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// 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.
|
||||
|
||||
// Code generated by model-api-gen. DO NOT EDIT.
|
||||
|
||||
package monitor
|
||||
|
||||
import (
|
||||
time "time"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
)
|
||||
|
||||
// SAlert is an autogenerated struct via yunion.io/x/onecloud/pkg/monitor/models.SAlert.
|
||||
type SAlert struct {
|
||||
apis.SVirtualResourceBase
|
||||
Frequency int64 `json:"frequency"`
|
||||
Settings interface{} `json:"settings"`
|
||||
Level string `json:"level"`
|
||||
Message string `json:"message"`
|
||||
UsedBy string `json:"used_by"`
|
||||
// Silenced bool
|
||||
ExecutionError string `json:"execution_error"`
|
||||
For int64 `json:"for"`
|
||||
EvalData interface{} `json:"eval_data"`
|
||||
State string `json:"state"`
|
||||
NoDataState string `json:"no_data_state"`
|
||||
ExecutionErrorState string `json:"execution_error_state"`
|
||||
LastStateChange time.Time `json:"last_state_change"`
|
||||
StateChanges int `json:"state_changes"`
|
||||
}
|
||||
|
||||
// SAlertJointsBase is an autogenerated struct via yunion.io/x/onecloud/pkg/monitor/models.SAlertJointsBase.
|
||||
type SAlertJointsBase struct {
|
||||
apis.SVirtualJointResourceBase
|
||||
AlertId string `json:"alert_id"`
|
||||
}
|
||||
|
||||
// SAlertnotification is an autogenerated struct via yunion.io/x/onecloud/pkg/monitor/models.SAlertnotification.
|
||||
type SAlertnotification struct {
|
||||
SAlertJointsBase
|
||||
NotificationId string `json:"notification_id"`
|
||||
State string `json:"state"`
|
||||
Index byte `json:"index"`
|
||||
UsedBy string `json:"used_by"`
|
||||
}
|
||||
|
||||
// SDataSource is an autogenerated struct via yunion.io/x/onecloud/pkg/monitor/models.SDataSource.
|
||||
type SDataSource struct {
|
||||
apis.SStandaloneResourceBase
|
||||
Type string `json:"type"`
|
||||
Url string `json:"url"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
Database string `json:"database"`
|
||||
IsDefault *bool `json:"is_default,omitempty"`
|
||||
}
|
||||
|
||||
// SNotification is an autogenerated struct via yunion.io/x/onecloud/pkg/monitor/models.SNotification.
|
||||
type SNotification struct {
|
||||
apis.SVirtualResourceBase
|
||||
Type string `json:"type"`
|
||||
IsDefault bool `json:"is_default"`
|
||||
SendReminder bool `json:"send_reminder"`
|
||||
DisableResolveMessage bool `json:"disable_resolve_message"`
|
||||
Frequency int64 `json:"frequency"`
|
||||
Settings interface{} `json:"settings"`
|
||||
}
|
||||
@@ -54,6 +54,13 @@ func NewMonitorV2Manager(keyword, keywordPlural string, columns, adminColumns []
|
||||
Keyword: keyword, KeywordPlural: keywordPlural}
|
||||
}
|
||||
|
||||
func NewJointMonitorV2Manager(keyword, keywordPlural string, columns, adminColumns []string, master, slave modulebase.Manager) modulebase.JointResourceManager {
|
||||
return modulebase.JointResourceManager{
|
||||
ResourceManager: NewMonitorV2Manager(keyword, keywordPlural, columns, adminColumns),
|
||||
Master: master,
|
||||
Slave: slave}
|
||||
}
|
||||
|
||||
func NewCloudwatcherManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager {
|
||||
return modulebase.ResourceManager{
|
||||
BaseManager: *modulebase.NewBaseManager("cloudwatcher", "", "v1", columns, adminColumns),
|
||||
|
||||
@@ -15,27 +15,88 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
)
|
||||
|
||||
var (
|
||||
Alerts modulebase.ResourceManager
|
||||
Notifications modulebase.ResourceManager
|
||||
Alerts *SAlertManager
|
||||
Notifications *SNotificationManager
|
||||
Alertnotification *SAlertnotificationManager
|
||||
)
|
||||
|
||||
func init() {
|
||||
Alerts = modules.NewMonitorV2Manager("alert", "alerts",
|
||||
type SAlertManager struct {
|
||||
*modulebase.ResourceManager
|
||||
}
|
||||
|
||||
func NewAlertManager() *SAlertManager {
|
||||
man := modules.NewMonitorV2Manager("alert", "alerts",
|
||||
[]string{"id", "name", "state", "frequency", "enabled", "settings"},
|
||||
[]string{})
|
||||
Notifications = modules.NewMonitorV2Manager(
|
||||
return &SAlertManager{
|
||||
ResourceManager: &man,
|
||||
}
|
||||
}
|
||||
|
||||
type SNotificationManager struct {
|
||||
*modulebase.ResourceManager
|
||||
}
|
||||
|
||||
func NewNotificationManager() *SNotificationManager {
|
||||
man := modules.NewMonitorV2Manager(
|
||||
"alert_notification", "alert_notifications",
|
||||
[]string{"id", "name", "type", "is_default", "disable_resolve_message", "send_reminder", "settings"},
|
||||
[]string{})
|
||||
for _, m := range []modulebase.ResourceManager{
|
||||
return &SNotificationManager{
|
||||
ResourceManager: &man,
|
||||
}
|
||||
}
|
||||
|
||||
type SAlertnotificationManager struct {
|
||||
*modulebase.JointResourceManager
|
||||
}
|
||||
|
||||
func NewAlertnotificationManager() *SAlertnotificationManager {
|
||||
man := modules.NewJointMonitorV2Manager("alertnotification", "alertnotifications",
|
||||
[]string{"Alert_ID", "Alert", "Notification_ID", "Notification", "Used_by", "State"},
|
||||
[]string{},
|
||||
Alerts, Notifications)
|
||||
return &SAlertnotificationManager{&man}
|
||||
}
|
||||
|
||||
func init() {
|
||||
Alerts = NewAlertManager()
|
||||
Notifications = NewNotificationManager()
|
||||
for _, m := range []modulebase.IBaseManager{
|
||||
Alerts,
|
||||
Notifications,
|
||||
} {
|
||||
modules.Register(&m)
|
||||
modules.Register(m)
|
||||
}
|
||||
|
||||
Alertnotification = NewAlertnotificationManager()
|
||||
for _, m := range []modulebase.IBaseManager{
|
||||
Alertnotification,
|
||||
} {
|
||||
modules.Register(m)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SAlertManager) DoCreate(s *mcclient.ClientSession, config *AlertConfig) (jsonutils.JSONObject, error) {
|
||||
input := config.ToAlertCreateInput()
|
||||
return m.Create(s, input.JSON(input))
|
||||
}
|
||||
|
||||
func (m *SAlertManager) DoTestRun(s *mcclient.ClientSession, id string, input *monitor.AlertTestRunInput) (*monitor.AlertTestRunOutput, error) {
|
||||
ret, err := m.PerformAction(s, id, "test-run", input.JSON(input))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := new(monitor.AlertTestRunOutput)
|
||||
err = ret.Unmarshal(out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,473 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb"
|
||||
)
|
||||
|
||||
// AlertConfig is a helper to generate monitor service alert related api input
|
||||
type AlertConfig struct {
|
||||
name string
|
||||
frequency int64
|
||||
level string
|
||||
enabled bool
|
||||
conditions []*AlertCondition
|
||||
}
|
||||
|
||||
func NewAlertConfig(name string, frequency string, enabled bool) (*AlertConfig, error) {
|
||||
freq, err := time.ParseDuration(frequency)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
input := &AlertConfig{
|
||||
name: name,
|
||||
frequency: int64(freq / time.Second),
|
||||
level: "",
|
||||
enabled: enabled,
|
||||
conditions: make([]*AlertCondition, 0),
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (c *AlertConfig) Level(l string) *AlertConfig {
|
||||
c.level = l
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *AlertConfig) Enable(e bool) *AlertConfig {
|
||||
c.enabled = e
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *AlertConfig) ToAlertCreateInput() monitor.AlertCreateInput {
|
||||
return monitor.AlertCreateInput{
|
||||
Name: c.name,
|
||||
Frequency: c.frequency,
|
||||
Settings: c.ToAlertSetting(),
|
||||
Enabled: &c.enabled,
|
||||
Level: c.level,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AlertConfig) ToAlertSetting() monitor.AlertSetting {
|
||||
conds := make([]monitor.AlertCondition, len(c.conditions))
|
||||
for i, cc := range c.conditions {
|
||||
conds[i] = cc.ToCondition()
|
||||
}
|
||||
return monitor.AlertSetting{
|
||||
Conditions: conds,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AlertConfig) Condition(database string, measurement string) *AlertCondition {
|
||||
cc := NewAlertCondition(database, measurement)
|
||||
c.conditions = append(c.conditions, cc)
|
||||
return cc
|
||||
}
|
||||
|
||||
func (c *AlertConfig) AND(cs ...*AlertCondition) *AlertConfig {
|
||||
if len(cs) == 0 {
|
||||
return c
|
||||
}
|
||||
for _, cond := range cs {
|
||||
cond.setOperator("AND")
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *AlertConfig) OR(cs ...*AlertCondition) *AlertConfig {
|
||||
if len(cs) == 0 {
|
||||
return c
|
||||
}
|
||||
for _, cond := range cs {
|
||||
cond.setOperator("OR")
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
type AlertCondition struct {
|
||||
operator string
|
||||
reducer *monitor.Condition
|
||||
evaluator *monitor.Condition
|
||||
query *AlertQuery
|
||||
}
|
||||
|
||||
func NewAlertCondition(
|
||||
database string,
|
||||
measurement string,
|
||||
) *AlertCondition {
|
||||
c := &AlertCondition{
|
||||
query: NewAlertQuery(database, measurement),
|
||||
}
|
||||
// set default avg reducer
|
||||
c.Avg()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *AlertCondition) ToCondition() monitor.AlertCondition {
|
||||
return monitor.AlertCondition{
|
||||
Type: "query",
|
||||
Query: c.query.ToAlertQuery(),
|
||||
Reducer: *c.reducer,
|
||||
Evaluator: *c.evaluator,
|
||||
Operator: c.operator,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AlertCondition) setOperator(op string) *AlertCondition {
|
||||
c.operator = op
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *AlertCondition) setReducer(typ string, params ...float64) *AlertCondition {
|
||||
c.reducer = &monitor.Condition{
|
||||
Type: typ,
|
||||
}
|
||||
c.reducer.Params = params
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *AlertCondition) Avg() *AlertCondition {
|
||||
return c.setReducer("avg")
|
||||
}
|
||||
|
||||
func (c *AlertCondition) Sum() *AlertCondition {
|
||||
return c.setReducer("sum")
|
||||
}
|
||||
|
||||
func (c *AlertCondition) Min() *AlertCondition {
|
||||
return c.setReducer("min")
|
||||
}
|
||||
|
||||
func (c *AlertCondition) Max() *AlertCondition {
|
||||
return c.setReducer("max")
|
||||
}
|
||||
|
||||
func (c *AlertCondition) Count() *AlertCondition {
|
||||
return c.setReducer("count")
|
||||
}
|
||||
|
||||
func (c *AlertCondition) Last() *AlertCondition {
|
||||
return c.setReducer("last")
|
||||
}
|
||||
|
||||
func (c *AlertCondition) Median() *AlertCondition {
|
||||
return c.setReducer("median")
|
||||
}
|
||||
|
||||
func (c *AlertCondition) setEvaluator(typ string, threshold float64) *AlertCondition {
|
||||
c.evaluator = &monitor.Condition{
|
||||
Type: typ,
|
||||
Params: []float64{threshold},
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// LessThan is evaluator part
|
||||
func (c *AlertCondition) LT(threshold float64) *AlertCondition {
|
||||
return c.setEvaluator("lt", threshold)
|
||||
}
|
||||
|
||||
// GreaterThan is evaluator part
|
||||
func (c *AlertCondition) GT(threshold float64) *AlertCondition {
|
||||
return c.setEvaluator("gt", threshold)
|
||||
}
|
||||
|
||||
func (c *AlertCondition) Query() *AlertQuery {
|
||||
return c.query
|
||||
}
|
||||
|
||||
type AlertQuery struct {
|
||||
from string
|
||||
to string
|
||||
alias string
|
||||
tz string
|
||||
database string
|
||||
measurement string
|
||||
interval string
|
||||
policy string
|
||||
resultFormat string
|
||||
|
||||
selects *AlertQuerySelects
|
||||
where *AlertQueryWhere
|
||||
groupBy *AlertQueryGroupBy
|
||||
}
|
||||
|
||||
func NewAlertQuery(database string, measurement string) *AlertQuery {
|
||||
q := &AlertQuery{
|
||||
selects: new(AlertQuerySelects),
|
||||
where: new(AlertQueryWhere),
|
||||
groupBy: new(AlertQueryGroupBy),
|
||||
}
|
||||
q = q.Database(database).Measurement(measurement)
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *AlertQuery) ToAlertQuery() monitor.AlertQuery {
|
||||
return monitor.AlertQuery{
|
||||
Model: q.ToMetricQuery(),
|
||||
From: q.from,
|
||||
To: q.to,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *AlertQuery) ToMetricQuery() monitor.MetricQuery {
|
||||
return monitor.MetricQuery{
|
||||
Alias: q.alias,
|
||||
Tz: q.tz,
|
||||
Database: q.database,
|
||||
Measurement: q.measurement,
|
||||
Tags: q.where.ToTags(),
|
||||
GroupBy: q.groupBy.ToGroupBy(),
|
||||
Selects: q.selects.ToSelects(),
|
||||
Interval: q.interval,
|
||||
Policy: q.policy,
|
||||
ResultFormat: q.resultFormat,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *AlertQuery) ToTsdbQuery() *tsdb.TsdbQuery {
|
||||
timeRange := tsdb.NewTimeRange(q.from, q.to)
|
||||
tsdbQ := &tsdb.TsdbQuery{
|
||||
TimeRange: timeRange,
|
||||
Queries: []*tsdb.Query{
|
||||
{
|
||||
MetricQuery: q.ToMetricQuery(),
|
||||
},
|
||||
},
|
||||
}
|
||||
return tsdbQ
|
||||
}
|
||||
|
||||
func (q *AlertQuery) From(from string) *AlertQuery {
|
||||
q.from = from
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *AlertQuery) To(to string) *AlertQuery {
|
||||
q.to = to
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *AlertQuery) Alias(alias string) *AlertQuery {
|
||||
q.alias = alias
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *AlertQuery) Tz(tz string) *AlertQuery {
|
||||
q.tz = tz
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *AlertQuery) Database(db string) *AlertQuery {
|
||||
q.database = db
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *AlertQuery) Measurement(m string) *AlertQuery {
|
||||
q.measurement = m
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *AlertQuery) Interval(i string) *AlertQuery {
|
||||
q.interval = i
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *AlertQuery) Policy(p string) *AlertQuery {
|
||||
q.policy = p
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *AlertQuery) Selects() *AlertQuerySelects {
|
||||
q.selects = &AlertQuerySelects{
|
||||
parts: make([]*AlertQuerySelect, 0),
|
||||
}
|
||||
return q.selects
|
||||
}
|
||||
|
||||
func (q *AlertQuery) Where() *AlertQueryWhere {
|
||||
w := &AlertQueryWhere{
|
||||
parts: make([]monitor.MetricQueryTag, 0),
|
||||
}
|
||||
w.AND()
|
||||
q.where = w
|
||||
return w
|
||||
}
|
||||
|
||||
func (q *AlertQuery) GroupBy() *AlertQueryGroupBy {
|
||||
g := &AlertQueryGroupBy{parts: make([]monitor.MetricQueryPart, 0)}
|
||||
q.groupBy = g
|
||||
return g
|
||||
}
|
||||
|
||||
type AlertQuerySelects struct {
|
||||
parts []*AlertQuerySelect
|
||||
}
|
||||
|
||||
type AlertQuerySelect struct {
|
||||
monitor.MetricQuerySelect
|
||||
}
|
||||
|
||||
func (s *AlertQuerySelects) Select(fieldName string) *AlertQuerySelect {
|
||||
if s.parts == nil {
|
||||
s.parts = make([]*AlertQuerySelect, 0)
|
||||
}
|
||||
sel := make([]monitor.MetricQueryPart, 0)
|
||||
sel = append(sel, monitor.MetricQueryPart{
|
||||
Type: "field",
|
||||
Params: []string{fieldName},
|
||||
})
|
||||
part := &AlertQuerySelect{sel}
|
||||
s.parts = append(s.parts, part)
|
||||
return part
|
||||
}
|
||||
|
||||
func (s *AlertQuerySelects) ToSelects() []monitor.MetricQuerySelect {
|
||||
ret := make([]monitor.MetricQuerySelect, len(s.parts))
|
||||
for i, p := range s.parts {
|
||||
ret[i] = p.MetricQuerySelect
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *AlertQuerySelect) addFunc(funcName string) *AlertQuerySelect {
|
||||
s.MetricQuerySelect = append(s.MetricQuerySelect, monitor.MetricQueryPart{
|
||||
Type: funcName,
|
||||
})
|
||||
return s
|
||||
}
|
||||
|
||||
// Aggregations method
|
||||
func (s *AlertQuerySelect) MEAN() *AlertQuerySelect {
|
||||
return s.addFunc("mean")
|
||||
}
|
||||
|
||||
func (s *AlertQuerySelect) COUNT() *AlertQuerySelect {
|
||||
return s.addFunc("count")
|
||||
}
|
||||
|
||||
func (s *AlertQuerySelect) DISTINCT() *AlertQuerySelect {
|
||||
return s.addFunc("distinct")
|
||||
}
|
||||
|
||||
func (s *AlertQuerySelect) SUM() *AlertQuerySelect {
|
||||
return s.addFunc("sum")
|
||||
}
|
||||
|
||||
// AS is alias method
|
||||
func (s *AlertQuerySelect) AS(alias string) *AlertQuerySelect {
|
||||
s.MetricQuerySelect = append(s.MetricQuerySelect, monitor.MetricQueryPart{
|
||||
Type: "alias",
|
||||
Params: []string{alias},
|
||||
})
|
||||
return s
|
||||
}
|
||||
|
||||
// MATH method
|
||||
func (s *AlertQuerySelect) MATH(op string, val string) *AlertQuerySelect {
|
||||
s.MetricQuerySelect = append(s.MetricQuerySelect, monitor.MetricQueryPart{
|
||||
Type: "math",
|
||||
Params: []string{fmt.Sprintf("%s %s", op, val)},
|
||||
})
|
||||
return s
|
||||
}
|
||||
|
||||
type AlertQueryWhere struct {
|
||||
parts []monitor.MetricQueryTag
|
||||
cond string
|
||||
}
|
||||
|
||||
func (w *AlertQueryWhere) add(tags ...monitor.MetricQueryTag) *AlertQueryWhere {
|
||||
w.parts = append(w.parts, tags...)
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *AlertQueryWhere) condAdd(cond string, tags ...monitor.MetricQueryTag) *AlertQueryWhere {
|
||||
for i := range tags {
|
||||
tags[i].Condition = cond
|
||||
}
|
||||
w.add(tags...)
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *AlertQueryWhere) filter(op string, key string, value string) *AlertQueryWhere {
|
||||
if len(w.parts) == 0 {
|
||||
w.add(w.newTag(op, key, value))
|
||||
return w
|
||||
}
|
||||
return w.condAdd(w.cond, w.newTag(op, key, value))
|
||||
}
|
||||
|
||||
func (w *AlertQueryWhere) Equal(key string, value string) *AlertQueryWhere {
|
||||
return w.filter("=", key, value)
|
||||
}
|
||||
|
||||
func (w *AlertQueryWhere) NotEqual(key string, value string) *AlertQueryWhere {
|
||||
return w.filter("!=", key, value)
|
||||
}
|
||||
|
||||
func (w *AlertQueryWhere) LT(key string, value string) *AlertQueryWhere {
|
||||
return w.filter("<", key, value)
|
||||
}
|
||||
|
||||
func (w *AlertQueryWhere) GT(key string, value string) *AlertQueryWhere {
|
||||
return w.filter(">", key, value)
|
||||
}
|
||||
|
||||
func (w *AlertQueryWhere) AND() *AlertQueryWhere {
|
||||
w.cond = "AND"
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *AlertQueryWhere) OR() *AlertQueryWhere {
|
||||
w.cond = "OR"
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *AlertQueryWhere) newTag(op string, key string, value string) monitor.MetricQueryTag {
|
||||
return monitor.MetricQueryTag{
|
||||
Key: key,
|
||||
Operator: op,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *AlertQueryWhere) ToTags() []monitor.MetricQueryTag {
|
||||
return w.parts
|
||||
}
|
||||
|
||||
type AlertQueryGroupBy struct {
|
||||
parts []monitor.MetricQueryPart
|
||||
}
|
||||
|
||||
func (g *AlertQueryGroupBy) addPart(typ string, params ...string) *AlertQueryGroupBy {
|
||||
g.parts = append(g.parts, monitor.MetricQueryPart{
|
||||
Type: typ,
|
||||
Params: params,
|
||||
})
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *AlertQueryGroupBy) TIME(val string) *AlertQueryGroupBy {
|
||||
return g.addPart("time", val)
|
||||
}
|
||||
|
||||
func (g *AlertQueryGroupBy) TAG(val string) *AlertQueryGroupBy {
|
||||
return g.addPart("tag", val)
|
||||
}
|
||||
|
||||
func (g *AlertQueryGroupBy) FILL_NULL() *AlertQueryGroupBy {
|
||||
return g.FILL("null")
|
||||
}
|
||||
|
||||
func (g *AlertQueryGroupBy) FILL(val string) *AlertQueryGroupBy {
|
||||
return g.addPart("fill", val)
|
||||
}
|
||||
|
||||
func (g *AlertQueryGroupBy) ToGroupBy() []monitor.MetricQueryPart {
|
||||
return g.parts
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb/driver/influxdb"
|
||||
)
|
||||
|
||||
func TestHelperWhere(t *testing.T) {
|
||||
Convey("Alert query where", t, func() {
|
||||
q := NewAlertCondition("", "").Query()
|
||||
w1 := q.Where()
|
||||
w1.Equal("hostname", "server1").NotEqual("hypervisor", "kvm").
|
||||
OR().GT("key", "val").LT("key1", "val2")
|
||||
So(w1.ToTags(), ShouldResemble, []monitor.MetricQueryTag{
|
||||
{
|
||||
Operator: "=",
|
||||
Key: "hostname",
|
||||
Value: "server1",
|
||||
},
|
||||
{
|
||||
Condition: "AND",
|
||||
Operator: "!=",
|
||||
Key: "hypervisor",
|
||||
Value: "kvm",
|
||||
},
|
||||
{
|
||||
Condition: "OR",
|
||||
Operator: ">",
|
||||
Key: "key",
|
||||
Value: "val",
|
||||
},
|
||||
{
|
||||
Condition: "OR",
|
||||
Operator: "<",
|
||||
Key: "key1",
|
||||
Value: "val2",
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestHelperSelects(t *testing.T) {
|
||||
Convey("Alert query selects", t, func() {
|
||||
sels := NewAlertCondition("", "").Query().Selects()
|
||||
sels.Select("name").COUNT().MATH("/", "100")
|
||||
sels.Select("io_util")
|
||||
|
||||
So(sels.parts[0].MetricQuerySelect, ShouldResemble, monitor.MetricQuerySelect{
|
||||
{
|
||||
Type: "field",
|
||||
Params: []string{"name"},
|
||||
},
|
||||
{
|
||||
Type: "count",
|
||||
},
|
||||
{
|
||||
Type: "math",
|
||||
Params: []string{"/ 100"},
|
||||
},
|
||||
})
|
||||
|
||||
So(sels.parts[1].MetricQuerySelect, ShouldResemble, monitor.MetricQuerySelect{
|
||||
{
|
||||
Type: "field",
|
||||
Params: []string{"io_util"},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestAlertQuery(t *testing.T) {
|
||||
Convey("Alert query test", t, func() {
|
||||
parser := new(influxdb.InfluxdbQueryParser)
|
||||
q := NewAlertQuery("telegraf", "diskio").From("5m").To("now")
|
||||
q.Selects().Select("await").MEAN()
|
||||
q.Where().Equal("hostname", "host1").Equal("provider", "kvm")
|
||||
q.GroupBy().TAG("*").FILL_NULL()
|
||||
qCtx := q.ToTsdbQuery()
|
||||
influxdbQ, err := parser.Parse(qCtx.Queries[0], nil)
|
||||
So(err, ShouldBeNil)
|
||||
rawQuery, err := influxdbQ.Build(qCtx)
|
||||
So(err, ShouldBeNil)
|
||||
So(rawQuery, ShouldEqual, `SELECT mean("await") FROM "diskio" WHERE ("hostname" = 'host1' AND "provider" = 'kvm') AND time > now() - 5m GROUP BY * fill(null)`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAlertConfig(t *testing.T) {
|
||||
Convey("Alert config test", t, func() {
|
||||
enabled := true
|
||||
// disabled := false
|
||||
conf, err := NewAlertConfig("alert1", "5s", true)
|
||||
So(err, ShouldBeNil)
|
||||
q := conf.Condition("telegraf", "cpu").Avg().LT(50).Query()
|
||||
sels := q.Selects()
|
||||
sels.Select("usage_active").MEAN()
|
||||
sels.Select("usage_irq").COUNT()
|
||||
q.Where().Equal("host_ip", "10.168.222.231")
|
||||
So(conf.ToAlertCreateInput(), ShouldResemble, monitor.AlertCreateInput{
|
||||
Name: "alert1",
|
||||
Frequency: 5,
|
||||
Settings: monitor.AlertSetting{
|
||||
Conditions: []monitor.AlertCondition{
|
||||
{
|
||||
Type: "query",
|
||||
Query: monitor.AlertQuery{
|
||||
Model: monitor.MetricQuery{
|
||||
Database: "telegraf",
|
||||
Measurement: "cpu",
|
||||
Selects: []monitor.MetricQuerySelect{
|
||||
{
|
||||
{
|
||||
Type: "field",
|
||||
Params: []string{"usage_active"},
|
||||
},
|
||||
{
|
||||
Type: "mean",
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
Type: "field",
|
||||
Params: []string{"usage_irq"},
|
||||
},
|
||||
{
|
||||
Type: "count",
|
||||
},
|
||||
},
|
||||
},
|
||||
Tags: []monitor.MetricQueryTag{
|
||||
{
|
||||
Key: "host_ip",
|
||||
Operator: "=",
|
||||
Value: "10.168.222.231",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Reducer: monitor.Condition{
|
||||
Type: "avg",
|
||||
},
|
||||
Evaluator: monitor.Condition{
|
||||
Type: "lt",
|
||||
Params: []float64{50},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Enabled: &enabled,
|
||||
Level: "",
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -16,9 +16,13 @@ package monitor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
monitor2 "yunion.io/x/onecloud/pkg/mcclient/modules/monitor"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
)
|
||||
|
||||
@@ -34,6 +38,85 @@ type AlertDeleteOptions struct {
|
||||
ID []string `help:"ID of alert to delete"`
|
||||
}
|
||||
|
||||
type AlertTestRunOptions struct {
|
||||
ID string `help:"ID of alert to delete"`
|
||||
Debug bool `help:"Show more debug info"`
|
||||
}
|
||||
|
||||
type AlertConditionOptions struct {
|
||||
REDUCER string `help:"Metric query reducer, e.g. 'avg'" choices:"avg|sum|min|max|count|last|median"`
|
||||
DATABASE string `help:"Metric database, e.g. 'telegraf'"`
|
||||
METRIC string `help:"Query metric format <measurement>.<field>, e.g. 'cpu.cpu_usage'"`
|
||||
COMPARATOR string `help:"Evaluator compare" choices:"gt|lt"`
|
||||
THRESHOLD float64 `help:"Alert threshold"`
|
||||
Period string `help:"Query metric period e.g. '5m', '1h'" default:"5m"`
|
||||
Tag []string `help:"Query tag, e.g. 'zone=zon0,name=vmname'"`
|
||||
}
|
||||
|
||||
func (opt AlertConditionOptions) Params(conf *monitor2.AlertConfig) (*monitor2.AlertCondition, error) {
|
||||
parts := strings.Split(opt.METRIC, ".")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("metric %s is invalid format", opt.METRIC)
|
||||
}
|
||||
cond := conf.Condition(opt.DATABASE, parts[0])
|
||||
if opt.COMPARATOR == "gt" {
|
||||
cond.GT(opt.THRESHOLD)
|
||||
}
|
||||
if opt.COMPARATOR == "lt" {
|
||||
cond.LT(opt.THRESHOLD)
|
||||
}
|
||||
switch opt.REDUCER {
|
||||
case "avg":
|
||||
cond.Avg()
|
||||
case "sum":
|
||||
cond.Sum()
|
||||
case "min":
|
||||
cond.Min()
|
||||
case "max":
|
||||
cond.Max()
|
||||
case "count":
|
||||
cond.Count()
|
||||
case "last":
|
||||
cond.Last()
|
||||
case "median":
|
||||
cond.Median()
|
||||
}
|
||||
|
||||
q := cond.Query().From(opt.Period)
|
||||
q.Selects().Select(parts[1])
|
||||
|
||||
for _, tag := range opt.Tag {
|
||||
parts := strings.Split(tag, "=")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid tag format: %s", tag)
|
||||
}
|
||||
q.Where().Equal(parts[0], parts[1])
|
||||
}
|
||||
|
||||
return cond, nil
|
||||
}
|
||||
|
||||
type AlertCreateOptions struct {
|
||||
AlertConditionOptions
|
||||
NAME string `help:"Name of the alert"`
|
||||
Frequency string `help:"Alert execute frequency, e.g. '5m', '1h'"`
|
||||
Enabled bool `help:"Enable alert"`
|
||||
Level string `help:"Alert level"`
|
||||
}
|
||||
|
||||
func (opt AlertCreateOptions) Params() (*monitor2.AlertConfig, error) {
|
||||
input, err := monitor2.NewAlertConfig(opt.NAME, opt.Frequency, opt.Enabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = opt.AlertConditionOptions.Params(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return input, nil
|
||||
}
|
||||
|
||||
type AlertUpdateOptions struct {
|
||||
ID string `help:"ID or name of the alert"`
|
||||
Name string `help:"Update alert name"`
|
||||
@@ -55,3 +138,23 @@ func (opt AlertUpdateOptions) Params() (*monitor.AlertUpdateInput, error) {
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
type AlertNotificationAttachOptions struct {
|
||||
ALERT string `help:"ID or name of alert"`
|
||||
NOTIFICATION string `help:"ID or name of alert notification"`
|
||||
UsedBy string `help:"UsedBy annotation"`
|
||||
}
|
||||
|
||||
type AlertNotificationListOptions struct {
|
||||
options.BaseListOptions
|
||||
Alert string `help:"ID or name of alert" short-token:"a"`
|
||||
Notification string `help:"ID or name of notification" short-token:"n"`
|
||||
}
|
||||
|
||||
func (o AlertNotificationListOptions) Params() (*jsonutils.JSONDict, error) {
|
||||
params, err := o.BaseListOptions.Params()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
@@ -98,7 +99,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio
|
||||
|
||||
emptySeriesCount := 0
|
||||
evalMatchCount := 0
|
||||
var matches []*alerting.EvalMatch
|
||||
var matches []*monitor.EvalMatch
|
||||
|
||||
for idx, series := range seriesList {
|
||||
reducedValue := c.Reducer.Reduce(series)
|
||||
@@ -109,7 +110,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio
|
||||
}
|
||||
|
||||
if context.IsTestRun {
|
||||
context.Logs = append(context.Logs, &alerting.ResultLogEntry{
|
||||
context.Logs = append(context.Logs, &monitor.ResultLogEntry{
|
||||
Message: fmt.Sprintf("Condition[%d]: Eval: %v, Metric: %s, Value: %v", c.Index, evalMatch, series.Name, reducedValue),
|
||||
})
|
||||
}
|
||||
@@ -118,7 +119,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio
|
||||
evalMatchCount++
|
||||
}
|
||||
tags := c.filterTags(series.Tags)
|
||||
matches = append(matches, &alerting.EvalMatch{
|
||||
matches = append(matches, &monitor.EvalMatch{
|
||||
Condition: c.GenerateFormatCond(&metas[idx]).String(),
|
||||
Metric: series.Name,
|
||||
Value: reducedValue,
|
||||
@@ -132,14 +133,14 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio
|
||||
evalMatch := c.Evaluator.Eval(nil)
|
||||
|
||||
if context.IsTestRun {
|
||||
context.Logs = append(context.Logs, &alerting.ResultLogEntry{
|
||||
context.Logs = append(context.Logs, &monitor.ResultLogEntry{
|
||||
Message: fmt.Sprintf("Condition: Eval: %v, Query returned No Series (reduced to null/no value)", evalMatch),
|
||||
})
|
||||
}
|
||||
|
||||
if evalMatch {
|
||||
evalMatchCount++
|
||||
matches = append(matches, &alerting.EvalMatch{
|
||||
matches = append(matches, &monitor.EvalMatch{
|
||||
Metric: "NoData",
|
||||
Value: nil,
|
||||
})
|
||||
@@ -170,7 +171,37 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
|
||||
metas := make([]tsdb.QueryResultMeta, 0)
|
||||
|
||||
if context.IsDebug {
|
||||
// TODO: record info when is debug mode
|
||||
data := jsonutils.NewDict()
|
||||
if req.TimeRange != nil {
|
||||
data.Set("from", jsonutils.NewInt(req.TimeRange.GetFromAsMsEpoch()))
|
||||
data.Set("to", jsonutils.NewInt(req.TimeRange.GetToAsMsEpoch()))
|
||||
}
|
||||
|
||||
type queryDto struct {
|
||||
RefId string `json:"refId"`
|
||||
Model monitor.MetricQuery `json:"model"`
|
||||
Datasource tsdb.DataSource `json:"datasource"`
|
||||
MaxDataPoints int64 `json:"maxDataPoints"`
|
||||
IntervalMs int64 `json:"intervalMs"`
|
||||
}
|
||||
|
||||
queries := []*queryDto{}
|
||||
for _, q := range req.Queries {
|
||||
queries = append(queries, &queryDto{
|
||||
RefId: q.RefId,
|
||||
Model: q.MetricQuery,
|
||||
Datasource: q.DataSource,
|
||||
MaxDataPoints: q.MaxDataPoints,
|
||||
IntervalMs: q.IntervalMs,
|
||||
})
|
||||
}
|
||||
|
||||
data.Set("queries", jsonutils.Marshal(queries))
|
||||
|
||||
context.Logs = append(context.Logs, &monitor.ResultLogEntry{
|
||||
Message: fmt.Sprintf("Condition[%d]: Query", c.Index),
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := c.HandleRequest(context.Ctx, ds.ToTSDBDataSource(""), req)
|
||||
@@ -182,8 +213,6 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
|
||||
return nil, errors.Wrap(err, "tsdb.HandleRequest() error")
|
||||
}
|
||||
|
||||
// log.Errorf("===query resp %s", jsonutils.Marshal(resp).PrettyString())
|
||||
|
||||
for _, v := range resp.Results {
|
||||
if v.Error != nil {
|
||||
return nil, errors.Wrap(err, "tsdb.HandleResult() response")
|
||||
@@ -198,12 +227,12 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
|
||||
queryResultData["series"] = v.Series
|
||||
}
|
||||
|
||||
/*if context.IsDebug && v.Meta != nil {
|
||||
if context.IsDebug {
|
||||
queryResultData["meta"] = v.Meta
|
||||
}*/
|
||||
}
|
||||
|
||||
if context.IsTestRun || context.IsDebug {
|
||||
context.Logs = append(context.Logs, &alerting.ResultLogEntry{
|
||||
context.Logs = append(context.Logs, &monitor.ResultLogEntry{
|
||||
Message: fmt.Sprintf("Condition[%d]: Query Result", c.Index),
|
||||
Data: queryResultData,
|
||||
})
|
||||
|
||||
@@ -30,8 +30,8 @@ type EvalContext struct {
|
||||
Firing bool
|
||||
IsTestRun bool
|
||||
IsDebug bool
|
||||
EvalMatches []*EvalMatch
|
||||
Logs []*ResultLogEntry
|
||||
EvalMatches []*monitor.EvalMatch
|
||||
Logs []*monitor.ResultLogEntry
|
||||
Error error
|
||||
ConditionEvals string
|
||||
StartTime time.Time
|
||||
@@ -52,7 +52,7 @@ func NewEvalContext(alertCtx context.Context, userCred mcclient.TokenCredential,
|
||||
UserCred: userCred,
|
||||
StartTime: time.Now(),
|
||||
Rule: rule,
|
||||
EvalMatches: make([]*EvalMatch, 0),
|
||||
EvalMatches: make([]*monitor.EvalMatch, 0),
|
||||
PrevAlertState: rule.State,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,14 @@ import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
)
|
||||
|
||||
type conditionStub struct {
|
||||
firing bool
|
||||
operator string
|
||||
matches []*EvalMatch
|
||||
matches []*monitor.EvalMatch
|
||||
noData bool
|
||||
}
|
||||
|
||||
@@ -59,7 +61,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
Convey("Show return false with not passing asdf", func() {
|
||||
ctx := NewEvalContext(context.TODO(), nil, &Rule{
|
||||
Conditions: []Condition{
|
||||
&conditionStub{firing: true, operator: "and", matches: []*EvalMatch{{}, {}}},
|
||||
&conditionStub{firing: true, operator: "and", matches: []*monitor.EvalMatch{{}, {}}},
|
||||
&conditionStub{firing: false, operator: "and"},
|
||||
}})
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
"yunion.io/x/onecloud/pkg/monitor/models"
|
||||
"yunion.io/x/onecloud/pkg/monitor/notifydrivers"
|
||||
)
|
||||
@@ -36,7 +37,7 @@ type ConditionResult struct {
|
||||
Firing bool
|
||||
NoDataFound bool
|
||||
Operator string
|
||||
EvalMatches []*EvalMatch
|
||||
EvalMatches []*monitor.EvalMatch
|
||||
}
|
||||
|
||||
// Condition is responsible for evaluating an alert condition.
|
||||
|
||||
@@ -41,17 +41,3 @@ func (j *Job) SetRunning(b bool) {
|
||||
j.running = b
|
||||
j.runningLock.Unlock()
|
||||
}
|
||||
|
||||
// ResultLogEntry represents log data for the alert evaluation.
|
||||
type ResultLogEntry struct {
|
||||
Message string
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// EvalMatch represents the series violating the threshold.
|
||||
type EvalMatch struct {
|
||||
Condition string `json:“condition`
|
||||
Value *float64 `json:"value"`
|
||||
Metric string `json:"metric"`
|
||||
Tags map[string]string `json:"tags"`
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/monitor/models"
|
||||
"yunion.io/x/onecloud/pkg/monitor/validators"
|
||||
)
|
||||
@@ -35,6 +37,10 @@ var (
|
||||
ErrFrequencyCouldNotBeParsed = errors.Error(`"evaluate every" field could not be parsed`)
|
||||
)
|
||||
|
||||
func init() {
|
||||
models.AlertManager.SetTester(NewAlertRuleTester())
|
||||
}
|
||||
|
||||
// Rule is the in-memory version of an alert rule.
|
||||
type Rule struct {
|
||||
Id string
|
||||
@@ -152,6 +158,40 @@ func NewRuleFromDBAlert(ruleDef *models.SAlert) (*Rule, error) {
|
||||
return model, nil
|
||||
}
|
||||
|
||||
type AlertRuleTester struct{}
|
||||
|
||||
func NewAlertRuleTester() models.AlertTestRunner {
|
||||
return new(AlertRuleTester)
|
||||
}
|
||||
|
||||
func (_ AlertRuleTester) DoTest(ruleDef *models.SAlert, userCred mcclient.TokenCredential, input monitor.AlertTestRunInput) (*monitor.AlertTestRunOutput, error) {
|
||||
rule, err := NewRuleFromDBAlert(ruleDef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
handler := NewEvalHandler()
|
||||
|
||||
ctx := NewEvalContext(context.Background(), userCred, rule)
|
||||
ctx.IsTestRun = true
|
||||
ctx.IsDebug = input.IsDebug
|
||||
|
||||
handler.Eval(ctx)
|
||||
|
||||
return ctx.ToTestRunResult(), nil
|
||||
}
|
||||
|
||||
func (ctx *EvalContext) ToTestRunResult() *monitor.AlertTestRunOutput {
|
||||
return &monitor.AlertTestRunOutput{
|
||||
Firing: ctx.Firing,
|
||||
EvalMatches: ctx.EvalMatches,
|
||||
Logs: ctx.Logs,
|
||||
Error: ctx.Error,
|
||||
ConditionEvals: ctx.ConditionEvals,
|
||||
StartTime: ctx.StartTime,
|
||||
EndTime: ctx.EndTime,
|
||||
}
|
||||
}
|
||||
|
||||
// ConditionFactory is the function signature for creating `Conditions`
|
||||
type ConditionFactory func(model *monitor.AlertCondition, index int) (Condition, error)
|
||||
|
||||
|
||||
@@ -45,8 +45,14 @@ func init() {
|
||||
AlertManager = NewAlertManager(SAlert{}, "alert", "alerts")
|
||||
}
|
||||
|
||||
type AlertTestRunner interface {
|
||||
DoTest(ruleDef *SAlert, userCred mcclient.TokenCredential, input monitor.AlertTestRunInput) (*monitor.AlertTestRunOutput, error)
|
||||
}
|
||||
|
||||
type SAlertManager struct {
|
||||
db.SVirtualResourceBaseManager
|
||||
|
||||
tester AlertTestRunner
|
||||
}
|
||||
|
||||
func NewAlertManager(dt interface{}, keyword, keywordPlural string) *SAlertManager {
|
||||
@@ -61,6 +67,14 @@ func NewAlertManager(dt interface{}, keyword, keywordPlural string) *SAlertManag
|
||||
return man
|
||||
}
|
||||
|
||||
func (man *SAlertManager) SetTester(tester AlertTestRunner) {
|
||||
man.tester = tester
|
||||
}
|
||||
|
||||
func (man *SAlertManager) GetTester() AlertTestRunner {
|
||||
return man.tester
|
||||
}
|
||||
|
||||
func (man *SAlertManager) FetchAllAlerts() ([]SAlert, error) {
|
||||
objs := make([]SAlert, 0)
|
||||
q := man.Query()
|
||||
@@ -391,3 +405,74 @@ func (alert *SAlert) SetFor(forTime time.Duration) error {
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (alert *SAlert) AllowPerformTestRun(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
data jsonutils.JSONObject,
|
||||
) bool {
|
||||
return db.IsProjectAllowPerform(userCred, alert, "test-run")
|
||||
}
|
||||
|
||||
func (alert *SAlert) PerformTestRun(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
input monitor.AlertTestRunInput,
|
||||
) (*monitor.AlertTestRunOutput, error) {
|
||||
return alert.testRunAlert(userCred, input)
|
||||
}
|
||||
|
||||
func (alert *SAlert) testRunAlert(userCred mcclient.TokenCredential, input monitor.AlertTestRunInput) (*monitor.AlertTestRunOutput, error) {
|
||||
return AlertManager.GetTester().DoTest(alert, userCred, input)
|
||||
}
|
||||
|
||||
/*func (alert *SAlert) AllowPerformAttachNotification(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
data jsonutils.JSONObject,
|
||||
) bool {
|
||||
return db.IsProjectAllowPerform(userCred, alert, "attach-notification")
|
||||
}
|
||||
|
||||
func (alert *SAlert) PerformAttachNotification(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
input monitor.AlertAttachNotificationInput,
|
||||
) (*monitor.AlertAttachNotificationOutput, error) {
|
||||
notiObj, err := NotificationManager.FetchByIdOrName(userCred, input.NotificationId)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == sql.ErrNoRows {
|
||||
return nil, httperrors.NewResourceNotFoundError("Alert notification %s not found", input.NotificationId)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
noti := notiObj.(*SNotification)
|
||||
ret, err := alert.AttachNotification(ctx, userCred, noti, monitor.AlertNotificationStateUnknown, input.UsedBy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &monitor.AlertAttachNotificationOutput{
|
||||
NotificationId: ret.NotificationId,
|
||||
UsedBy: ret.UsedBy,
|
||||
}, nil
|
||||
}*/
|
||||
|
||||
func (alert *SAlert) CustomizeDelete(
|
||||
ctx context.Context, userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject, data jsonutils.JSONObject,
|
||||
) error {
|
||||
notis, err := alert.GetNotifications()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, noti := range notis {
|
||||
if err := noti.Detach(ctx, userCred); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
|
||||
package models
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
)
|
||||
|
||||
type SAlertJointsManager struct {
|
||||
db.SVirtualJointResourceBaseManager
|
||||
|
||||
@@ -16,11 +16,14 @@ package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
@@ -78,6 +81,52 @@ func (man *SAlertNotificationManager) Get(alertId string, notiId string) (*SAler
|
||||
return obj, err
|
||||
}
|
||||
|
||||
func (man *SAlertNotificationManager) ValidateCreateData(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
ownerId mcclient.IIdentityProvider,
|
||||
query jsonutils.JSONObject,
|
||||
input monitor.AlertnotificationCreateInput,
|
||||
) (*jsonutils.JSONDict, error) {
|
||||
if input.AlertId == "" {
|
||||
return nil, httperrors.NewMissingParameterError("alert_id")
|
||||
}
|
||||
if input.NotificationId == "" {
|
||||
return nil, httperrors.NewMissingParameterError("notification_id")
|
||||
}
|
||||
_, err := AlertManager.FetchById(input.AlertId)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == sql.ErrNoRows {
|
||||
return nil, httperrors.NewResourceNotFoundError("not find alert %s", input.AlertId)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
_, err = NotificationManager.FetchById(input.NotificationId)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == sql.ErrNoRows {
|
||||
return nil, httperrors.NewResourceNotFoundError("not find notification %s", input.NotificationId)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
ret := input.JSON(input)
|
||||
ret.Add(jsonutils.NewString(string(monitor.AlertNotificationStateUnknown)), "state")
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (joint *SAlertnotification) getExtraDetails(noti SNotification, out monitor.AlertnotificationDetails) monitor.AlertnotificationDetails {
|
||||
out.Notification = noti.GetName()
|
||||
return out
|
||||
}
|
||||
|
||||
func (joint *SAlertnotification) GetExtraDetails(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
isList bool,
|
||||
) (monitor.AlertnotificationDetails, error) {
|
||||
return monitor.AlertnotificationDetails{}, nil
|
||||
}
|
||||
|
||||
func (joint *SAlertnotification) DoSave(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
if err := AlertNotificationManager.TableSpec().Insert(joint); err != nil {
|
||||
return err
|
||||
|
||||
@@ -54,6 +54,7 @@ func NewNotificationManager() *SNotificationManager {
|
||||
"alert_notifications",
|
||||
),
|
||||
}
|
||||
man.SetAlias("notification", "notifications")
|
||||
man.SetVirtualObject(man)
|
||||
return man
|
||||
}
|
||||
@@ -176,3 +177,27 @@ func (n *SNotification) AttachToAlert(
|
||||
}
|
||||
return alert.AttachNotification(ctx, userCred, n, monitor.AlertNotificationStateUnknown, "")
|
||||
}
|
||||
|
||||
func (n *SNotification) GetAlertNotificationCount() (int, error) {
|
||||
alertNotis := AlertNotificationManager.Query()
|
||||
return alertNotis.Equals("notification_id", n.Id).CountWithError()
|
||||
}
|
||||
|
||||
func (n *SNotification) IsAttached() (bool, error) {
|
||||
cnt, err := n.GetAlertNotificationCount()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return cnt > 0, nil
|
||||
}
|
||||
|
||||
func (n *SNotification) ValidateDeleteCondition(ctx context.Context) error {
|
||||
cnt, err := n.GetAlertNotificationCount()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cnt > 0 {
|
||||
return httperrors.NewNotEmptyError("Alert notification used by %d alert", cnt)
|
||||
}
|
||||
return n.SVirtualResourceBase.ValidateDeleteCondition(ctx)
|
||||
}
|
||||
|
||||
@@ -102,8 +102,6 @@ func (e *InfluxdbExecutor) Query(ctx context.Context, dsInfo *tsdb.DataSource, t
|
||||
return nil, response.Err
|
||||
}
|
||||
|
||||
// log.Errorf("==influxdb response: %s", jsonutils.Marshal(response).PrettyString())
|
||||
|
||||
result.Results = make(map[string]*tsdb.QueryResult)
|
||||
ret := e.ResponseParser.Parse(&response, query)
|
||||
ret.Meta = tsdb.QueryResultMeta{
|
||||
|
||||
@@ -79,7 +79,7 @@ func (ic *intervalCalculator) Calculate(timerange *TimeRange, minInterval time.D
|
||||
|
||||
func GetIntervalFrom(dsInfo *DataSource, queryModel *Query, defaultInterval time.Duration) (time.Duration, error) {
|
||||
interval := queryModel.Interval
|
||||
if interval == "" && dsInfo.TimeInterval != "" {
|
||||
if interval == "" && (dsInfo != nil && dsInfo.TimeInterval != "") {
|
||||
interval = dsInfo.TimeInterval
|
||||
}
|
||||
if interval == "" {
|
||||
|
||||
@@ -80,6 +80,22 @@ func ValidateAlertConditionQuery(input monitor.AlertQuery) error {
|
||||
if err := ValidateToValue(input.To); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ValidateAlertQueryModel(input.Model); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateAlertQueryModel(input monitor.MetricQuery) error {
|
||||
if len(input.Selects) == 0 {
|
||||
return httperrors.NewInputParameterError("no select data in query")
|
||||
}
|
||||
if len(input.Database) == 0 {
|
||||
return httperrors.NewInputParameterError("no database in query")
|
||||
}
|
||||
if len(input.Measurement) == 0 {
|
||||
return httperrors.NewInputParameterError("no measurement in query")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user