influxdb/notification/check/check.go

228 lines
6.1 KiB
Go

package check
import (
"encoding/json"
"fmt"
"github.com/influxdata/flux/ast"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influxdb/v2/kit/platform/errors"
"github.com/influxdata/influxdb/v2/notification"
"github.com/influxdata/influxdb/v2/notification/flux"
"github.com/influxdata/influxdb/v2/query/fluxlang"
)
// Base will embed inside a check.
type Base struct {
ID platform.ID `json:"id,omitempty"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
OwnerID platform.ID `json:"ownerID,omitempty"`
OrgID platform.ID `json:"orgID,omitempty"`
Query influxdb.DashboardQuery `json:"query"`
// Care should be taken to prevent TaskID from being exposed publicly.
TaskID platform.ID `json:"taskID,omitempty"`
// } todo: separate these
// NonCustomCheckBase will embed inside non-custom checks.
// type NonCustomCheckBase struct {
StatusMessageTemplate string `json:"statusMessageTemplate"`
Cron string `json:"cron,omitempty"`
Every *notification.Duration `json:"every,omitempty"`
// Offset represents a delay before execution.
// It gets marshalled from a string duration, i.e.: "10s" is 10 seconds
Offset *notification.Duration `json:"offset,omitempty"`
Tags []influxdb.Tag `json:"tags"`
influxdb.CRUDLog
}
// Valid returns err if the check is invalid.
func (b Base) Valid(lang fluxlang.FluxLanguageService) error {
if !b.ID.Valid() {
return &errors.Error{
Code: errors.EInvalid,
Msg: "Check ID is invalid",
}
}
if b.Name == "" {
return &errors.Error{
Code: errors.EInvalid,
Msg: "Check Name can't be empty",
}
}
if !b.OrgID.Valid() {
return &errors.Error{
Code: errors.EInvalid,
Msg: "Check OrgID is invalid",
}
}
if b.Every == nil {
return &errors.Error{
Code: errors.EInvalid,
Msg: "Check Every must exist",
}
}
if len(b.Every.Values) == 0 {
return &errors.Error{
Code: errors.EInvalid,
Msg: "Check Every can't be empty",
}
}
if b.Offset != nil && len(b.Offset.Values) == 0 {
return &errors.Error{
Code: errors.EInvalid,
Msg: "Check Offset can't be empty",
}
}
if b.Offset != nil && b.Offset.TimeDuration() >= b.Every.TimeDuration() {
return &errors.Error{
Code: errors.EInvalid,
Msg: "Offset should not be equal or greater than the interval",
}
}
for _, tag := range b.Tags {
if err := tag.Valid(); err != nil {
return err
}
}
return nil
}
func (b Base) generateFluxASTMessageFunction() ast.Statement {
fn := flux.Function(flux.FunctionParams("r"), flux.String(b.StatusMessageTemplate))
return flux.DefineVariable("messageFn", fn)
}
func (b Base) generateTaskOption() ast.Statement {
props := []*ast.Property{}
props = append(props, flux.Property("name", flux.String(b.Name)))
if b.Every != nil {
props = append(props, flux.Property("every", (*ast.DurationLiteral)(b.Every)))
}
if b.Offset != nil {
props = append(props, flux.Property("offset", (*ast.DurationLiteral)(b.Offset)))
}
return flux.DefineTaskOption(flux.Object(props...))
}
func (b Base) generateFluxASTCheckDefinition(checkType string) ast.Statement {
props := append([]*ast.Property{}, flux.Property("_check_id", flux.String(b.ID.String())))
props = append(props, flux.Property("_check_name", flux.String(b.Name)))
props = append(props, flux.Property("_type", flux.String(checkType)))
// TODO(desa): eventually tags will be flattened out into the data struct
tagProps := []*ast.Property{}
for _, tag := range b.Tags {
tagProps = append(tagProps, flux.Property(tag.Key, flux.String(tag.Value)))
}
props = append(props, flux.Property("tags", flux.Object(tagProps...)))
return flux.DefineVariable("check", flux.Object(props...))
}
// GetID implements influxdb.Getter interface.
func (b Base) GetID() platform.ID {
return b.ID
}
// GetOrgID implements influxdb.Getter interface.
func (b Base) GetOrgID() platform.ID {
return b.OrgID
}
// GetOwnerID gets the ownerID associated with a Base.
func (b Base) GetOwnerID() platform.ID {
return b.OwnerID
}
// GetTaskID retrieves the task ID for a check.
func (b Base) GetTaskID() platform.ID {
return b.TaskID
}
// GetCRUDLog implements influxdb.Getter interface.
func (b Base) GetCRUDLog() influxdb.CRUDLog {
return b.CRUDLog
}
// GetName implements influxdb.Getter interface.
func (b *Base) GetName() string {
return b.Name
}
// GetDescription implements influxdb.Getter interface.
func (b *Base) GetDescription() string {
return b.Description
}
// SetID will set the primary key.
func (b *Base) SetID(id platform.ID) {
b.ID = id
}
// SetOrgID will set the org key.
func (b *Base) SetOrgID(id platform.ID) {
b.OrgID = id
}
// ClearPrivateData remove any data that we don't want to be exposed publicly.
func (b *Base) ClearPrivateData() {
b.TaskID = 0
}
// SetTaskID sets the taskID for a check.
func (b *Base) SetTaskID(id platform.ID) {
b.TaskID = id
}
// SetOwnerID sets the taskID for a check.
func (b *Base) SetOwnerID(id platform.ID) {
b.OwnerID = id
}
// SetName implements influxdb.Updator interface.
func (b *Base) SetName(name string) {
b.Name = name
}
// SetDescription implements influxdb.Updator interface.
func (b *Base) SetDescription(description string) {
b.Description = description
}
var typeToCheck = map[string](func() influxdb.Check){
"deadman": func() influxdb.Check { return &Deadman{} },
"threshold": func() influxdb.Check { return &Threshold{} },
"custom": func() influxdb.Check { return &Custom{} },
}
// UnmarshalJSON will convert
func UnmarshalJSON(b []byte) (influxdb.Check, error) {
var raw struct {
Type string `json:"type"`
}
if err := json.Unmarshal(b, &raw); err != nil {
return nil, &errors.Error{
Code: errors.EInvalid,
Msg: "unable to detect the check type from json",
}
}
convertedFunc, ok := typeToCheck[raw.Type]
if !ok {
return nil, &errors.Error{
Msg: fmt.Sprintf("invalid check type %s", raw.Type),
}
}
converted := convertedFunc()
err := json.Unmarshal(b, converted)
return converted, err
}