feat(influxdb): add check struct

pull/14521/head
Jade McGough 2019-07-19 02:42:01 -07:00 committed by Kelvin Wang
parent b2fe0d1d63
commit ec9ecf23a1
8 changed files with 704 additions and 0 deletions

View File

@ -127,6 +127,8 @@ const (
NotificationRuleResourceType = ResourceType("notificationRules") // 14
// NotificationEndpointResourceType gives permission to one or more notificationEndpoints.
NotificationEndpointResourceType = ResourceType("notificationEndpoints") // 15
// ChecksResourceType gives permission to one or more Checks.
ChecksResourceType = ResourceType("checks") // 16
)
// AllResourceTypes is the list of all known resource types.
@ -147,6 +149,7 @@ var AllResourceTypes = []ResourceType{
DocumentsResourceType, // 13
NotificationRuleResourceType, // 14
NotificationEndpointResourceType, // 15
ChecksResourceType, // 16
// NOTE: when modifying this list, please update the swagger for components.schemas.Permission resource enum.
}
@ -163,6 +166,7 @@ var OrgResourceTypes = []ResourceType{
DocumentsResourceType, // 13
NotificationRuleResourceType, // 14
NotificationEndpointResourceType, // 15
ChecksResourceType, // 16
}
// Valid checks if the resource type is a member of the ResourceType enum.
@ -189,6 +193,7 @@ func (t ResourceType) Valid() (err error) {
case DocumentsResourceType: // 13
case NotificationRuleResourceType: // 14
case NotificationEndpointResourceType: // 15
case ChecksResourceType: // 16
default:
err = ErrInvalidResourceType
}

127
check.go Normal file
View File

@ -0,0 +1,127 @@
package influxdb
import (
"context"
"encoding/json"
)
// consts for checks config.
const (
CheckDefaultPageSize = 100
CheckMaxPageSize = 500
)
// Check represents the information required to generate a periodic check task.
type Check interface {
Valid() error
Type() string
json.Marshaler
Updator
Getter
}
// ops for checks error
var (
OpFindCheckByID = "FindCheckByID"
OpFindCheck = "FindCheck"
OpFindChecks = "FindChecks"
OpCreateCheck = "CreateCheck"
OpUpdateCheck = "UpdateCheck"
OpDeleteCheck = "DeleteCheck"
)
// CheckService represents a service for managing checks.
type CheckService interface {
// UserResourceMappingService must be part of all CheckService service,
// for create, delete.
UserResourceMappingService
// OrganizationService is needed for search filter
OrganizationService
// FindCheckByID returns a single check by ID.
FindCheckByID(ctx context.Context, id ID) (Check, error)
// FindCheck returns the first check that matches filter.
FindCheck(ctx context.Context, filter CheckFilter) (Check, error)
// FindChecks returns a list of checks that match filter and the total count of matching checkns.
// Additional options provide pagination & sorting.
FindChecks(ctx context.Context, filter CheckFilter, opt ...FindOptions) ([]Check, int, error)
// CreateCheck creates a new check and sets b.ID with the new identifier.
CreateCheck(ctx context.Context, c Check) error
// UpdateCheck updates the whole check.
// Returns the new check state after update.
UpdateCheck(ctx context.Context, id ID, c Check) (Check, error)
// PatchCheck updates a single bucket with changeset.
// Returns the new check state after update.
PatchCheck(ctx context.Context, id ID, upd CheckUpdate) (Check, error)
// DeleteCheck will delete the check by id.
DeleteCheck(ctx context.Context, id ID) error
}
// CheckUpdate are properties than can be updated on a check
type CheckUpdate struct {
Name *string `json:"name,omitempty"`
Status *Status `json:"status,omitempty"`
Description *string `json:"description,omitempty"`
}
// Valid returns err is the update is invalid.
func (n *CheckUpdate) Valid() error {
if n.Name != nil && *n.Name == "" {
return &Error{
Code: EInvalid,
Msg: "Check Name can't be empty",
}
}
if n.Description != nil && *n.Description == "" {
return &Error{
Code: EInvalid,
Msg: "Check Description can't be empty",
}
}
if n.Status != nil {
if err := n.Status.Valid(); err != nil {
return err
}
}
return nil
}
// CheckFilter represents a set of filters that restrict the returned results.
type CheckFilter struct {
ID *ID
Name *string
OrgID *ID
Org *string
}
// QueryParams Converts CheckFilter fields to url query params.
func (f CheckFilter) QueryParams() map[string][]string {
qp := map[string][]string{}
if f.ID != nil {
qp["id"] = []string{f.ID.String()}
}
if f.Name != nil {
qp["name"] = []string{*f.Name}
}
if f.OrgID != nil {
qp["orgID"] = []string{f.OrgID.String()}
}
if f.Org != nil {
qp["org"] = []string{*f.Org}
}
return qp
}

