mirror of https://github.com/grafana/grafana
prometheushacktoberfestmetricsmonitoringalertinggrafanagoinfluxdbmysqlpostgresanalyticsdata-visualizationdashboardbusiness-intelligenceelasticsearch
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
825 lines
27 KiB
825 lines
27 KiB
package timeinterval
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"path"
|
|
"slices"
|
|
"testing"
|
|
|
|
"github.com/prometheus/alertmanager/config"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/client-go/dynamic"
|
|
|
|
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/resource/timeinterval/v0alpha1"
|
|
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/resource/timeinterval/v0alpha1/fakes"
|
|
"github.com/grafana/grafana/pkg/bus"
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
|
"github.com/grafana/grafana/pkg/services/org"
|
|
"github.com/grafana/grafana/pkg/tests/api/alerting"
|
|
"github.com/grafana/grafana/pkg/tests/apis"
|
|
"github.com/grafana/grafana/pkg/tests/testinfra"
|
|
"github.com/grafana/grafana/pkg/tests/testsuite"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
//go:embed test-data/*.*
|
|
var testData embed.FS
|
|
|
|
func TestMain(m *testing.M) {
|
|
testsuite.Run(m)
|
|
}
|
|
|
|
func getTestHelper(t *testing.T) *apis.K8sTestHelper {
|
|
return apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{})
|
|
}
|
|
|
|
func TestIntegrationResourceIdentifier(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
client := newClient(t, helper.Org1.Admin)
|
|
|
|
newInterval := &v0alpha1.TimeInterval{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.Spec{
|
|
Name: "time-newInterval",
|
|
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(2),
|
|
},
|
|
}
|
|
|
|
t.Run("create should fail if object name is specified", func(t *testing.T) {
|
|
interval := newInterval.Copy().(*v0alpha1.TimeInterval)
|
|
interval.Name = "time-newInterval"
|
|
_, err := client.Create(ctx, interval, v1.CreateOptions{})
|
|
require.Truef(t, errors.IsBadRequest(err), "Expected BadRequest but got %s", err)
|
|
})
|
|
|
|
var resourceID string
|
|
t.Run("create should succeed and provide resource name", func(t *testing.T) {
|
|
actual, err := client.Create(ctx, newInterval, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.NotEmptyf(t, actual.Name, "Resource name should not be empty")
|
|
require.NotEmptyf(t, actual.UID, "Resource UID should not be empty")
|
|
resourceID = actual.Name
|
|
})
|
|
|
|
var existingInterval *v0alpha1.TimeInterval
|
|
t.Run("resource should be available by the identifier", func(t *testing.T) {
|
|
actual, err := client.Get(ctx, resourceID, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.NotEmptyf(t, actual.Name, "Resource name should not be empty")
|
|
require.Equal(t, newInterval.Spec, actual.Spec)
|
|
existingInterval = actual
|
|
})
|
|
|
|
t.Run("update should rename interval if name in the specification changes", func(t *testing.T) {
|
|
if existingInterval == nil {
|
|
t.Skip()
|
|
}
|
|
updated := existingInterval.Copy().(*v0alpha1.TimeInterval)
|
|
updated.Spec.Name = "another-newInterval"
|
|
actual, err := client.Update(ctx, updated, v1.UpdateOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, updated.Spec, actual.Spec)
|
|
require.NotEqualf(t, updated.Name, actual.Name, "Update should change the resource name but it didn't")
|
|
require.NotEqualf(t, updated.ResourceVersion, actual.ResourceVersion, "Update should change the resource version but it didn't")
|
|
|
|
resource, err := client.Get(ctx, actual.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, actual, resource)
|
|
})
|
|
}
|
|
|
|
func TestIntegrationTimeIntervalAccessControl(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
|
|
org1 := helper.Org1
|
|
|
|
type testCase struct {
|
|
user apis.User
|
|
canRead bool
|
|
canUpdate bool
|
|
canCreate bool
|
|
canDelete bool
|
|
}
|
|
|
|
reader := helper.CreateUser("IntervalsReader", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
|
{
|
|
Actions: []string{
|
|
accesscontrol.ActionAlertingNotificationsTimeIntervalsRead,
|
|
},
|
|
},
|
|
})
|
|
writer := helper.CreateUser("IntervalsWriter", "Org1", org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
|
{
|
|
Actions: []string{
|
|
accesscontrol.ActionAlertingNotificationsTimeIntervalsRead,
|
|
accesscontrol.ActionAlertingNotificationsTimeIntervalsWrite,
|
|
},
|
|
},
|
|
})
|
|
|
|
deleter := helper.CreateUser("IntervalsDeleter", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
|
{
|
|
Actions: []string{
|
|
accesscontrol.ActionAlertingNotificationsTimeIntervalsRead,
|
|
accesscontrol.ActionAlertingNotificationsTimeIntervalsDelete,
|
|
},
|
|
},
|
|
})
|
|
|
|
testCases := []testCase{
|
|
{
|
|
user: org1.Admin,
|
|
canRead: true,
|
|
canUpdate: true,
|
|
canCreate: true,
|
|
canDelete: true,
|
|
},
|
|
{
|
|
user: org1.Editor,
|
|
canRead: true,
|
|
canUpdate: true,
|
|
canCreate: true,
|
|
canDelete: true,
|
|
},
|
|
{
|
|
user: org1.Viewer,
|
|
canRead: true,
|
|
},
|
|
{
|
|
user: reader,
|
|
canRead: true,
|
|
},
|
|
{
|
|
user: writer,
|
|
canRead: true,
|
|
canCreate: true,
|
|
canUpdate: true,
|
|
},
|
|
{
|
|
user: deleter,
|
|
canRead: true,
|
|
canDelete: true,
|
|
},
|
|
}
|
|
|
|
adminClient := newClient(t, helper.Org1.Admin)
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(fmt.Sprintf("user '%s'", tc.user.Identity.GetLogin()), func(t *testing.T) {
|
|
client := newClient(t, tc.user)
|
|
var expected = &v0alpha1.TimeInterval{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.Spec{
|
|
Name: fmt.Sprintf("time-interval-1-%s", tc.user.Identity.GetLogin()),
|
|
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(2),
|
|
},
|
|
}
|
|
expected.SetProvenanceStatus("")
|
|
d, err := json.Marshal(expected)
|
|
require.NoError(t, err)
|
|
|
|
if tc.canCreate {
|
|
t.Run("should be able to create time interval", func(t *testing.T) {
|
|
actual, err := client.Create(ctx, expected, v1.CreateOptions{})
|
|
require.NoErrorf(t, err, "Payload %s", string(d))
|
|
require.Equal(t, expected.Spec, actual.Spec)
|
|
|
|
t.Run("should fail if already exists", func(t *testing.T) {
|
|
_, err := client.Create(ctx, actual, v1.CreateOptions{})
|
|
require.Truef(t, errors.IsBadRequest(err), "expected bad request but got %s", err)
|
|
})
|
|
|
|
expected = actual
|
|
})
|
|
} else {
|
|
t.Run("should be forbidden to create", func(t *testing.T) {
|
|
_, err := client.Create(ctx, expected, v1.CreateOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "Payload %s", string(d))
|
|
})
|
|
|
|
// create resource to proceed with other tests
|
|
expected, err = adminClient.Create(ctx, expected, v1.CreateOptions{})
|
|
require.NoErrorf(t, err, "Payload %s", string(d))
|
|
require.NotNil(t, expected)
|
|
}
|
|
|
|
if tc.canRead {
|
|
t.Run("should be able to list time intervals", func(t *testing.T) {
|
|
list, err := client.List(ctx, v1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, list.Items, 1)
|
|
})
|
|
|
|
t.Run("should be able to read time interval by resource identifier", func(t *testing.T) {
|
|
got, err := client.Get(ctx, expected.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, expected, got)
|
|
|
|
t.Run("should get NotFound if resource does not exist", func(t *testing.T) {
|
|
_, err := client.Get(ctx, "Notfound", v1.GetOptions{})
|
|
require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
|
|
})
|
|
})
|
|
} else {
|
|
t.Run("should be forbidden to list time intervals", func(t *testing.T) {
|
|
_, err := client.List(ctx, v1.ListOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
|
|
t.Run("should be forbidden to read time interval by name", func(t *testing.T) {
|
|
_, err := client.Get(ctx, expected.Name, v1.GetOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
|
|
t.Run("should get forbidden even if name does not exist", func(t *testing.T) {
|
|
_, err := client.Get(ctx, "Notfound", v1.GetOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
})
|
|
}
|
|
|
|
updatedExpected := expected.Copy().(*v0alpha1.TimeInterval)
|
|
updatedExpected.Spec.TimeIntervals = fakes.IntervalGenerator{}.GenerateMany(2)
|
|
|
|
d, err = json.Marshal(updatedExpected)
|
|
require.NoError(t, err)
|
|
|
|
if tc.canUpdate {
|
|
t.Run("should be able to update time interval", func(t *testing.T) {
|
|
updated, err := client.Update(ctx, updatedExpected, v1.UpdateOptions{})
|
|
require.NoErrorf(t, err, "Payload %s", string(d))
|
|
|
|
expected = updated
|
|
|
|
t.Run("should get NotFound if name does not exist", func(t *testing.T) {
|
|
up := updatedExpected.Copy().(*v0alpha1.TimeInterval)
|
|
up.Name = "notFound"
|
|
_, err := client.Update(ctx, up, v1.UpdateOptions{})
|
|
require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
|
|
})
|
|
})
|
|
} else {
|
|
t.Run("should be forbidden to update time interval", func(t *testing.T) {
|
|
_, err := client.Update(ctx, updatedExpected, v1.UpdateOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
|
|
t.Run("should get forbidden even if resource does not exist", func(t *testing.T) {
|
|
up := updatedExpected.Copy().(*v0alpha1.TimeInterval)
|
|
up.Name = "notFound"
|
|
_, err := client.Update(ctx, up, v1.UpdateOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
})
|
|
}
|
|
|
|
deleteOptions := v1.DeleteOptions{Preconditions: &v1.Preconditions{ResourceVersion: util.Pointer(expected.ResourceVersion)}}
|
|
|
|
if tc.canDelete {
|
|
t.Run("should be able to delete time interval", func(t *testing.T) {
|
|
err := client.Delete(ctx, expected.Name, deleteOptions)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("should get NotFound if name does not exist", func(t *testing.T) {
|
|
err := client.Delete(ctx, "notfound", v1.DeleteOptions{})
|
|
require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
|
|
})
|
|
})
|
|
} else {
|
|
t.Run("should be forbidden to delete time interval", func(t *testing.T) {
|
|
err := client.Delete(ctx, expected.Name, deleteOptions)
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
|
|
t.Run("should be forbidden even if resource does not exist", func(t *testing.T) {
|
|
err := client.Delete(ctx, "notfound", v1.DeleteOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
})
|
|
require.NoError(t, adminClient.Delete(ctx, expected.Name, v1.DeleteOptions{}))
|
|
}
|
|
|
|
if tc.canRead {
|
|
t.Run("should get empty list if no mute timings", func(t *testing.T) {
|
|
list, err := client.List(ctx, v1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, list.Items, 0)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIntegrationTimeIntervalProvisioning(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
|
|
org := helper.Org1
|
|
|
|
admin := org.Admin
|
|
adminClient := newClient(t, helper.Org1.Admin)
|
|
|
|
env := helper.GetEnv()
|
|
ac := acimpl.ProvideAccessControl(env.FeatureToggles)
|
|
db, err := store.ProvideDBStore(env.Cfg, env.FeatureToggles, env.SQLStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac, bus.ProvideBus(tracing.InitializeTracerForTest()))
|
|
require.NoError(t, err)
|
|
|
|
created, err := adminClient.Create(ctx, &v0alpha1.TimeInterval{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.Spec{
|
|
Name: "time-interval-1",
|
|
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(2),
|
|
},
|
|
}, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "none", created.GetProvenanceStatus())
|
|
|
|
t.Run("should provide provenance status", func(t *testing.T) {
|
|
require.NoError(t, db.SetProvenance(ctx, &definitions.MuteTimeInterval{
|
|
MuteTimeInterval: config.MuteTimeInterval{
|
|
Name: created.Spec.Name,
|
|
},
|
|
}, admin.Identity.GetOrgID(), "API"))
|
|
|
|
got, err := adminClient.Get(ctx, created.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "API", got.GetProvenanceStatus())
|
|
})
|
|
t.Run("should not let update if provisioned", func(t *testing.T) {
|
|
updated := created.Copy().(*v0alpha1.TimeInterval)
|
|
updated.Spec.TimeIntervals = fakes.IntervalGenerator{}.GenerateMany(2)
|
|
|
|
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
|
|
t.Run("should not let delete if provisioned", func(t *testing.T) {
|
|
err := adminClient.Delete(ctx, created.Name, v1.DeleteOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
}
|
|
|
|
func TestIntegrationTimeIntervalOptimisticConcurrency(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
|
|
adminClient := newClient(t, helper.Org1.Admin)
|
|
|
|
interval := v0alpha1.TimeInterval{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.Spec{
|
|
Name: "time-interval",
|
|
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(2),
|
|
},
|
|
}
|
|
|
|
created, err := adminClient.Create(ctx, &interval, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, created)
|
|
require.NotEmpty(t, created.ResourceVersion)
|
|
|
|
t.Run("should forbid if version does not match", func(t *testing.T) {
|
|
updated := created.Copy().(*v0alpha1.TimeInterval)
|
|
updated.ResourceVersion = "test"
|
|
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
|
require.Truef(t, errors.IsConflict(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
t.Run("should update if version matches", func(t *testing.T) {
|
|
updated := created.Copy().(*v0alpha1.TimeInterval)
|
|
updated.Spec.TimeIntervals = fakes.IntervalGenerator{}.GenerateMany(2)
|
|
actualUpdated, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, updated.Spec, actualUpdated.Spec)
|
|
require.NotEqual(t, updated.ResourceVersion, actualUpdated.ResourceVersion)
|
|
})
|
|
t.Run("should update if version is empty", func(t *testing.T) {
|
|
updated := created.Copy().(*v0alpha1.TimeInterval)
|
|
updated.ResourceVersion = ""
|
|
updated.Spec.TimeIntervals = fakes.IntervalGenerator{}.GenerateMany(2)
|
|
|
|
actualUpdated, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, updated.Spec, actualUpdated.Spec)
|
|
require.NotEqual(t, created.ResourceVersion, actualUpdated.ResourceVersion)
|
|
})
|
|
t.Run("should fail to delete if version does not match", func(t *testing.T) {
|
|
actual, err := adminClient.Get(ctx, created.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
err = adminClient.Delete(ctx, actual.Name, v1.DeleteOptions{
|
|
Preconditions: &v1.Preconditions{
|
|
ResourceVersion: util.Pointer("something"),
|
|
},
|
|
})
|
|
require.Truef(t, errors.IsConflict(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
t.Run("should succeed if version matches", func(t *testing.T) {
|
|
actual, err := adminClient.Get(ctx, created.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
err = adminClient.Delete(ctx, actual.Name, v1.DeleteOptions{
|
|
Preconditions: &v1.Preconditions{
|
|
ResourceVersion: util.Pointer(actual.ResourceVersion),
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
})
|
|
t.Run("should succeed if version is empty", func(t *testing.T) {
|
|
actual, err := adminClient.Create(ctx, &interval, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
err = adminClient.Delete(ctx, actual.Name, v1.DeleteOptions{
|
|
Preconditions: &v1.Preconditions{
|
|
ResourceVersion: util.Pointer(actual.ResourceVersion),
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestIntegrationTimeIntervalPatch(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
|
|
adminClient := newClient(t, helper.Org1.Admin)
|
|
|
|
interval := v0alpha1.TimeInterval{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.Spec{
|
|
Name: "time-interval",
|
|
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(2),
|
|
},
|
|
}
|
|
|
|
current, err := adminClient.Create(ctx, &interval, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, current)
|
|
require.NotEmpty(t, current.ResourceVersion)
|
|
|
|
t.Run("should patch with merge patch", func(t *testing.T) {
|
|
patch := `{
|
|
"spec": {
|
|
"time_intervals" : []
|
|
}
|
|
}`
|
|
|
|
result, err := adminClient.Patch(ctx, current.Name, types.MergePatchType, []byte(patch), v1.PatchOptions{})
|
|
require.NoError(t, err)
|
|
require.Empty(t, result.Spec.TimeIntervals)
|
|
current = result
|
|
})
|
|
|
|
t.Run("should patch with json patch", func(t *testing.T) {
|
|
expected := fakes.IntervalGenerator{}.Generate()
|
|
|
|
patch := []map[string]interface{}{
|
|
{
|
|
"op": "add",
|
|
"path": "/spec/time_intervals/-",
|
|
"value": expected,
|
|
},
|
|
}
|
|
|
|
patchData, err := json.Marshal(patch)
|
|
require.NoError(t, err)
|
|
|
|
result, err := adminClient.Patch(ctx, current.Name, types.JSONPatchType, patchData, v1.PatchOptions{})
|
|
require.NoError(t, err)
|
|
expectedSpec := v0alpha1.Spec{
|
|
Name: current.Spec.Name,
|
|
TimeIntervals: []v0alpha1.Interval{
|
|
expected,
|
|
},
|
|
}
|
|
require.EqualValues(t, expectedSpec, result.Spec)
|
|
current = result
|
|
})
|
|
}
|
|
|
|
func TestIntegrationTimeIntervalListSelector(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
|
|
adminClient := newClient(t, helper.Org1.Admin)
|
|
|
|
interval1 := &v0alpha1.TimeInterval{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.Spec{
|
|
Name: "test1",
|
|
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(2),
|
|
},
|
|
}
|
|
interval1, err := adminClient.Create(ctx, interval1, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
interval2 := &v0alpha1.TimeInterval{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.Spec{
|
|
Name: "test2",
|
|
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(2),
|
|
},
|
|
}
|
|
interval2, err = adminClient.Create(ctx, interval2, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
env := helper.GetEnv()
|
|
ac := acimpl.ProvideAccessControl(env.FeatureToggles)
|
|
db, err := store.ProvideDBStore(env.Cfg, env.FeatureToggles, env.SQLStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac, bus.ProvideBus(tracing.InitializeTracerForTest()))
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.SetProvenance(ctx, &definitions.MuteTimeInterval{
|
|
MuteTimeInterval: config.MuteTimeInterval{
|
|
Name: interval2.Spec.Name,
|
|
},
|
|
}, helper.Org1.Admin.Identity.GetOrgID(), "API"))
|
|
interval2, err = adminClient.Get(ctx, interval2.Name, v1.GetOptions{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
intervals, err := adminClient.List(ctx, v1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, intervals.Items, 2)
|
|
|
|
t.Run("should filter by interval name", func(t *testing.T) {
|
|
list, err := adminClient.List(ctx, v1.ListOptions{
|
|
FieldSelector: "spec.name=" + interval1.Spec.Name,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, list.Items, 1)
|
|
require.Equal(t, interval1.Name, list.Items[0].Name)
|
|
})
|
|
|
|
t.Run("should filter by interval metadata name", func(t *testing.T) {
|
|
list, err := adminClient.List(ctx, v1.ListOptions{
|
|
FieldSelector: "metadata.name=" + interval2.Name,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, list.Items, 1)
|
|
require.Equal(t, interval2.Name, list.Items[0].Name)
|
|
})
|
|
|
|
t.Run("should filter by multiple filters", func(t *testing.T) {
|
|
list, err := adminClient.List(ctx, v1.ListOptions{
|
|
FieldSelector: fmt.Sprintf("metadata.name=%s,spec.name=%s", interval2.Name, interval2.Spec.Name),
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, list.Items, 1)
|
|
require.Equal(t, interval2.Name, list.Items[0].Name)
|
|
})
|
|
|
|
t.Run("should be empty when filter does not match", func(t *testing.T) {
|
|
list, err := adminClient.List(ctx, v1.ListOptions{
|
|
FieldSelector: fmt.Sprintf("metadata.name=%s", "unknown"),
|
|
})
|
|
require.NoError(t, err)
|
|
require.Empty(t, list.Items)
|
|
})
|
|
}
|
|
|
|
func TestIntegrationTimeIntervalReferentialIntegrity(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
env := helper.GetEnv()
|
|
ac := acimpl.ProvideAccessControl(env.FeatureToggles)
|
|
db, err := store.ProvideDBStore(env.Cfg, env.FeatureToggles, env.SQLStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac, bus.ProvideBus(tracing.InitializeTracerForTest()))
|
|
require.NoError(t, err)
|
|
orgID := helper.Org1.Admin.Identity.GetOrgID()
|
|
|
|
cliCfg := helper.Org1.Admin.NewRestConfig()
|
|
legacyCli := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
|
|
|
|
// Prepare environment and create notification policy and rule that use time interval
|
|
alertmanagerRaw, err := testData.ReadFile(path.Join("test-data", "notification-settings.json"))
|
|
require.NoError(t, err)
|
|
var amConfig definitions.PostableUserConfig
|
|
require.NoError(t, json.Unmarshal(alertmanagerRaw, &amConfig))
|
|
|
|
success, err := legacyCli.PostConfiguration(t, amConfig)
|
|
require.Truef(t, success, "Failed to post Alertmanager configuration: %s", err)
|
|
|
|
postGroupRaw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1.json"))
|
|
require.NoError(t, err)
|
|
var ruleGroup definitions.PostableRuleGroupConfig
|
|
require.NoError(t, json.Unmarshal(postGroupRaw, &ruleGroup))
|
|
|
|
folderUID := "test-folder"
|
|
legacyCli.CreateFolder(t, folderUID, "TEST")
|
|
_, status, data := legacyCli.PostRulesGroupWithStatus(t, folderUID, &ruleGroup)
|
|
require.Equalf(t, http.StatusAccepted, status, "Failed to post Rule: %s", data)
|
|
|
|
currentRoute := legacyCli.GetRoute(t)
|
|
currentRuleGroup := legacyCli.GetRulesGroup(t, folderUID, ruleGroup.Name)
|
|
|
|
adminClient := newClient(t, helper.Org1.Admin)
|
|
|
|
intervals, err := adminClient.List(ctx, v1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, intervals.Items, 2)
|
|
intervalIdx := slices.IndexFunc(intervals.Items, func(interval v0alpha1.TimeInterval) bool {
|
|
return interval.Spec.Name == "test-interval"
|
|
})
|
|
interval := intervals.Items[intervalIdx]
|
|
|
|
replace := func(input []string, oldName, newName string) []string {
|
|
result := make([]string, 0, len(input))
|
|
for _, s := range input {
|
|
if s == oldName {
|
|
result = append(result, newName)
|
|
continue
|
|
}
|
|
result = append(result, s)
|
|
}
|
|
return result
|
|
}
|
|
|
|
t.Run("Update", func(t *testing.T) {
|
|
t.Run("should rename all references if name changes", func(t *testing.T) {
|
|
renamed := interval.Copy().(*v0alpha1.TimeInterval)
|
|
renamed.Spec.Name += "-new"
|
|
|
|
actual, err := adminClient.Update(ctx, renamed, v1.UpdateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
updatedRuleGroup := legacyCli.GetRulesGroup(t, folderUID, ruleGroup.Name)
|
|
for idx, rule := range updatedRuleGroup.Rules {
|
|
expectedTimeIntervals := currentRuleGroup.Rules[idx].GrafanaManagedAlert.NotificationSettings.MuteTimeIntervals
|
|
expectedTimeIntervals = replace(expectedTimeIntervals, interval.Spec.Name, actual.Spec.Name)
|
|
assert.Equalf(t, expectedTimeIntervals, rule.GrafanaManagedAlert.NotificationSettings.MuteTimeIntervals, "time interval in rules should have been renamed but it did not")
|
|
}
|
|
|
|
updatedRoute := legacyCli.GetRoute(t)
|
|
for idx, route := range updatedRoute.Routes {
|
|
expectedTimeIntervals := replace(currentRoute.Routes[idx].MuteTimeIntervals, interval.Spec.Name, actual.Spec.Name)
|
|
assert.Equalf(t, expectedTimeIntervals, route.MuteTimeIntervals, "time interval in routes should have been renamed but it did not")
|
|
}
|
|
|
|
interval = *actual
|
|
})
|
|
|
|
t.Run("should fail if at least one resource is provisioned", func(t *testing.T) {
|
|
require.NoError(t, err)
|
|
renamed := interval.Copy().(*v0alpha1.TimeInterval)
|
|
renamed.Spec.Name += util.GenerateShortUID()
|
|
|
|
t.Run("provisioned route", func(t *testing.T) {
|
|
require.NoError(t, db.SetProvenance(ctx, ¤tRoute, orgID, "API"))
|
|
t.Cleanup(func() {
|
|
require.NoError(t, db.DeleteProvenance(ctx, ¤tRoute, orgID))
|
|
})
|
|
actual, err := adminClient.Update(ctx, renamed, v1.UpdateOptions{})
|
|
require.Errorf(t, err, "Expected error but got successful result: %v", actual)
|
|
require.Truef(t, errors.IsConflict(err), "Expected Conflict, got: %s", err)
|
|
})
|
|
|
|
t.Run("provisioned rules", func(t *testing.T) {
|
|
ruleUid := currentRuleGroup.Rules[0].GrafanaManagedAlert.UID
|
|
resource := &ngmodels.AlertRule{UID: ruleUid}
|
|
require.NoError(t, db.SetProvenance(ctx, resource, orgID, "API"))
|
|
t.Cleanup(func() {
|
|
require.NoError(t, db.DeleteProvenance(ctx, resource, orgID))
|
|
})
|
|
|
|
actual, err := adminClient.Update(ctx, renamed, v1.UpdateOptions{})
|
|
require.Errorf(t, err, "Expected error but got successful result: %v", actual)
|
|
require.Truef(t, errors.IsConflict(err), "Expected Conflict, got: %s", err)
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("Delete", func(t *testing.T) {
|
|
t.Run("should fail to delete if time interval is used in rule and routes", func(t *testing.T) {
|
|
err := adminClient.Delete(ctx, interval.Name, v1.DeleteOptions{})
|
|
require.Truef(t, errors.IsConflict(err), "Expected Conflict, got: %s", err)
|
|
})
|
|
|
|
t.Run("should fail to delete if time interval is used in only rule", func(t *testing.T) {
|
|
route := legacyCli.GetRoute(t)
|
|
route.Routes[0].MuteTimeIntervals = nil
|
|
legacyCli.UpdateRoute(t, route, true)
|
|
|
|
err = adminClient.Delete(ctx, interval.Name, v1.DeleteOptions{})
|
|
require.Truef(t, errors.IsConflict(err), "Expected Conflict, got: %s", err)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestIntegrationTimeIntervalValidation(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
|
|
adminClient := newClient(t, helper.Org1.Admin)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
interval v0alpha1.Spec
|
|
}{
|
|
{
|
|
name: "missing name",
|
|
interval: v0alpha1.Spec{
|
|
Name: "",
|
|
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(1),
|
|
},
|
|
},
|
|
{
|
|
name: "invalid interval",
|
|
interval: v0alpha1.Spec{
|
|
Name: "test",
|
|
TimeIntervals: []v0alpha1.Interval{
|
|
{
|
|
DaysOfMonth: []string{"1-31"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
i := &v0alpha1.TimeInterval{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: tc.interval,
|
|
}
|
|
_, err := adminClient.Create(ctx, i, v1.CreateOptions{})
|
|
require.Error(t, err)
|
|
require.Truef(t, errors.IsBadRequest(err), "Expected BadRequest, got: %s", err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func newClient(t *testing.T, user apis.User) *apis.TypedClient[v0alpha1.TimeInterval, v0alpha1.TimeIntervalList] {
|
|
t.Helper()
|
|
|
|
client, err := dynamic.NewForConfig(user.NewRestConfig())
|
|
require.NoError(t, err)
|
|
|
|
return &apis.TypedClient[v0alpha1.TimeInterval, v0alpha1.TimeIntervalList]{
|
|
Client: client.Resource(
|
|
schema.GroupVersionResource{
|
|
Group: v0alpha1.Kind().Group(),
|
|
Version: v0alpha1.Kind().Version(),
|
|
Resource: v0alpha1.Kind().Plural(),
|
|
}).Namespace("default"),
|
|
}
|
|
}
|
|
|