2021-05-13 22:05:33 +08:00
|
|
|
package store_test
|
2021-03-09 04:19:21 +08:00
|
|
|
|
|
|
|
import (
|
2025-01-28 01:47:33 +08:00
|
|
|
"bytes"
|
2022-02-08 16:52:03 +08:00
|
|
|
"context"
|
2022-10-06 14:22:58 +08:00
|
|
|
"fmt"
|
2021-03-09 04:19:21 +08:00
|
|
|
"testing"
|
2024-01-22 20:07:11 +08:00
|
|
|
"time"
|
2022-08-09 22:28:36 +08:00
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
"github.com/golang/snappy"
|
2022-08-09 22:28:36 +08:00
|
|
|
"github.com/stretchr/testify/require"
|
2025-01-28 01:47:33 +08:00
|
|
|
"google.golang.org/protobuf/proto"
|
2021-03-09 04:19:21 +08:00
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
2023-01-14 07:29:29 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
2021-03-09 04:19:21 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
2025-01-28 01:47:33 +08:00
|
|
|
pb "github.com/grafana/grafana/pkg/services/ngalert/store/proto/v1"
|
2021-05-13 22:05:33 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/ngalert/tests"
|
2023-01-14 07:29:29 +08:00
|
|
|
"github.com/grafana/grafana/pkg/util"
|
2021-03-09 04:19:21 +08:00
|
|
|
)
|
|
|
|
|
2021-05-01 01:21:57 +08:00
|
|
|
const baseIntervalSeconds = 10
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
func TestIntegration_CompressedAlertRuleStateOperations(t *testing.T) {
|
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("skipping integration test")
|
|
|
|
}
|
|
|
|
|
2022-10-06 14:22:58 +08:00
|
|
|
ctx := context.Background()
|
2025-01-28 01:47:33 +08:00
|
|
|
ng, dbstore := tests.SetupTestEnv(
|
|
|
|
t,
|
|
|
|
baseIntervalSeconds,
|
|
|
|
tests.WithFeatureToggles(
|
|
|
|
featuremgmt.WithFeatures(featuremgmt.FlagAlertingSaveStateCompressed),
|
|
|
|
),
|
|
|
|
)
|
2022-10-06 14:22:58 +08:00
|
|
|
|
|
|
|
const mainOrgID int64 = 1
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
alertRule1 := tests.CreateTestAlertRule(t, ctx, dbstore, 60, mainOrgID)
|
|
|
|
orgID := alertRule1.OrgID
|
|
|
|
alertRule2 := tests.CreateTestAlertRule(t, ctx, dbstore, 60, mainOrgID)
|
|
|
|
require.Equal(t, orgID, alertRule2.OrgID)
|
2022-10-06 14:22:58 +08:00
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
setupInstances func() []models.AlertInstance
|
|
|
|
listQuery *models.ListAlertInstancesQuery
|
|
|
|
validate func(t *testing.T, alerts []*models.AlertInstance)
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "can save and read alert rule state",
|
|
|
|
setupInstances: func() []models.AlertInstance {
|
|
|
|
return []models.AlertInstance{
|
|
|
|
createAlertInstance(alertRule1.OrgID, alertRule1.UID, "labelsHash1", string(models.InstanceStateError), models.InstanceStateFiring),
|
|
|
|
}
|
2022-10-06 14:22:58 +08:00
|
|
|
},
|
2025-01-28 01:47:33 +08:00
|
|
|
listQuery: &models.ListAlertInstancesQuery{
|
|
|
|
RuleOrgID: alertRule1.OrgID,
|
|
|
|
RuleUID: alertRule1.UID,
|
|
|
|
},
|
|
|
|
validate: func(t *testing.T, alerts []*models.AlertInstance) {
|
|
|
|
require.Len(t, alerts, 1)
|
|
|
|
require.Equal(t, "labelsHash1", alerts[0].LabelsHash)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "can save and read alert rule state with multiple instances",
|
|
|
|
setupInstances: func() []models.AlertInstance {
|
|
|
|
return []models.AlertInstance{
|
|
|
|
createAlertInstance(alertRule1.OrgID, alertRule1.UID, "hash1", "", models.InstanceStateFiring),
|
|
|
|
createAlertInstance(alertRule1.OrgID, alertRule1.UID, "hash2", "", models.InstanceStateFiring),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
listQuery: &models.ListAlertInstancesQuery{
|
|
|
|
RuleOrgID: alertRule1.OrgID,
|
|
|
|
RuleUID: alertRule1.UID,
|
|
|
|
},
|
|
|
|
validate: func(t *testing.T, alerts []*models.AlertInstance) {
|
|
|
|
require.Len(t, alerts, 2)
|
|
|
|
containsHash(t, alerts, "hash1")
|
|
|
|
containsHash(t, alerts, "hash2")
|
|
|
|
},
|
|
|
|
},
|
2022-10-06 14:22:58 +08:00
|
|
|
}
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
for _, tc := range tests {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
instances := tc.setupInstances()
|
|
|
|
err := ng.InstanceStore.SaveAlertInstancesForRule(ctx, alertRule1.GetKeyWithGroup(), instances)
|
|
|
|
require.NoError(t, err)
|
|
|
|
alerts, err := ng.InstanceStore.ListAlertInstances(ctx, tc.listQuery)
|
|
|
|
require.NoError(t, err)
|
|
|
|
tc.validate(t, alerts)
|
|
|
|
})
|
2022-10-06 14:22:58 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
func TestIntegration_CompressedAlertRuleStateOperations_NoNormalState(t *testing.T) {
|
2022-06-10 23:46:21 +08:00
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("skipping integration test")
|
|
|
|
}
|
2025-01-28 01:47:33 +08:00
|
|
|
|
2022-02-08 16:52:03 +08:00
|
|
|
ctx := context.Background()
|
2025-01-28 01:47:33 +08:00
|
|
|
ng, dbstore := tests.SetupTestEnv(
|
|
|
|
t,
|
|
|
|
baseIntervalSeconds,
|
|
|
|
tests.WithFeatureToggles(
|
|
|
|
featuremgmt.WithFeatures(
|
|
|
|
featuremgmt.FlagAlertingSaveStateCompressed,
|
|
|
|
featuremgmt.FlagAlertingNoNormalState,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
2021-03-09 04:19:21 +08:00
|
|
|
|
2021-09-29 22:16:40 +08:00
|
|
|
const mainOrgID int64 = 1
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
alertRule1 := tests.CreateTestAlertRule(t, ctx, dbstore, 60, mainOrgID)
|
|
|
|
orgID := alertRule1.OrgID
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
setupInstances func() []models.AlertInstance
|
|
|
|
listQuery *models.ListAlertInstancesQuery
|
|
|
|
validate func(t *testing.T, alerts []*models.AlertInstance)
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "should ignore Normal state with no reason if feature flag is enabled",
|
|
|
|
setupInstances: func() []models.AlertInstance {
|
|
|
|
return []models.AlertInstance{
|
|
|
|
createAlertInstance(orgID, util.GenerateShortUID(), util.GenerateShortUID(), "", models.InstanceStateNormal),
|
|
|
|
createAlertInstance(orgID, util.GenerateShortUID(), "errorHash", "error", models.InstanceStateNormal),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
listQuery: &models.ListAlertInstancesQuery{
|
|
|
|
RuleOrgID: orgID,
|
|
|
|
},
|
|
|
|
validate: func(t *testing.T, alerts []*models.AlertInstance) {
|
|
|
|
require.Len(t, alerts, 1)
|
|
|
|
containsHash(t, alerts, "errorHash")
|
|
|
|
for _, instance := range alerts {
|
|
|
|
if instance.CurrentState == models.InstanceStateNormal && instance.CurrentReason == "" {
|
|
|
|
require.Fail(t, "List operation expected to return all states except Normal but the result contains Normal states")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range tests {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
instances := tc.setupInstances()
|
|
|
|
err := ng.InstanceStore.SaveAlertInstancesForRule(ctx, alertRule1.GetKeyWithGroup(), instances)
|
|
|
|
require.NoError(t, err)
|
|
|
|
alerts, err := ng.InstanceStore.ListAlertInstances(ctx, tc.listQuery)
|
|
|
|
require.NoError(t, err)
|
|
|
|
tc.validate(t, alerts)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// containsHash is a helper function to check if an instance with
|
|
|
|
// a given labels hash exists in the list of alert instances.
|
|
|
|
func containsHash(t *testing.T, instances []*models.AlertInstance, hash string) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
for _, i := range instances {
|
|
|
|
if i.LabelsHash == hash {
|
|
|
|
return
|
2023-01-14 07:29:29 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
require.Fail(t, fmt.Sprintf("%v does not contain an instance with hash %s", instances, hash))
|
|
|
|
}
|
|
|
|
|
|
|
|
func createAlertInstance(orgID int64, ruleUID, labelsHash, reason string, state models.InstanceStateType) models.AlertInstance {
|
|
|
|
return models.AlertInstance{
|
|
|
|
AlertInstanceKey: models.AlertInstanceKey{
|
|
|
|
RuleOrgID: orgID,
|
|
|
|
RuleUID: ruleUID,
|
|
|
|
LabelsHash: labelsHash,
|
|
|
|
},
|
|
|
|
CurrentState: state,
|
|
|
|
CurrentReason: reason,
|
|
|
|
Labels: models.InstanceLabels{"label1": "value1"},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestIntegrationAlertInstanceOperations(t *testing.T) {
|
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("skipping integration test")
|
|
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
ng, dbstore := tests.SetupTestEnv(t, baseIntervalSeconds)
|
|
|
|
|
|
|
|
const mainOrgID int64 = 1
|
|
|
|
|
2022-02-08 16:52:03 +08:00
|
|
|
alertRule1 := tests.CreateTestAlertRule(t, ctx, dbstore, 60, mainOrgID)
|
2021-05-01 01:21:57 +08:00
|
|
|
orgID := alertRule1.OrgID
|
2021-03-09 04:19:21 +08:00
|
|
|
|
2022-02-08 16:52:03 +08:00
|
|
|
alertRule2 := tests.CreateTestAlertRule(t, ctx, dbstore, 60, mainOrgID)
|
2021-05-01 01:21:57 +08:00
|
|
|
require.Equal(t, orgID, alertRule2.OrgID)
|
2021-03-09 04:19:21 +08:00
|
|
|
|
2022-02-08 16:52:03 +08:00
|
|
|
alertRule3 := tests.CreateTestAlertRule(t, ctx, dbstore, 60, mainOrgID)
|
2021-05-01 01:21:57 +08:00
|
|
|
require.Equal(t, orgID, alertRule3.OrgID)
|
2021-03-09 04:19:21 +08:00
|
|
|
|
2022-02-08 16:52:03 +08:00
|
|
|
alertRule4 := tests.CreateTestAlertRule(t, ctx, dbstore, 60, mainOrgID)
|
2021-05-01 01:21:57 +08:00
|
|
|
require.Equal(t, orgID, alertRule4.OrgID)
|
2021-03-09 04:19:21 +08:00
|
|
|
|
|
|
|
t.Run("can save and read new alert instance", func(t *testing.T) {
|
2022-10-06 14:22:58 +08:00
|
|
|
labels := models.InstanceLabels{"test": "testValue"}
|
|
|
|
_, hash, _ := labels.StringAndHash()
|
|
|
|
instance := models.AlertInstance{
|
|
|
|
AlertInstanceKey: models.AlertInstanceKey{
|
|
|
|
RuleOrgID: alertRule1.OrgID,
|
|
|
|
RuleUID: alertRule1.UID,
|
|
|
|
LabelsHash: hash,
|
|
|
|
},
|
|
|
|
CurrentState: models.InstanceStateFiring,
|
|
|
|
CurrentReason: string(models.InstanceStateError),
|
|
|
|
Labels: labels,
|
|
|
|
}
|
2025-01-28 01:47:33 +08:00
|
|
|
err := ng.InstanceStore.SaveAlertInstance(ctx, instance)
|
2021-03-09 04:19:21 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-09-27 03:38:53 +08:00
|
|
|
listCmd := &models.ListAlertInstancesQuery{
|
2022-10-06 14:22:58 +08:00
|
|
|
RuleOrgID: instance.RuleOrgID,
|
|
|
|
RuleUID: instance.RuleUID,
|
2021-03-09 04:19:21 +08:00
|
|
|
}
|
2025-01-28 01:47:33 +08:00
|
|
|
alerts, err := ng.InstanceStore.ListAlertInstances(ctx, listCmd)
|
2021-03-09 04:19:21 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-03-28 16:34:35 +08:00
|
|
|
require.Len(t, alerts, 1)
|
|
|
|
require.Equal(t, instance.Labels, alerts[0].Labels)
|
|
|
|
require.Equal(t, alertRule1.OrgID, alerts[0].RuleOrgID)
|
|
|
|
require.Equal(t, alertRule1.UID, alerts[0].RuleUID)
|
|
|
|
require.Equal(t, instance.CurrentReason, alerts[0].CurrentReason)
|
2021-03-09 04:19:21 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("can save and read new alert instance with no labels", func(t *testing.T) {
|
2022-10-06 14:22:58 +08:00
|
|
|
labels := models.InstanceLabels{}
|
|
|
|
_, hash, _ := labels.StringAndHash()
|
|
|
|
instance := models.AlertInstance{
|
|
|
|
AlertInstanceKey: models.AlertInstanceKey{
|
|
|
|
RuleOrgID: alertRule2.OrgID,
|
|
|
|
RuleUID: alertRule2.UID,
|
|
|
|
LabelsHash: hash,
|
|
|
|
},
|
|
|
|
CurrentState: models.InstanceStateNormal,
|
|
|
|
Labels: labels,
|
2021-03-09 04:19:21 +08:00
|
|
|
}
|
2025-01-28 01:47:33 +08:00
|
|
|
err := ng.InstanceStore.SaveAlertInstance(ctx, instance)
|
2021-03-09 04:19:21 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-09-27 03:38:53 +08:00
|
|
|
listCmd := &models.ListAlertInstancesQuery{
|
2022-10-06 14:22:58 +08:00
|
|
|
RuleOrgID: instance.RuleOrgID,
|
|
|
|
RuleUID: instance.RuleUID,
|
2021-03-09 04:19:21 +08:00
|
|
|
}
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
alerts, err := ng.InstanceStore.ListAlertInstances(ctx, listCmd)
|
2021-03-09 04:19:21 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-03-28 16:34:35 +08:00
|
|
|
require.Len(t, alerts, 1)
|
|
|
|
require.Equal(t, alertRule2.OrgID, alerts[0].RuleOrgID)
|
|
|
|
require.Equal(t, alertRule2.UID, alerts[0].RuleUID)
|
|
|
|
require.Equal(t, instance.Labels, alerts[0].Labels)
|
2021-03-09 04:19:21 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("can save two instances with same org_id, uid and different labels", func(t *testing.T) {
|
2022-10-06 14:22:58 +08:00
|
|
|
labels := models.InstanceLabels{"test": "testValue"}
|
|
|
|
_, hash, _ := labels.StringAndHash()
|
|
|
|
instance1 := models.AlertInstance{
|
|
|
|
AlertInstanceKey: models.AlertInstanceKey{
|
|
|
|
RuleOrgID: alertRule3.OrgID,
|
|
|
|
RuleUID: alertRule3.UID,
|
|
|
|
LabelsHash: hash,
|
|
|
|
},
|
|
|
|
CurrentState: models.InstanceStateFiring,
|
|
|
|
Labels: labels,
|
2021-03-09 04:19:21 +08:00
|
|
|
}
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
err := ng.InstanceStore.SaveAlertInstance(ctx, instance1)
|
2021-03-09 04:19:21 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-10-06 14:22:58 +08:00
|
|
|
labels = models.InstanceLabels{"test": "testValue2"}
|
|
|
|
_, hash, _ = labels.StringAndHash()
|
|
|
|
instance2 := models.AlertInstance{
|
|
|
|
AlertInstanceKey: models.AlertInstanceKey{
|
|
|
|
RuleOrgID: instance1.RuleOrgID,
|
|
|
|
RuleUID: instance1.RuleUID,
|
|
|
|
LabelsHash: hash,
|
|
|
|
},
|
|
|
|
CurrentState: models.InstanceStateFiring,
|
|
|
|
Labels: labels,
|
2021-03-09 04:19:21 +08:00
|
|
|
}
|
2025-01-28 01:47:33 +08:00
|
|
|
err = ng.InstanceStore.SaveAlertInstance(ctx, instance2)
|
2021-03-09 04:19:21 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
listQuery := &models.ListAlertInstancesQuery{
|
2022-10-06 14:22:58 +08:00
|
|
|
RuleOrgID: instance1.RuleOrgID,
|
|
|
|
RuleUID: instance1.RuleUID,
|
2021-03-09 04:19:21 +08:00
|
|
|
}
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
alerts, err := ng.InstanceStore.ListAlertInstances(ctx, listQuery)
|
2021-03-09 04:19:21 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-03-28 16:34:35 +08:00
|
|
|
require.Len(t, alerts, 2)
|
2021-03-09 04:19:21 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("can list all added instances in org", func(t *testing.T) {
|
|
|
|
listQuery := &models.ListAlertInstancesQuery{
|
2021-05-03 19:19:15 +08:00
|
|
|
RuleOrgID: orgID,
|
2021-03-09 04:19:21 +08:00
|
|
|
}
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
alerts, err := ng.InstanceStore.ListAlertInstances(ctx, listQuery)
|
2021-03-09 04:19:21 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-03-28 16:34:35 +08:00
|
|
|
require.Len(t, alerts, 4)
|
2021-03-09 04:19:21 +08:00
|
|
|
})
|
|
|
|
|
2023-01-14 07:29:29 +08:00
|
|
|
t.Run("should ignore Normal state with no reason if feature flag is enabled", func(t *testing.T) {
|
2025-01-28 01:47:33 +08:00
|
|
|
ng, _ := tests.SetupTestEnv(
|
|
|
|
t,
|
|
|
|
baseIntervalSeconds,
|
|
|
|
tests.WithFeatureToggles(
|
|
|
|
featuremgmt.WithFeatures(featuremgmt.FlagAlertingNoNormalState),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
2023-01-14 07:29:29 +08:00
|
|
|
labels := models.InstanceLabels{"test": util.GenerateShortUID()}
|
|
|
|
instance1 := models.AlertInstance{
|
|
|
|
AlertInstanceKey: models.AlertInstanceKey{
|
|
|
|
RuleOrgID: orgID,
|
|
|
|
RuleUID: util.GenerateShortUID(),
|
|
|
|
LabelsHash: util.GenerateShortUID(),
|
|
|
|
},
|
|
|
|
CurrentState: models.InstanceStateNormal,
|
|
|
|
CurrentReason: "",
|
|
|
|
Labels: labels,
|
|
|
|
}
|
|
|
|
instance2 := models.AlertInstance{
|
|
|
|
AlertInstanceKey: models.AlertInstanceKey{
|
|
|
|
RuleOrgID: orgID,
|
|
|
|
RuleUID: util.GenerateShortUID(),
|
|
|
|
LabelsHash: util.GenerateShortUID(),
|
|
|
|
},
|
|
|
|
CurrentState: models.InstanceStateNormal,
|
|
|
|
CurrentReason: models.StateReasonError,
|
|
|
|
Labels: labels,
|
|
|
|
}
|
2025-01-28 01:47:33 +08:00
|
|
|
err := ng.InstanceStore.SaveAlertInstance(ctx, instance1)
|
2023-04-07 00:06:25 +08:00
|
|
|
require.NoError(t, err)
|
2025-01-28 01:47:33 +08:00
|
|
|
err = ng.InstanceStore.SaveAlertInstance(ctx, instance2)
|
2023-01-14 07:29:29 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-03-09 04:19:21 +08:00
|
|
|
listQuery := &models.ListAlertInstancesQuery{
|
2021-05-03 19:19:15 +08:00
|
|
|
RuleOrgID: orgID,
|
2021-03-09 04:19:21 +08:00
|
|
|
}
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
alerts, err := ng.InstanceStore.ListAlertInstances(ctx, listQuery)
|
2023-01-14 07:29:29 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-03-28 16:34:35 +08:00
|
|
|
containsHash(t, alerts, instance2.LabelsHash)
|
2023-01-14 07:29:29 +08:00
|
|
|
|
2023-03-28 16:34:35 +08:00
|
|
|
for _, instance := range alerts {
|
2023-01-14 07:29:29 +08:00
|
|
|
if instance.CurrentState == models.InstanceStateNormal && instance.CurrentReason == "" {
|
|
|
|
require.Fail(t, "List operation expected to return all states except Normal but the result contains Normal states")
|
|
|
|
}
|
|
|
|
}
|
2021-03-09 04:19:21 +08:00
|
|
|
})
|
|
|
|
|
2022-10-06 14:22:58 +08:00
|
|
|
t.Run("update instance with same org_id, uid and different state", func(t *testing.T) {
|
|
|
|
labels := models.InstanceLabels{"test": "testValue"}
|
|
|
|
_, hash, _ := labels.StringAndHash()
|
|
|
|
instance1 := models.AlertInstance{
|
|
|
|
AlertInstanceKey: models.AlertInstanceKey{
|
|
|
|
RuleOrgID: alertRule4.OrgID,
|
|
|
|
RuleUID: alertRule4.UID,
|
|
|
|
LabelsHash: hash,
|
|
|
|
},
|
|
|
|
CurrentState: models.InstanceStateFiring,
|
|
|
|
Labels: labels,
|
2021-03-09 04:19:21 +08:00
|
|
|
}
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
err := ng.InstanceStore.SaveAlertInstance(ctx, instance1)
|
2021-03-09 04:19:21 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-10-06 14:22:58 +08:00
|
|
|
instance2 := models.AlertInstance{
|
|
|
|
AlertInstanceKey: models.AlertInstanceKey{
|
|
|
|
RuleOrgID: alertRule4.OrgID,
|
|
|
|
RuleUID: instance1.RuleUID,
|
|
|
|
LabelsHash: instance1.LabelsHash,
|
|
|
|
},
|
|
|
|
CurrentState: models.InstanceStateNormal,
|
|
|
|
Labels: instance1.Labels,
|
2021-03-09 04:19:21 +08:00
|
|
|
}
|
2025-01-28 01:47:33 +08:00
|
|
|
err = ng.InstanceStore.SaveAlertInstance(ctx, instance2)
|
2021-03-09 04:19:21 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
listQuery := &models.ListAlertInstancesQuery{
|
2021-05-03 19:19:15 +08:00
|
|
|
RuleOrgID: alertRule4.OrgID,
|
|
|
|
RuleUID: alertRule4.UID,
|
2021-03-09 04:19:21 +08:00
|
|
|
}
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
alerts, err := ng.InstanceStore.ListAlertInstances(ctx, listQuery)
|
2021-03-09 04:19:21 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-03-28 16:34:35 +08:00
|
|
|
require.Len(t, alerts, 1)
|
2021-03-09 04:19:21 +08:00
|
|
|
|
2023-03-28 16:34:35 +08:00
|
|
|
require.Equal(t, instance2.RuleOrgID, alerts[0].RuleOrgID)
|
|
|
|
require.Equal(t, instance2.RuleUID, alerts[0].RuleUID)
|
|
|
|
require.Equal(t, instance2.Labels, alerts[0].Labels)
|
|
|
|
require.Equal(t, instance2.CurrentState, alerts[0].CurrentState)
|
2021-03-09 04:19:21 +08:00
|
|
|
})
|
|
|
|
}
|
2024-01-22 20:07:11 +08:00
|
|
|
|
|
|
|
func TestIntegrationFullSync(t *testing.T) {
|
2024-12-16 22:30:38 +08:00
|
|
|
batchSize := 1
|
|
|
|
|
2024-01-22 20:07:11 +08:00
|
|
|
ctx := context.Background()
|
2025-01-28 01:47:33 +08:00
|
|
|
ng, _ := tests.SetupTestEnv(t, baseIntervalSeconds)
|
2024-01-22 20:07:11 +08:00
|
|
|
|
|
|
|
orgID := int64(1)
|
|
|
|
|
|
|
|
ruleUIDs := []string{"a", "b", "c", "d"}
|
|
|
|
|
|
|
|
instances := make([]models.AlertInstance, len(ruleUIDs))
|
|
|
|
for i, ruleUID := range ruleUIDs {
|
|
|
|
instances[i] = generateTestAlertInstance(orgID, ruleUID)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run("Should do a proper full sync", func(t *testing.T) {
|
2025-01-28 01:47:33 +08:00
|
|
|
err := ng.InstanceStore.FullSync(ctx, instances, batchSize)
|
2024-01-22 20:07:11 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
res, err := ng.InstanceStore.ListAlertInstances(ctx, &models.ListAlertInstancesQuery{
|
2024-01-22 20:07:11 +08:00
|
|
|
RuleOrgID: orgID,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res, len(instances))
|
|
|
|
for _, ruleUID := range ruleUIDs {
|
|
|
|
found := false
|
|
|
|
for _, instance := range res {
|
|
|
|
if instance.RuleUID == ruleUID {
|
|
|
|
found = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
t.Errorf("Instance with RuleUID '%s' not found", ruleUID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2024-12-16 22:30:38 +08:00
|
|
|
|
2024-01-22 20:07:11 +08:00
|
|
|
t.Run("Should remove non existing entries on sync", func(t *testing.T) {
|
2025-01-28 01:47:33 +08:00
|
|
|
err := ng.InstanceStore.FullSync(ctx, instances[1:], batchSize)
|
2024-01-22 20:07:11 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
res, err := ng.InstanceStore.ListAlertInstances(ctx, &models.ListAlertInstancesQuery{
|
2024-01-22 20:07:11 +08:00
|
|
|
RuleOrgID: orgID,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res, len(instances)-1)
|
|
|
|
for _, instance := range res {
|
|
|
|
if instance.RuleUID == "a" {
|
|
|
|
t.Error("Instance with RuleUID 'a' should not be exist anymore")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2024-12-16 22:30:38 +08:00
|
|
|
|
2024-01-22 20:07:11 +08:00
|
|
|
t.Run("Should add new entries on sync", func(t *testing.T) {
|
|
|
|
newRuleUID := "y"
|
2025-01-28 01:47:33 +08:00
|
|
|
err := ng.InstanceStore.FullSync(ctx, append(instances, generateTestAlertInstance(orgID, newRuleUID)), batchSize)
|
2024-12-16 22:30:38 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
res, err := ng.InstanceStore.ListAlertInstances(ctx, &models.ListAlertInstancesQuery{
|
2024-12-16 22:30:38 +08:00
|
|
|
RuleOrgID: orgID,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res, len(instances)+1)
|
|
|
|
for _, ruleUID := range append(ruleUIDs, newRuleUID) {
|
|
|
|
found := false
|
|
|
|
for _, instance := range res {
|
|
|
|
if instance.RuleUID == ruleUID {
|
|
|
|
found = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
t.Errorf("Instance with RuleUID '%s' not found", ruleUID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Should save all instances when batch size is bigger than 1", func(t *testing.T) {
|
|
|
|
batchSize = 2
|
|
|
|
newRuleUID := "y"
|
2025-01-28 01:47:33 +08:00
|
|
|
err := ng.InstanceStore.FullSync(ctx, append(instances, generateTestAlertInstance(orgID, newRuleUID)), batchSize)
|
2024-01-22 20:07:11 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
res, err := ng.InstanceStore.ListAlertInstances(ctx, &models.ListAlertInstancesQuery{
|
2024-01-22 20:07:11 +08:00
|
|
|
RuleOrgID: orgID,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res, len(instances)+1)
|
|
|
|
for _, ruleUID := range append(ruleUIDs, newRuleUID) {
|
|
|
|
found := false
|
|
|
|
for _, instance := range res {
|
|
|
|
if instance.RuleUID == ruleUID {
|
|
|
|
found = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
t.Errorf("Instance with RuleUID '%s' not found", ruleUID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2024-12-16 22:30:38 +08:00
|
|
|
|
|
|
|
t.Run("Should not fail when the instances are empty", func(t *testing.T) {
|
|
|
|
// First, insert some data into the table.
|
|
|
|
initialInstances := []models.AlertInstance{
|
|
|
|
generateTestAlertInstance(orgID, "preexisting-1"),
|
|
|
|
generateTestAlertInstance(orgID, "preexisting-2"),
|
|
|
|
}
|
2025-01-28 01:47:33 +08:00
|
|
|
err := ng.InstanceStore.FullSync(ctx, initialInstances, 5)
|
2024-12-16 22:30:38 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Now call FullSync with no instances. According to the code, this should return nil
|
|
|
|
// and should not delete anything in the table.
|
2025-01-28 01:47:33 +08:00
|
|
|
err = ng.InstanceStore.FullSync(ctx, []models.AlertInstance{}, 5)
|
2024-12-16 22:30:38 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Check that the previously inserted instances are still present.
|
2025-01-28 01:47:33 +08:00
|
|
|
res, err := ng.InstanceStore.ListAlertInstances(ctx, &models.ListAlertInstancesQuery{
|
2024-12-16 22:30:38 +08:00
|
|
|
RuleOrgID: orgID,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res, 2, "Expected the preexisting instances to remain since empty sync does nothing")
|
|
|
|
|
|
|
|
found1, found2 := false, false
|
|
|
|
for _, r := range res {
|
|
|
|
if r.RuleUID == "preexisting-1" {
|
|
|
|
found1 = true
|
|
|
|
}
|
|
|
|
if r.RuleUID == "preexisting-2" {
|
|
|
|
found2 = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
require.True(t, found1, "Expected preexisting-1 to remain")
|
|
|
|
require.True(t, found2, "Expected preexisting-2 to remain")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Should handle invalid instances by skipping them", func(t *testing.T) {
|
|
|
|
// Create a batch with one valid and one invalid instance
|
|
|
|
validInstance := generateTestAlertInstance(orgID, "valid")
|
|
|
|
|
|
|
|
invalidInstance := generateTestAlertInstance(orgID, "")
|
|
|
|
// Make the invalid instance actually invalid
|
|
|
|
invalidInstance.AlertInstanceKey.RuleUID = ""
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
err := ng.InstanceStore.FullSync(ctx, []models.AlertInstance{validInstance, invalidInstance}, 2)
|
2024-12-16 22:30:38 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Only the valid instance should be saved.
|
2025-01-28 01:47:33 +08:00
|
|
|
res, err := ng.InstanceStore.ListAlertInstances(ctx, &models.ListAlertInstancesQuery{
|
2024-12-16 22:30:38 +08:00
|
|
|
RuleOrgID: orgID,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res, 1)
|
|
|
|
require.Equal(t, "valid", res[0].RuleUID)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Should handle batchSize larger than the number of instances", func(t *testing.T) {
|
|
|
|
// Insert a small number of instances but use a large batchSize
|
|
|
|
smallSet := []models.AlertInstance{
|
|
|
|
generateTestAlertInstance(orgID, "batch-test1"),
|
|
|
|
generateTestAlertInstance(orgID, "batch-test2"),
|
|
|
|
}
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
err := ng.InstanceStore.FullSync(ctx, smallSet, 100)
|
2024-12-16 22:30:38 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
res, err := ng.InstanceStore.ListAlertInstances(ctx, &models.ListAlertInstancesQuery{
|
2024-12-16 22:30:38 +08:00
|
|
|
RuleOrgID: orgID,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res, len(smallSet))
|
|
|
|
found1, found2 := false, false
|
|
|
|
for _, r := range res {
|
|
|
|
if r.RuleUID == "batch-test1" {
|
|
|
|
found1 = true
|
|
|
|
}
|
|
|
|
if r.RuleUID == "batch-test2" {
|
|
|
|
found2 = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
require.True(t, found1)
|
|
|
|
require.True(t, found2)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Should handle a large set of instances with a moderate batchSize", func(t *testing.T) {
|
|
|
|
// Clear everything first.
|
2025-01-28 01:47:33 +08:00
|
|
|
err := ng.InstanceStore.FullSync(ctx, []models.AlertInstance{}, 1)
|
2024-12-16 22:30:38 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
largeCount := 300
|
|
|
|
largeSet := make([]models.AlertInstance, largeCount)
|
|
|
|
for i := 0; i < largeCount; i++ {
|
|
|
|
largeSet[i] = generateTestAlertInstance(orgID, fmt.Sprintf("large-%d", i))
|
|
|
|
}
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
err = ng.InstanceStore.FullSync(ctx, largeSet, 50)
|
2024-12-16 22:30:38 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
res, err := ng.InstanceStore.ListAlertInstances(ctx, &models.ListAlertInstancesQuery{
|
2024-12-16 22:30:38 +08:00
|
|
|
RuleOrgID: orgID,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res, largeCount)
|
|
|
|
})
|
2024-01-22 20:07:11 +08:00
|
|
|
}
|
|
|
|
|
2025-01-28 01:47:33 +08:00
|
|
|
func TestIntegration_ProtoInstanceDBStore_VerifyCompressedData(t *testing.T) {
|
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("skipping integration test")
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
ng, dbstore := tests.SetupTestEnv(
|
|
|
|
t,
|
|
|
|
baseIntervalSeconds,
|
|
|
|
tests.WithFeatureToggles(
|
|
|
|
featuremgmt.WithFeatures(
|
|
|
|
featuremgmt.FlagAlertingSaveStateCompressed,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
alertRule := tests.CreateTestAlertRule(t, ctx, dbstore, 60, 1)
|
|
|
|
|
|
|
|
labelsHash := "hash1"
|
|
|
|
reason := "reason"
|
|
|
|
state := models.InstanceStateFiring
|
|
|
|
instances := []models.AlertInstance{
|
|
|
|
createAlertInstance(alertRule.OrgID, alertRule.UID, labelsHash, reason, state),
|
|
|
|
}
|
|
|
|
|
|
|
|
err := ng.InstanceStore.SaveAlertInstancesForRule(ctx, alertRule.GetKeyWithGroup(), instances)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Query raw data from the database
|
|
|
|
type compressedRow struct {
|
|
|
|
OrgID int64 `xorm:"org_id"`
|
|
|
|
RuleUID string `xorm:"rule_uid"`
|
|
|
|
Data []byte `xorm:"data"`
|
|
|
|
}
|
|
|
|
var rawData compressedRow
|
|
|
|
err = dbstore.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
|
|
|
|
_, err := sess.SQL("SELECT * FROM alert_rule_state").Get(&rawData)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Decompress and compare
|
|
|
|
require.NotNil(t, rawData)
|
|
|
|
decompressedInstances, err := decompressAlertInstances(rawData.Data)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
require.Len(t, decompressedInstances, 1)
|
|
|
|
require.Equal(t, instances[0].LabelsHash, decompressedInstances[0].LabelsHash)
|
|
|
|
require.Equal(t, string(instances[0].CurrentState), decompressedInstances[0].CurrentState)
|
|
|
|
require.Equal(t, instances[0].CurrentReason, decompressedInstances[0].CurrentReason)
|
|
|
|
}
|
|
|
|
|
|
|
|
func decompressAlertInstances(compressed []byte) ([]*pb.AlertInstance, error) {
|
|
|
|
if len(compressed) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
reader := snappy.NewReader(bytes.NewReader(compressed))
|
|
|
|
var b bytes.Buffer
|
|
|
|
if _, err := b.ReadFrom(reader); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to read compressed data: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var instances pb.AlertInstances
|
|
|
|
if err := proto.Unmarshal(b.Bytes(), &instances); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to unmarshal protobuf: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return instances.Instances, nil
|
|
|
|
}
|
|
|
|
|
2024-01-22 20:07:11 +08:00
|
|
|
func generateTestAlertInstance(orgID int64, ruleID string) models.AlertInstance {
|
|
|
|
return models.AlertInstance{
|
|
|
|
AlertInstanceKey: models.AlertInstanceKey{
|
|
|
|
RuleOrgID: orgID,
|
|
|
|
RuleUID: ruleID,
|
|
|
|
LabelsHash: "abc",
|
|
|
|
},
|
|
|
|
CurrentState: models.InstanceStateFiring,
|
|
|
|
Labels: map[string]string{
|
|
|
|
"hello": "world",
|
|
|
|
},
|
|
|
|
ResultFingerprint: "abc",
|
|
|
|
CurrentStateEnd: time.Now(),
|
|
|
|
CurrentStateSince: time.Now(),
|
|
|
|
LastEvalTime: time.Now(),
|
2024-07-13 00:26:58 +08:00
|
|
|
LastSentAt: util.Pointer(time.Now()),
|
|
|
|
ResolvedAt: util.Pointer(time.Now()),
|
2024-01-22 20:07:11 +08:00
|
|
|
CurrentReason: "abc",
|
|
|
|
}
|
|
|
|
}
|