influxdb/chronograf/.kapacitor/client_test.go

1654 lines
37 KiB
Go

package kapacitor
import (
"context"
"fmt"
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/influxdata/platform/chronograf"
client "github.com/influxdata/kapacitor/client/v1"
)
type MockKapa struct {
ResTask client.Task
ResTasks []client.Task
TaskError error
UpdateError error
CreateError error
ListError error
DeleteError error
LastStatus client.TaskStatus
*client.CreateTaskOptions
client.Link
*client.TaskOptions
*client.ListTasksOptions
*client.UpdateTaskOptions
}
func (m *MockKapa) CreateTask(opt client.CreateTaskOptions) (client.Task, error) {
m.CreateTaskOptions = &opt
return m.ResTask, m.CreateError
}
func (m *MockKapa) Task(link client.Link, opt *client.TaskOptions) (client.Task, error) {
m.Link = link
m.TaskOptions = opt
return m.ResTask, m.TaskError
}
func (m *MockKapa) ListTasks(opt *client.ListTasksOptions) ([]client.Task, error) {
m.ListTasksOptions = opt
return m.ResTasks, m.ListError
}
func (m *MockKapa) UpdateTask(link client.Link, opt client.UpdateTaskOptions) (client.Task, error) {
m.Link = link
m.LastStatus = opt.Status
if m.UpdateTaskOptions == nil {
m.UpdateTaskOptions = &opt
}
return m.ResTask, m.UpdateError
}
func (m *MockKapa) DeleteTask(link client.Link) error {
m.Link = link
return m.DeleteError
}
type MockID struct {
ID string
}
func (m *MockID) Generate() (string, error) {
return m.ID, nil
}
func TestClient_All(t *testing.T) {
type fields struct {
URL string
Username string
Password string
ID chronograf.ID
Ticker chronograf.Ticker
kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error)
}
type args struct {
ctx context.Context
}
kapa := &MockKapa{}
tests := []struct {
name string
fields fields
args args
want map[string]*Task
wantErr bool
resTask client.Task
resTasks []client.Task
resError error
createTaskOptions client.CreateTaskOptions
link client.Link
taskOptions *client.TaskOptions
listTasksOptions *client.ListTasksOptions
updateTaskOptions client.UpdateTaskOptions
}{
{
name: "return no tasks",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
},
listTasksOptions: &client.ListTasksOptions{},
want: map[string]*Task{},
},
{
name: "return a non-reversible task",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
},
listTasksOptions: &client.ListTasksOptions{},
resTasks: []client.Task{
client.Task{
ID: "howdy",
Status: client.Enabled,
},
},
want: map[string]*Task{
"howdy": &Task{
ID: "howdy",
HrefOutput: "/kapacitor/v1/tasks/howdy/output",
Rule: chronograf.AlertRule{
ID: "howdy",
Name: "howdy",
TICKScript: "",
Type: "invalid",
Status: "enabled",
DBRPs: []chronograf.DBRP{},
},
TICKScript: "",
},
},
},
{
name: "return a reversible task",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
},
listTasksOptions: &client.ListTasksOptions{},
resTasks: []client.Task{
client.Task{
ID: "rule 1",
Status: client.Enabled,
Type: client.StreamTask,
DBRPs: []client.DBRP{
{
Database: "_internal",
RetentionPolicy: "autogen",
},
},
TICKscript: `var db = '_internal'
var rp = 'monitor'
var measurement = 'cq'
var groupBy = []
var whereFilter = lambda: TRUE
var name = 'rule 1'
var idVar = name + ':{{.Group}}'
var message = ''
var idTag = 'alertID'
var levelTag = 'level'
var messageField = 'message'
var durationField = 'duration'
var outputDB = 'chronograf'
var outputRP = 'autogen'
var outputMeasurement = 'alerts'
var triggerType = 'threshold'
var crit = 90000
var data = stream
|from()
.database(db)
.retentionPolicy(rp)
.measurement(measurement)
.groupBy(groupBy)
.where(whereFilter)
|eval(lambda: "queryOk")
.as('value')
var trigger = data
|alert()
.crit(lambda: "value" > crit)
.stateChangesOnly()
.message(message)
.id(idVar)
.idTag(idTag)
.levelTag(levelTag)
.messageField(messageField)
.durationField(durationField)
trigger
|eval(lambda: float("value"))
.as('value')
.keep()
|influxDBOut()
.create()
.database(outputDB)
.retentionPolicy(outputRP)
.measurement(outputMeasurement)
.tag('alertName', name)
.tag('triggerType', triggerType)
trigger
|httpOut('output')
`,
},
},
want: map[string]*Task{
"rule 1": &Task{
ID: "rule 1",
HrefOutput: "/kapacitor/v1/tasks/rule 1/output",
Rule: chronograf.AlertRule{
DBRPs: []chronograf.DBRP{
{
DB: "_internal",
RP: "autogen",
},
},
Type: "stream",
Status: "enabled",
ID: "rule 1",
Name: "rule 1",
TICKScript: `var db = '_internal'
var rp = 'monitor'
var measurement = 'cq'
var groupBy = []
var whereFilter = lambda: TRUE
var name = 'rule 1'
var idVar = name + ':{{.Group}}'
var message = ''
var idTag = 'alertID'
var levelTag = 'level'
var messageField = 'message'
var durationField = 'duration'
var outputDB = 'chronograf'
var outputRP = 'autogen'
var outputMeasurement = 'alerts'
var triggerType = 'threshold'
var crit = 90000
var data = stream
|from()
.database(db)
.retentionPolicy(rp)
.measurement(measurement)
.groupBy(groupBy)
.where(whereFilter)
|eval(lambda: "queryOk")
.as('value')
var trigger = data
|alert()
.crit(lambda: "value" > crit)
.stateChangesOnly()
.message(message)
.id(idVar)
.idTag(idTag)
.levelTag(levelTag)
.messageField(messageField)
.durationField(durationField)
trigger
|eval(lambda: float("value"))
.as('value')
.keep()
|influxDBOut()
.create()
.database(outputDB)
.retentionPolicy(outputRP)
.measurement(outputMeasurement)
.tag('alertName', name)
.tag('triggerType', triggerType)
trigger
|httpOut('output')
`,
Trigger: "threshold",
TriggerValues: chronograf.TriggerValues{
Operator: "greater than",
Value: "90000",
},
AlertNodes: chronograf.AlertNodes{
IsStateChangesOnly: true,
},
Query: &chronograf.QueryConfig{
Database: "_internal",
RetentionPolicy: "monitor",
Measurement: "cq",
Fields: []chronograf.Field{
{
Value: "queryOk",
Type: "field",
},
},
GroupBy: chronograf.GroupBy{
Tags: []string{},
},
AreTagsAccepted: false,
},
},
},
},
},
}
for _, tt := range tests {
kapa.ResTask = tt.resTask
kapa.ResTasks = tt.resTasks
kapa.ListError = tt.resError
t.Run(tt.name, func(t *testing.T) {
c := &Client{
URL: tt.fields.URL,
Username: tt.fields.Username,
Password: tt.fields.Password,
ID: tt.fields.ID,
Ticker: tt.fields.Ticker,
kapaClient: tt.fields.kapaClient,
}
got, err := c.All(tt.args.ctx)
if (err != nil) != tt.wantErr {
t.Errorf("Client.All() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !cmp.Equal(got, tt.want) {
t.Errorf("%q. Client.All() = -got/+want %s", tt.name, cmp.Diff(got, tt.want))
}
if !reflect.DeepEqual(kapa.ListTasksOptions, tt.listTasksOptions) {
t.Errorf("Client.All() = listTasksOptions %v, want %v", kapa.ListTasksOptions, tt.listTasksOptions)
}
if !reflect.DeepEqual(kapa.TaskOptions, tt.taskOptions) {
t.Errorf("Client.All() = taskOptions %v, want %v", kapa.TaskOptions, tt.taskOptions)
}
if !reflect.DeepEqual(kapa.ListTasksOptions, tt.listTasksOptions) {
t.Errorf("Client.All() = listTasksOptions %v, want %v", kapa.ListTasksOptions, tt.listTasksOptions)
}
if !reflect.DeepEqual(kapa.Link, tt.link) {
t.Errorf("Client.All() = Link %v, want %v", kapa.Link, tt.link)
}
})
}
}
func TestClient_Get(t *testing.T) {
type fields struct {
URL string
Username string
Password string
ID chronograf.ID
Ticker chronograf.Ticker
kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error)
}
type args struct {
ctx context.Context
id string
}
kapa := &MockKapa{}
tests := []struct {
name string
fields fields
args args
want *Task
wantErr bool
resTask client.Task
resTasks []client.Task
resError error
createTaskOptions client.CreateTaskOptions
link client.Link
taskOptions *client.TaskOptions
listTasksOptions *client.ListTasksOptions
updateTaskOptions client.UpdateTaskOptions
}{
{
name: "return no task",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
},
args: args{
id: "myid",
},
taskOptions: nil,
wantErr: true,
resError: fmt.Errorf("No such task"),
link: client.Link{
Href: "/kapacitor/v1/tasks/myid",
},
},
{
name: "return non-reversible task",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
},
args: args{
id: "myid",
},
taskOptions: nil,
resTask: client.Task{
ID: "myid",
Status: client.Enabled,
Type: client.StreamTask,
DBRPs: []client.DBRP{
{
Database: "_internal",
RetentionPolicy: "autogen",
},
},
},
want: &Task{
ID: "myid",
HrefOutput: "/kapacitor/v1/tasks/myid/output",
Rule: chronograf.AlertRule{
Type: "stream",
Status: "enabled",
ID: "myid",
Name: "myid",
DBRPs: []chronograf.DBRP{
{
DB: "_internal",
RP: "autogen",
},
},
},
},
link: client.Link{
Href: "/kapacitor/v1/tasks/myid",
},
},
{
name: "return reversible task",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
},
args: args{
id: "rule 1",
},
taskOptions: nil,
resTask: client.Task{
ID: "rule 1",
Status: client.Enabled,
Type: client.StreamTask,
DBRPs: []client.DBRP{
{
Database: "_internal",
RetentionPolicy: "autogen",
},
},
TICKscript: `var db = '_internal'
var rp = 'monitor'
var measurement = 'cq'
var groupBy = []
var whereFilter = lambda: TRUE
var name = 'rule 1'
var idVar = name + ':{{.Group}}'
var message = ''
var idTag = 'alertID'
var levelTag = 'level'
var messageField = 'message'
var durationField = 'duration'
var outputDB = 'chronograf'
var outputRP = 'autogen'
var outputMeasurement = 'alerts'
var triggerType = 'threshold'
var crit = 90000
var data = stream
|from()
.database(db)
.retentionPolicy(rp)
.measurement(measurement)
.groupBy(groupBy)
.where(whereFilter)
|eval(lambda: "queryOk")
.as('value')
var trigger = data
|alert()
.crit(lambda: "value" > crit)
.stateChangesOnly()
.message(message)
.id(idVar)
.idTag(idTag)
.levelTag(levelTag)
.messageField(messageField)
.durationField(durationField)
trigger
|eval(lambda: float("value"))
.as('value')
.keep()
|influxDBOut()
.create()
.database(outputDB)
.retentionPolicy(outputRP)
.measurement(outputMeasurement)
.tag('alertName', name)
.tag('triggerType', triggerType)
trigger
|httpOut('output')
`,
},
want: &Task{
ID: "rule 1",
HrefOutput: "/kapacitor/v1/tasks/rule 1/output",
Rule: chronograf.AlertRule{
Type: "stream",
Status: "enabled",
DBRPs: []chronograf.DBRP{
{
DB: "_internal",
RP: "autogen",
},
},
ID: "rule 1",
Name: "rule 1",
TICKScript: `var db = '_internal'
var rp = 'monitor'
var measurement = 'cq'
var groupBy = []
var whereFilter = lambda: TRUE
var name = 'rule 1'
var idVar = name + ':{{.Group}}'
var message = ''
var idTag = 'alertID'
var levelTag = 'level'
var messageField = 'message'
var durationField = 'duration'
var outputDB = 'chronograf'
var outputRP = 'autogen'
var outputMeasurement = 'alerts'
var triggerType = 'threshold'
var crit = 90000
var data = stream
|from()
.database(db)
.retentionPolicy(rp)
.measurement(measurement)
.groupBy(groupBy)
.where(whereFilter)
|eval(lambda: "queryOk")
.as('value')
var trigger = data
|alert()
.crit(lambda: "value" > crit)
.stateChangesOnly()
.message(message)
.id(idVar)
.idTag(idTag)
.levelTag(levelTag)
.messageField(messageField)
.durationField(durationField)
trigger
|eval(lambda: float("value"))
.as('value')
.keep()
|influxDBOut()
.create()
.database(outputDB)
.retentionPolicy(outputRP)
.measurement(outputMeasurement)
.tag('alertName', name)
.tag('triggerType', triggerType)
trigger
|httpOut('output')
`,
Trigger: "threshold",
TriggerValues: chronograf.TriggerValues{
Operator: "greater than",
Value: "90000",
},
AlertNodes: chronograf.AlertNodes{
IsStateChangesOnly: true,
},
Query: &chronograf.QueryConfig{
Database: "_internal",
RetentionPolicy: "monitor",
Measurement: "cq",
Fields: []chronograf.Field{
{
Value: "queryOk",
Type: "field",
},
},
GroupBy: chronograf.GroupBy{
Tags: []string{},
},
AreTagsAccepted: false,
},
},
},
link: client.Link{
Href: "/kapacitor/v1/tasks/rule 1",
},
},
}
for _, tt := range tests {
kapa.ResTask = tt.resTask
kapa.ResTasks = tt.resTasks
kapa.TaskError = tt.resError
t.Run(tt.name, func(t *testing.T) {
c := &Client{
URL: tt.fields.URL,
Username: tt.fields.Username,
Password: tt.fields.Password,
ID: tt.fields.ID,
Ticker: tt.fields.Ticker,
kapaClient: tt.fields.kapaClient,
}
got, err := c.Get(tt.args.ctx, tt.args.id)
if (err != nil) != tt.wantErr {
t.Errorf("Client.Get() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !cmp.Equal(got, tt.want) {
t.Errorf("%q. Client.All() = -got/+want %s", tt.name, cmp.Diff(got, tt.want))
}
if !reflect.DeepEqual(kapa.ListTasksOptions, tt.listTasksOptions) {
t.Errorf("Client.Get() = listTasksOptions %v, want %v", kapa.ListTasksOptions, tt.listTasksOptions)
}
if !reflect.DeepEqual(kapa.TaskOptions, tt.taskOptions) {
t.Errorf("Client.Get() = taskOptions %v, want %v", kapa.TaskOptions, tt.taskOptions)
}
if !reflect.DeepEqual(kapa.ListTasksOptions, tt.listTasksOptions) {
t.Errorf("Client.Get() = listTasksOptions %v, want %v", kapa.ListTasksOptions, tt.listTasksOptions)
}
if !reflect.DeepEqual(kapa.Link, tt.link) {
t.Errorf("Client.Get() = Link %v, want %v", kapa.Link, tt.link)
}
})
}
}
func TestClient_updateStatus(t *testing.T) {
type fields struct {
URL string
Username string
Password string
ID chronograf.ID
Ticker chronograf.Ticker
kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error)
}
type args struct {
ctx context.Context
href string
status client.TaskStatus
}
kapa := &MockKapa{}
tests := []struct {
name string
fields fields
args args
resTask client.Task
want *Task
resError error
wantErr bool
updateTaskOptions *client.UpdateTaskOptions
}{
{
name: "disable alert rule",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/howdy",
status: client.Disabled,
},
resTask: client.Task{
ID: "howdy",
Status: client.Disabled,
Type: client.StreamTask,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
Link: client.Link{
Href: "/kapacitor/v1/tasks/howdy",
},
},
updateTaskOptions: &client.UpdateTaskOptions{
TICKscript: "",
Status: client.Disabled,
},
want: &Task{
ID: "howdy",
Href: "/kapacitor/v1/tasks/howdy",
HrefOutput: "/kapacitor/v1/tasks/howdy/output",
Rule: chronograf.AlertRule{
ID: "howdy",
Name: "howdy",
Type: "stream",
DBRPs: []chronograf.DBRP{
{
DB: "db",
RP: "rp",
},
},
Status: "disabled",
},
},
},
{
name: "fail to enable alert rule",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/howdy",
status: client.Enabled,
},
updateTaskOptions: &client.UpdateTaskOptions{
TICKscript: "",
Status: client.Enabled,
},
resError: fmt.Errorf("error"),
wantErr: true,
},
{
name: "enable alert rule",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/howdy",
status: client.Enabled,
},
resTask: client.Task{
ID: "howdy",
Type: client.StreamTask,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
Status: client.Enabled,
Link: client.Link{
Href: "/kapacitor/v1/tasks/howdy",
},
},
updateTaskOptions: &client.UpdateTaskOptions{
TICKscript: "",
Status: client.Enabled,
},
want: &Task{
ID: "howdy",
Href: "/kapacitor/v1/tasks/howdy",
HrefOutput: "/kapacitor/v1/tasks/howdy/output",
Rule: chronograf.AlertRule{
ID: "howdy",
Name: "howdy",
Type: "stream",
DBRPs: []chronograf.DBRP{
{
DB: "db",
RP: "rp",
},
},
Status: "enabled",
},
},
},
}
for _, tt := range tests {
kapa.ResTask = tt.resTask
kapa.UpdateError = tt.resError
kapa.UpdateTaskOptions = nil
t.Run(tt.name, func(t *testing.T) {
c := &Client{
URL: tt.fields.URL,
Username: tt.fields.Username,
Password: tt.fields.Password,
ID: tt.fields.ID,
Ticker: tt.fields.Ticker,
kapaClient: tt.fields.kapaClient,
}
got, err := c.updateStatus(tt.args.ctx, tt.args.href, tt.args.status)
if (err != nil) != tt.wantErr {
t.Errorf("Client.updateStatus() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !cmp.Equal(got, tt.want) {
t.Errorf("%q. Client.updateStatus() = -got/+want %s", tt.name, cmp.Diff(got, tt.want))
}
if !reflect.DeepEqual(kapa.UpdateTaskOptions, tt.updateTaskOptions) {
t.Errorf("Client.updateStatus() = %v, want %v", kapa.UpdateTaskOptions, tt.updateTaskOptions)
}
})
}
}
func TestClient_Update(t *testing.T) {
type fields struct {
URL string
Username string
Password string
ID chronograf.ID
Ticker chronograf.Ticker
kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error)
}
type args struct {
ctx context.Context
href string
rule chronograf.AlertRule
}
kapa := &MockKapa{}
tests := []struct {
name string
fields fields
args args
resTask client.Task
want *Task
resError error
wantErr bool
updateTaskOptions *client.UpdateTaskOptions
wantStatus client.TaskStatus
}{
{
name: "update alert rule error",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/howdy",
rule: chronograf.AlertRule{
ID: "howdy",
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
},
},
},
resError: fmt.Errorf("error"),
updateTaskOptions: &client.UpdateTaskOptions{
TICKscript: "",
Type: client.StreamTask,
Status: client.Disabled,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
},
wantErr: true,
wantStatus: client.Disabled,
},
{
name: "update alert rule",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/howdy",
rule: chronograf.AlertRule{
ID: "howdy",
Name: "myname",
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
Measurement: "meas",
Fields: []chronograf.Field{
{
Type: "field",
Value: "usage_user",
},
},
},
Trigger: "threshold",
TriggerValues: chronograf.TriggerValues{
Operator: greaterThan,
},
},
},
resTask: client.Task{
ID: "howdy",
Type: client.StreamTask,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
Status: client.Enabled,
Link: client.Link{
Href: "/kapacitor/v1/tasks/howdy",
},
},
updateTaskOptions: &client.UpdateTaskOptions{
TICKscript: "",
Type: client.StreamTask,
Status: client.Disabled,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
},
want: &Task{
ID: "howdy",
Href: "/kapacitor/v1/tasks/howdy",
HrefOutput: "/kapacitor/v1/tasks/howdy/output",
Rule: chronograf.AlertRule{
DBRPs: []chronograf.DBRP{
{
DB: "db",
RP: "rp",
},
},
Status: "enabled",
Type: "stream",
ID: "howdy",
Name: "howdy",
},
},
wantStatus: client.Enabled,
},
{
name: "stays disabled when already disabled",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/howdy",
rule: chronograf.AlertRule{
ID: "howdy",
Name: "myname",
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
Measurement: "meas",
Fields: []chronograf.Field{
{
Type: "field",
Value: "usage_user",
},
},
},
Trigger: "threshold",
TriggerValues: chronograf.TriggerValues{
Operator: greaterThan,
},
},
},
resTask: client.Task{
ID: "howdy",
Type: client.StreamTask,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
Status: client.Disabled,
Link: client.Link{
Href: "/kapacitor/v1/tasks/howdy",
},
},
updateTaskOptions: &client.UpdateTaskOptions{
TICKscript: "",
Type: client.StreamTask,
Status: client.Disabled,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
},
want: &Task{
ID: "howdy",
Href: "/kapacitor/v1/tasks/howdy",
HrefOutput: "/kapacitor/v1/tasks/howdy/output",
Rule: chronograf.AlertRule{
ID: "howdy",
Name: "howdy",
DBRPs: []chronograf.DBRP{
{
DB: "db",
RP: "rp",
},
},
Status: "disabled",
Type: "stream",
},
},
wantStatus: client.Disabled,
},
{
name: "error because relative cannot have inside range",
wantErr: true,
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/error",
rule: chronograf.AlertRule{
ID: "error",
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
Fields: []chronograf.Field{
{
Value: "usage_user",
Type: "field",
},
},
},
Trigger: Relative,
TriggerValues: chronograf.TriggerValues{
Operator: insideRange,
},
},
},
},
{
name: "error because rule has an unknown trigger mechanism",
wantErr: true,
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/error",
rule: chronograf.AlertRule{
ID: "error",
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
},
},
},
},
{
name: "error because query has no fields",
wantErr: true,
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/error",
rule: chronograf.AlertRule{
ID: "error",
Trigger: Threshold,
TriggerValues: chronograf.TriggerValues{
Period: "1d",
},
Name: "myname",
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
Measurement: "meas",
},
},
},
},
{
name: "error because alert has no name",
wantErr: true,
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/error",
rule: chronograf.AlertRule{
ID: "error",
Trigger: Deadman,
TriggerValues: chronograf.TriggerValues{
Period: "1d",
},
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
Measurement: "meas",
},
},
},
},
{
name: "error because alert period cannot be an empty string in deadman alert",
wantErr: true,
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/error",
rule: chronograf.AlertRule{
ID: "error",
Name: "myname",
Trigger: Deadman,
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
Measurement: "meas",
},
},
},
},
}
for _, tt := range tests {
kapa.ResTask = tt.resTask
kapa.UpdateError = tt.resError
t.Run(tt.name, func(t *testing.T) {
c := &Client{
URL: tt.fields.URL,
Username: tt.fields.Username,
Password: tt.fields.Password,
ID: tt.fields.ID,
Ticker: tt.fields.Ticker,
kapaClient: tt.fields.kapaClient,
}
got, err := c.Update(tt.args.ctx, tt.args.href, tt.args.rule)
if (err != nil) != tt.wantErr {
t.Errorf("Client.Update() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr {
return
}
if !cmp.Equal(got, tt.want) {
t.Errorf("%q. Client.Update() = -got/+want %s", tt.name, cmp.Diff(got, tt.want))
}
var cmpOptions = cmp.Options{
cmpopts.IgnoreFields(client.UpdateTaskOptions{}, "TICKscript"),
}
if !cmp.Equal(kapa.UpdateTaskOptions, tt.updateTaskOptions, cmpOptions...) {
t.Errorf("Client.Update() = %s", cmp.Diff(got, tt.updateTaskOptions, cmpOptions...))
}
if tt.wantStatus != kapa.LastStatus {
t.Errorf("Client.Update() = %v, want %v", kapa.LastStatus, tt.wantStatus)
}
})
}
}
func TestClient_Create(t *testing.T) {
type fields struct {
URL string
Username string
Password string
ID chronograf.ID
Ticker chronograf.Ticker
kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error)
}
type args struct {
ctx context.Context
rule chronograf.AlertRule
}
kapa := &MockKapa{}
tests := []struct {
name string
fields fields
args args
resTask client.Task
want *Task
resError error
wantErr bool
createTaskOptions *client.CreateTaskOptions
}{
{
name: "create alert rule with tags",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
ID: &MockID{
ID: "howdy",
},
},
args: args{
ctx: context.Background(),
rule: chronograf.AlertRule{
ID: "",
Name: "myname's",
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
Measurement: "meas",
GroupBy: chronograf.GroupBy{
Tags: []string{
"tag1",
"tag2",
},
},
},
Trigger: Deadman,
TriggerValues: chronograf.TriggerValues{
Period: "1d",
},
},
},
resTask: client.Task{
ID: "chronograf-v1-howdy",
Status: client.Enabled,
Type: client.StreamTask,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
Link: client.Link{
Href: "/kapacitor/v1/tasks/chronograf-v1-howdy",
},
},
createTaskOptions: &client.CreateTaskOptions{
TICKscript: `var db = 'db'
var rp = 'rp'
var measurement = 'meas'
var groupBy = ['tag1', 'tag2']
var whereFilter = lambda: TRUE
var period = 1d
var name = 'myname\'s'
var idVar = name + ':{{.Group}}'
var message = ''
var idTag = 'alertID'
var levelTag = 'level'
var messageField = 'message'
var durationField = 'duration'
var outputDB = 'chronograf'
var outputRP = 'autogen'
var outputMeasurement = 'alerts'
var triggerType = 'deadman'
var threshold = 0.0
var data = stream
|from()
.database(db)
.retentionPolicy(rp)
.measurement(measurement)
.groupBy(groupBy)
.where(whereFilter)
var trigger = data
|deadman(threshold, period)
.stateChangesOnly()
.message(message)
.id(idVar)
.idTag(idTag)
.levelTag(levelTag)
.messageField(messageField)
.durationField(durationField)
trigger
|eval(lambda: "emitted")
.as('value')
.keep('value', messageField, durationField)
|eval(lambda: float("value"))
.as('value')
.keep()
|influxDBOut()
.create()
.database(outputDB)
.retentionPolicy(outputRP)
.measurement(outputMeasurement)
.tag('alertName', name)
.tag('triggerType', triggerType)
trigger
|httpOut('output')
`,
ID: "chronograf-v1-howdy",
Type: client.StreamTask,
Status: client.Enabled,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
},
want: &Task{
ID: "chronograf-v1-howdy",
Href: "/kapacitor/v1/tasks/chronograf-v1-howdy",
HrefOutput: "/kapacitor/v1/tasks/chronograf-v1-howdy/output",
Rule: chronograf.AlertRule{
Type: "stream",
DBRPs: []chronograf.DBRP{
{
DB: "db",
RP: "rp",
},
},
Status: "enabled",
ID: "chronograf-v1-howdy",
Name: "chronograf-v1-howdy",
},
},
},
{
name: "create alert rule with no tags",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
ID: &MockID{
ID: "howdy",
},
},
args: args{
ctx: context.Background(),
rule: chronograf.AlertRule{
ID: "",
Name: "myname's",
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
Measurement: "meas",
},
Trigger: Deadman,
TriggerValues: chronograf.TriggerValues{
Period: "1d",
},
},
},
resTask: client.Task{
ID: "chronograf-v1-howdy",
Status: client.Enabled,
Type: client.StreamTask,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
Link: client.Link{
Href: "/kapacitor/v1/tasks/chronograf-v1-howdy",
},
},
createTaskOptions: &client.CreateTaskOptions{
TICKscript: `var db = 'db'
var rp = 'rp'
var measurement = 'meas'
var groupBy = []
var whereFilter = lambda: TRUE
var period = 1d
var name = 'myname\'s'
var idVar = name
var message = ''
var idTag = 'alertID'
var levelTag = 'level'
var messageField = 'message'
var durationField = 'duration'
var outputDB = 'chronograf'
var outputRP = 'autogen'
var outputMeasurement = 'alerts'
var triggerType = 'deadman'
var threshold = 0.0
var data = stream
|from()
.database(db)
.retentionPolicy(rp)
.measurement(measurement)
.groupBy(groupBy)
.where(whereFilter)
var trigger = data
|deadman(threshold, period)
.stateChangesOnly()
.message(message)
.id(idVar)
.idTag(idTag)
.levelTag(levelTag)
.messageField(messageField)
.durationField(durationField)
trigger
|eval(lambda: "emitted")
.as('value')
.keep('value', messageField, durationField)
|eval(lambda: float("value"))
.as('value')
.keep()
|influxDBOut()
.create()
.database(outputDB)
.retentionPolicy(outputRP)
.measurement(outputMeasurement)
.tag('alertName', name)
.tag('triggerType', triggerType)
trigger
|httpOut('output')
`,
ID: "chronograf-v1-howdy",
Type: client.StreamTask,
Status: client.Enabled,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
},
want: &Task{
ID: "chronograf-v1-howdy",
Href: "/kapacitor/v1/tasks/chronograf-v1-howdy",
HrefOutput: "/kapacitor/v1/tasks/chronograf-v1-howdy/output",
Rule: chronograf.AlertRule{
Type: "stream",
DBRPs: []chronograf.DBRP{
{
DB: "db",
RP: "rp",
},
},
Status: "enabled",
ID: "chronograf-v1-howdy",
Name: "chronograf-v1-howdy",
},
},
},
{
name: "create alert rule error",
fields: fields{
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
ID: &MockID{
ID: "howdy",
},
},
args: args{
ctx: context.Background(),
rule: chronograf.AlertRule{
ID: "howdy",
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
},
},
},
resError: fmt.Errorf("error"),
createTaskOptions: &client.CreateTaskOptions{
ID: "chronograf-v1-howdy",
Type: client.StreamTask,
Status: client.Enabled,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
},
wantErr: true,
},
}
for _, tt := range tests {
kapa.ResTask = tt.resTask
kapa.CreateError = tt.resError
t.Run(tt.name, func(t *testing.T) {
c := &Client{
URL: tt.fields.URL,
Username: tt.fields.Username,
Password: tt.fields.Password,
ID: tt.fields.ID,
Ticker: tt.fields.Ticker,
kapaClient: tt.fields.kapaClient,
}
got, err := c.Create(tt.args.ctx, tt.args.rule)
if (err != nil) != tt.wantErr {
t.Errorf("Client.Create() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr {
return
}
if !cmp.Equal(got, tt.want) {
t.Errorf("%q. Client.Create() = -got/+want %s", tt.name, cmp.Diff(got, tt.want))
}
if !reflect.DeepEqual(kapa.CreateTaskOptions, tt.createTaskOptions) {
t.Errorf("Client.Create() = %v, want %v", kapa.CreateTaskOptions, tt.createTaskOptions)
}
})
}
}