5074 lines
142 KiB
Go
5074 lines
142 KiB
Go
package pkger
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/influxdata/influxdb/v2"
|
|
errors2 "github.com/influxdata/influxdb/v2/kit/platform/errors"
|
|
"github.com/influxdata/influxdb/v2/notification"
|
|
icheck "github.com/influxdata/influxdb/v2/notification/check"
|
|
"github.com/influxdata/influxdb/v2/notification/endpoint"
|
|
"github.com/influxdata/influxdb/v2/task/taskmodel"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestParse(t *testing.T) {
|
|
t.Run("template with a bucket", func(t *testing.T) {
|
|
t.Run("with valid bucket template should be valid", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/bucket", func(t *testing.T, template *Template) {
|
|
buckets := template.Summary().Buckets
|
|
require.Len(t, buckets, 2)
|
|
|
|
actual := buckets[0]
|
|
expectedBucket := SummaryBucket{
|
|
SummaryIdentifier: SummaryIdentifier{
|
|
Kind: KindBucket,
|
|
MetaName: "rucket-11",
|
|
EnvReferences: []SummaryReference{},
|
|
},
|
|
Name: "rucket-11",
|
|
Description: "bucket 1 description",
|
|
RetentionPeriod: time.Hour,
|
|
LabelAssociations: []SummaryLabel{},
|
|
}
|
|
assert.Equal(t, expectedBucket, actual)
|
|
|
|
actual = buckets[1]
|
|
expectedBucket = SummaryBucket{
|
|
SummaryIdentifier: SummaryIdentifier{
|
|
Kind: KindBucket,
|
|
MetaName: "rucket-22",
|
|
EnvReferences: []SummaryReference{},
|
|
},
|
|
Name: "display name",
|
|
Description: "bucket 2 description",
|
|
LabelAssociations: []SummaryLabel{},
|
|
}
|
|
assert.Equal(t, expectedBucket, actual)
|
|
})
|
|
})
|
|
|
|
t.Run("with valid bucket and schema should be valid", func(t *testing.T) {
|
|
template := validParsedTemplateFromFile(t, "testdata/bucket_schema.yml", EncodingYAML)
|
|
buckets := template.Summary().Buckets
|
|
require.Len(t, buckets, 1)
|
|
|
|
exp := SummaryBucket{
|
|
SummaryIdentifier: SummaryIdentifier{
|
|
Kind: KindBucket,
|
|
MetaName: "explicit-11",
|
|
EnvReferences: []SummaryReference{},
|
|
},
|
|
Name: "my_explicit",
|
|
SchemaType: "explicit",
|
|
LabelAssociations: []SummaryLabel{},
|
|
MeasurementSchemas: []SummaryMeasurementSchema{
|
|
{
|
|
Name: "cpu",
|
|
Columns: []SummaryMeasurementSchemaColumn{
|
|
{Name: "host", Type: "tag"},
|
|
{Name: "time", Type: "timestamp"},
|
|
{Name: "usage_user", Type: "field", DataType: "float"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
assert.Equal(t, exp, buckets[0])
|
|
})
|
|
|
|
t.Run("with env refs should be valid", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/bucket_ref.yml", func(t *testing.T, template *Template) {
|
|
actual := template.Summary().Buckets
|
|
require.Len(t, actual, 1)
|
|
|
|
expectedEnvRefs := []SummaryReference{
|
|
{
|
|
Field: "metadata.name",
|
|
EnvRefKey: "meta-name",
|
|
DefaultValue: "meta",
|
|
},
|
|
{
|
|
Field: "spec.name",
|
|
EnvRefKey: "spec-name",
|
|
DefaultValue: "spectacles",
|
|
},
|
|
{
|
|
Field: "spec.associations[0].name",
|
|
EnvRefKey: "label-meta-name",
|
|
},
|
|
}
|
|
assert.Equal(t, expectedEnvRefs, actual[0].EnvReferences)
|
|
})
|
|
})
|
|
|
|
t.Run("should handle bad config", func(t *testing.T) {
|
|
tests := []testTemplateResourceError{
|
|
{
|
|
name: "missing name",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
spec:
|
|
`,
|
|
},
|
|
{
|
|
name: "mixed valid and missing name",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: rucket-11
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
spec:
|
|
`,
|
|
},
|
|
{
|
|
name: "mixed valid and multiple bad names",
|
|
resourceErrs: 2,
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: rucket-11
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
spec:
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
spec:
|
|
`,
|
|
},
|
|
{
|
|
name: "duplicate bucket names",
|
|
resourceErrs: 1,
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: valid-name
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: valid-name
|
|
`,
|
|
},
|
|
{
|
|
name: "duplicate meta name and spec name",
|
|
resourceErrs: 1,
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: rucket-1
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: valid-name
|
|
spec:
|
|
name: rucket-1
|
|
`,
|
|
},
|
|
{
|
|
name: "spec name too short",
|
|
resourceErrs: 1,
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: rucket-1
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: invalid-name
|
|
spec:
|
|
name: f
|
|
`,
|
|
},
|
|
{
|
|
name: "invalid measurement name",
|
|
resourceErrs: 1,
|
|
validationErrs: 1,
|
|
valFields: []string{strings.Join([]string{fieldSpec, fieldMeasurementSchemas}, ".")},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: foo-1
|
|
spec:
|
|
name: foo
|
|
schemaType: explicit
|
|
measurementSchemas:
|
|
- name: _cpu
|
|
columns:
|
|
- name: time
|
|
type: timestamp
|
|
- name: usage_user
|
|
type: field
|
|
dataType: float
|
|
`,
|
|
},
|
|
{
|
|
name: "invalid semantic type",
|
|
resourceErrs: 1,
|
|
validationErrs: 1,
|
|
valFields: []string{strings.Join([]string{fieldSpec, fieldMeasurementSchemas, fieldMeasurementSchemaColumns}, ".")},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: foo-1
|
|
spec:
|
|
name: foo
|
|
schemaType: explicit
|
|
measurementSchemas:
|
|
- name: _cpu
|
|
columns:
|
|
- name: time
|
|
type: field
|
|
- name: usage_user
|
|
type: field
|
|
dataType: float
|
|
`,
|
|
},
|
|
{
|
|
name: "missing time column",
|
|
resourceErrs: 1,
|
|
validationErrs: 1,
|
|
valFields: []string{strings.Join([]string{fieldSpec, fieldMeasurementSchemas}, ".")},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: foo-1
|
|
spec:
|
|
name: foo
|
|
schemaType: explicit
|
|
measurementSchemas:
|
|
- name: cpu
|
|
columns:
|
|
- name: usage_user
|
|
type: field
|
|
dataType: float
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, KindBucket, tt)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("template with a label", func(t *testing.T) {
|
|
t.Run("with valid label template should be valid", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/label", func(t *testing.T, template *Template) {
|
|
labels := template.Summary().Labels
|
|
require.Len(t, labels, 3)
|
|
|
|
expectedLabel := sumLabelGen("label-1", "label-1", "#FFFFFF", "label 1 description")
|
|
assert.Equal(t, expectedLabel, labels[0])
|
|
|
|
expectedLabel = sumLabelGen("label-2", "label-2", "#000000", "label 2 description")
|
|
assert.Equal(t, expectedLabel, labels[1])
|
|
|
|
expectedLabel = sumLabelGen("label-3", "display name", "", "label 3 description")
|
|
assert.Equal(t, expectedLabel, labels[2])
|
|
})
|
|
})
|
|
|
|
t.Run("with env refs should be valid", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/label_ref.yml", func(t *testing.T, template *Template) {
|
|
actual := template.Summary().Labels
|
|
require.Len(t, actual, 1)
|
|
|
|
expected := sumLabelGen("env-meta-name", "env-spec-name", "", "",
|
|
SummaryReference{
|
|
Field: "metadata.name",
|
|
EnvRefKey: "meta-name",
|
|
},
|
|
SummaryReference{
|
|
Field: "spec.name",
|
|
EnvRefKey: "spec-name",
|
|
},
|
|
)
|
|
assert.Contains(t, actual, expected)
|
|
})
|
|
})
|
|
|
|
t.Run("with missing label name should error", func(t *testing.T) {
|
|
tests := []testTemplateResourceError{
|
|
{
|
|
name: "missing name",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
spec:
|
|
`,
|
|
},
|
|
{
|
|
name: "mixed valid and missing name",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: valid-name
|
|
spec:
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
spec:
|
|
`,
|
|
},
|
|
{
|
|
name: "duplicate names",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: valid-name
|
|
spec:
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: valid-name
|
|
spec:
|
|
`,
|
|
},
|
|
{
|
|
name: "multiple labels with missing name",
|
|
resourceErrs: 2,
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
`,
|
|
},
|
|
{
|
|
name: "duplicate meta name and spec name",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: valid-name
|
|
spec:
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: label-1
|
|
spec:
|
|
name: valid-name
|
|
`,
|
|
},
|
|
{
|
|
name: "spec name to short",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: valid-name
|
|
spec:
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: label-1
|
|
spec:
|
|
name: a
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, KindLabel, tt)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("template with buckets and labels associated", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/bucket_associates_label", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Labels, 2)
|
|
|
|
bkts := sum.Buckets
|
|
require.Len(t, bkts, 3)
|
|
|
|
expectedLabels := []struct {
|
|
bktName string
|
|
labels []string
|
|
}{
|
|
{
|
|
bktName: "rucket-1",
|
|
labels: []string{"label-1"},
|
|
},
|
|
{
|
|
bktName: "rucket-2",
|
|
labels: []string{"label-2"},
|
|
},
|
|
{
|
|
bktName: "rucket-3",
|
|
labels: []string{"label-1", "label-2"},
|
|
},
|
|
}
|
|
for i, expected := range expectedLabels {
|
|
bkt := bkts[i]
|
|
require.Len(t, bkt.LabelAssociations, len(expected.labels))
|
|
|
|
for j, label := range expected.labels {
|
|
assert.Equal(t, label, bkt.LabelAssociations[j].Name)
|
|
}
|
|
}
|
|
|
|
expectedMappings := []SummaryLabelMapping{
|
|
{
|
|
ResourceMetaName: "rucket-1",
|
|
ResourceName: "rucket-1",
|
|
LabelMetaName: "label-1",
|
|
LabelName: "label-1",
|
|
},
|
|
{
|
|
ResourceMetaName: "rucket-2",
|
|
ResourceName: "rucket-2",
|
|
LabelMetaName: "label-2",
|
|
LabelName: "label-2",
|
|
},
|
|
{
|
|
ResourceMetaName: "rucket-3",
|
|
ResourceName: "rucket-3",
|
|
LabelMetaName: "label-1",
|
|
LabelName: "label-1",
|
|
},
|
|
{
|
|
ResourceMetaName: "rucket-3",
|
|
ResourceName: "rucket-3",
|
|
LabelMetaName: "label-2",
|
|
LabelName: "label-2",
|
|
},
|
|
}
|
|
|
|
for _, expectedMapping := range expectedMappings {
|
|
expectedMapping.Status = StateStatusNew
|
|
expectedMapping.ResourceType = influxdb.BucketsResourceType
|
|
assert.Contains(t, sum.LabelMappings, expectedMapping)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("association doesn't exist then provides an error", func(t *testing.T) {
|
|
tests := []testTemplateResourceError{
|
|
{
|
|
name: "no labels provided",
|
|
assErrs: 1,
|
|
assIdxs: []int{0},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: rucket-1
|
|
spec:
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
`,
|
|
},
|
|
{
|
|
name: "mixed found and not found",
|
|
assErrs: 1,
|
|
assIdxs: []int{1},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: label-1
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: rucket-3
|
|
spec:
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
- kind: Label
|
|
name: NOT TO BE FOUND
|
|
`,
|
|
},
|
|
{
|
|
name: "multiple not found",
|
|
assErrs: 1,
|
|
assIdxs: []int{0, 1},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: rucket-3
|
|
spec:
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
- kind: Label
|
|
name: label-2
|
|
`,
|
|
},
|
|
{
|
|
name: "duplicate valid nested labels",
|
|
assErrs: 1,
|
|
assIdxs: []int{1},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: label-1
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Bucket
|
|
metadata:
|
|
name: rucket-3
|
|
spec:
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
- kind: Label
|
|
name: label-1
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, KindBucket, tt)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("template with checks", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/checks", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Checks, 2)
|
|
|
|
check1 := sum.Checks[0]
|
|
assert.Equal(t, KindCheckThreshold, check1.Kind)
|
|
thresholdCheck, ok := check1.Check.(*icheck.Threshold)
|
|
require.Truef(t, ok, "got: %#v", check1)
|
|
|
|
expectedBase := icheck.Base{
|
|
Name: "check-0",
|
|
Description: "desc_0",
|
|
Every: mustDuration(t, time.Minute),
|
|
Offset: mustDuration(t, 15*time.Second),
|
|
StatusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }",
|
|
Tags: []influxdb.Tag{
|
|
{Key: "tag_1", Value: "val_1"},
|
|
{Key: "tag_2", Value: "val_2"},
|
|
},
|
|
}
|
|
expectedBase.Query.Text = "from(bucket: \"rucket_1\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"cpu\")\n |> filter(fn: (r) => r._field == \"usage_idle\")\n |> aggregateWindow(every: 1m, fn: mean)\n |> yield(name: \"mean\")"
|
|
assert.Equal(t, expectedBase, thresholdCheck.Base)
|
|
|
|
expectedThresholds := []icheck.ThresholdConfig{
|
|
icheck.Greater{
|
|
ThresholdConfigBase: icheck.ThresholdConfigBase{
|
|
AllValues: true,
|
|
Level: notification.Critical,
|
|
},
|
|
Value: 50.0,
|
|
},
|
|
icheck.Lesser{
|
|
ThresholdConfigBase: icheck.ThresholdConfigBase{Level: notification.Warn},
|
|
Value: 49.9,
|
|
},
|
|
icheck.Range{
|
|
ThresholdConfigBase: icheck.ThresholdConfigBase{Level: notification.Info},
|
|
Within: true,
|
|
Min: 30.0,
|
|
Max: 45.0,
|
|
},
|
|
icheck.Range{
|
|
ThresholdConfigBase: icheck.ThresholdConfigBase{Level: notification.Ok},
|
|
Min: 30.0,
|
|
Max: 35.0,
|
|
},
|
|
}
|
|
assert.Equal(t, expectedThresholds, thresholdCheck.Thresholds)
|
|
assert.Equal(t, influxdb.Inactive, check1.Status)
|
|
assert.Len(t, check1.LabelAssociations, 1)
|
|
|
|
check2 := sum.Checks[1]
|
|
assert.Equal(t, KindCheckDeadman, check2.Kind)
|
|
deadmanCheck, ok := check2.Check.(*icheck.Deadman)
|
|
require.Truef(t, ok, "got: %#v", check2)
|
|
|
|
expectedBase = icheck.Base{
|
|
Name: "display name",
|
|
Description: "desc_1",
|
|
Every: mustDuration(t, 5*time.Minute),
|
|
Offset: mustDuration(t, 10*time.Second),
|
|
StatusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }",
|
|
Tags: []influxdb.Tag{
|
|
{Key: "tag_1", Value: "val_1"},
|
|
{Key: "tag_2", Value: "val_2"},
|
|
},
|
|
}
|
|
expectedBase.Query.Text = "from(bucket: \"rucket_1\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"cpu\")\n |> filter(fn: (r) => r._field == \"usage_idle\")\n |> aggregateWindow(every: 1m, fn: mean)\n |> yield(name: \"mean\")"
|
|
assert.Equal(t, expectedBase, deadmanCheck.Base)
|
|
assert.Equal(t, influxdb.Active, check2.Status)
|
|
assert.Equal(t, mustDuration(t, 10*time.Minute), deadmanCheck.StaleTime)
|
|
assert.Equal(t, mustDuration(t, 90*time.Second), deadmanCheck.TimeSince)
|
|
assert.True(t, deadmanCheck.ReportZero)
|
|
assert.Len(t, check2.LabelAssociations, 1)
|
|
|
|
expectedMappings := []SummaryLabelMapping{
|
|
{
|
|
LabelMetaName: "label-1",
|
|
LabelName: "label-1",
|
|
ResourceMetaName: "check-0",
|
|
ResourceName: "check-0",
|
|
},
|
|
{
|
|
LabelMetaName: "label-1",
|
|
LabelName: "label-1",
|
|
ResourceMetaName: "check-1",
|
|
ResourceName: "display name",
|
|
},
|
|
}
|
|
for _, expected := range expectedMappings {
|
|
expected.Status = StateStatusNew
|
|
expected.ResourceType = influxdb.ChecksResourceType
|
|
assert.Contains(t, sum.LabelMappings, expected)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("with env refs should be successful", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/checks_ref.yml", func(t *testing.T, template *Template) {
|
|
actual := template.Summary().Checks
|
|
require.Len(t, actual, 1)
|
|
|
|
expectedEnvRefs := []SummaryReference{
|
|
{
|
|
Field: "metadata.name",
|
|
EnvRefKey: "meta-name",
|
|
DefaultValue: "meta",
|
|
},
|
|
{
|
|
Field: "spec.name",
|
|
EnvRefKey: "spec-name",
|
|
DefaultValue: "spectacles",
|
|
},
|
|
{
|
|
Field: "spec.associations[0].name",
|
|
EnvRefKey: "label-meta-name",
|
|
},
|
|
}
|
|
assert.Equal(t, expectedEnvRefs, actual[0].EnvReferences)
|
|
})
|
|
})
|
|
|
|
t.Run("handles bad config", func(t *testing.T) {
|
|
tests := []struct {
|
|
kind Kind
|
|
resErr testTemplateResourceError
|
|
}{
|
|
{
|
|
kind: KindCheckDeadman,
|
|
resErr: testTemplateResourceError{
|
|
name: "duplicate name",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckDeadman
|
|
metadata:
|
|
name: check-1
|
|
spec:
|
|
every: 5m
|
|
level: cRiT
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckDeadman
|
|
metadata:
|
|
name: check-1
|
|
spec:
|
|
every: 5m
|
|
level: cRiT
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindCheckThreshold,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing every duration",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldEvery},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckThreshold
|
|
metadata:
|
|
name: check-0
|
|
spec:
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
thresholds:
|
|
- type: outside_range
|
|
level: ok
|
|
min: 30.0
|
|
max: 35.0
|
|
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindCheckThreshold,
|
|
resErr: testTemplateResourceError{
|
|
name: "invalid threshold value provided",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldLevel},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckThreshold
|
|
metadata:
|
|
name: check-0
|
|
spec:
|
|
every: 1m
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
thresholds:
|
|
- type: greater
|
|
level: RANDO
|
|
value: 50.0
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindCheckThreshold,
|
|
resErr: testTemplateResourceError{
|
|
name: "invalid threshold type provided",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldType},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckThreshold
|
|
metadata:
|
|
name: check-0
|
|
spec:
|
|
every: 1m
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
thresholds:
|
|
- type: RANDO_TYPE
|
|
level: CRIT
|
|
value: 50.0
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindCheckThreshold,
|
|
resErr: testTemplateResourceError{
|
|
name: "invalid min for inside range",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldMin},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckThreshold
|
|
metadata:
|
|
name: check-0
|
|
spec:
|
|
every: 1m
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
thresholds:
|
|
- type: inside_range
|
|
level: INfO
|
|
min: 45.0
|
|
max: 30.0
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindCheckThreshold,
|
|
resErr: testTemplateResourceError{
|
|
name: "no threshold values provided",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldCheckThresholds},
|
|
templateStr: `---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckThreshold
|
|
metadata:
|
|
name: check-0
|
|
spec:
|
|
every: 1m
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
thresholds:
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindCheckThreshold,
|
|
resErr: testTemplateResourceError{
|
|
name: "threshold missing query",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldQuery},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckThreshold
|
|
metadata:
|
|
name: check-0
|
|
spec:
|
|
every: 1m
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
thresholds:
|
|
- type: greater
|
|
level: CRIT
|
|
value: 50.0
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindCheckThreshold,
|
|
resErr: testTemplateResourceError{
|
|
name: "invalid status provided",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldStatus},
|
|
templateStr: `---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckThreshold
|
|
metadata:
|
|
name: check-0
|
|
spec:
|
|
every: 1m
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
status: RANDO STATUS
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
thresholds:
|
|
- type: greater
|
|
level: CRIT
|
|
value: 50.0
|
|
allValues: true
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindCheckThreshold,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing status message template",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldCheckStatusMessageTemplate},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckThreshold
|
|
metadata:
|
|
name: check-0
|
|
spec:
|
|
every: 1m
|
|
query: >
|
|
from(bucket: "rucket_1")
|
|
thresholds:
|
|
- type: greater
|
|
level: CRIT
|
|
value: 50.0
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindCheckDeadman,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing every",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldEvery},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckDeadman
|
|
metadata:
|
|
name: check-1
|
|
spec:
|
|
level: cRiT
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
timeSince: 90s
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindCheckDeadman,
|
|
resErr: testTemplateResourceError{
|
|
name: "deadman missing every",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldQuery},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckDeadman
|
|
metadata:
|
|
name: check-1
|
|
spec:
|
|
every: 5m
|
|
level: cRiT
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
timeSince: 90s
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindCheckDeadman,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing association label",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldAssociations},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckDeadman
|
|
metadata:
|
|
name: check-1
|
|
spec:
|
|
every: 5m
|
|
level: cRiT
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
timeSince: 90s
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindCheckDeadman,
|
|
resErr: testTemplateResourceError{
|
|
name: "duplicate association labels",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldAssociations},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: label-1
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckDeadman
|
|
metadata:
|
|
name: check-1
|
|
spec:
|
|
every: 5m
|
|
level: cRiT
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
timeSince: 90s
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
- kind: Label
|
|
name: label-1
|
|
`,
|
|
},
|
|
},
|
|
/* checks are not name unique
|
|
{
|
|
kind: KindCheckDeadman,
|
|
resErr: testTemplateResourceError{
|
|
name: "duplicate meta name and spec name",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldAssociations},
|
|
templateStr: `
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckDeadman
|
|
metadata:
|
|
name: check-1
|
|
spec:
|
|
every: 5m
|
|
level: cRiT
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
timeSince: 90s
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: CheckDeadman
|
|
metadata:
|
|
name: valid-name
|
|
spec:
|
|
name: check-1
|
|
every: 5m
|
|
level: cRiT
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
|
|
timeSince: 90s
|
|
`,
|
|
},
|
|
},
|
|
*/
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, tt.kind, tt.resErr)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("template with dashboard", func(t *testing.T) {
|
|
t.Run("single chart should be successful", func(t *testing.T) {
|
|
t.Run("gauge chart", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/dashboard_gauge", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Dashboards, 1)
|
|
|
|
actual := sum.Dashboards[0]
|
|
assert.Equal(t, KindDashboard, actual.Kind)
|
|
assert.Equal(t, "dash-1", actual.Name)
|
|
assert.Equal(t, "desc1", actual.Description)
|
|
|
|
require.Len(t, actual.Charts, 1)
|
|
actualChart := actual.Charts[0]
|
|
assert.Equal(t, 3, actualChart.Height)
|
|
assert.Equal(t, 6, actualChart.Width)
|
|
assert.Equal(t, 1, actualChart.XPosition)
|
|
assert.Equal(t, 2, actualChart.YPosition)
|
|
|
|
props, ok := actualChart.Properties.(influxdb.GaugeViewProperties)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "gauge", props.GetType())
|
|
assert.Equal(t, "gauge note", props.Note)
|
|
assert.True(t, props.ShowNoteWhenEmpty)
|
|
|
|
require.Len(t, props.Queries, 1)
|
|
q := props.Queries[0]
|
|
queryText := `from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "boltdb_writes_total") |> filter(fn: (r) => r._field == "counter")`
|
|
assert.Equal(t, queryText, q.Text)
|
|
assert.Equal(t, "advanced", q.EditMode)
|
|
|
|
require.Len(t, props.ViewColors, 3)
|
|
c := props.ViewColors[0]
|
|
assert.Equal(t, "laser", c.Name)
|
|
assert.Equal(t, "min", c.Type)
|
|
assert.Equal(t, "#8F8AF4", c.Hex)
|
|
assert.Equal(t, 0.0, c.Value)
|
|
})
|
|
})
|
|
|
|
t.Run("handles invalid config", func(t *testing.T) {
|
|
tests := []testTemplateResourceError{
|
|
{
|
|
name: "color mixing a hex value",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].colors[0].hex"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: gauge
|
|
name: gauge
|
|
note: gauge note
|
|
noteOnEmpty: true
|
|
xPos: 1
|
|
yPos: 2
|
|
width: 6
|
|
height: 3
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "boltdb_writes_total") |> filter(fn: (r) => r._field == "counter")
|
|
colors:
|
|
- name: laser
|
|
type: min
|
|
value: 0
|
|
- name: laser
|
|
type: threshold
|
|
hex: "#8F8AF4"
|
|
value: 700
|
|
- name: laser
|
|
type: max
|
|
hex: "#8F8AF4"
|
|
value: 5000
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, KindDashboard, tt)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("heatmap chart", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/dashboard_heatmap", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Dashboards, 1)
|
|
|
|
actual := sum.Dashboards[0]
|
|
assert.Equal(t, KindDashboard, actual.Kind)
|
|
assert.Equal(t, "dash-0", actual.Name)
|
|
assert.Equal(t, "a dashboard w/ heatmap chart", actual.Description)
|
|
|
|
require.Len(t, actual.Charts, 1)
|
|
actualChart := actual.Charts[0]
|
|
assert.Equal(t, 3, actualChart.Height)
|
|
assert.Equal(t, 6, actualChart.Width)
|
|
assert.Equal(t, 1, actualChart.XPosition)
|
|
assert.Equal(t, 2, actualChart.YPosition)
|
|
|
|
props, ok := actualChart.Properties.(influxdb.HeatmapViewProperties)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "heatmap", props.GetType())
|
|
assert.Equal(t, "heatmap note", props.Note)
|
|
assert.Equal(t, int32(10), props.BinSize)
|
|
assert.Equal(t, []string{"xTotalTicks", "xTickStart", "xTickStep"}, props.GenerateXAxisTicks)
|
|
assert.Equal(t, 15, props.XTotalTicks)
|
|
assert.Equal(t, 0.0, props.XTickStart)
|
|
assert.Equal(t, 1000.0, props.XTickStep)
|
|
assert.Equal(t, []string{"yTotalTicks", "yTickStart", "yTickStep"}, props.GenerateYAxisTicks)
|
|
assert.Equal(t, 10, props.YTotalTicks)
|
|
assert.Equal(t, 0.0, props.YTickStart)
|
|
assert.Equal(t, 100.0, props.YTickStep)
|
|
assert.Equal(t, true, props.LegendColorizeRows)
|
|
assert.Equal(t, false, props.LegendHide)
|
|
assert.Equal(t, 1.0, props.LegendOpacity)
|
|
assert.Equal(t, 5, props.LegendOrientationThreshold)
|
|
assert.True(t, props.ShowNoteWhenEmpty)
|
|
|
|
assert.Equal(t, []float64{0, 10}, props.XDomain)
|
|
assert.Equal(t, []float64{0, 100}, props.YDomain)
|
|
|
|
require.Len(t, props.Queries, 1)
|
|
q := props.Queries[0]
|
|
queryText := `from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")`
|
|
assert.Equal(t, queryText, q.Text)
|
|
assert.Equal(t, "advanced", q.EditMode)
|
|
|
|
require.Len(t, props.ViewColors, 12)
|
|
c := props.ViewColors[0]
|
|
assert.Equal(t, "#000004", c)
|
|
})
|
|
})
|
|
|
|
t.Run("handles invalid config", func(t *testing.T) {
|
|
tests := []testTemplateResourceError{
|
|
{
|
|
name: "a color is missing a hex value",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].colors[2].hex"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-0
|
|
spec:
|
|
charts:
|
|
- kind: heatmap
|
|
name: heatmap
|
|
xPos: 1
|
|
yPos: 2
|
|
width: 6
|
|
height: 3
|
|
binSize: 10
|
|
xCol: _time
|
|
yCol: _value
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")
|
|
colors:
|
|
- hex: "#fbb61a"
|
|
- hex: "#f4df53"
|
|
- hex: ""
|
|
axes:
|
|
- name: "x"
|
|
label: "x_label"
|
|
prefix: "x_prefix"
|
|
suffix: "x_suffix"
|
|
domain:
|
|
- 0
|
|
- 10
|
|
- name: "y"
|
|
label: "y_label"
|
|
prefix: "y_prefix"
|
|
suffix: "y_suffix"
|
|
domain:
|
|
- 0
|
|
- 100
|
|
`,
|
|
},
|
|
{
|
|
name: "missing axes",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].axes"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-0
|
|
spec:
|
|
charts:
|
|
- kind: heatmap
|
|
name: heatmap
|
|
xPos: 1
|
|
yPos: 2
|
|
width: 6
|
|
height: 3
|
|
binSize: 10
|
|
xCol: _time
|
|
yCol: _value
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")
|
|
colors:
|
|
- hex: "#000004"
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, KindDashboard, tt)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("histogram chart", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/dashboard_histogram", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Dashboards, 1)
|
|
|
|
actual := sum.Dashboards[0]
|
|
assert.Equal(t, KindDashboard, actual.Kind)
|
|
assert.Equal(t, "dash-0", actual.Name)
|
|
assert.Equal(t, "a dashboard w/ single histogram chart", actual.Description)
|
|
|
|
require.Len(t, actual.Charts, 1)
|
|
actualChart := actual.Charts[0]
|
|
assert.Equal(t, 3, actualChart.Height)
|
|
assert.Equal(t, 6, actualChart.Width)
|
|
|
|
props, ok := actualChart.Properties.(influxdb.HistogramViewProperties)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "histogram", props.GetType())
|
|
assert.Equal(t, "histogram note", props.Note)
|
|
assert.Equal(t, 30, props.BinCount)
|
|
assert.Equal(t, true, props.LegendColorizeRows)
|
|
assert.Equal(t, false, props.LegendHide)
|
|
assert.Equal(t, 1.0, props.LegendOpacity)
|
|
assert.Equal(t, 5, props.LegendOrientationThreshold)
|
|
assert.True(t, props.ShowNoteWhenEmpty)
|
|
assert.Equal(t, []float64{0, 10}, props.XDomain)
|
|
assert.Equal(t, []string{"a", "b"}, props.FillColumns)
|
|
require.Len(t, props.Queries, 1)
|
|
q := props.Queries[0]
|
|
queryText := `from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "boltdb_reads_total") |> filter(fn: (r) => r._field == "counter")`
|
|
assert.Equal(t, queryText, q.Text)
|
|
assert.Equal(t, "advanced", q.EditMode)
|
|
|
|
require.Len(t, props.ViewColors, 3)
|
|
assert.Equal(t, "#8F8AF4", props.ViewColors[0].Hex)
|
|
assert.Equal(t, "#F4CF31", props.ViewColors[1].Hex)
|
|
assert.Equal(t, "#FFFFFF", props.ViewColors[2].Hex)
|
|
})
|
|
})
|
|
|
|
t.Run("handles invalid config", func(t *testing.T) {
|
|
tests := []testTemplateResourceError{
|
|
{
|
|
name: "missing x-axis",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].axes"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-0
|
|
spec:
|
|
description: a dashboard w/ single histogram chart
|
|
charts:
|
|
- kind: Histogram
|
|
name: histogram chart
|
|
xCol: _value
|
|
width: 6
|
|
height: 3
|
|
binCount: 30
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "boltdb_reads_total") |> filter(fn: (r) => r._field == "counter")
|
|
colors:
|
|
- hex: "#8F8AF4"
|
|
type: scale
|
|
value: 0
|
|
name: mycolor
|
|
axes:
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, KindDashboard, tt)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("markdown chart", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/dashboard_markdown", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Dashboards, 1)
|
|
|
|
actual := sum.Dashboards[0]
|
|
assert.Equal(t, KindDashboard, actual.Kind)
|
|
assert.Equal(t, "dash-0", actual.Name)
|
|
assert.Equal(t, "a dashboard w/ single markdown chart", actual.Description)
|
|
|
|
require.Len(t, actual.Charts, 1)
|
|
actualChart := actual.Charts[0]
|
|
|
|
props, ok := actualChart.Properties.(influxdb.MarkdownViewProperties)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "markdown", props.GetType())
|
|
assert.Equal(t, "## markdown note", props.Note)
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("mosaic chart", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/dashboard_mosaic.yml", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Dashboards, 1)
|
|
|
|
actual := sum.Dashboards[0]
|
|
assert.Equal(t, KindDashboard, actual.Kind)
|
|
assert.Equal(t, "dash-0", actual.Name)
|
|
assert.Equal(t, "a dashboard w/ single mosaic chart", actual.Description)
|
|
|
|
require.Len(t, actual.Charts, 1)
|
|
actualChart := actual.Charts[0]
|
|
assert.Equal(t, 3, actualChart.Height)
|
|
assert.Equal(t, 6, actualChart.Width)
|
|
assert.Equal(t, 1, actualChart.XPosition)
|
|
assert.Equal(t, 2, actualChart.YPosition)
|
|
|
|
props, ok := actualChart.Properties.(influxdb.MosaicViewProperties)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "mosaic note", props.Note)
|
|
assert.Equal(t, "y", props.HoverDimension)
|
|
assert.True(t, props.ShowNoteWhenEmpty)
|
|
|
|
require.Len(t, props.Queries, 1)
|
|
q := props.Queries[0]
|
|
expectedQuery := `from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")`
|
|
assert.Equal(t, expectedQuery, q.Text)
|
|
assert.Equal(t, "advanced", q.EditMode)
|
|
|
|
assert.Equal(t, ",", props.YLabelColumnSeparator)
|
|
assert.Equal(t, []string{"foo"}, props.YLabelColumns)
|
|
assert.Equal(t, []string{"_value", "foo"}, props.YSeriesColumns)
|
|
assert.Equal(t, []float64{0, 10}, props.XDomain)
|
|
assert.Equal(t, []float64{0, 100}, props.YDomain)
|
|
assert.Equal(t, "x_label", props.XAxisLabel)
|
|
assert.Equal(t, "y_label", props.YAxisLabel)
|
|
assert.Equal(t, "x_prefix", props.XPrefix)
|
|
assert.Equal(t, "y_prefix", props.YPrefix)
|
|
assert.Equal(t, "x_suffix", props.XSuffix)
|
|
assert.Equal(t, "y_suffix", props.YSuffix)
|
|
assert.Equal(t, []string{"xTotalTicks", "xTickStart", "xTickStep"}, props.GenerateXAxisTicks)
|
|
assert.Equal(t, 15, props.XTotalTicks)
|
|
assert.Equal(t, 0.0, props.XTickStart)
|
|
assert.Equal(t, 1000.0, props.XTickStep)
|
|
assert.Equal(t, true, props.LegendColorizeRows)
|
|
assert.Equal(t, false, props.LegendHide)
|
|
assert.Equal(t, 1.0, props.LegendOpacity)
|
|
assert.Equal(t, 5, props.LegendOrientationThreshold)
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("band chart", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/dashboard_band.yml", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Dashboards, 1)
|
|
|
|
actual := sum.Dashboards[0]
|
|
assert.Equal(t, KindDashboard, actual.Kind)
|
|
assert.Equal(t, "dash-1", actual.Name)
|
|
assert.Equal(t, "a dashboard w/ single band chart", actual.Description)
|
|
|
|
require.Len(t, actual.Charts, 1)
|
|
actualChart := actual.Charts[0]
|
|
assert.Equal(t, 3, actualChart.Height)
|
|
assert.Equal(t, 6, actualChart.Width)
|
|
assert.Equal(t, 1, actualChart.XPosition)
|
|
assert.Equal(t, 2, actualChart.YPosition)
|
|
|
|
props, ok := actualChart.Properties.(influxdb.BandViewProperties)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "band note", props.Note)
|
|
assert.True(t, props.ShowNoteWhenEmpty)
|
|
assert.Equal(t, "y", props.HoverDimension)
|
|
assert.Equal(t, "foo", props.UpperColumn)
|
|
assert.Equal(t, "baz", props.MainColumn)
|
|
assert.Equal(t, "bar", props.LowerColumn)
|
|
assert.Equal(t, []string{"xTotalTicks", "xTickStart", "xTickStep"}, props.GenerateXAxisTicks)
|
|
assert.Equal(t, 15, props.XTotalTicks)
|
|
assert.Equal(t, 0.0, props.XTickStart)
|
|
assert.Equal(t, 1000.0, props.XTickStep)
|
|
assert.Equal(t, []string{"yTotalTicks", "yTickStart", "yTickStep"}, props.GenerateYAxisTicks)
|
|
assert.Equal(t, 10, props.YTotalTicks)
|
|
assert.Equal(t, 0.0, props.YTickStart)
|
|
assert.Equal(t, 100.0, props.YTickStep)
|
|
assert.Equal(t, true, props.LegendColorizeRows)
|
|
assert.Equal(t, false, props.LegendHide)
|
|
assert.Equal(t, 1.0, props.LegendOpacity)
|
|
assert.Equal(t, 5, props.LegendOrientationThreshold)
|
|
assert.Equal(t, true, props.StaticLegend.ColorizeRows)
|
|
assert.Equal(t, 0.2, props.StaticLegend.HeightRatio)
|
|
assert.Equal(t, true, props.StaticLegend.Show)
|
|
assert.Equal(t, 1.0, props.StaticLegend.Opacity)
|
|
assert.Equal(t, 5, props.StaticLegend.OrientationThreshold)
|
|
assert.Equal(t, "y", props.StaticLegend.ValueAxis)
|
|
assert.Equal(t, 1.0, props.StaticLegend.WidthRatio)
|
|
|
|
require.Len(t, props.ViewColors, 1)
|
|
c := props.ViewColors[0]
|
|
assert.Equal(t, "laser", c.Name)
|
|
assert.Equal(t, "scale", c.Type)
|
|
assert.Equal(t, "#8F8AF4", c.Hex)
|
|
assert.Equal(t, 3.0, c.Value)
|
|
|
|
require.Len(t, props.Queries, 1)
|
|
q := props.Queries[0]
|
|
expectedQuery := `from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")`
|
|
assert.Equal(t, expectedQuery, q.Text)
|
|
assert.Equal(t, "advanced", q.EditMode)
|
|
|
|
for _, key := range []string{"x", "y"} {
|
|
xAxis, ok := props.Axes[key]
|
|
require.True(t, ok, "key="+key)
|
|
assert.Equal(t, key+"_label", xAxis.Label, "key="+key)
|
|
assert.Equal(t, key+"_prefix", xAxis.Prefix, "key="+key)
|
|
assert.Equal(t, key+"_suffix", xAxis.Suffix, "key="+key)
|
|
}
|
|
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("scatter chart", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/dashboard_scatter", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Dashboards, 1)
|
|
|
|
actual := sum.Dashboards[0]
|
|
assert.Equal(t, KindDashboard, actual.Kind)
|
|
assert.Equal(t, "dash-0", actual.Name)
|
|
assert.Equal(t, "a dashboard w/ single scatter chart", actual.Description)
|
|
|
|
require.Len(t, actual.Charts, 1)
|
|
actualChart := actual.Charts[0]
|
|
assert.Equal(t, 3, actualChart.Height)
|
|
assert.Equal(t, 6, actualChart.Width)
|
|
assert.Equal(t, 1, actualChart.XPosition)
|
|
assert.Equal(t, 2, actualChart.YPosition)
|
|
|
|
props, ok := actualChart.Properties.(influxdb.ScatterViewProperties)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "scatter note", props.Note)
|
|
assert.True(t, props.ShowNoteWhenEmpty)
|
|
|
|
require.Len(t, props.Queries, 1)
|
|
q := props.Queries[0]
|
|
expectedQuery := `from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")`
|
|
assert.Equal(t, expectedQuery, q.Text)
|
|
assert.Equal(t, "advanced", q.EditMode)
|
|
|
|
assert.Equal(t, []float64{0, 10}, props.XDomain)
|
|
assert.Equal(t, []float64{0, 100}, props.YDomain)
|
|
assert.Equal(t, "x_label", props.XAxisLabel)
|
|
assert.Equal(t, "y_label", props.YAxisLabel)
|
|
assert.Equal(t, "x_prefix", props.XPrefix)
|
|
assert.Equal(t, "y_prefix", props.YPrefix)
|
|
assert.Equal(t, "x_suffix", props.XSuffix)
|
|
assert.Equal(t, "y_suffix", props.YSuffix)
|
|
assert.Equal(t, []string{"xTotalTicks", "xTickStart", "xTickStep"}, props.GenerateXAxisTicks)
|
|
assert.Equal(t, 15, props.XTotalTicks)
|
|
assert.Equal(t, 0.0, props.XTickStart)
|
|
assert.Equal(t, 1000.0, props.XTickStep)
|
|
assert.Equal(t, []string{"yTotalTicks", "yTickStart", "yTickStep"}, props.GenerateYAxisTicks)
|
|
assert.Equal(t, 10, props.YTotalTicks)
|
|
assert.Equal(t, 0.0, props.YTickStart)
|
|
assert.Equal(t, 100.0, props.YTickStep)
|
|
assert.Equal(t, true, props.LegendColorizeRows)
|
|
assert.Equal(t, false, props.LegendHide)
|
|
assert.Equal(t, 1.0, props.LegendOpacity)
|
|
assert.Equal(t, 5, props.LegendOrientationThreshold)
|
|
})
|
|
})
|
|
|
|
t.Run("handles invalid config", func(t *testing.T) {
|
|
tests := []testTemplateResourceError{
|
|
{
|
|
name: "missing axes",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].axes"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-0
|
|
spec:
|
|
description: a dashboard w/ single scatter chart
|
|
charts:
|
|
- kind: Scatter
|
|
name: scatter chart
|
|
xPos: 1
|
|
yPos: 2
|
|
xCol: _time
|
|
yCol: _value
|
|
width: 6
|
|
height: 3
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")
|
|
colors:
|
|
- hex: "#8F8AF4"
|
|
- hex: "#F4CF31"
|
|
`,
|
|
},
|
|
{
|
|
name: "no width provided",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].width"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-0
|
|
spec:
|
|
description: a dashboard w/ single scatter chart
|
|
charts:
|
|
- kind: Scatter
|
|
name: scatter chart
|
|
xPos: 1
|
|
yPos: 2
|
|
xCol: _time
|
|
yCol: _value
|
|
height: 3
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")
|
|
colors:
|
|
- hex: "#8F8AF4"
|
|
- hex: "#F4CF31"
|
|
- hex: "#FFFFFF"
|
|
axes:
|
|
- name : "x"
|
|
label: x_label
|
|
prefix: x_prefix
|
|
suffix: x_suffix
|
|
domain:
|
|
- 0
|
|
- 10
|
|
- name: "y"
|
|
label: y_label
|
|
prefix: y_prefix
|
|
suffix: y_suffix
|
|
domain:
|
|
- 0
|
|
- 100
|
|
`,
|
|
},
|
|
{
|
|
name: "no height provided",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].height"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-0
|
|
spec:
|
|
description: a dashboard w/ single scatter chart
|
|
charts:
|
|
- kind: Scatter
|
|
name: scatter chart
|
|
xPos: 1
|
|
yPos: 2
|
|
xCol: _time
|
|
yCol: _value
|
|
width: 6
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")
|
|
colors:
|
|
- hex: "#8F8AF4"
|
|
- hex: "#F4CF31"
|
|
- hex: "#FFFFFF"
|
|
axes:
|
|
- name : "x"
|
|
label: x_label
|
|
prefix: x_prefix
|
|
suffix: x_suffix
|
|
domain:
|
|
- 0
|
|
- 10
|
|
- name: "y"
|
|
label: y_label
|
|
prefix: y_prefix
|
|
suffix: y_suffix
|
|
domain:
|
|
- 0
|
|
- 100
|
|
`,
|
|
},
|
|
{
|
|
name: "missing hex color",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].colors[0].hex"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-0
|
|
spec:
|
|
description: a dashboard w/ single scatter chart
|
|
charts:
|
|
- kind: Scatter
|
|
name: scatter chart
|
|
note: scatter note
|
|
noteOnEmpty: true
|
|
prefix: sumtin
|
|
suffix: days
|
|
xPos: 1
|
|
yPos: 2
|
|
xCol: _time
|
|
yCol: _value
|
|
width: 6
|
|
height: 3
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")
|
|
colors:
|
|
- hex: ""
|
|
axes:
|
|
- name : "x"
|
|
label: x_label
|
|
prefix: x_prefix
|
|
suffix: x_suffix
|
|
domain:
|
|
- 0
|
|
- 10
|
|
- name: "y"
|
|
label: y_label
|
|
prefix: y_prefix
|
|
suffix: y_suffix
|
|
domain:
|
|
- 0
|
|
- 100
|
|
`,
|
|
},
|
|
{
|
|
name: "missing x axis",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].axes"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-0
|
|
spec:
|
|
description: a dashboard w/ single scatter chart
|
|
charts:
|
|
- kind: Scatter
|
|
name: scatter chart
|
|
xPos: 1
|
|
yPos: 2
|
|
xCol: _time
|
|
yCol: _value
|
|
width: 6
|
|
height: 3
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")
|
|
colors:
|
|
- hex: "#8F8AF4"
|
|
- hex: "#F4CF31"
|
|
- hex: "#FFFFFF"
|
|
axes:
|
|
- name: "y"
|
|
label: y_label
|
|
prefix: y_prefix
|
|
suffix: y_suffix
|
|
domain:
|
|
- 0
|
|
- 100
|
|
`,
|
|
},
|
|
{
|
|
name: "missing y axis",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].axes"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-0
|
|
spec:
|
|
description: a dashboard w/ single scatter chart
|
|
charts:
|
|
- kind: Scatter
|
|
name: scatter chart
|
|
xPos: 1
|
|
yPos: 2
|
|
xCol: _time
|
|
yCol: _value
|
|
width: 6
|
|
height: 3
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")
|
|
colors:
|
|
- hex: "#8F8AF4"
|
|
- hex: "#F4CF31"
|
|
- hex: "#FFFFFF"
|
|
axes:
|
|
- name : "x"
|
|
label: x_label
|
|
prefix: x_prefix
|
|
suffix: x_suffix
|
|
domain:
|
|
- 0
|
|
- 10
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, KindDashboard, tt)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("single stat chart", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/dashboard", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Dashboards, 2)
|
|
|
|
actual := sum.Dashboards[0]
|
|
assert.Equal(t, KindDashboard, actual.Kind)
|
|
assert.Equal(t, "dash-1", actual.MetaName)
|
|
assert.Equal(t, "display name", actual.Name)
|
|
assert.Equal(t, "desc1", actual.Description)
|
|
|
|
require.Len(t, actual.Charts, 1)
|
|
actualChart := actual.Charts[0]
|
|
assert.Equal(t, 3, actualChart.Height)
|
|
assert.Equal(t, 6, actualChart.Width)
|
|
assert.Equal(t, 1, actualChart.XPosition)
|
|
assert.Equal(t, 2, actualChart.YPosition)
|
|
|
|
props, ok := actualChart.Properties.(influxdb.SingleStatViewProperties)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "single-stat", props.GetType())
|
|
assert.Equal(t, "single stat note", props.Note)
|
|
assert.True(t, props.ShowNoteWhenEmpty)
|
|
assert.True(t, props.DecimalPlaces.IsEnforced)
|
|
assert.Equal(t, int32(1), props.DecimalPlaces.Digits)
|
|
assert.Equal(t, "days", props.Suffix)
|
|
assert.Equal(t, "true", props.TickSuffix)
|
|
assert.Equal(t, "sumtin", props.Prefix)
|
|
assert.Equal(t, "true", props.TickPrefix)
|
|
|
|
require.Len(t, props.Queries, 1)
|
|
q := props.Queries[0]
|
|
queryText := `from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "processes") |> filter(fn: (r) => r._field == "running" or r._field == "blocked") |> aggregateWindow(every: v.windowPeriod, fn: max) |> yield(name: "max")`
|
|
assert.Equal(t, queryText, q.Text)
|
|
assert.Equal(t, "advanced", q.EditMode)
|
|
|
|
require.Len(t, props.ViewColors, 1)
|
|
c := props.ViewColors[0]
|
|
assert.Equal(t, "laser", c.Name)
|
|
assert.Equal(t, "text", c.Type)
|
|
assert.Equal(t, "#8F8AF4", c.Hex)
|
|
assert.Equal(t, 3.0, c.Value)
|
|
|
|
actual2 := sum.Dashboards[1]
|
|
assert.Equal(t, "dash-2", actual2.MetaName)
|
|
assert.Equal(t, "dash-2", actual2.Name)
|
|
assert.Equal(t, "desc", actual2.Description)
|
|
})
|
|
})
|
|
|
|
t.Run("handles invalid config", func(t *testing.T) {
|
|
tests := []testTemplateResourceError{
|
|
{
|
|
name: "color missing hex value",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].colors[0].hex"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: Single_Stat
|
|
name: single stat
|
|
xPos: 1
|
|
yPos: 2
|
|
width: 6
|
|
height: 3
|
|
decimalPlaces: 1
|
|
shade: true
|
|
hoverDimension: y
|
|
queries:
|
|
- query: "from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == \"processes\") |> filter(fn: (r) => r._field == \"running\" or r._field == \"blocked\") |> aggregateWindow(every: v.windowPeriod, fn: max) |> yield(name: \"max\")"
|
|
colors:
|
|
- name: laser
|
|
type: text
|
|
value: 3
|
|
`,
|
|
},
|
|
{
|
|
name: "no width provided",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].width"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: Single_Stat
|
|
name: single stat
|
|
xPos: 1
|
|
yPos: 2
|
|
height: 3
|
|
queries:
|
|
- query: "from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == \"processes\") |> filter(fn: (r) => r._field == \"running\" or r._field == \"blocked\") |> aggregateWindow(every: v.windowPeriod, fn: max) |> yield(name: \"max\")"
|
|
colors:
|
|
- name: laser
|
|
type: text
|
|
hex: "#8F8AF4"
|
|
`,
|
|
},
|
|
{
|
|
name: "no height provided",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].height"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: Single_Stat
|
|
name: single stat
|
|
xPos: 1
|
|
yPos: 2
|
|
width: 3
|
|
queries:
|
|
- query: "from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == \"processes\") |> filter(fn: (r) => r._field == \"running\" or r._field == \"blocked\") |> aggregateWindow(every: v.windowPeriod, fn: max) |> yield(name: \"max\")"
|
|
colors:
|
|
- name: laser
|
|
type: text
|
|
hex: "#8F8AF4"
|
|
`,
|
|
},
|
|
{
|
|
name: "duplicate metadata names",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
`,
|
|
},
|
|
{
|
|
name: "spec name too short",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldName},
|
|
templateStr: `
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
name: d
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, KindDashboard, tt)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("single stat plus line chart", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/dashboard_single_stat_plus_line", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Dashboards, 1)
|
|
|
|
actual := sum.Dashboards[0]
|
|
assert.Equal(t, KindDashboard, actual.Kind)
|
|
assert.Equal(t, "dash-1", actual.Name)
|
|
assert.Equal(t, "desc1", actual.Description)
|
|
|
|
require.Len(t, actual.Charts, 1)
|
|
actualChart := actual.Charts[0]
|
|
assert.Equal(t, 3, actualChart.Height)
|
|
assert.Equal(t, 6, actualChart.Width)
|
|
assert.Equal(t, 1, actualChart.XPosition)
|
|
assert.Equal(t, 2, actualChart.YPosition)
|
|
|
|
props, ok := actualChart.Properties.(influxdb.LinePlusSingleStatProperties)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "single stat plus line note", props.Note)
|
|
assert.True(t, props.ShowNoteWhenEmpty)
|
|
assert.True(t, props.DecimalPlaces.IsEnforced)
|
|
assert.Equal(t, int32(1), props.DecimalPlaces.Digits)
|
|
assert.Equal(t, "days", props.Suffix)
|
|
assert.Equal(t, "sumtin", props.Prefix)
|
|
assert.Equal(t, "overlaid", props.Position)
|
|
assert.Equal(t, []string{"xTotalTicks", "xTickStart", "xTickStep"}, props.GenerateXAxisTicks)
|
|
assert.Equal(t, 15, props.XTotalTicks)
|
|
assert.Equal(t, 0.0, props.XTickStart)
|
|
assert.Equal(t, 1000.0, props.XTickStep)
|
|
assert.Equal(t, []string{"yTotalTicks", "yTickStart", "yTickStep"}, props.GenerateYAxisTicks)
|
|
assert.Equal(t, 10, props.YTotalTicks)
|
|
assert.Equal(t, 0.0, props.YTickStart)
|
|
assert.Equal(t, 100.0, props.YTickStep)
|
|
assert.Equal(t, true, props.LegendColorizeRows)
|
|
assert.Equal(t, false, props.LegendHide)
|
|
assert.Equal(t, 1.0, props.LegendOpacity)
|
|
assert.Equal(t, 5, props.LegendOrientationThreshold)
|
|
assert.Equal(t, true, props.StaticLegend.ColorizeRows)
|
|
assert.Equal(t, 0.2, props.StaticLegend.HeightRatio)
|
|
assert.Equal(t, true, props.StaticLegend.Show)
|
|
assert.Equal(t, 1.0, props.StaticLegend.Opacity)
|
|
assert.Equal(t, 5, props.StaticLegend.OrientationThreshold)
|
|
assert.Equal(t, "y", props.StaticLegend.ValueAxis)
|
|
assert.Equal(t, 1.0, props.StaticLegend.WidthRatio)
|
|
|
|
require.Len(t, props.Queries, 1)
|
|
q := props.Queries[0]
|
|
expectedQuery := `from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")`
|
|
assert.Equal(t, expectedQuery, q.Text)
|
|
assert.Equal(t, "advanced", q.EditMode)
|
|
|
|
for _, key := range []string{"x", "y"} {
|
|
xAxis, ok := props.Axes[key]
|
|
require.True(t, ok, "key="+key)
|
|
assert.Equal(t, "10", xAxis.Base, "key="+key)
|
|
assert.Equal(t, key+"_label", xAxis.Label, "key="+key)
|
|
assert.Equal(t, key+"_prefix", xAxis.Prefix, "key="+key)
|
|
assert.Equal(t, "linear", xAxis.Scale, "key="+key)
|
|
assert.Equal(t, key+"_suffix", xAxis.Suffix, "key="+key)
|
|
}
|
|
|
|
require.Len(t, props.ViewColors, 2)
|
|
c := props.ViewColors[0]
|
|
assert.Equal(t, "base", c.ID)
|
|
assert.Equal(t, "laser", c.Name)
|
|
assert.Equal(t, "text", c.Type)
|
|
assert.Equal(t, "#8F8AF4", c.Hex)
|
|
assert.Equal(t, 3.0, c.Value)
|
|
|
|
c = props.ViewColors[1]
|
|
assert.Equal(t, "base", c.ID)
|
|
assert.Equal(t, "android", c.Name)
|
|
assert.Equal(t, "scale", c.Type)
|
|
assert.Equal(t, "#F4CF31", c.Hex)
|
|
assert.Equal(t, 1.0, c.Value)
|
|
})
|
|
})
|
|
|
|
t.Run("handles invalid config", func(t *testing.T) {
|
|
tests := []testTemplateResourceError{
|
|
{
|
|
name: "color missing hex value",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].colors[0].hex"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: Single_Stat_Plus_Line
|
|
name: single stat plus line
|
|
xPos: 1
|
|
yPos: 2
|
|
width: 6
|
|
height: 3
|
|
position: overlaid
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")
|
|
colors:
|
|
- name: laser
|
|
type: text
|
|
value: 3
|
|
axes:
|
|
- name : "x"
|
|
label: x_label
|
|
prefix: x_prefix
|
|
suffix: x_suffix
|
|
base: 10
|
|
scale: linear
|
|
- name: "y"
|
|
label: y_label
|
|
prefix: y_prefix
|
|
suffix: y_suffix
|
|
base: 10
|
|
scale: linear
|
|
`,
|
|
},
|
|
{
|
|
name: "no width provided",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].width"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: Single_Stat_Plus_Line
|
|
name: single stat plus line
|
|
xPos: 1
|
|
yPos: 2
|
|
height: 3
|
|
shade: true
|
|
hoverDimension: "y"
|
|
position: overlaid
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")
|
|
colors:
|
|
- name: laser
|
|
type: text
|
|
hex: "#8F8AF4"
|
|
value: 3
|
|
- name: android
|
|
type: scale
|
|
hex: "#F4CF31"
|
|
value: 1
|
|
axes:
|
|
- name : "x"
|
|
label: x_label
|
|
prefix: x_prefix
|
|
suffix: x_suffix
|
|
base: 10
|
|
scale: linear
|
|
- name: "y"
|
|
label: y_label
|
|
prefix: y_prefix
|
|
suffix: y_suffix
|
|
base: 10
|
|
scale: linear
|
|
`,
|
|
},
|
|
{
|
|
name: "no height provided",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].height"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: Single_Stat_Plus_Line
|
|
name: single stat plus line
|
|
xPos: 1
|
|
yPos: 2
|
|
width: 6
|
|
position: overlaid
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")
|
|
colors:
|
|
- name: laser
|
|
type: text
|
|
hex: "#8F8AF4"
|
|
value: 3
|
|
- name: android
|
|
type: scale
|
|
hex: "#F4CF31"
|
|
value: 1
|
|
axes:
|
|
- name : "x"
|
|
label: x_label
|
|
prefix: x_prefix
|
|
suffix: x_suffix
|
|
base: 10
|
|
scale: linear
|
|
- name: "y"
|
|
label: y_label
|
|
prefix: y_prefix
|
|
suffix: y_suffix
|
|
base: 10
|
|
scale: linear
|
|
`,
|
|
},
|
|
{
|
|
name: "missing x axis",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].axes"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: Single_Stat_Plus_Line
|
|
name: single stat plus line
|
|
xPos: 1
|
|
yPos: 2
|
|
width: 6
|
|
height: 3
|
|
position: overlaid
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")
|
|
colors:
|
|
- name: laser
|
|
type: text
|
|
hex: "#8F8AF4"
|
|
value: 3
|
|
- name: android
|
|
type: scale
|
|
hex: "#F4CF31"
|
|
value: 1
|
|
axes:
|
|
- name: "y"
|
|
label: y_label
|
|
prefix: y_prefix
|
|
suffix: y_suffix
|
|
base: 10
|
|
scale: linear
|
|
`,
|
|
},
|
|
{
|
|
name: "missing y axis",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].axes"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: Single_Stat_Plus_Line
|
|
name: single stat plus line
|
|
xPos: 1
|
|
yPos: 2
|
|
width: 6
|
|
height: 3
|
|
position: overlaid
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "mem") |> filter(fn: (r) => r._field == "used_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")
|
|
colors:
|
|
- name: laser
|
|
type: text
|
|
hex: "#8F8AF4"
|
|
value: 3
|
|
- name: android
|
|
type: scale
|
|
hex: "#F4CF31"
|
|
value: 1
|
|
axes:
|
|
- name : "x"
|
|
label: x_label
|
|
prefix: x_prefix
|
|
suffix: x_suffix
|
|
base: 10
|
|
scale: linear
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, KindDashboard, tt)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("table chart", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/dashboard_table", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Dashboards, 1)
|
|
|
|
actual := sum.Dashboards[0]
|
|
assert.Equal(t, KindDashboard, actual.Kind)
|
|
assert.Equal(t, "dash-1", actual.Name)
|
|
assert.Equal(t, "desc1", actual.Description)
|
|
|
|
require.Len(t, actual.Charts, 1)
|
|
actualChart := actual.Charts[0]
|
|
assert.Equal(t, 3, actualChart.Height)
|
|
assert.Equal(t, 6, actualChart.Width)
|
|
assert.Equal(t, 1, actualChart.XPosition)
|
|
assert.Equal(t, 2, actualChart.YPosition)
|
|
|
|
props, ok := actualChart.Properties.(influxdb.TableViewProperties)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "table note", props.Note)
|
|
assert.True(t, props.ShowNoteWhenEmpty)
|
|
assert.True(t, props.DecimalPlaces.IsEnforced)
|
|
assert.Equal(t, int32(1), props.DecimalPlaces.Digits)
|
|
assert.Equal(t, "YYYY:MMMM:DD", props.TimeFormat)
|
|
|
|
require.Len(t, props.Queries, 1)
|
|
q := props.Queries[0]
|
|
expectedQuery := `from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "boltdb_writes_total") |> filter(fn: (r) => r._field == "counter")`
|
|
assert.Equal(t, expectedQuery, q.Text)
|
|
assert.Equal(t, "advanced", q.EditMode)
|
|
|
|
require.Len(t, props.ViewColors, 1)
|
|
c := props.ViewColors[0]
|
|
assert.Equal(t, "laser", c.Name)
|
|
assert.Equal(t, "min", c.Type)
|
|
assert.Equal(t, "#8F8AF4", c.Hex)
|
|
assert.Equal(t, 3.0, c.Value)
|
|
|
|
tableOpts := props.TableOptions
|
|
assert.True(t, tableOpts.VerticalTimeAxis)
|
|
assert.Equal(t, "_time", tableOpts.SortBy.InternalName)
|
|
assert.Equal(t, "truncate", tableOpts.Wrapping)
|
|
assert.True(t, tableOpts.FixFirstColumn)
|
|
|
|
assert.Contains(t, props.FieldOptions, influxdb.RenamableField{
|
|
InternalName: "_value",
|
|
DisplayName: "MB",
|
|
Visible: true,
|
|
})
|
|
assert.Contains(t, props.FieldOptions, influxdb.RenamableField{
|
|
InternalName: "_time",
|
|
DisplayName: "time (ms)",
|
|
Visible: true,
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("handles invalid config", func(t *testing.T) {
|
|
tests := []testTemplateResourceError{
|
|
{
|
|
name: "color missing hex value",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].colors[0].hex"},
|
|
templateStr: `
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: Table
|
|
name: table
|
|
xPos: 1
|
|
yPos: 2
|
|
width: 6
|
|
height: 3
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "boltdb_writes_total") |> filter(fn: (r) => r._field == "counter")
|
|
colors:
|
|
- name: laser
|
|
type: min
|
|
hex:
|
|
value: 3.0`,
|
|
},
|
|
{
|
|
name: "no width provided",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].width"},
|
|
templateStr: `
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: Table
|
|
name: table
|
|
xPos: 1
|
|
yPos: 2
|
|
height: 3
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "boltdb_writes_total") |> filter(fn: (r) => r._field == "counter")
|
|
colors:
|
|
- name: laser
|
|
type: min
|
|
hex: peru
|
|
value: 3.0`,
|
|
},
|
|
{
|
|
name: "no height provided",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].height"},
|
|
templateStr: `
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: Table
|
|
name: table
|
|
xPos: 1
|
|
yPos: 2
|
|
width: 6
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "boltdb_writes_total") |> filter(fn: (r) => r._field == "counter")
|
|
colors:
|
|
- name: laser
|
|
type: min
|
|
hex: peru
|
|
value: 3.0`,
|
|
},
|
|
{
|
|
name: "invalid wrapping table option",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].tableOptions.wrapping"},
|
|
templateStr: `
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: Table
|
|
name: table
|
|
xPos: 1
|
|
yPos: 2
|
|
width: 6
|
|
height: 3
|
|
tableOptions:
|
|
sortBy: _time
|
|
wrapping: WRONGO wrapping
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "boltdb_writes_total") |> filter(fn: (r) => r._field == "counter")
|
|
colors:
|
|
- name: laser
|
|
type: min
|
|
hex: "#8F8AF4"
|
|
value: 3.0
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, KindDashboard, tt)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("xy chart", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/dashboard_xy", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Dashboards, 1)
|
|
|
|
actual := sum.Dashboards[0]
|
|
assert.Equal(t, KindDashboard, actual.Kind)
|
|
assert.Equal(t, "dash-1", actual.Name)
|
|
assert.Equal(t, "desc1", actual.Description)
|
|
|
|
require.Len(t, actual.Charts, 1)
|
|
actualChart := actual.Charts[0]
|
|
assert.Equal(t, 3, actualChart.Height)
|
|
assert.Equal(t, 6, actualChart.Width)
|
|
assert.Equal(t, 1, actualChart.XPosition)
|
|
assert.Equal(t, 2, actualChart.YPosition)
|
|
|
|
props, ok := actualChart.Properties.(influxdb.XYViewProperties)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "xy", props.GetType())
|
|
assert.Equal(t, true, props.ShadeBelow)
|
|
assert.Equal(t, "y", props.HoverDimension)
|
|
assert.Equal(t, "xy chart note", props.Note)
|
|
assert.True(t, props.ShowNoteWhenEmpty)
|
|
assert.Equal(t, "stacked", props.Position)
|
|
assert.Equal(t, []string{"xTotalTicks", "xTickStart", "xTickStep"}, props.GenerateXAxisTicks)
|
|
assert.Equal(t, 15, props.XTotalTicks)
|
|
assert.Equal(t, 0.0, props.XTickStart)
|
|
assert.Equal(t, 1000.0, props.XTickStep)
|
|
assert.Equal(t, []string{"yTotalTicks", "yTickStart", "yTickStep"}, props.GenerateYAxisTicks)
|
|
assert.Equal(t, 10, props.YTotalTicks)
|
|
assert.Equal(t, 0.0, props.YTickStart)
|
|
assert.Equal(t, 100.0, props.YTickStep)
|
|
assert.Equal(t, true, props.LegendColorizeRows)
|
|
assert.Equal(t, false, props.LegendHide)
|
|
assert.Equal(t, 1.0, props.LegendOpacity)
|
|
assert.Equal(t, 5, props.LegendOrientationThreshold)
|
|
assert.Equal(t, true, props.StaticLegend.ColorizeRows)
|
|
assert.Equal(t, 0.2, props.StaticLegend.HeightRatio)
|
|
assert.Equal(t, true, props.StaticLegend.Show)
|
|
assert.Equal(t, 1.0, props.StaticLegend.Opacity)
|
|
assert.Equal(t, 5, props.StaticLegend.OrientationThreshold)
|
|
assert.Equal(t, "y", props.StaticLegend.ValueAxis)
|
|
assert.Equal(t, 1.0, props.StaticLegend.WidthRatio)
|
|
|
|
require.Len(t, props.Queries, 1)
|
|
q := props.Queries[0]
|
|
queryText := `from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "boltdb_writes_total") |> filter(fn: (r) => r._field == "counter")`
|
|
assert.Equal(t, queryText, q.Text)
|
|
assert.Equal(t, "advanced", q.EditMode)
|
|
|
|
require.Len(t, props.ViewColors, 1)
|
|
c := props.ViewColors[0]
|
|
assert.Equal(t, "laser", c.Name)
|
|
assert.Equal(t, "scale", c.Type)
|
|
assert.Equal(t, "#8F8AF4", c.Hex)
|
|
assert.Equal(t, 3.0, c.Value)
|
|
})
|
|
})
|
|
|
|
t.Run("handles invalid config", func(t *testing.T) {
|
|
tests := []testTemplateResourceError{
|
|
{
|
|
name: "color missing hex value",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, "charts[0].colors[0].hex"},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: XY
|
|
name: xy chart
|
|
xPos: 1
|
|
yPos: 2
|
|
width: 6
|
|
height: 3
|
|
geom: line
|
|
position: stacked
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "boltdb_writes_total") |> filter(fn: (r) => r._field == "counter")
|
|
colors:
|
|
- name: laser
|
|
type: scale
|
|
value: 3
|
|
axes:
|
|
- name : "x"
|
|
label: x_label
|
|
prefix: x_prefix
|
|
suffix: x_suffix
|
|
base: 10
|
|
scale: linear
|
|
- name: "y"
|
|
label: y_label
|
|
prefix: y_prefix
|
|
suffix: y_suffix
|
|
base: 10
|
|
scale: linear
|
|
`,
|
|
},
|
|
{
|
|
name: "invalid geom flag",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldDashCharts, fieldChartGeom},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
description: desc1
|
|
charts:
|
|
- kind: XY
|
|
name: xy chart
|
|
xPos: 1
|
|
yPos: 2
|
|
width: 6
|
|
height: 3
|
|
position: stacked
|
|
staticLegend:
|
|
queries:
|
|
- query: >
|
|
from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "boltdb_writes_total") |> filter(fn: (r) => r._field == "counter")
|
|
colors:
|
|
- name: laser
|
|
type: scale
|
|
hex: "#8F8AF4"
|
|
value: 3
|
|
axes:
|
|
- name : "x"
|
|
label: x_label
|
|
prefix: x_prefix
|
|
suffix: x_suffix
|
|
base: 10
|
|
scale: linear
|
|
- name: "y"
|
|
label: y_label
|
|
prefix: y_prefix
|
|
suffix: y_suffix
|
|
base: 10
|
|
scale: linear
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, KindDashboard, tt)
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("with params option should be parameterizable", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/dashboard_params.yml", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Dashboards, 1)
|
|
|
|
actual := sum.Dashboards[0]
|
|
assert.Equal(t, KindDashboard, actual.Kind)
|
|
assert.Equal(t, "dash-1", actual.MetaName)
|
|
|
|
require.Len(t, actual.Charts, 1)
|
|
actualChart := actual.Charts[0]
|
|
assert.Equal(t, 3, actualChart.Height)
|
|
assert.Equal(t, 6, actualChart.Width)
|
|
assert.Equal(t, 1, actualChart.XPosition)
|
|
assert.Equal(t, 2, actualChart.YPosition)
|
|
|
|
props, ok := actualChart.Properties.(influxdb.SingleStatViewProperties)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "single-stat", props.GetType())
|
|
|
|
require.Len(t, props.Queries, 1)
|
|
|
|
// parmas
|
|
queryText := `option params = {
|
|
bucket: "bar",
|
|
start: -24h0m0s,
|
|
stop: now(),
|
|
name: "max",
|
|
floatVal: 37.2,
|
|
minVal: 10,
|
|
}
|
|
|
|
from(bucket: params.bucket)
|
|
|> range(start: params.start, stop: params.stop)
|
|
|> filter(fn: (r) => r._measurement == "processes")
|
|
|> filter(fn: (r) => r.floater == params.floatVal)
|
|
|> filter(fn: (r) => r._value > params.minVal)
|
|
|> aggregateWindow(every: v.windowPeriod, fn: max)
|
|
|> yield(name: params.name)
|
|
`
|
|
|
|
q := props.Queries[0]
|
|
assert.Equal(t, queryText, q.Text)
|
|
assert.Equal(t, "advanced", q.EditMode)
|
|
|
|
expectedRefs := []SummaryReference{
|
|
{
|
|
Field: "spec.charts[0].queries[0].params.bucket",
|
|
EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.bucket`,
|
|
ValType: "string",
|
|
DefaultValue: "bar",
|
|
},
|
|
{
|
|
Field: "spec.charts[0].queries[0].params.floatVal",
|
|
EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.floatVal`,
|
|
ValType: "float",
|
|
DefaultValue: 37.2,
|
|
},
|
|
{
|
|
Field: "spec.charts[0].queries[0].params.minVal",
|
|
EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.minVal`,
|
|
ValType: "integer",
|
|
DefaultValue: int64(10),
|
|
},
|
|
{
|
|
Field: "spec.charts[0].queries[0].params.name",
|
|
EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.name`,
|
|
ValType: "string",
|
|
DefaultValue: "max",
|
|
},
|
|
{
|
|
Field: "spec.charts[0].queries[0].params.start",
|
|
EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.start`,
|
|
ValType: "duration",
|
|
DefaultValue: "-24h0m0s",
|
|
},
|
|
{
|
|
Field: "spec.charts[0].queries[0].params.stop",
|
|
EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.stop`,
|
|
ValType: "time",
|
|
DefaultValue: "now()",
|
|
},
|
|
}
|
|
assert.Equal(t, expectedRefs, actual.EnvReferences)
|
|
})
|
|
})
|
|
|
|
t.Run("with env refs should be valid", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/dashboard_ref.yml", func(t *testing.T, template *Template) {
|
|
actual := template.Summary().Dashboards
|
|
require.Len(t, actual, 1)
|
|
|
|
expected := []SummaryReference{
|
|
{
|
|
Field: "spec.associations[0].name",
|
|
EnvRefKey: "label-meta-name",
|
|
},
|
|
{
|
|
Field: "metadata.name",
|
|
EnvRefKey: "meta-name",
|
|
DefaultValue: "meta",
|
|
},
|
|
{
|
|
Field: "spec.name",
|
|
EnvRefKey: "spec-name",
|
|
DefaultValue: "spectacles",
|
|
},
|
|
}
|
|
assert.Equal(t, expected, actual[0].EnvReferences)
|
|
})
|
|
})
|
|
|
|
t.Run("and labels associated should be successful", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/dashboard_associates_label", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Dashboards, 1)
|
|
|
|
actual := sum.Dashboards[0]
|
|
assert.Equal(t, "dash-1", actual.Name)
|
|
|
|
require.Len(t, actual.LabelAssociations, 2)
|
|
assert.Equal(t, "label-1", actual.LabelAssociations[0].Name)
|
|
assert.Equal(t, "label-2", actual.LabelAssociations[1].Name)
|
|
|
|
expectedMappings := []SummaryLabelMapping{
|
|
{
|
|
Status: StateStatusNew,
|
|
ResourceType: influxdb.DashboardsResourceType,
|
|
ResourceMetaName: "dash-1",
|
|
ResourceName: "dash-1",
|
|
LabelMetaName: "label-1",
|
|
LabelName: "label-1",
|
|
},
|
|
{
|
|
Status: StateStatusNew,
|
|
ResourceType: influxdb.DashboardsResourceType,
|
|
ResourceMetaName: "dash-1",
|
|
ResourceName: "dash-1",
|
|
LabelMetaName: "label-2",
|
|
LabelName: "label-2",
|
|
},
|
|
}
|
|
|
|
for _, expectedMapping := range expectedMappings {
|
|
assert.Contains(t, sum.LabelMappings, expectedMapping)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("association doesn't exist then provides an error", func(t *testing.T) {
|
|
tests := []testTemplateResourceError{
|
|
{
|
|
name: "no labels provided",
|
|
assErrs: 1,
|
|
assIdxs: []int{0},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
`,
|
|
},
|
|
{
|
|
name: "mixed found and not found",
|
|
assErrs: 1,
|
|
assIdxs: []int{1},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: label-1
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
- kind: Label
|
|
name: unfound label
|
|
`,
|
|
},
|
|
{
|
|
name: "multiple not found",
|
|
assErrs: 1,
|
|
assIdxs: []int{0, 1},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: label-1
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
associations:
|
|
- kind: Label
|
|
name: not found 1
|
|
- kind: Label
|
|
name: unfound label
|
|
`,
|
|
},
|
|
{
|
|
name: "duplicate valid nested labels",
|
|
assErrs: 1,
|
|
assIdxs: []int{1},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: label-1
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Dashboard
|
|
metadata:
|
|
name: dash-1
|
|
spec:
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
- kind: Label
|
|
name: label-1
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, KindDashboard, tt)
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("template with notification endpoints", func(t *testing.T) {
|
|
t.Run("and labels associated should be successful", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/notification_endpoint", func(t *testing.T, template *Template) {
|
|
expectedEndpoints := []SummaryNotificationEndpoint{
|
|
{
|
|
SummaryIdentifier: SummaryIdentifier{
|
|
Kind: KindNotificationEndpointHTTP,
|
|
MetaName: "http-basic-auth-notification-endpoint",
|
|
},
|
|
NotificationEndpoint: &endpoint.HTTP{
|
|
Base: endpoint.Base{
|
|
Name: "basic endpoint name",
|
|
Description: "http basic auth desc",
|
|
Status: taskmodel.TaskStatusInactive,
|
|
},
|
|
URL: "https://www.example.com/endpoint/basicauth",
|
|
AuthMethod: "basic",
|
|
Method: "POST",
|
|
Username: influxdb.SecretField{Value: strPtr("secret username")},
|
|
Password: influxdb.SecretField{Value: strPtr("secret password")},
|
|
},
|
|
},
|
|
{
|
|
SummaryIdentifier: SummaryIdentifier{
|
|
Kind: KindNotificationEndpointHTTP,
|
|
MetaName: "http-bearer-auth-notification-endpoint",
|
|
},
|
|
NotificationEndpoint: &endpoint.HTTP{
|
|
Base: endpoint.Base{
|
|
Name: "http-bearer-auth-notification-endpoint",
|
|
Description: "http bearer auth desc",
|
|
Status: taskmodel.TaskStatusActive,
|
|
},
|
|
URL: "https://www.example.com/endpoint/bearerauth",
|
|
AuthMethod: "bearer",
|
|
Method: "PUT",
|
|
Token: influxdb.SecretField{Value: strPtr("secret token")},
|
|
},
|
|
},
|
|
{
|
|
SummaryIdentifier: SummaryIdentifier{
|
|
Kind: KindNotificationEndpointHTTP,
|
|
MetaName: "http-none-auth-notification-endpoint",
|
|
},
|
|
NotificationEndpoint: &endpoint.HTTP{
|
|
Base: endpoint.Base{
|
|
Name: "http-none-auth-notification-endpoint",
|
|
Description: "http none auth desc",
|
|
Status: taskmodel.TaskStatusActive,
|
|
},
|
|
URL: "https://www.example.com/endpoint/noneauth",
|
|
AuthMethod: "none",
|
|
Method: "GET",
|
|
},
|
|
},
|
|
{
|
|
SummaryIdentifier: SummaryIdentifier{
|
|
Kind: KindNotificationEndpointPagerDuty,
|
|
MetaName: "pager-duty-notification-endpoint",
|
|
},
|
|
NotificationEndpoint: &endpoint.PagerDuty{
|
|
Base: endpoint.Base{
|
|
Name: "pager duty name",
|
|
Description: "pager duty desc",
|
|
Status: taskmodel.TaskStatusActive,
|
|
},
|
|
ClientURL: "http://localhost:8080/orgs/7167eb6719fa34e5/alert-history",
|
|
RoutingKey: influxdb.SecretField{Value: strPtr("secret routing-key")},
|
|
},
|
|
},
|
|
{
|
|
SummaryIdentifier: SummaryIdentifier{
|
|
Kind: KindNotificationEndpointHTTP,
|
|
MetaName: "slack-notification-endpoint",
|
|
},
|
|
NotificationEndpoint: &endpoint.Slack{
|
|
Base: endpoint.Base{
|
|
Name: "slack name",
|
|
Description: "slack desc",
|
|
Status: taskmodel.TaskStatusActive,
|
|
},
|
|
URL: "https://hooks.slack.com/services/bip/piddy/boppidy",
|
|
Token: influxdb.SecretField{Value: strPtr("tokenval")},
|
|
},
|
|
},
|
|
}
|
|
|
|
sum := template.Summary()
|
|
endpoints := sum.NotificationEndpoints
|
|
require.Len(t, endpoints, len(expectedEndpoints))
|
|
require.Len(t, sum.LabelMappings, len(expectedEndpoints))
|
|
|
|
for i := range expectedEndpoints {
|
|
expected, actual := expectedEndpoints[i], endpoints[i]
|
|
assert.Equalf(t, expected.NotificationEndpoint, actual.NotificationEndpoint, "index=%d", i)
|
|
require.Len(t, actual.LabelAssociations, 1)
|
|
assert.Equal(t, "label-1", actual.LabelAssociations[0].Name)
|
|
|
|
assert.Contains(t, sum.LabelMappings, SummaryLabelMapping{
|
|
Status: StateStatusNew,
|
|
ResourceType: influxdb.NotificationEndpointResourceType,
|
|
ResourceMetaName: expected.MetaName,
|
|
ResourceName: expected.NotificationEndpoint.GetName(),
|
|
LabelMetaName: "label-1",
|
|
LabelName: "label-1",
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("with env refs should be valid", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/notification_endpoint_ref.yml", func(t *testing.T, template *Template) {
|
|
actual := template.Summary().NotificationEndpoints
|
|
require.Len(t, actual, 1)
|
|
|
|
expectedEnvRefs := []SummaryReference{
|
|
{
|
|
Field: "metadata.name",
|
|
EnvRefKey: "meta-name",
|
|
DefaultValue: "meta",
|
|
},
|
|
{
|
|
Field: "spec.name",
|
|
EnvRefKey: "spec-name",
|
|
DefaultValue: "spectacles",
|
|
},
|
|
{
|
|
Field: "spec.associations[0].name",
|
|
EnvRefKey: "label-meta-name",
|
|
},
|
|
}
|
|
assert.Equal(t, expectedEnvRefs, actual[0].EnvReferences)
|
|
})
|
|
})
|
|
|
|
t.Run("handles bad config", func(t *testing.T) {
|
|
tests := []struct {
|
|
kind Kind
|
|
resErr testTemplateResourceError
|
|
}{
|
|
{
|
|
kind: KindNotificationEndpointSlack,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing slack url",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldNotificationEndpointURL},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointSlack
|
|
metadata:
|
|
name: slack-notification-endpoint
|
|
spec:
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationEndpointPagerDuty,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing pager duty url",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldNotificationEndpointURL},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointPagerDuty
|
|
metadata:
|
|
name: pager-duty-notification-endpoint
|
|
spec:
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationEndpointHTTP,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing http url",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldNotificationEndpointURL},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointHTTP
|
|
metadata:
|
|
name: http-none-auth-notification-endpoint
|
|
spec:
|
|
type: none
|
|
method: get
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationEndpointHTTP,
|
|
resErr: testTemplateResourceError{
|
|
name: "bad url",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldNotificationEndpointURL},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointHTTP
|
|
metadata:
|
|
name: http-none-auth-notification-endpoint
|
|
spec:
|
|
type: none
|
|
method: get
|
|
url: d_____-_8**(*https://www.examples.coms
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationEndpointHTTP,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing http method",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldNotificationEndpointHTTPMethod},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointHTTP
|
|
metadata:
|
|
name: http-none-auth-notification-endpoint
|
|
spec:
|
|
type: none
|
|
url: https://www.example.com/endpoint/noneauth
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationEndpointHTTP,
|
|
resErr: testTemplateResourceError{
|
|
name: "invalid http method",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldNotificationEndpointHTTPMethod},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointHTTP
|
|
metadata:
|
|
name: http-basic-auth-notification-endpoint
|
|
spec:
|
|
type: none
|
|
description: http none auth desc
|
|
method: GHOST
|
|
url: https://www.example.com/endpoint/noneauth
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationEndpointHTTP,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing basic username",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldNotificationEndpointUsername},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointHTTP
|
|
metadata:
|
|
name: http-basic-auth-notification-endpoint
|
|
spec:
|
|
type: basic
|
|
method: POST
|
|
url: https://www.example.com/endpoint/basicauth
|
|
password: "secret password"
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationEndpointHTTP,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing basic password",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldNotificationEndpointPassword},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointHTTP
|
|
metadata:
|
|
name: http-basic-auth-notification-endpoint
|
|
spec:
|
|
type: basic
|
|
method: POST
|
|
url: https://www.example.com/endpoint/basicauth
|
|
username: username
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationEndpointHTTP,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing basic password and username",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldNotificationEndpointPassword, fieldNotificationEndpointUsername},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointHTTP
|
|
metadata:
|
|
name: http-basic-auth-notification-endpoint
|
|
spec:
|
|
description: http basic auth desc
|
|
type: basic
|
|
method: pOsT
|
|
url: https://www.example.com/endpoint/basicauth
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationEndpointHTTP,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing bearer token",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldNotificationEndpointToken},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointHTTP
|
|
metadata:
|
|
name: http-bearer-auth-notification-endpoint
|
|
spec:
|
|
description: http bearer auth desc
|
|
type: bearer
|
|
method: puT
|
|
url: https://www.example.com/endpoint/bearerauth
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationEndpointHTTP,
|
|
resErr: testTemplateResourceError{
|
|
name: "invalid http type",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldType},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointHTTP
|
|
metadata:
|
|
name: http-basic-auth-notification-endpoint
|
|
spec:
|
|
type: RANDOM WRONG TYPE
|
|
description: http none auth desc
|
|
method: get
|
|
url: https://www.example.com/endpoint/noneauth
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationEndpointSlack,
|
|
resErr: testTemplateResourceError{
|
|
name: "duplicate endpoints",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointSlack
|
|
metadata:
|
|
name: slack-notification-endpoint
|
|
spec:
|
|
url: https://hooks.slack.com/services/bip/piddy/boppidy
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointSlack
|
|
metadata:
|
|
name: slack_notification_endpoint
|
|
spec:
|
|
url: https://hooks.slack.com/services/bip/piddy/boppidy
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationEndpointSlack,
|
|
resErr: testTemplateResourceError{
|
|
name: "invalid status",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldStatus},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointSlack
|
|
metadata:
|
|
name: slack-notification-endpoint
|
|
spec:
|
|
description: slack desc
|
|
url: https://hooks.slack.com/services/bip/piddy/boppidy
|
|
status: RANDO STATUS
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationEndpointSlack,
|
|
resErr: testTemplateResourceError{
|
|
name: "duplicate meta name and spec name",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointSlack
|
|
metadata:
|
|
name: slack
|
|
spec:
|
|
description: slack desc
|
|
url: https://hooks.slack.com/services/bip/piddy/boppidy
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointSlack
|
|
metadata:
|
|
name: slack-notification-endpoint
|
|
spec:
|
|
name: slack
|
|
description: slack desc
|
|
url: https://hooks.slack.com/services/bip/piddy/boppidy
|
|
`,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, tt.kind, tt.resErr)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("template with notification rules", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/notification_rule", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
rules := sum.NotificationRules
|
|
require.Len(t, rules, 1)
|
|
|
|
rule := rules[0]
|
|
assert.Equal(t, KindNotificationRule, rule.Kind)
|
|
assert.Equal(t, "rule_0", rule.Name)
|
|
assert.Equal(t, "endpoint-0", rule.EndpointMetaName)
|
|
assert.Equal(t, "desc_0", rule.Description)
|
|
assert.Equal(t, (10 * time.Minute).String(), rule.Every)
|
|
assert.Equal(t, (30 * time.Second).String(), rule.Offset)
|
|
expectedMsgTempl := "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
|
|
assert.Equal(t, expectedMsgTempl, rule.MessageTemplate)
|
|
assert.Equal(t, influxdb.Active, rule.Status)
|
|
|
|
expectedStatusRules := []SummaryStatusRule{
|
|
{CurrentLevel: "CRIT", PreviousLevel: "OK"},
|
|
{CurrentLevel: "WARN"},
|
|
}
|
|
assert.Equal(t, expectedStatusRules, rule.StatusRules)
|
|
|
|
expectedTagRules := []SummaryTagRule{
|
|
{Key: "k1", Value: "v1", Operator: "equal"},
|
|
{Key: "k1", Value: "v2", Operator: "equal"},
|
|
}
|
|
assert.Equal(t, expectedTagRules, rule.TagRules)
|
|
|
|
require.Len(t, sum.Labels, 2)
|
|
require.Len(t, rule.LabelAssociations, 2)
|
|
assert.Equal(t, "label-1", rule.LabelAssociations[0].MetaName)
|
|
assert.Equal(t, "label-2", rule.LabelAssociations[1].MetaName)
|
|
})
|
|
})
|
|
|
|
t.Run("with env refs should be valid", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/notification_rule_ref.yml", func(t *testing.T, template *Template) {
|
|
actual := template.Summary().NotificationRules
|
|
require.Len(t, actual, 1)
|
|
|
|
expectedEnvRefs := []SummaryReference{
|
|
{
|
|
Field: "metadata.name",
|
|
EnvRefKey: "meta-name",
|
|
DefaultValue: "meta",
|
|
},
|
|
{
|
|
Field: "spec.name",
|
|
EnvRefKey: "spec-name",
|
|
DefaultValue: "spectacles",
|
|
},
|
|
{
|
|
Field: "spec.associations[0].name",
|
|
EnvRefKey: "label-meta-name",
|
|
},
|
|
{
|
|
Field: "spec.endpointName",
|
|
EnvRefKey: "endpoint-meta-name",
|
|
},
|
|
}
|
|
assert.Equal(t, expectedEnvRefs, actual[0].EnvReferences)
|
|
})
|
|
})
|
|
|
|
t.Run("handles bad config", func(t *testing.T) {
|
|
templateWithValidEndpint := func(resource string) string {
|
|
return fmt.Sprintf(`
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationEndpointSlack
|
|
metadata:
|
|
name: endpoint-0
|
|
spec:
|
|
url: https://hooks.slack.com/services/bip/piddy/boppidy
|
|
---
|
|
%s
|
|
`, resource)
|
|
}
|
|
|
|
tests := []struct {
|
|
kind Kind
|
|
resErr testTemplateResourceError
|
|
}{
|
|
{
|
|
kind: KindNotificationRule,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing name",
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: templateWithValidEndpint(`apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationRule
|
|
metadata:
|
|
spec:
|
|
endpointName: endpoint-0
|
|
every: 10m
|
|
messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
|
|
statusRules:
|
|
- currentLevel: WARN
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationRule,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing endpoint name",
|
|
valFields: []string{fieldSpec, fieldNotificationRuleEndpointName},
|
|
templateStr: templateWithValidEndpint(`apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationRule
|
|
metadata:
|
|
name: rule-0
|
|
spec:
|
|
every: 10m
|
|
messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
|
|
statusRules:
|
|
- currentLevel: WARN
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationRule,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing every",
|
|
valFields: []string{fieldSpec, fieldEvery},
|
|
templateStr: templateWithValidEndpint(`apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationRule
|
|
metadata:
|
|
name: rule-0
|
|
spec:
|
|
endpointName: endpoint-0
|
|
messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
|
|
statusRules:
|
|
- currentLevel: WARN
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationRule,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing status rules",
|
|
valFields: []string{fieldSpec, fieldNotificationRuleStatusRules},
|
|
templateStr: templateWithValidEndpint(`apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationRule
|
|
metadata:
|
|
name: rule-0
|
|
spec:
|
|
every: 10m
|
|
endpointName: endpoint-0
|
|
messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationRule,
|
|
resErr: testTemplateResourceError{
|
|
name: "bad current status rule level",
|
|
valFields: []string{fieldSpec, fieldNotificationRuleStatusRules},
|
|
templateStr: templateWithValidEndpint(`apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationRule
|
|
metadata:
|
|
name: rule-0
|
|
spec:
|
|
every: 10m
|
|
endpointName: endpoint-0
|
|
messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
|
|
statusRules:
|
|
- currentLevel: WRONGO
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationRule,
|
|
resErr: testTemplateResourceError{
|
|
name: "bad previous status rule level",
|
|
valFields: []string{fieldSpec, fieldNotificationRuleStatusRules},
|
|
templateStr: templateWithValidEndpint(`apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationRule
|
|
metadata:
|
|
name: rule-0
|
|
spec:
|
|
endpointName: endpoint-0
|
|
every: 10m
|
|
messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
|
|
statusRules:
|
|
- currentLevel: CRIT
|
|
previousLevel: WRONG
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationRule,
|
|
resErr: testTemplateResourceError{
|
|
name: "bad tag rule operator",
|
|
valFields: []string{fieldSpec, fieldNotificationRuleTagRules},
|
|
templateStr: templateWithValidEndpint(`apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationRule
|
|
metadata:
|
|
name: rule-0
|
|
spec:
|
|
endpointName: endpoint-0
|
|
every: 10m
|
|
messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
|
|
statusRules:
|
|
- currentLevel: WARN
|
|
tagRules:
|
|
- key: k1
|
|
value: v2
|
|
operator: WRONG
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationRule,
|
|
resErr: testTemplateResourceError{
|
|
name: "bad status provided",
|
|
valFields: []string{fieldSpec, fieldStatus},
|
|
templateStr: templateWithValidEndpint(`apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationRule
|
|
metadata:
|
|
name: rule-0
|
|
spec:
|
|
endpointName: endpoint-0
|
|
every: 10m
|
|
messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
|
|
status: RANDO STATUS
|
|
statusRules:
|
|
- currentLevel: WARN
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationRule,
|
|
resErr: testTemplateResourceError{
|
|
name: "label association does not exist",
|
|
valFields: []string{fieldSpec, fieldAssociations},
|
|
templateStr: templateWithValidEndpint(`apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationRule
|
|
metadata:
|
|
name: rule-0
|
|
spec:
|
|
endpointName: endpoint-0
|
|
every: 10m
|
|
messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
|
|
statusRules:
|
|
- currentLevel: WARN
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationRule,
|
|
resErr: testTemplateResourceError{
|
|
name: "label association dupe",
|
|
valFields: []string{fieldSpec, fieldAssociations},
|
|
templateStr: templateWithValidEndpint(`apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: label-1
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationRule
|
|
metadata:
|
|
name: rule-0
|
|
spec:
|
|
endpointName: endpoint-0
|
|
every: 10m
|
|
messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
|
|
statusRules:
|
|
- currentLevel: WARN
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
- kind: Label
|
|
name: label-1
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationRule,
|
|
resErr: testTemplateResourceError{
|
|
name: "duplicate meta names",
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: templateWithValidEndpint(`
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationRule
|
|
metadata:
|
|
name: rule-0
|
|
spec:
|
|
endpointName: endpoint-0
|
|
every: 10m
|
|
messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
|
|
statusRules:
|
|
- currentLevel: WARN
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationRule
|
|
metadata:
|
|
name: rule-0
|
|
spec:
|
|
endpointName: endpoint-0
|
|
every: 10m
|
|
messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
|
|
statusRules:
|
|
- currentLevel: WARN
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
kind: KindNotificationRule,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing endpoint association in template",
|
|
valFields: []string{fieldSpec, fieldNotificationRuleEndpointName},
|
|
templateStr: `
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: NotificationRule
|
|
metadata:
|
|
name: rule-0
|
|
spec:
|
|
endpointName: RANDO_ENDPOINT_NAME
|
|
every: 10m
|
|
messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
|
|
statusRules:
|
|
- currentLevel: WARN
|
|
`,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, tt.kind, tt.resErr)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("template with tasks", func(t *testing.T) {
|
|
t.Run("happy path", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/tasks", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
tasks := sum.Tasks
|
|
require.Len(t, tasks, 2)
|
|
|
|
for _, ta := range tasks {
|
|
assert.Equal(t, KindTask, ta.Kind)
|
|
}
|
|
|
|
sort.Slice(tasks, func(i, j int) bool {
|
|
return tasks[i].MetaName < tasks[j].MetaName
|
|
})
|
|
|
|
baseEqual := func(t *testing.T, i int, status influxdb.Status, actual SummaryTask) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, "task-"+strconv.Itoa(i), actual.Name)
|
|
assert.Equal(t, "desc_"+strconv.Itoa(i), actual.Description)
|
|
assert.Equal(t, status, actual.Status)
|
|
|
|
expectedQuery := "from(bucket: \"rucket_1\")\n |> range(start: -5d, stop: -1h)\n |> filter(fn: (r) => r._measurement == \"cpu\")\n |> filter(fn: (r) => r._field == \"usage_idle\")\n |> aggregateWindow(every: 1m, fn: mean)\n |> yield(name: \"mean\")"
|
|
assert.Equal(t, expectedQuery, actual.Query)
|
|
|
|
require.Len(t, actual.LabelAssociations, 1)
|
|
assert.Equal(t, "label-1", actual.LabelAssociations[0].Name)
|
|
}
|
|
|
|
require.Len(t, sum.Labels, 1)
|
|
|
|
task0 := tasks[0]
|
|
baseEqual(t, 1, influxdb.Active, task0)
|
|
assert.Equal(t, "15 * * * *", task0.Cron)
|
|
|
|
task1 := tasks[1]
|
|
baseEqual(t, 0, influxdb.Inactive, task1)
|
|
assert.Equal(t, (25 * time.Hour).String(), task1.Every)
|
|
assert.Equal(t, (15 * time.Second).String(), task1.Offset)
|
|
})
|
|
})
|
|
|
|
t.Run("with params option should be parameterizable", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/tasks_params.yml", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Tasks, 1)
|
|
|
|
actual := sum.Tasks[0]
|
|
assert.Equal(t, KindTask, actual.Kind)
|
|
assert.Equal(t, "task-uuid", actual.MetaName)
|
|
|
|
queryText := `option params = {
|
|
bucket: "bar",
|
|
start: -24h0m0s,
|
|
stop: now(),
|
|
name: "max",
|
|
floatVal: 37.2,
|
|
minVal: 10,
|
|
}
|
|
|
|
from(bucket: params.bucket)
|
|
|> range(start: params.start, stop: params.stop)
|
|
|> filter(fn: (r) => r._measurement == "processes")
|
|
|> filter(fn: (r) => r.floater == params.floatVal)
|
|
|> filter(fn: (r) => r._value > params.minVal)
|
|
|> aggregateWindow(every: v.windowPeriod, fn: max)
|
|
|> yield(name: params.name)
|
|
`
|
|
|
|
assert.Equal(t, queryText, actual.Query)
|
|
|
|
expectedRefs := []SummaryReference{
|
|
{
|
|
Field: "spec.params.bucket",
|
|
EnvRefKey: `tasks[task-uuid].spec.params.bucket`,
|
|
ValType: "string",
|
|
DefaultValue: "bar",
|
|
},
|
|
{
|
|
Field: "spec.params.floatVal",
|
|
EnvRefKey: `tasks[task-uuid].spec.params.floatVal`,
|
|
ValType: "float",
|
|
DefaultValue: 37.2,
|
|
},
|
|
{
|
|
Field: "spec.params.minVal",
|
|
EnvRefKey: `tasks[task-uuid].spec.params.minVal`,
|
|
ValType: "integer",
|
|
DefaultValue: int64(10),
|
|
},
|
|
{
|
|
Field: "spec.params.name",
|
|
EnvRefKey: `tasks[task-uuid].spec.params.name`,
|
|
ValType: "string",
|
|
DefaultValue: "max",
|
|
},
|
|
{
|
|
Field: "spec.params.start",
|
|
EnvRefKey: `tasks[task-uuid].spec.params.start`,
|
|
ValType: "duration",
|
|
DefaultValue: "-24h0m0s",
|
|
},
|
|
{
|
|
Field: "spec.params.stop",
|
|
EnvRefKey: `tasks[task-uuid].spec.params.stop`,
|
|
ValType: "time",
|
|
DefaultValue: "now()",
|
|
},
|
|
}
|
|
assert.Equal(t, expectedRefs, actual.EnvReferences)
|
|
})
|
|
})
|
|
|
|
t.Run("with task option should be valid", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/task_v2.yml", func(t *testing.T, template *Template) {
|
|
actual := template.Summary().Tasks
|
|
require.Len(t, actual, 1)
|
|
|
|
expectedEnvRefs := []SummaryReference{
|
|
{
|
|
Field: "spec.task.every",
|
|
EnvRefKey: "tasks[task-1].spec.task.every",
|
|
ValType: "duration",
|
|
DefaultValue: time.Minute,
|
|
},
|
|
{
|
|
Field: "spec.name",
|
|
EnvRefKey: "tasks[task-1].spec.task.name",
|
|
ValType: "string",
|
|
DefaultValue: "bar",
|
|
},
|
|
{
|
|
Field: "spec.task.name",
|
|
EnvRefKey: "tasks[task-1].spec.task.name",
|
|
ValType: "string",
|
|
DefaultValue: "bar",
|
|
},
|
|
{
|
|
Field: "spec.task.offset",
|
|
EnvRefKey: "tasks[task-1].spec.task.offset",
|
|
ValType: "duration",
|
|
DefaultValue: time.Minute * 3,
|
|
},
|
|
}
|
|
assert.Equal(t, expectedEnvRefs, actual[0].EnvReferences)
|
|
})
|
|
})
|
|
|
|
t.Run("with task spec should be valid", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/task_v2_taskSpec.yml", func(t *testing.T, template *Template) {
|
|
actual := template.Summary().Tasks
|
|
require.Len(t, actual, 1)
|
|
|
|
expectedEnvRefs := []SummaryReference{
|
|
{
|
|
Field: "spec.task.every",
|
|
EnvRefKey: "tasks[task-1].spec.task.every",
|
|
ValType: "duration",
|
|
DefaultValue: time.Minute,
|
|
},
|
|
{
|
|
Field: "spec.name",
|
|
EnvRefKey: "tasks[task-1].spec.task.name",
|
|
ValType: "string",
|
|
DefaultValue: "foo",
|
|
},
|
|
{
|
|
Field: "spec.task.name",
|
|
EnvRefKey: "tasks[task-1].spec.task.name",
|
|
ValType: "string",
|
|
DefaultValue: "foo",
|
|
},
|
|
{
|
|
Field: "spec.task.offset",
|
|
EnvRefKey: "tasks[task-1].spec.task.offset",
|
|
ValType: "duration",
|
|
DefaultValue: time.Minute,
|
|
},
|
|
}
|
|
|
|
queryText := `option task = {name: "foo", every: 1m0s, offset: 1m0s}
|
|
|
|
from(bucket: "rucket_1")
|
|
|> range(start: -5d, stop: -1h)
|
|
|> filter(fn: (r) => r._measurement == "cpu")
|
|
|> filter(fn: (r) => r._field == "usage_idle")
|
|
|> aggregateWindow(every: 1m, fn: mean)
|
|
|> yield(name: "mean")
|
|
`
|
|
|
|
assert.Equal(t, queryText, actual[0].Query)
|
|
|
|
assert.Equal(t, expectedEnvRefs, actual[0].EnvReferences)
|
|
})
|
|
})
|
|
|
|
t.Run("with params option should be valid", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/task_v2_params.yml", func(t *testing.T, template *Template) {
|
|
actual := template.Summary().Tasks
|
|
require.Len(t, actual, 1)
|
|
|
|
expectedEnvRefs := []SummaryReference{
|
|
{
|
|
Field: "spec.params.this",
|
|
EnvRefKey: "tasks[task-1].spec.params.this",
|
|
ValType: "string",
|
|
DefaultValue: "foo",
|
|
},
|
|
}
|
|
|
|
queryText := `option params = {this: "foo"}
|
|
|
|
from(bucket: "rucket_1")
|
|
|> range(start: -5d, stop: -1h)
|
|
|> filter(fn: (r) => r._measurement == params.this)
|
|
|> filter(fn: (r) => r._field == "usage_idle")
|
|
|> aggregateWindow(every: 1m, fn: mean)
|
|
|> yield(name: "mean")
|
|
`
|
|
|
|
assert.Equal(t, queryText, actual[0].Query)
|
|
|
|
assert.Equal(t, expectedEnvRefs, actual[0].EnvReferences)
|
|
})
|
|
})
|
|
|
|
t.Run("with env refs should be valid", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/task_ref.yml", func(t *testing.T, template *Template) {
|
|
actual := template.Summary().Tasks
|
|
require.Len(t, actual, 1)
|
|
|
|
expectedEnvRefs := []SummaryReference{
|
|
{
|
|
Field: "spec.associations[0].name",
|
|
EnvRefKey: "label-meta-name",
|
|
},
|
|
{
|
|
Field: "metadata.name",
|
|
EnvRefKey: "meta-name",
|
|
DefaultValue: "meta",
|
|
},
|
|
{
|
|
Field: "spec.name",
|
|
EnvRefKey: "spec-name",
|
|
DefaultValue: "spectacles",
|
|
},
|
|
}
|
|
assert.Equal(t, expectedEnvRefs, actual[0].EnvReferences)
|
|
})
|
|
})
|
|
|
|
t.Run("handles bad config", func(t *testing.T) {
|
|
tests := []struct {
|
|
kind Kind
|
|
resErr testTemplateResourceError
|
|
}{
|
|
{
|
|
kind: KindTask,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing name",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Task
|
|
metadata:
|
|
spec:
|
|
description: desc_1
|
|
cron: 15 * * * *
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindTask,
|
|
resErr: testTemplateResourceError{
|
|
name: "invalid status",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldStatus},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Task
|
|
metadata:
|
|
name: task-0
|
|
spec:
|
|
cron: 15 * * * *
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
status: RANDO WRONGO
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindTask,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing query",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldQuery},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Task
|
|
metadata:
|
|
name: task-0
|
|
spec:
|
|
description: desc_0
|
|
every: 10m
|
|
offset: 15s
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindTask,
|
|
resErr: testTemplateResourceError{
|
|
name: "missing every and cron fields",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldEvery, fieldTaskCron},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Task
|
|
metadata:
|
|
name: task-0
|
|
spec:
|
|
description: desc_0
|
|
offset: 15s
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindTask,
|
|
resErr: testTemplateResourceError{
|
|
name: "invalid association",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldAssociations},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Task
|
|
metadata:
|
|
name: task-1
|
|
spec:
|
|
cron: 15 * * * *
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindTask,
|
|
resErr: testTemplateResourceError{
|
|
name: "duplicate association",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldAssociations},
|
|
templateStr: `---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Label
|
|
metadata:
|
|
name: label-1
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Task
|
|
metadata:
|
|
name: task-0
|
|
spec:
|
|
every: 10m
|
|
offset: 15s
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
status: inactive
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
- kind: Label
|
|
name: label-1
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
kind: KindTask,
|
|
resErr: testTemplateResourceError{
|
|
name: "duplicate meta names",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Task
|
|
metadata:
|
|
name: task-0
|
|
spec:
|
|
every: 10m
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Task
|
|
metadata:
|
|
name: task-0
|
|
spec:
|
|
every: 10m
|
|
query: >
|
|
from(bucket: "rucket_1") |> yield(name: "mean")
|
|
`,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, tt.kind, tt.resErr)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("template with telegraf config", func(t *testing.T) {
|
|
t.Run("and associated labels should be successful", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/telegraf", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.TelegrafConfigs, 2)
|
|
|
|
actual := sum.TelegrafConfigs[0]
|
|
assert.Equal(t, KindTelegraf, actual.Kind)
|
|
assert.Equal(t, "display name", actual.TelegrafConfig.Name)
|
|
assert.Equal(t, "desc", actual.TelegrafConfig.Description)
|
|
|
|
require.Len(t, actual.LabelAssociations, 2)
|
|
assert.Equal(t, "label-1", actual.LabelAssociations[0].Name)
|
|
assert.Equal(t, "label-2", actual.LabelAssociations[1].Name)
|
|
|
|
actual = sum.TelegrafConfigs[1]
|
|
assert.Equal(t, "tele-2", actual.TelegrafConfig.Name)
|
|
assert.Empty(t, actual.LabelAssociations)
|
|
|
|
require.Len(t, sum.LabelMappings, 2)
|
|
expectedMapping := SummaryLabelMapping{
|
|
Status: StateStatusNew,
|
|
ResourceMetaName: "first-tele-config",
|
|
ResourceName: "display name",
|
|
LabelMetaName: "label-1",
|
|
LabelName: "label-1",
|
|
ResourceType: influxdb.TelegrafsResourceType,
|
|
}
|
|
assert.Equal(t, expectedMapping, sum.LabelMappings[0])
|
|
expectedMapping.LabelMetaName = "label-2"
|
|
expectedMapping.LabelName = "label-2"
|
|
assert.Equal(t, expectedMapping, sum.LabelMappings[1])
|
|
})
|
|
})
|
|
|
|
t.Run("with env refs should be valid", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/telegraf_ref.yml", func(t *testing.T, template *Template) {
|
|
actual := template.Summary().TelegrafConfigs
|
|
require.Len(t, actual, 1)
|
|
|
|
expectedEnvRefs := []SummaryReference{
|
|
{
|
|
Field: "metadata.name",
|
|
EnvRefKey: "meta-name",
|
|
DefaultValue: "meta",
|
|
},
|
|
{
|
|
Field: "spec.name",
|
|
EnvRefKey: "spec-name",
|
|
DefaultValue: "spectacles",
|
|
},
|
|
{
|
|
Field: "spec.associations[0].name",
|
|
EnvRefKey: "label-meta-name",
|
|
},
|
|
}
|
|
assert.Equal(t, expectedEnvRefs, actual[0].EnvReferences)
|
|
})
|
|
})
|
|
|
|
t.Run("handles bad config", func(t *testing.T) {
|
|
tests := []testTemplateResourceError{
|
|
{
|
|
name: "config missing",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldTelegrafConfig},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Telegraf
|
|
metadata:
|
|
name: first-tele-config
|
|
spec:
|
|
`,
|
|
},
|
|
{
|
|
name: "duplicate metadata names",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Telegraf
|
|
metadata:
|
|
name: tele-0
|
|
spec:
|
|
config: fake tele config
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Telegraf
|
|
metadata:
|
|
name: tele-0
|
|
spec:
|
|
config: fake tele config
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, KindTelegraf, tt)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("template with a variable", func(t *testing.T) {
|
|
t.Run("with valid fields should produce summary", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/variables", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
|
|
require.Len(t, sum.Variables, 4)
|
|
for _, v := range sum.Variables {
|
|
assert.Equal(t, KindVariable, v.Kind)
|
|
}
|
|
|
|
varEquals := func(t *testing.T, name, vType string, vals interface{}, selected []string, v SummaryVariable) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, name, v.Name)
|
|
assert.Equal(t, name+" desc", v.Description)
|
|
if selected == nil {
|
|
selected = []string{}
|
|
}
|
|
assert.Equal(t, selected, v.Selected)
|
|
require.NotNil(t, v.Arguments)
|
|
assert.Equal(t, vType, v.Arguments.Type)
|
|
assert.Equal(t, vals, v.Arguments.Values)
|
|
}
|
|
|
|
// validates we support all known variable types
|
|
varEquals(t,
|
|
"var-const-3",
|
|
"constant",
|
|
influxdb.VariableConstantValues([]string{"first val"}),
|
|
nil,
|
|
sum.Variables[0],
|
|
)
|
|
|
|
varEquals(t,
|
|
"var-map-4",
|
|
"map",
|
|
influxdb.VariableMapValues{"k1": "v1"},
|
|
nil,
|
|
sum.Variables[1],
|
|
)
|
|
|
|
varEquals(t,
|
|
"query var",
|
|
"query",
|
|
influxdb.VariableQueryValues{
|
|
Query: `buckets() |> filter(fn: (r) => r.name !~ /^_/) |> rename(columns: {name: "_value"}) |> keep(columns: ["_value"])`,
|
|
Language: "flux",
|
|
},
|
|
[]string{"rucket"},
|
|
sum.Variables[2],
|
|
)
|
|
|
|
varEquals(t,
|
|
"var-query-2",
|
|
"query",
|
|
influxdb.VariableQueryValues{
|
|
Query: "an influxql query of sorts",
|
|
Language: "influxql",
|
|
},
|
|
nil,
|
|
sum.Variables[3],
|
|
)
|
|
})
|
|
})
|
|
|
|
t.Run("with env refs should be valid", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/variable_ref.yml", func(t *testing.T, template *Template) {
|
|
actual := template.Summary().Variables
|
|
require.Len(t, actual, 1)
|
|
|
|
expectedEnvRefs := []SummaryReference{
|
|
{
|
|
Field: "metadata.name",
|
|
EnvRefKey: "meta-name",
|
|
DefaultValue: "meta",
|
|
},
|
|
{
|
|
Field: "spec.name",
|
|
EnvRefKey: "spec-name",
|
|
DefaultValue: "spectacles",
|
|
},
|
|
{
|
|
Field: "spec.associations[0].name",
|
|
EnvRefKey: "label-meta-name",
|
|
},
|
|
{
|
|
Field: "spec.selected[0]",
|
|
EnvRefKey: "the-selected",
|
|
DefaultValue: "second val",
|
|
},
|
|
{
|
|
Field: "spec.selected[1]",
|
|
EnvRefKey: "the-2nd",
|
|
},
|
|
}
|
|
assert.Equal(t, expectedEnvRefs, actual[0].EnvReferences)
|
|
})
|
|
})
|
|
|
|
t.Run("and labels associated", func(t *testing.T) {
|
|
testfileRunner(t, "testdata/variable_associates_label.yml", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
require.Len(t, sum.Labels, 1)
|
|
|
|
vars := sum.Variables
|
|
require.Len(t, vars, 1)
|
|
|
|
expectedLabelMappings := []struct {
|
|
varName string
|
|
labels []string
|
|
}{
|
|
{
|
|
varName: "var-1",
|
|
labels: []string{"label-1"},
|
|
},
|
|
}
|
|
for i, expected := range expectedLabelMappings {
|
|
v := vars[i]
|
|
require.Len(t, v.LabelAssociations, len(expected.labels))
|
|
|
|
for j, label := range expected.labels {
|
|
assert.Equal(t, label, v.LabelAssociations[j].Name)
|
|
}
|
|
}
|
|
|
|
expectedMappings := []SummaryLabelMapping{
|
|
{
|
|
Status: StateStatusNew,
|
|
ResourceMetaName: "var-1",
|
|
ResourceName: "var-1",
|
|
LabelMetaName: "label-1",
|
|
LabelName: "label-1",
|
|
},
|
|
}
|
|
|
|
require.Len(t, sum.LabelMappings, len(expectedMappings))
|
|
for i, expected := range expectedMappings {
|
|
expected.ResourceType = influxdb.VariablesResourceType
|
|
assert.Equal(t, expected, sum.LabelMappings[i])
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("handles bad config", func(t *testing.T) {
|
|
tests := []testTemplateResourceError{
|
|
{
|
|
name: "name missing",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Variable
|
|
metadata:
|
|
spec:
|
|
description: var-map-4 desc
|
|
type: map
|
|
values:
|
|
k1: v1
|
|
`,
|
|
},
|
|
{
|
|
name: "map var missing values",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldValues},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Variable
|
|
metadata:
|
|
name: var-map-4
|
|
spec:
|
|
description: var-map-4 desc
|
|
type: map
|
|
`,
|
|
},
|
|
{
|
|
name: "const var missing values",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldValues},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Variable
|
|
metadata:
|
|
name: var-const-3
|
|
spec:
|
|
description: var-const-3 desc
|
|
type: constant
|
|
`,
|
|
},
|
|
{
|
|
name: "query var missing query",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldQuery},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Variable
|
|
metadata:
|
|
name: var-query-2
|
|
spec:
|
|
description: var-query-2 desc
|
|
type: query
|
|
language: influxql
|
|
`,
|
|
},
|
|
{
|
|
name: "query var missing query language",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldLanguage},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Variable
|
|
metadata:
|
|
name: var-query-2
|
|
spec:
|
|
description: var-query-2 desc
|
|
type: query
|
|
query: an influxql query of sorts
|
|
`,
|
|
},
|
|
{
|
|
name: "query var provides incorrect query language",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldLanguage},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Variable
|
|
metadata:
|
|
name: var-query-2
|
|
spec:
|
|
description: var-query-2 desc
|
|
type: query
|
|
query: an influxql query of sorts
|
|
language: wrong Language
|
|
`,
|
|
},
|
|
{
|
|
name: "duplicate var names",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldMetadata, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Variable
|
|
metadata:
|
|
name: var-query-2
|
|
spec:
|
|
description: var-query-2 desc
|
|
type: query
|
|
query: an influxql query of sorts
|
|
language: influxql
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Variable
|
|
metadata:
|
|
name: var-query-2
|
|
spec:
|
|
description: var-query-2 desc
|
|
type: query
|
|
query: an influxql query of sorts
|
|
language: influxql
|
|
`,
|
|
},
|
|
{
|
|
name: "duplicate meta name and spec name",
|
|
validationErrs: 1,
|
|
valFields: []string{fieldSpec, fieldName},
|
|
templateStr: `apiVersion: influxdata.com/v2alpha1
|
|
kind: Variable
|
|
metadata:
|
|
name: var-query-2
|
|
spec:
|
|
description: var-query-2 desc
|
|
type: query
|
|
query: an influxql query of sorts
|
|
language: influxql
|
|
---
|
|
apiVersion: influxdata.com/v2alpha1
|
|
kind: Variable
|
|
metadata:
|
|
name: valid-query
|
|
spec:
|
|
name: var-query-2
|
|
description: var-query-2 desc
|
|
type: query
|
|
query: an influxql query of sorts
|
|
language: influxql
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testTemplateErrors(t, KindVariable, tt)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("referencing secrets", func(t *testing.T) {
|
|
hasSecret := func(t *testing.T, refs map[string]bool, key string) {
|
|
t.Helper()
|
|
b, ok := refs[key]
|
|
assert.True(t, ok)
|
|
assert.False(t, b)
|
|
}
|
|
|
|
testfileRunner(t, "testdata/notification_endpoint_secrets.yml", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
|
|
endpoints := sum.NotificationEndpoints
|
|
require.Len(t, endpoints, 1)
|
|
|
|
expected := &endpoint.PagerDuty{
|
|
Base: endpoint.Base{
|
|
Name: "pager-duty-notification-endpoint",
|
|
Status: taskmodel.TaskStatusActive,
|
|
},
|
|
ClientURL: "http://localhost:8080/orgs/7167eb6719fa34e5/alert-history",
|
|
RoutingKey: influxdb.SecretField{Key: "-routing-key", Value: strPtr("not empty")},
|
|
}
|
|
actual, ok := endpoints[0].NotificationEndpoint.(*endpoint.PagerDuty)
|
|
require.True(t, ok)
|
|
assert.Equal(t, expected.Base.Name, actual.Name)
|
|
require.Nil(t, actual.RoutingKey.Value)
|
|
assert.Equal(t, "routing-key", actual.RoutingKey.Key)
|
|
|
|
hasSecret(t, template.mSecrets, "routing-key")
|
|
})
|
|
})
|
|
|
|
t.Run("referencing env", func(t *testing.T) {
|
|
hasEnv := func(t *testing.T, refs map[string]bool, key string) {
|
|
t.Helper()
|
|
_, ok := refs[key]
|
|
assert.True(t, ok)
|
|
}
|
|
|
|
testfileRunner(t, "testdata/env_refs.yml", func(t *testing.T, template *Template) {
|
|
sum := template.Summary()
|
|
|
|
require.Len(t, sum.Buckets, 1)
|
|
assert.Equal(t, "env-bkt-1-name-ref", sum.Buckets[0].Name)
|
|
assert.Len(t, sum.Buckets[0].LabelAssociations, 1)
|
|
hasEnv(t, template.mEnv, "bkt-1-name-ref")
|
|
|
|
require.Len(t, sum.Checks, 1)
|
|
assert.Equal(t, "env-check-1-name-ref", sum.Checks[0].Check.GetName())
|
|
assert.Len(t, sum.Checks[0].LabelAssociations, 1)
|
|
hasEnv(t, template.mEnv, "check-1-name-ref")
|
|
|
|
require.Len(t, sum.Dashboards, 1)
|
|
assert.Equal(t, "env-dash-1-name-ref", sum.Dashboards[0].Name)
|
|
assert.Len(t, sum.Dashboards[0].LabelAssociations, 1)
|
|
hasEnv(t, template.mEnv, "dash-1-name-ref")
|
|
|
|
require.Len(t, sum.NotificationEndpoints, 1)
|
|
assert.Equal(t, "env-endpoint-1-name-ref", sum.NotificationEndpoints[0].NotificationEndpoint.GetName())
|
|
hasEnv(t, template.mEnv, "endpoint-1-name-ref")
|
|
|
|
require.Len(t, sum.Labels, 1)
|
|
assert.Equal(t, "env-label-1-name-ref", sum.Labels[0].Name)
|
|
hasEnv(t, template.mEnv, "label-1-name-ref")
|
|
|
|
require.Len(t, sum.NotificationRules, 1)
|
|
assert.Equal(t, "env-rule-1-name-ref", sum.NotificationRules[0].Name)
|
|
assert.Equal(t, "env-endpoint-1-name-ref", sum.NotificationRules[0].EndpointMetaName)
|
|
hasEnv(t, template.mEnv, "rule-1-name-ref")
|
|
|
|
require.Len(t, sum.Tasks, 1)
|
|
assert.Equal(t, "env-task-1-name-ref", sum.Tasks[0].Name)
|
|
hasEnv(t, template.mEnv, "task-1-name-ref")
|
|
|
|
require.Len(t, sum.TelegrafConfigs, 1)
|
|
assert.Equal(t, "env-telegraf-1-name-ref", sum.TelegrafConfigs[0].TelegrafConfig.Name)
|
|
hasEnv(t, template.mEnv, "telegraf-1-name-ref")
|
|
|
|
require.Len(t, sum.Variables, 1)
|
|
assert.Equal(t, "env-var-1-name-ref", sum.Variables[0].Name)
|
|
hasEnv(t, template.mEnv, "var-1-name-ref")
|
|
|
|
t.Log("applying env vars should populate env fields")
|
|
{
|
|
err := template.applyEnvRefs(map[string]interface{}{
|
|
"bkt-1-name-ref": "bucket-1",
|
|
"label-1-name-ref": "label-1",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
sum := template.Summary()
|
|
|
|
require.Len(t, sum.Buckets, 1)
|
|
assert.Equal(t, "bucket-1", sum.Buckets[0].Name)
|
|
assert.Len(t, sum.Buckets[0].LabelAssociations, 1)
|
|
hasEnv(t, template.mEnv, "bkt-1-name-ref")
|
|
|
|
require.Len(t, sum.Labels, 1)
|
|
assert.Equal(t, "label-1", sum.Labels[0].Name)
|
|
hasEnv(t, template.mEnv, "label-1-name-ref")
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("jsonnet support disabled by default", func(t *testing.T) {
|
|
template := validParsedTemplateFromFile(t, "testdata/bucket_associates_labels.jsonnet", EncodingJsonnet)
|
|
require.Equal(t, &Template{}, template)
|
|
})
|
|
|
|
t.Run("jsonnet support", func(t *testing.T) {
|
|
template := validParsedTemplateFromFile(t, "testdata/bucket_associates_labels.jsonnet", EncodingJsonnet, EnableJsonnet())
|
|
|
|
sum := template.Summary()
|
|
|
|
labels := []SummaryLabel{
|
|
sumLabelGen("label-1", "label-1", "#eee888", "desc_1"),
|
|
}
|
|
assert.Equal(t, labels, sum.Labels)
|
|
|
|
bkts := []SummaryBucket{
|
|
{
|
|
SummaryIdentifier: SummaryIdentifier{
|
|
Kind: KindBucket,
|
|
MetaName: "rucket-1",
|
|
EnvReferences: []SummaryReference{},
|
|
},
|
|
Name: "rucket-1",
|
|
Description: "desc_1",
|
|
RetentionPeriod: 10000 * time.Second,
|
|
LabelAssociations: labels,
|
|
},
|
|
{
|
|
SummaryIdentifier: SummaryIdentifier{
|
|
Kind: KindBucket,
|
|
MetaName: "rucket-2",
|
|
EnvReferences: []SummaryReference{},
|
|
},
|
|
Name: "rucket-2",
|
|
Description: "desc-2",
|
|
RetentionPeriod: 20000 * time.Second,
|
|
LabelAssociations: labels,
|
|
},
|
|
{
|
|
SummaryIdentifier: SummaryIdentifier{
|
|
Kind: KindBucket,
|
|
MetaName: "rucket-3",
|
|
EnvReferences: []SummaryReference{},
|
|
},
|
|
Name: "rucket-3",
|
|
Description: "desc_3",
|
|
RetentionPeriod: 30000 * time.Second,
|
|
LabelAssociations: labels,
|
|
},
|
|
}
|
|
assert.Equal(t, bkts, sum.Buckets)
|
|
})
|
|
}
|
|
|
|
func TestCombine(t *testing.T) {
|
|
newTemplateFromYmlStr := func(t *testing.T, templateStr string) *Template {
|
|
t.Helper()
|
|
return newParsedTemplate(t, FromString(templateStr), EncodingYAML, ValidSkipParseError())
|
|
}
|
|
|
|
associationsEqual := func(t *testing.T, summaryLabels []SummaryLabel, names ...string) {
|
|
t.Helper()
|
|
|
|
require.Len(t, summaryLabels, len(names))
|
|
|
|
m := make(map[string]bool)
|
|
for _, n := range names {
|
|
m[n] = true
|
|
}
|
|
|
|
for _, l := range summaryLabels {
|
|
if !m[l.Name] {
|
|
assert.Fail(t, "did not find label: "+l.Name)
|
|
}
|
|
delete(m, l.Name)
|
|
}
|
|
|
|
if len(m) > 0 {
|
|
var unexpectedLabels []string
|
|
for name := range m {
|
|
unexpectedLabels = append(unexpectedLabels, name)
|
|
}
|
|
assert.Failf(t, "additional labels found", "got: %v", unexpectedLabels)
|
|
}
|
|
}
|
|
|
|
t.Run("multiple templates with associations across files", func(t *testing.T) {
|
|
var templates []*Template
|
|
numLabels := 5
|
|
for i := 0; i < numLabels; i++ {
|
|
template := newTemplateFromYmlStr(t, fmt.Sprintf(`
|
|
apiVersion: %[1]s
|
|
kind: Label
|
|
metadata:
|
|
name: label-%d
|
|
`, APIVersion, i))
|
|
templates = append(templates, template)
|
|
}
|
|
|
|
templates = append(templates, newTemplateFromYmlStr(t, fmt.Sprintf(`
|
|
apiVersion: %[1]s
|
|
kind: Bucket
|
|
metadata:
|
|
name: rucket-1
|
|
spec:
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
`, APIVersion)))
|
|
|
|
templates = append(templates, newTemplateFromYmlStr(t, fmt.Sprintf(`
|
|
apiVersion: %[1]s
|
|
kind: Bucket
|
|
metadata:
|
|
name: rucket-2
|
|
spec:
|
|
associations:
|
|
- kind: Label
|
|
name: label-2
|
|
`, APIVersion)))
|
|
|
|
templates = append(templates, newTemplateFromYmlStr(t, fmt.Sprintf(`
|
|
apiVersion: %[1]s
|
|
kind: Bucket
|
|
metadata:
|
|
name: rucket-3
|
|
spec:
|
|
associations:
|
|
- kind: Label
|
|
name: label-1
|
|
- kind: Label
|
|
name: label-2
|
|
`, APIVersion)))
|
|
|
|
combinedTemplate, err := Combine(templates)
|
|
require.NoError(t, err)
|
|
|
|
sum := combinedTemplate.Summary()
|
|
|
|
require.Len(t, sum.Labels, numLabels)
|
|
for i := 0; i < numLabels; i++ {
|
|
assert.Equal(t, fmt.Sprintf("label-%d", i), sum.Labels[i].Name)
|
|
}
|
|
|
|
require.Len(t, sum.Labels, numLabels)
|
|
for i := 0; i < numLabels; i++ {
|
|
assert.Equal(t, fmt.Sprintf("label-%d", i), sum.Labels[i].Name)
|
|
}
|
|
|
|
require.Len(t, sum.Buckets, 3)
|
|
assert.Equal(t, "rucket-1", sum.Buckets[0].Name)
|
|
associationsEqual(t, sum.Buckets[0].LabelAssociations, "label-1")
|
|
assert.Equal(t, "rucket-2", sum.Buckets[1].Name)
|
|
associationsEqual(t, sum.Buckets[1].LabelAssociations, "label-2")
|
|
assert.Equal(t, "rucket-3", sum.Buckets[2].Name)
|
|
associationsEqual(t, sum.Buckets[2].LabelAssociations, "label-1", "label-2")
|
|
})
|
|
}
|
|
|
|
func Test_normalizeGithubURLToContent(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "raw url passes untouched",
|
|
input: "https://raw.githubusercontent.com/influxdata/community-templates/master/github/github.yml",
|
|
expected: "https://raw.githubusercontent.com/influxdata/community-templates/master/github/github.yml",
|
|
},
|
|
{
|
|
name: "URL that is to short is unchanged",
|
|
input: "https://github.com/influxdata/community-templates",
|
|
expected: "https://github.com/influxdata/community-templates",
|
|
},
|
|
{
|
|
name: "URL that does not end in required extention is unchanged",
|
|
input: "https://github.com/influxdata/community-templates/master/github",
|
|
expected: "https://github.com/influxdata/community-templates/master/github",
|
|
},
|
|
{
|
|
name: "converts base url with ext yaml to raw content url",
|
|
input: "https://github.com/influxdata/community-templates/blob/master/github/github.yaml",
|
|
expected: "https://raw.githubusercontent.com/influxdata/community-templates/master/github/github.yaml",
|
|
},
|
|
{
|
|
name: "converts base url with ext yml to raw content url",
|
|
input: "https://github.com/influxdata/community-templates/blob/master/github/github.yml",
|
|
expected: "https://raw.githubusercontent.com/influxdata/community-templates/master/github/github.yml",
|
|
},
|
|
{
|
|
name: "converts base url with ext json to raw content url",
|
|
input: "https://github.com/influxdata/community-templates/blob/master/github/github.json",
|
|
expected: "https://raw.githubusercontent.com/influxdata/community-templates/master/github/github.json",
|
|
},
|
|
{
|
|
name: "converts base url with ext jsonnet to raw content url",
|
|
input: "https://github.com/influxdata/community-templates/blob/master/github/github.jsonnet",
|
|
expected: "https://raw.githubusercontent.com/influxdata/community-templates/master/github/github.jsonnet",
|
|
},
|
|
{
|
|
name: "url with unexpected content type is unchanged 1",
|
|
input: "https://github.com/influxdata/community-templates/blob/master/github/github.jason",
|
|
expected: "https://github.com/influxdata/community-templates/blob/master/github/github.jason",
|
|
},
|
|
{
|
|
name: "url with unexpected content type is unchanged 2",
|
|
input: "https://github.com/influxdata/community-templates/blob/master/github/github.rando",
|
|
expected: "https://github.com/influxdata/community-templates/blob/master/github/github.rando",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
fn := func(t *testing.T) {
|
|
actual := normalizeGithubURLToContent(tt.input)
|
|
|
|
assert.Equal(t, tt.expected, actual)
|
|
}
|
|
|
|
t.Run(tt.name, fn)
|
|
}
|
|
}
|
|
|
|
func Test_IsParseError(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "base case",
|
|
err: &parseErr{},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "wrapped by influxdb error",
|
|
err: &errors2.Error{
|
|
Err: &parseErr{},
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "deeply nested in influxdb error",
|
|
err: &errors2.Error{
|
|
Err: &errors2.Error{
|
|
Err: &errors2.Error{
|
|
Err: &errors2.Error{
|
|
Err: &parseErr{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "influxdb error without nested parse err",
|
|
err: &errors2.Error{
|
|
Err: errors.New("nope"),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "plain error",
|
|
err: errors.New("nope"),
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
fn := func(t *testing.T) {
|
|
isParseErr := IsParseErr(tt.err)
|
|
assert.Equal(t, tt.expected, isParseErr)
|
|
}
|
|
t.Run(tt.name, fn)
|
|
}
|
|
}
|
|
|
|
func Test_TemplateValidationErr(t *testing.T) {
|
|
iPtr := func(i int) *int {
|
|
return &i
|
|
}
|
|
|
|
compIntSlcs := func(t *testing.T, expected []int, actuals []*int) {
|
|
t.Helper()
|
|
|
|
if len(expected) >= len(actuals) {
|
|
require.FailNow(t, "expected array is larger than actuals")
|
|
}
|
|
|
|
for i, actual := range actuals {
|
|
if i == len(expected) {
|
|
assert.Nil(t, actual)
|
|
continue
|
|
}
|
|
assert.Equal(t, expected[i], *actual)
|
|
}
|
|
}
|
|
|
|
pErr := &parseErr{
|
|
Resources: []resourceErr{
|
|
{
|
|
Kind: KindDashboard.String(),
|
|
Idx: intPtr(0),
|
|
ValidationErrs: []validationErr{
|
|
{
|
|
Field: "charts",
|
|
Index: iPtr(1),
|
|
Nested: []validationErr{
|
|
{
|
|
Field: "colors",
|
|
Index: iPtr(0),
|
|
Nested: []validationErr{
|
|
{
|
|
Field: "hex",
|
|
Msg: "hex value required",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Field: "kind",
|
|
Msg: "chart kind must be provided",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
errs := pErr.ValidationErrs()
|
|
require.Len(t, errs, 2)
|
|
assert.Equal(t, KindDashboard.String(), errs[0].Kind)
|
|
assert.Equal(t, []string{"root", "charts", "colors", "hex"}, errs[0].Fields)
|
|
compIntSlcs(t, []int{0, 1, 0}, errs[0].Indexes)
|
|
assert.Equal(t, "hex value required", errs[0].Reason)
|
|
|
|
assert.Equal(t, KindDashboard.String(), errs[1].Kind)
|
|
assert.Equal(t, []string{"root", "charts", "kind"}, errs[1].Fields)
|
|
compIntSlcs(t, []int{0, 1}, errs[1].Indexes)
|
|
assert.Equal(t, "chart kind must be provided", errs[1].Reason)
|
|
}
|
|
|
|
func Test_validGeometry(t *testing.T) {
|
|
tests := []struct {
|
|
geom string
|
|
expected bool
|
|
}{
|
|
{
|
|
geom: "line", expected: true,
|
|
},
|
|
{
|
|
geom: "step", expected: true,
|
|
},
|
|
{
|
|
geom: "stacked", expected: true,
|
|
},
|
|
{
|
|
geom: "monotoneX", expected: true,
|
|
},
|
|
{
|
|
geom: "bar", expected: true,
|
|
},
|
|
{
|
|
geom: "rando", expected: false,
|
|
},
|
|
{
|
|
geom: "not a valid geom", expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
fn := func(t *testing.T) {
|
|
isValid := len(validGeometry(tt.geom)) == 0
|
|
assert.Equal(t, tt.expected, isValid)
|
|
}
|
|
|
|
t.Run(tt.geom, fn)
|
|
}
|
|
}
|
|
|
|
type testTemplateResourceError struct {
|
|
name string
|
|
encoding Encoding
|
|
templateStr string
|
|
resourceErrs int
|
|
validationErrs int
|
|
valFields []string
|
|
assErrs int
|
|
assIdxs []int
|
|
}
|
|
|
|
// defaults to yaml encoding if encoding not provided
|
|
// defaults num resources to 1 if resource errs not provided.
|
|
func testTemplateErrors(t *testing.T, k Kind, tt testTemplateResourceError) {
|
|
t.Helper()
|
|
encoding := EncodingYAML
|
|
if tt.encoding != EncodingUnknown {
|
|
encoding = tt.encoding
|
|
}
|
|
|
|
resErrs := 1
|
|
if tt.resourceErrs > 0 {
|
|
resErrs = tt.resourceErrs
|
|
}
|
|
|
|
fn := func(t *testing.T) {
|
|
t.Helper()
|
|
|
|
_, err := Parse(encoding, FromString(tt.templateStr))
|
|
require.Error(t, err)
|
|
|
|
require.True(t, IsParseErr(err), err)
|
|
|
|
pErr := err.(*parseErr)
|
|
require.Len(t, pErr.Resources, resErrs)
|
|
|
|
defer func() {
|
|
if t.Failed() {
|
|
t.Logf("received unexpected err: %s", pErr)
|
|
}
|
|
}()
|
|
|
|
resErr := pErr.Resources[0]
|
|
assert.Equal(t, k.String(), resErr.Kind)
|
|
|
|
for i, vFail := range resErr.ValidationErrs {
|
|
if len(tt.valFields) == i {
|
|
break
|
|
}
|
|
expectedField := tt.valFields[i]
|
|
findErr(t, expectedField, vFail)
|
|
}
|
|
|
|
if tt.assErrs == 0 {
|
|
return
|
|
}
|
|
|
|
assFails := pErr.Resources[0].AssociationErrs
|
|
for i, assFail := range assFails {
|
|
if len(tt.valFields) == i {
|
|
break
|
|
}
|
|
expectedField := tt.valFields[i]
|
|
findErr(t, expectedField, assFail)
|
|
}
|
|
}
|
|
t.Run(tt.name, fn)
|
|
}
|
|
|
|
func findErr(t *testing.T, expectedField string, vErr validationErr) validationErr {
|
|
t.Helper()
|
|
|
|
fields := strings.Split(expectedField, ".")
|
|
if len(fields) == 1 {
|
|
require.Equal(t, expectedField, vErr.Field)
|
|
return vErr
|
|
}
|
|
|
|
currentFieldName, idx := nextField(t, fields[0])
|
|
if idx > -1 {
|
|
require.NotNil(t, vErr.Index)
|
|
require.Equal(t, idx, *vErr.Index)
|
|
}
|
|
require.Equal(t, currentFieldName, vErr.Field)
|
|
|
|
next := strings.Join(fields[1:], ".")
|
|
nestedField, _ := nextField(t, next)
|
|
for _, n := range vErr.Nested {
|
|
if n.Field == nestedField {
|
|
return findErr(t, next, n)
|
|
}
|
|
}
|
|
assert.Fail(t, "did not find field: "+expectedField)
|
|
|
|
return vErr
|
|
}
|
|
|
|
func nextField(t *testing.T, field string) (string, int) {
|
|
t.Helper()
|
|
|
|
fields := strings.Split(field, ".")
|
|
if len(fields) == 1 && !strings.HasSuffix(fields[0], "]") {
|
|
return field, -1
|
|
}
|
|
parts := strings.Split(fields[0], "[")
|
|
if len(parts) == 1 {
|
|
return parts[0], -1
|
|
}
|
|
fieldName := parts[0]
|
|
|
|
if strIdx := strings.Index(parts[1], "]"); strIdx > -1 {
|
|
idx, err := strconv.Atoi(parts[1][:strIdx])
|
|
require.NoError(t, err)
|
|
return fieldName, idx
|
|
}
|
|
return "", -1
|
|
}
|
|
|
|
func validParsedTemplateFromFile(t *testing.T, path string, encoding Encoding, opts ...ValidateOptFn) *Template {
|
|
t.Helper()
|
|
|
|
var readFn ReaderFn
|
|
templateBytes, ok := availableTemplateFiles[path]
|
|
if ok {
|
|
readFn = FromReader(bytes.NewBuffer(templateBytes), "file://"+path)
|
|
} else {
|
|
readFn = FromFile(path)
|
|
atomic.AddInt64(&missedTemplateCacheCounter, 1)
|
|
}
|
|
|
|
opt := &validateOpt{}
|
|
for _, o := range opts {
|
|
o(opt)
|
|
}
|
|
|
|
template := newParsedTemplate(t, readFn, encoding, opts...)
|
|
if encoding == EncodingJsonnet && !opt.enableJsonnet {
|
|
require.Equal(t, &Template{}, template)
|
|
return template
|
|
}
|
|
|
|
u := url.URL{
|
|
Scheme: "file",
|
|
Path: path,
|
|
}
|
|
require.Equal(t, []string{u.String()}, template.Sources())
|
|
return template
|
|
}
|
|
|
|
func newParsedTemplate(t *testing.T, fn ReaderFn, encoding Encoding, opts ...ValidateOptFn) *Template {
|
|
t.Helper()
|
|
|
|
opt := &validateOpt{}
|
|
for _, o := range opts {
|
|
o(opt)
|
|
}
|
|
|
|
template, err := Parse(encoding, fn, opts...)
|
|
if encoding == EncodingJsonnet && !opt.enableJsonnet {
|
|
require.Error(t, err)
|
|
return &Template{}
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
for _, k := range template.Objects {
|
|
require.Contains(t, k.APIVersion, "influxdata.com/v2alpha")
|
|
}
|
|
|
|
require.True(t, template.isParsed)
|
|
return template
|
|
}
|
|
|
|
func testfileRunner(t *testing.T, path string, testFn func(t *testing.T, template *Template)) {
|
|
t.Helper()
|
|
|
|
tests := []struct {
|
|
name string
|
|
extension string
|
|
encoding Encoding
|
|
}{
|
|
{
|
|
name: "yaml",
|
|
extension: ".yml",
|
|
encoding: EncodingYAML,
|
|
},
|
|
{
|
|
name: "json",
|
|
extension: ".json",
|
|
encoding: EncodingJSON,
|
|
},
|
|
}
|
|
|
|
ext := filepath.Ext(path)
|
|
switch ext {
|
|
case ".yml":
|
|
tests = tests[:1]
|
|
case ".json":
|
|
tests = tests[1:]
|
|
}
|
|
|
|
path = strings.TrimSuffix(path, ext)
|
|
|
|
for _, tt := range tests {
|
|
fn := func(t *testing.T) {
|
|
t.Helper()
|
|
|
|
template := validParsedTemplateFromFile(t, path+tt.extension, tt.encoding)
|
|
if testFn != nil {
|
|
testFn(t, template)
|
|
}
|
|
}
|
|
t.Run(tt.name, fn)
|
|
}
|
|
}
|
|
|
|
func sumLabelGen(metaName, name, color, desc string, envRefs ...SummaryReference) SummaryLabel {
|
|
if envRefs == nil {
|
|
envRefs = make([]SummaryReference, 0)
|
|
}
|
|
return SummaryLabel{
|
|
SummaryIdentifier: SummaryIdentifier{
|
|
Kind: KindLabel,
|
|
MetaName: metaName,
|
|
EnvReferences: envRefs,
|
|
},
|
|
Name: name,
|
|
Properties: struct {
|
|
Color string `json:"color"`
|
|
Description string `json:"description"`
|
|
}{
|
|
Color: color,
|
|
Description: desc,
|
|
},
|
|
}
|
|
}
|
|
|
|
func strPtr(s string) *string {
|
|
return &s
|
|
}
|
|
|
|
func mustDuration(t *testing.T, d time.Duration) *notification.Duration {
|
|
t.Helper()
|
|
dur, err := notification.FromTimeDuration(d)
|
|
require.NoError(t, err)
|
|
return &dur
|
|
}
|