154
notification/check/check.go Normal file
View File

@ -0,0 +1,154 @@
package check
import (
"encoding/json"
"fmt"
"github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/notification"
)
// Base will embed inside a check.
type Base struct {
ID influxdb.ID `json:"id,omitempty"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
AuthorizationID influxdb.ID `json:"authorizationID,omitempty"`
OrgID influxdb.ID `json:"orgID,omitempty"`
Status influxdb.Status `json:"status"`
Query influxdb.DashboardQuery `json:"query"`
StatusMessageTemplate string `json:"statusMessageTemplate"`
Cron string `json:"cron,omitempty"`
Every influxdb.Duration `json:"every,omitempty"`
// Offset represents a delay before execution.
// It gets marshalled from a string duration, i.e.: "10s" is 10 seconds
Offset influxdb.Duration `json:"offset,omitempty"`
Tags []notification.Tag `json:"tags"`
influxdb.CRUDLog
}
// Valid returns err if the check is invalid.
func (b Base) Valid() error {
if !b.ID.Valid() {
return &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "Check ID is invalid",
}
}
if b.Name == "" {
return &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "Check Name can't be empty",
}
}
if !b.AuthorizationID.Valid() {
return &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "Check AuthorizationID is invalid",
}
}
if !b.OrgID.Valid() {
return &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "Check OrgID is invalid",
}
}
if b.Status != influxdb.Active && b.Status != influxdb.Inactive {
return &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "invalid status",
}
}
for _, tag := range b.Tags {
if err := tag.Valid(); err != nil {
return err
}
}
return nil
}
// GetID implements influxdb.Getter interface.
func (b Base) GetID() influxdb.ID {
return b.ID
}
// GetOrgID implements influxdb.Getter interface.
func (b Base) GetOrgID() influxdb.ID {
return b.OrgID
}
// 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
}
// GetStatus implements influxdb.Getter interface.
func (b *Base) GetStatus() influxdb.Status {
return b.Status
}
// SetID will set the primary key.
func (b *Base) SetID(id influxdb.ID) {
b.ID = id
}
// SetOrgID will set the org key.
func (b *Base) SetOrgID(id influxdb.ID) {
b.OrgID = 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
}
// SetStatus implements influxdb.Updator interface.
func (b *Base) SetStatus(status influxdb.Status) {
b.Status = status
}
var typeToCheck = map[string](func() influxdb.Check){
"deadman": func() influxdb.Check { return &Deadman{} },
"threshold": func() influxdb.Check { return &Threshold{} },
}
type rawRuleJSON struct {
Typ string `json:"type"`
}
// UnmarshalJSON will convert
func UnmarshalJSON(b []byte) (influxdb.Check, error) {
var raw rawRuleJSON
if err := json.Unmarshal(b, &raw); err != nil {
return nil, &influxdb.Error{
Msg: "unable to detect the check type from json",
}
}
convertedFunc, ok := typeToCheck[raw.Typ]
if !ok {
return nil, &influxdb.Error{
Msg: fmt.Sprintf("invalid check type %s", raw.Typ),
}
}
converted := convertedFunc()
err := json.Unmarshal(b, converted)
return converted, err
}

View File

@ -0,0 +1,230 @@
package check_test
import (
"encoding/json"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/influxdb/notification"
"github.com/influxdata/influxdb/mock"
"github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/notification/check"
influxTesting "github.com/influxdata/influxdb/testing"
)
const (
id1 = "020f755c3c082000"
id2 = "020f755c3c082001"
id3 = "020f755c3c082002"
)
func numPtr(f float64) *float64 {
p := new(float64)
*p = f
return p
}
var goodBase = check.Base{
ID: influxTesting.MustIDBase16(id1),
Name: "name1",
AuthorizationID: influxTesting.MustIDBase16(id2),
OrgID: influxTesting.MustIDBase16(id3),
Status: influxdb.Active,
StatusMessageTemplate: "temp1",
Tags: []notification.Tag{
{Key: "k1", Value: "v1"},
{Key: "k2", Value: "v2"},
},
}
func TestValidCheck(t *testing.T) {
cases := []struct {
name string
src influxdb.Check
err error
}{
{
name: "invalid check id",
src: &check.Deadman{},
err: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "Check ID is invalid",
},
},
{
name: "empty name",
src: &check.Threshold{
Base: check.Base{
ID: influxTesting.MustIDBase16(id1),
},
},
err: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "Check Name can't be empty",
},
},
{
name: "invalid auth id",
src: &check.Threshold{
Base: check.Base{
ID: influxTesting.MustIDBase16(id1),
Name: "name1",
},
},
err: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "Check AuthorizationID is invalid",
},
},
{
name: "invalid org id",
src: &check.Threshold{
Base: check.Base{
ID: influxTesting.MustIDBase16(id1),
Name: "name1",
AuthorizationID: influxTesting.MustIDBase16(id2),
},
},
err: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "Check OrgID is invalid",
},
},
{
name: "invalid status",
src: &check.Deadman{
Base: check.Base{
ID: influxTesting.MustIDBase16(id1),
Name: "name1",
AuthorizationID: influxTesting.MustIDBase16(id2),
OrgID: influxTesting.MustIDBase16(id3),
},
},
err: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "invalid status",
},
},
{
name: "invalid tag",
src: &check.Deadman{
Base: check.Base{
ID: influxTesting.MustIDBase16(id1),
Name: "name1",
AuthorizationID: influxTesting.MustIDBase16(id2),
OrgID: influxTesting.MustIDBase16(id3),
Status: influxdb.Active,
StatusMessageTemplate: "temp1",
Tags: []notification.Tag{{Key: "key1"}},
},
},
err: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "tag must contain a key and a value",
},
},
{
name: "bad thredshold",
src: &check.Threshold{
Base: goodBase,
Thresholds: []check.ThresholdConfig{{}},
},
err: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "threshold must have at least one lowerBound or upperBound value",
},
},
}
for _, c := range cases {
got := c.src.Valid()
influxTesting.ErrorsEqual(t, got, c.err)
}
}
var timeGen1 = mock.TimeGenerator{FakeValue: time.Date(2006, time.July, 13, 4, 19, 10, 0, time.UTC)}
var timeGen2 = mock.TimeGenerator{FakeValue: time.Date(2006, time.July, 14, 5, 23, 53, 10, time.UTC)}
func TestJSON(t *testing.T) {
cases := []struct {
name string
src influxdb.Check
}{
{
name: "simple Deadman",
src: &check.Deadman{
Base: check.Base{
ID: influxTesting.MustIDBase16(id1),
AuthorizationID: influxTesting.MustIDBase16(id2),
Name: "name1",
OrgID: influxTesting.MustIDBase16(id3),
Status: influxdb.Active,
Every: influxdb.Duration{Duration: time.Hour},
Tags: []notification.Tag{
{
Key: "k1",
Value: "v1",
},
{
Key: "k2",
Value: "v2",
},
},
CRUDLog: influxdb.CRUDLog{
CreatedAt: timeGen1.Now(),
UpdatedAt: timeGen2.Now(),
},
},
TimeSince: 33,
ReportZero: true,
Level: notification.Warn,
},
},
{
name: "simple threshold",
src: &check.Threshold{
Base: check.Base{
ID: influxTesting.MustIDBase16(id1),
Name: "name1",
AuthorizationID: influxTesting.MustIDBase16(id2),
OrgID: influxTesting.MustIDBase16(id3),
Status: influxdb.Active,
Every: influxdb.Duration{Duration: time.Hour},
Tags: []notification.Tag{
{
Key: "k1",
Value: "v1",
},
{
Key: "k2",
Value: "v2",
},
},
CRUDLog: influxdb.CRUDLog{
CreatedAt: timeGen1.Now(),
UpdatedAt: timeGen2.Now(),
},
},
Thresholds: []check.ThresholdConfig{
{AllValues: true, LowerBound: numPtr(-1.36)},
{LowerBound: numPtr(10000), UpperBound: numPtr(500)},
},
},
},
}
for _, c := range cases {
b, err := json.Marshal(c.src)
if err != nil {
t.Fatalf("%s marshal failed, err: %s", c.name, err.Error())
}
got, err := check.UnmarshalJSON(b)
if err != nil {
t.Fatalf("%s unmarshal failed, err: %s", c.name, err.Error())
}
if diff := cmp.Diff(got, c.src); diff != "" {
t.Errorf("failed %s, Check are different -got/+want\ndiff %s", c.name, diff)
}
}
}

View File

@ -0,0 +1,39 @@
package check
import (
"encoding/json"
"github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/notification"
)
var _ influxdb.Check = &Deadman{}
// Deadman is the deadman check.
type Deadman struct {
Base
// seconds before deadman triggers
TimeSince uint `json:"timeSince"`
// If only zero values reported since time, trigger alert.
ReportZero bool `json:"reportZero"`
Level notification.CheckLevel `json:"level"`
}
// Type returns the type of the check.
func (c Deadman) Type() string {
return "deadman"
}
type deadmanAlias Deadman
// MarshalJSON implement json.Marshaler interface.
func (c Deadman) MarshalJSON() ([]byte, error) {
return json.Marshal(
struct {
deadmanAlias
Type string `json:"type"`
}{
deadmanAlias: deadmanAlias(c),
Type: c.Type(),
})
}

View File

@ -0,0 +1,68 @@
package check
import (
"encoding/json"
"github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/notification"
)
var _ influxdb.Check = &Threshold{}
// Threshold is the threshold check.
type Threshold struct {
Base
Thresholds []ThresholdConfig `json:"thresholds"`
}
// Type returns the type of the check.
func (c Threshold) Type() string {
return "threshold"
}
// Valid returns error if something is invalid.
func (c Threshold) Valid() error {
if err := c.Base.Valid(); err != nil {
return err
}
for _, cc := range c.Thresholds {
if err := cc.Valid(); err != nil {
return err
}
}
return nil
}
type thresholdAlias Threshold
// MarshalJSON implement json.Marshaler interface.
func (c Threshold) MarshalJSON() ([]byte, error) {
return json.Marshal(
struct {
thresholdAlias
Type string `json:"type"`
}{
thresholdAlias: thresholdAlias(c),
Type: c.Type(),
})
}
// ThresholdConfig is the base of all threshold config.
type ThresholdConfig struct {
// If true, only alert if all values meet threshold.
AllValues bool `json:"allValues"`
Level notification.CheckLevel `json:"level"`
LowerBound *float64 `json:"lowerBound,omitempty"`
UpperBound *float64 `json:"upperBound,omitempty"`
}
// Valid returns error if something is invalid.
func (c ThresholdConfig) Valid() error {
if c.LowerBound == nil && c.UpperBound == nil {
return &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "threshold must have at least one lowerBound or upperBound value",
}
}
return nil
}

View File

@ -12,6 +12,17 @@ type Tag struct {
Value string `json:"value"`
}
// Valid returns an error if the tag is missing fields
func (t Tag) Valid() error {
if t.Key == "" || t.Value == "" {
return &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "tag must contain a key and a value",
}
}
return nil
}
// TagRule is the struct of tag rule.
type TagRule struct {
Tag
@ -38,6 +49,9 @@ var availableOperator = map[Operator]bool{
// Valid returns error for invalid operators.
func (tr TagRule) Valid() error {
if err := tr.Tag.Valid(); err != nil {
return err
}
if _, ok := availableOperator[tr.Operator]; !ok {
return &influxdb.Error{
Code: influxdb.EInvalid,

67
notification/tag_test.go Normal file
View File

@ -0,0 +1,67 @@
package notification_test
import (
"testing"
"github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/notification"
influxTesting "github.com/influxdata/influxdb/testing"
)
func TestTagValid(t *testing.T) {
cases := []struct {
name string
src notification.TagRule
err error
}{
{
name: "regular status rule",
src: notification.TagRule{
Tag: notification.Tag{Key: "k1", Value: "v1"},
Operator: notification.Equal,
},
},
{
name: "empty",
src: notification.TagRule{},
err: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "tag must contain a key and a value",
},
},
{
name: "empty key",
src: notification.TagRule{
Tag: notification.Tag{Value: "v1"},
},
err: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "tag must contain a key and a value",
},
},
{
name: "empty value",
src: notification.TagRule{
Tag: notification.Tag{Key: "k1"},
},
err: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "tag must contain a key and a value",
},
},
{
name: "invalid operator",
src: notification.TagRule{
Tag: notification.Tag{Key: "k1", Value: "v1"},
},
err: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "Operator \"\" is invalid",
},
},
}
for _, c := range cases {
err := c.src.Valid()
influxTesting.ErrorsEqual(t, err, c.err)
}
}