Alerting: Store instance annotations in alert rule state (#114975)
Alerting: Store annotations in alert instance state
This commit is contained in:
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
pb "github.com/grafana/grafana/pkg/services/ngalert/store/proto/v1"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/tests"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
const baseIntervalSeconds = 10
|
||||
@@ -51,7 +50,15 @@ func TestIntegration_CompressedAlertRuleStateOperations(t *testing.T) {
|
||||
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),
|
||||
*models.AlertInstanceGen(
|
||||
models.InstanceMuts.WithOrgID(alertRule1.OrgID),
|
||||
models.InstanceMuts.WithRuleUID(alertRule1.UID),
|
||||
models.InstanceMuts.WithLabelsHash("labelsHash1"),
|
||||
models.InstanceMuts.WithReason(string(models.InstanceStateError)),
|
||||
models.InstanceMuts.WithState(models.InstanceStateFiring),
|
||||
models.InstanceMuts.WithLabels(models.InstanceLabels{"label1": "value1"}),
|
||||
models.InstanceMuts.WithAnnotations(models.InstanceAnnotations{"annotation1": "value1"}),
|
||||
),
|
||||
}
|
||||
},
|
||||
listQuery: &models.ListAlertInstancesQuery{
|
||||
@@ -67,8 +74,22 @@ func TestIntegration_CompressedAlertRuleStateOperations(t *testing.T) {
|
||||
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),
|
||||
*models.AlertInstanceGen(
|
||||
models.InstanceMuts.WithOrgID(alertRule1.OrgID),
|
||||
models.InstanceMuts.WithRuleUID(alertRule1.UID),
|
||||
models.InstanceMuts.WithLabelsHash("hash1"),
|
||||
models.InstanceMuts.WithState(models.InstanceStateFiring),
|
||||
models.InstanceMuts.WithLabels(models.InstanceLabels{"label1": "value1"}),
|
||||
models.InstanceMuts.WithAnnotations(models.InstanceAnnotations{"annotation1": "value1"}),
|
||||
),
|
||||
*models.AlertInstanceGen(
|
||||
models.InstanceMuts.WithOrgID(alertRule1.OrgID),
|
||||
models.InstanceMuts.WithRuleUID(alertRule1.UID),
|
||||
models.InstanceMuts.WithLabelsHash("hash2"),
|
||||
models.InstanceMuts.WithState(models.InstanceStateFiring),
|
||||
models.InstanceMuts.WithLabels(models.InstanceLabels{"label1": "value1"}),
|
||||
models.InstanceMuts.WithAnnotations(models.InstanceAnnotations{"annotation1": "value1"}),
|
||||
),
|
||||
}
|
||||
},
|
||||
listQuery: &models.ListAlertInstancesQuery{
|
||||
@@ -109,19 +130,6 @@ func containsHash(t *testing.T, instances []*models.AlertInstance, hash string)
|
||||
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) {
|
||||
testutil.SkipIntegrationTestInShortMode(t)
|
||||
|
||||
@@ -312,7 +320,10 @@ func TestIntegrationFullSync(t *testing.T) {
|
||||
|
||||
instances := make([]models.AlertInstance, len(ruleUIDs))
|
||||
for i, ruleUID := range ruleUIDs {
|
||||
instances[i] = generateTestAlertInstance(orgID, ruleUID)
|
||||
instances[i] = *models.AlertInstanceGen(
|
||||
models.InstanceMuts.WithOrgID(orgID),
|
||||
models.InstanceMuts.WithRuleUID(ruleUID),
|
||||
)
|
||||
}
|
||||
|
||||
t.Run("Should do a proper full sync", func(t *testing.T) {
|
||||
@@ -356,7 +367,7 @@ func TestIntegrationFullSync(t *testing.T) {
|
||||
|
||||
t.Run("Should add new entries on sync", func(t *testing.T) {
|
||||
newRuleUID := "y"
|
||||
err := ng.InstanceStore.FullSync(ctx, append(instances, generateTestAlertInstance(orgID, newRuleUID)), batchSize, nil)
|
||||
err := ng.InstanceStore.FullSync(ctx, append(instances, *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID(newRuleUID))), batchSize, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := ng.InstanceStore.ListAlertInstances(ctx, &models.ListAlertInstancesQuery{
|
||||
@@ -381,7 +392,7 @@ func TestIntegrationFullSync(t *testing.T) {
|
||||
t.Run("Should save all instances when batch size is bigger than 1", func(t *testing.T) {
|
||||
batchSize = 2
|
||||
newRuleUID := "y"
|
||||
err := ng.InstanceStore.FullSync(ctx, append(instances, generateTestAlertInstance(orgID, newRuleUID)), batchSize, nil)
|
||||
err := ng.InstanceStore.FullSync(ctx, append(instances, *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID(newRuleUID))), batchSize, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := ng.InstanceStore.ListAlertInstances(ctx, &models.ListAlertInstancesQuery{
|
||||
@@ -406,8 +417,8 @@ func TestIntegrationFullSync(t *testing.T) {
|
||||
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"),
|
||||
*models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID("preexisting-1")),
|
||||
*models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID("preexisting-2")),
|
||||
}
|
||||
err := ng.InstanceStore.FullSync(ctx, initialInstances, 5, nil)
|
||||
require.NoError(t, err)
|
||||
@@ -439,9 +450,9 @@ func TestIntegrationFullSync(t *testing.T) {
|
||||
|
||||
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")
|
||||
validInstance := *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID("valid"))
|
||||
|
||||
invalidInstance := generateTestAlertInstance(orgID, "")
|
||||
invalidInstance := *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID(""))
|
||||
// Make the invalid instance actually invalid
|
||||
invalidInstance.RuleUID = ""
|
||||
|
||||
@@ -460,8 +471,8 @@ func TestIntegrationFullSync(t *testing.T) {
|
||||
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"),
|
||||
*models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID("batch-test1")),
|
||||
*models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID("batch-test2")),
|
||||
}
|
||||
|
||||
err := ng.InstanceStore.FullSync(ctx, smallSet, 100, nil)
|
||||
@@ -493,7 +504,7 @@ func TestIntegrationFullSync(t *testing.T) {
|
||||
largeCount := 300
|
||||
largeSet := make([]models.AlertInstance, largeCount)
|
||||
for i := 0; i < largeCount; i++ {
|
||||
largeSet[i] = generateTestAlertInstance(orgID, fmt.Sprintf("large-%d", i))
|
||||
largeSet[i] = *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID(fmt.Sprintf("large-%d", i)))
|
||||
}
|
||||
|
||||
err = ng.InstanceStore.FullSync(ctx, largeSet, 50, nil)
|
||||
@@ -520,7 +531,10 @@ func TestIntegrationFullSyncWithJitter(t *testing.T) {
|
||||
|
||||
instances := make([]models.AlertInstance, len(ruleUIDs))
|
||||
for i, ruleUID := range ruleUIDs {
|
||||
instances[i] = generateTestAlertInstance(orgID, ruleUID)
|
||||
instances[i] = *models.AlertInstanceGen(
|
||||
models.InstanceMuts.WithOrgID(orgID),
|
||||
models.InstanceMuts.WithRuleUID(ruleUID),
|
||||
)
|
||||
}
|
||||
|
||||
// Simple jitter function for testing
|
||||
@@ -565,7 +579,7 @@ func TestIntegrationFullSyncWithJitter(t *testing.T) {
|
||||
t.Run("Should handle zero delays (immediate execution)", func(t *testing.T) {
|
||||
testInstances := make([]models.AlertInstance, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
testInstances[i] = generateTestAlertInstance(orgID, fmt.Sprintf("immediate-%d", i))
|
||||
testInstances[i] = *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID(fmt.Sprintf("immediate-%d", i)))
|
||||
}
|
||||
|
||||
// Function that returns zero delays
|
||||
@@ -592,7 +606,7 @@ func TestIntegrationFullSyncWithJitter(t *testing.T) {
|
||||
t.Run("Should execute jitter delays correctly and save data", func(t *testing.T) {
|
||||
testInstances := make([]models.AlertInstance, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
testInstances[i] = generateTestAlertInstance(orgID, fmt.Sprintf("jitter-test-%d", i))
|
||||
testInstances[i] = *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID(fmt.Sprintf("jitter-test-%d", i)))
|
||||
}
|
||||
|
||||
// Track jitter function calls
|
||||
@@ -652,11 +666,16 @@ func TestIntegration_ProtoInstanceDBStore_VerifyCompressedData(t *testing.T) {
|
||||
|
||||
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),
|
||||
*models.AlertInstanceGen(
|
||||
models.InstanceMuts.WithOrgID(alertRule.OrgID),
|
||||
models.InstanceMuts.WithRuleUID(alertRule.UID),
|
||||
models.InstanceMuts.WithLabelsHash("hash1"),
|
||||
models.InstanceMuts.WithReason("reason"),
|
||||
models.InstanceMuts.WithState(models.InstanceStateFiring),
|
||||
models.InstanceMuts.WithLabels(models.InstanceLabels{"label1": "value1"}),
|
||||
models.InstanceMuts.WithAnnotations(models.InstanceAnnotations{"annotation1": "value1"}),
|
||||
),
|
||||
}
|
||||
|
||||
err := ng.InstanceStore.SaveAlertInstancesForRule(ctx, alertRule.GetKeyWithGroup(), instances)
|
||||
@@ -704,25 +723,3 @@ func decompressAlertInstances(compressed []byte) ([]*pb.AlertInstance, error) {
|
||||
|
||||
return instances.Instances, nil
|
||||
}
|
||||
|
||||
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(),
|
||||
LastSentAt: util.Pointer(time.Now()),
|
||||
FiredAt: util.Pointer(time.Now()),
|
||||
ResolvedAt: util.Pointer(time.Now()),
|
||||
CurrentReason: "abc",
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user