Alerting: Store instance annotations in alert rule state (#114975)
Alerting: Store annotations in alert instance state
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
type AlertInstance struct {
|
||||
AlertInstanceKey `xorm:"extends"`
|
||||
Labels InstanceLabels
|
||||
Annotations InstanceAnnotations
|
||||
CurrentState InstanceStateType
|
||||
CurrentReason string
|
||||
CurrentStateSince time.Time
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// InstanceAnnotations is an extension to map[string]string with methods
|
||||
// for database serialization.
|
||||
type InstanceAnnotations map[string]string
|
||||
|
||||
// FromDB loads annotations stored in the database as JSON into InstanceAnnotations.
|
||||
// FromDB is part of the xorm Conversion interface.
|
||||
func (a *InstanceAnnotations) FromDB(b []byte) error {
|
||||
if len(b) == 0 {
|
||||
*a = nil
|
||||
return nil
|
||||
}
|
||||
annotations := make(map[string]string)
|
||||
err := json.Unmarshal(b, &annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*a = annotations
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB serializes InstanceAnnotations to JSON for database storage.
|
||||
// ToDB is part of the xorm Conversion interface.
|
||||
func (a *InstanceAnnotations) ToDB() ([]byte, error) {
|
||||
if a == nil || len(*a) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return json.Marshal(*a)
|
||||
}
|
||||
@@ -32,9 +32,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
RuleMuts = AlertRuleMutators{}
|
||||
NSMuts = NotificationSettingsMutators{}
|
||||
RuleGen = &AlertRuleGenerator{
|
||||
RuleMuts = AlertRuleMutators{}
|
||||
NSMuts = NotificationSettingsMutators{}
|
||||
InstanceMuts = AlertInstanceMutators{}
|
||||
RuleGen = &AlertRuleGenerator{
|
||||
mutators: []AlertRuleMutator{
|
||||
RuleMuts.WithUniqueUID(), RuleMuts.WithUniqueTitle(),
|
||||
},
|
||||
@@ -928,6 +929,50 @@ func AlertInstanceGen(mutators ...AlertInstanceMutator) *AlertInstance {
|
||||
return instance
|
||||
}
|
||||
|
||||
type AlertInstanceMutators struct{}
|
||||
|
||||
func (a AlertInstanceMutators) WithOrgID(orgID int64) AlertInstanceMutator {
|
||||
return func(i *AlertInstance) {
|
||||
i.RuleOrgID = orgID
|
||||
}
|
||||
}
|
||||
|
||||
func (a AlertInstanceMutators) WithRuleUID(ruleUID string) AlertInstanceMutator {
|
||||
return func(i *AlertInstance) {
|
||||
i.RuleUID = ruleUID
|
||||
}
|
||||
}
|
||||
|
||||
func (a AlertInstanceMutators) WithLabelsHash(hash string) AlertInstanceMutator {
|
||||
return func(i *AlertInstance) {
|
||||
i.LabelsHash = hash
|
||||
}
|
||||
}
|
||||
|
||||
func (a AlertInstanceMutators) WithReason(reason string) AlertInstanceMutator {
|
||||
return func(i *AlertInstance) {
|
||||
i.CurrentReason = reason
|
||||
}
|
||||
}
|
||||
|
||||
func (a AlertInstanceMutators) WithState(state InstanceStateType) AlertInstanceMutator {
|
||||
return func(i *AlertInstance) {
|
||||
i.CurrentState = state
|
||||
}
|
||||
}
|
||||
|
||||
func (a AlertInstanceMutators) WithLabels(labels InstanceLabels) AlertInstanceMutator {
|
||||
return func(i *AlertInstance) {
|
||||
i.Labels = labels
|
||||
}
|
||||
}
|
||||
|
||||
func (a AlertInstanceMutators) WithAnnotations(annotations InstanceAnnotations) AlertInstanceMutator {
|
||||
return func(i *AlertInstance) {
|
||||
i.Annotations = annotations
|
||||
}
|
||||
}
|
||||
|
||||
type Mutator[T any] func(*T)
|
||||
|
||||
// CopyNotificationSettings creates a deep copy of NotificationSettings.
|
||||
|
||||
@@ -341,6 +341,7 @@ func (c *cache) GetAlertInstances() []ngModels.AlertInstance {
|
||||
states = append(states, ngModels.AlertInstance{
|
||||
AlertInstanceKey: key,
|
||||
Labels: ngModels.InstanceLabels(v2.Labels),
|
||||
Annotations: v2.Annotations,
|
||||
CurrentState: ngModels.InstanceStateType(v2.State.String()),
|
||||
CurrentReason: v2.StateReason,
|
||||
LastEvalTime: v2.LastEvaluationTime,
|
||||
|
||||
@@ -184,8 +184,11 @@ func (st *Manager) Warm(ctx context.Context, orgReader OrgReader, rulesReader Ru
|
||||
continue
|
||||
}
|
||||
|
||||
// nil safety.
|
||||
annotations := ruleForEntry.Annotations
|
||||
// Use persisted annotations if available, otherwise fall back to rule annotations
|
||||
annotations := entry.Annotations
|
||||
if len(annotations) == 0 {
|
||||
annotations = ruleForEntry.Annotations
|
||||
}
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func TestIntegrationWarmStateCache(t *testing.T) {
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: util.Pointer(evaluationTime),
|
||||
ResolvedAt: util.Pointer(evaluationTime),
|
||||
Annotations: map[string]string{"testAnnoKey": "testAnnoValue"},
|
||||
Annotations: rule.Annotations, // alert instance has no stored annotations, falls back to the rule annotations
|
||||
ResultFingerprint: data.Fingerprint(math.MaxUint64),
|
||||
}, {
|
||||
AlertRuleUID: rule.UID,
|
||||
@@ -87,7 +87,7 @@ func TestIntegrationWarmStateCache(t *testing.T) {
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-1 * time.Minute)),
|
||||
ResolvedAt: nil,
|
||||
Annotations: map[string]string{"testAnnoKey": "testAnnoValue"},
|
||||
Annotations: map[string]string{"testAnnotation": "value-2"},
|
||||
ResultFingerprint: data.Fingerprint(math.MaxUint64 - 1),
|
||||
},
|
||||
{
|
||||
@@ -101,7 +101,7 @@ func TestIntegrationWarmStateCache(t *testing.T) {
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-1 * time.Minute)),
|
||||
ResolvedAt: nil,
|
||||
Annotations: map[string]string{"testAnnoKey": "testAnnoValue"},
|
||||
Annotations: map[string]string{"testAnnotation": "value-3"},
|
||||
ResultFingerprint: data.Fingerprint(0),
|
||||
},
|
||||
{
|
||||
@@ -115,7 +115,7 @@ func TestIntegrationWarmStateCache(t *testing.T) {
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-1 * time.Minute)),
|
||||
ResolvedAt: nil,
|
||||
Annotations: map[string]string{"testAnnoKey": "testAnnoValue"},
|
||||
Annotations: map[string]string{"testAnnotation": "value-4"},
|
||||
ResultFingerprint: data.Fingerprint(1),
|
||||
},
|
||||
{
|
||||
@@ -129,7 +129,7 @@ func TestIntegrationWarmStateCache(t *testing.T) {
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: nil,
|
||||
ResolvedAt: nil,
|
||||
Annotations: map[string]string{"testAnnoKey": "testAnnoValue"},
|
||||
Annotations: map[string]string{"testAnnotation": "value-5"},
|
||||
ResultFingerprint: data.Fingerprint(2),
|
||||
},
|
||||
}
|
||||
@@ -169,6 +169,7 @@ func TestIntegrationWarmStateCache(t *testing.T) {
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-1 * time.Minute)),
|
||||
ResolvedAt: nil,
|
||||
Labels: labels,
|
||||
Annotations: models.InstanceAnnotations{"testAnnotation": "value-2"},
|
||||
ResultFingerprint: data.Fingerprint(math.MaxUint64 - 1).String(),
|
||||
})
|
||||
|
||||
@@ -187,6 +188,7 @@ func TestIntegrationWarmStateCache(t *testing.T) {
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-1 * time.Minute)),
|
||||
ResolvedAt: nil,
|
||||
Labels: labels,
|
||||
Annotations: models.InstanceAnnotations{"testAnnotation": "value-3"},
|
||||
ResultFingerprint: data.Fingerprint(0).String(),
|
||||
})
|
||||
|
||||
@@ -205,6 +207,7 @@ func TestIntegrationWarmStateCache(t *testing.T) {
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-1 * time.Minute)),
|
||||
ResolvedAt: nil,
|
||||
Labels: labels,
|
||||
Annotations: models.InstanceAnnotations{"testAnnotation": "value-4"},
|
||||
ResultFingerprint: data.Fingerprint(1).String(),
|
||||
})
|
||||
|
||||
@@ -223,6 +226,7 @@ func TestIntegrationWarmStateCache(t *testing.T) {
|
||||
LastSentAt: nil,
|
||||
ResolvedAt: nil,
|
||||
Labels: labels,
|
||||
Annotations: models.InstanceAnnotations{"testAnnotation": "value-5"},
|
||||
ResultFingerprint: data.Fingerprint(2).String(),
|
||||
})
|
||||
|
||||
@@ -241,6 +245,7 @@ func TestIntegrationWarmStateCache(t *testing.T) {
|
||||
LastSentAt: nil,
|
||||
ResolvedAt: nil,
|
||||
Labels: labels,
|
||||
Annotations: models.InstanceAnnotations{"testAnnotation": "value-6"},
|
||||
ResultFingerprint: data.Fingerprint(2).String(),
|
||||
})
|
||||
|
||||
@@ -1497,6 +1502,7 @@ func TestIntegrationStaleResultsHandler(t *testing.T) {
|
||||
},
|
||||
CurrentState: models.InstanceStateNormal,
|
||||
Labels: labels1,
|
||||
Annotations: rule.Annotations,
|
||||
LastEvalTime: lastEval,
|
||||
CurrentStateSince: lastEval,
|
||||
CurrentStateEnd: lastEval.Add(3 * interval),
|
||||
@@ -1512,6 +1518,7 @@ func TestIntegrationStaleResultsHandler(t *testing.T) {
|
||||
},
|
||||
CurrentState: models.InstanceStateFiring,
|
||||
Labels: labels2,
|
||||
Annotations: rule.Annotations,
|
||||
LastEvalTime: lastEval,
|
||||
CurrentStateSince: lastEval,
|
||||
CurrentStateEnd: lastEval.Add(3 * interval),
|
||||
@@ -1562,7 +1569,7 @@ func TestIntegrationStaleResultsHandler(t *testing.T) {
|
||||
LastSentAt: &lastEval,
|
||||
ResolvedAt: &lastEval,
|
||||
EvaluationDuration: 0,
|
||||
Annotations: map[string]string{"testAnnoKey": "testAnnoValue"},
|
||||
Annotations: rule.Annotations,
|
||||
ResultFingerprint: data.Labels{"test1": "testValue1"}.Fingerprint(),
|
||||
},
|
||||
},
|
||||
@@ -1810,7 +1817,7 @@ func TestIntegrationDeleteStateByRuleUID(t *testing.T) {
|
||||
Labels: data.Labels{"test1": "testValue1"},
|
||||
State: eval.Normal,
|
||||
EvaluationDuration: 0,
|
||||
Annotations: map[string]string{"testAnnoKey": "testAnnoValue"},
|
||||
Annotations: map[string]string{"testAnnotation": "value-2"},
|
||||
},
|
||||
{
|
||||
AlertRuleUID: rule.UID,
|
||||
@@ -1818,7 +1825,7 @@ func TestIntegrationDeleteStateByRuleUID(t *testing.T) {
|
||||
Labels: data.Labels{"test2": "testValue2"},
|
||||
State: eval.Alerting,
|
||||
EvaluationDuration: 0,
|
||||
Annotations: map[string]string{"testAnnoKey": "testAnnoValue"},
|
||||
Annotations: map[string]string{"testAnnotation": "value-2"},
|
||||
},
|
||||
},
|
||||
startingStateCacheCount: 2,
|
||||
@@ -1959,7 +1966,7 @@ func TestIntegrationResetStateByRuleUID(t *testing.T) {
|
||||
Labels: data.Labels{"test1": "testValue1"},
|
||||
State: eval.Normal,
|
||||
EvaluationDuration: 0,
|
||||
Annotations: map[string]string{"testAnnoKey": "testAnnoValue"},
|
||||
Annotations: map[string]string{"testAnnotation": "value-2"},
|
||||
},
|
||||
{
|
||||
AlertRuleUID: rule.UID,
|
||||
@@ -1967,7 +1974,7 @@ func TestIntegrationResetStateByRuleUID(t *testing.T) {
|
||||
Labels: data.Labels{"test2": "testValue2"},
|
||||
State: eval.Alerting,
|
||||
EvaluationDuration: 0,
|
||||
Annotations: map[string]string{"testAnnoKey": "testAnnoValue"},
|
||||
Annotations: map[string]string{"testAnnotation": "value-2"},
|
||||
},
|
||||
},
|
||||
startingStateCacheCount: 2,
|
||||
|
||||
@@ -89,6 +89,7 @@ func (a *SyncStatePersister) saveAlertStates(ctx context.Context, states ...Stat
|
||||
instance := ngModels.AlertInstance{
|
||||
AlertInstanceKey: key,
|
||||
Labels: ngModels.InstanceLabels(s.Labels),
|
||||
Annotations: s.Annotations,
|
||||
CurrentState: ngModels.InstanceStateType(s.State.State.String()),
|
||||
CurrentReason: s.StateReason,
|
||||
LastEvalTime: s.LastEvaluationTime,
|
||||
|
||||
@@ -90,6 +90,7 @@ func (a *SyncRuleStatePersister) Sync(ctx context.Context, span trace.Span, rule
|
||||
instance := models.AlertInstance{
|
||||
AlertInstanceKey: key,
|
||||
Labels: models.InstanceLabels(s.Labels),
|
||||
Annotations: s.Annotations,
|
||||
CurrentState: models.InstanceStateType(s.State.State.String()),
|
||||
CurrentReason: s.StateReason,
|
||||
LastEvalTime: s.LastEvaluationTime,
|
||||
|
||||
@@ -99,6 +99,10 @@ func (st InstanceDBStore) SaveAlertInstance(ctx context.Context, alertInstance m
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
annotationsJSON, err := alertInstance.Annotations.ToDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params := append(make([]any, 0),
|
||||
alertInstance.RuleOrgID,
|
||||
alertInstance.RuleUID,
|
||||
@@ -113,12 +117,13 @@ func (st InstanceDBStore) SaveAlertInstance(ctx context.Context, alertInstance m
|
||||
nullableTimeToUnix(alertInstance.ResolvedAt),
|
||||
nullableTimeToUnix(alertInstance.LastSentAt),
|
||||
alertInstance.ResultFingerprint,
|
||||
annotationsJSON,
|
||||
)
|
||||
|
||||
upsertSQL := st.SQLStore.GetDialect().UpsertSQL(
|
||||
"alert_instance",
|
||||
[]string{"rule_org_id", "rule_uid", "labels_hash"},
|
||||
[]string{"rule_org_id", "rule_uid", "labels", "labels_hash", "current_state", "current_reason", "current_state_since", "current_state_end", "last_eval_time", "fired_at", "resolved_at", "last_sent_at", "result_fingerprint"})
|
||||
[]string{"rule_org_id", "rule_uid", "labels", "labels_hash", "current_state", "current_reason", "current_state_since", "current_state_end", "last_eval_time", "fired_at", "resolved_at", "last_sent_at", "result_fingerprint", "annotations"})
|
||||
_, err = sess.SQL(upsertSQL, params...).Query()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -359,10 +364,10 @@ func (st InstanceDBStore) insertInstancesBatch(sess *sqlstore.DBSession, batch [
|
||||
|
||||
query := strings.Builder{}
|
||||
placeholders := make([]string, 0, len(batch))
|
||||
args := make([]any, 0, len(batch)*12)
|
||||
args := make([]any, 0, len(batch)*13)
|
||||
|
||||
query.WriteString("INSERT INTO alert_instance ")
|
||||
query.WriteString("(rule_org_id, rule_uid, labels, labels_hash, current_state, current_reason, current_state_since, current_state_end, last_eval_time, fired_at, resolved_at, last_sent_at) VALUES ")
|
||||
query.WriteString("(rule_org_id, rule_uid, labels, labels_hash, current_state, current_reason, current_state_since, current_state_end, last_eval_time, fired_at, resolved_at, last_sent_at, annotations) VALUES ")
|
||||
|
||||
for _, instance := range batch {
|
||||
if err := models.ValidateAlertInstance(instance); err != nil {
|
||||
@@ -376,7 +381,13 @@ func (st InstanceDBStore) insertInstancesBatch(sess *sqlstore.DBSession, batch [
|
||||
continue
|
||||
}
|
||||
|
||||
placeholders = append(placeholders, "(?,?,?,?,?,?,?,?,?,?,?,?)")
|
||||
annotationsJSON, err := instance.Annotations.ToDB()
|
||||
if err != nil {
|
||||
st.Logger.Warn("Skipping instance with invalid annotations", "err", err, "rule_uid", instance.RuleUID)
|
||||
continue
|
||||
}
|
||||
|
||||
placeholders = append(placeholders, "(?,?,?,?,?,?,?,?,?,?,?,?,?)")
|
||||
args = append(args,
|
||||
instance.RuleOrgID,
|
||||
instance.RuleUID,
|
||||
@@ -390,6 +401,7 @@ func (st InstanceDBStore) insertInstancesBatch(sess *sqlstore.DBSession, batch [
|
||||
nullableTimeToUnix(instance.FiredAt),
|
||||
nullableTimeToUnix(instance.ResolvedAt),
|
||||
nullableTimeToUnix(instance.LastSentAt),
|
||||
annotationsJSON,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ type AlertInstance struct {
|
||||
ResolvedAt *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=resolved_at,json=resolvedAt,proto3" json:"resolved_at,omitempty"`
|
||||
ResultFingerprint string `protobuf:"bytes,10,opt,name=result_fingerprint,json=resultFingerprint,proto3" json:"result_fingerprint,omitempty"`
|
||||
FiredAt *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=fired_at,json=firedAt,proto3" json:"fired_at,omitempty"`
|
||||
Annotations map[string]string `protobuf:"bytes,12,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -146,6 +147,13 @@ func (x *AlertInstance) GetFiredAt() *timestamppb.Timestamp {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AlertInstance) GetAnnotations() map[string]string {
|
||||
if x != nil {
|
||||
return x.Annotations
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AlertInstances struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Instances []*AlertInstance `protobuf:"bytes,1,rep,name=instances,proto3" json:"instances,omitempty"`
|
||||
@@ -197,7 +205,7 @@ var file_alert_rule_state_proto_rawDesc = string([]byte{
|
||||
0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x6e, 0x67, 0x61, 0x6c, 0x65, 0x72,
|
||||
0x74, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb3, 0x05, 0x0a, 0x0d,
|
||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc7, 0x06, 0x0a, 0x0d,
|
||||
0x41, 0x6c, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x0a,
|
||||
0x0b, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0a, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x43,
|
||||
@@ -237,20 +245,29 @@ var file_alert_rule_state_proto_rawDesc = string([]byte{
|
||||
0x35, 0x0a, 0x08, 0x66, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x66,
|
||||
0x69, 0x72, 0x65, 0x64, 0x41, 0x74, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73,
|
||||
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
|
||||
0x01, 0x22, 0x4f, 0x0a, 0x0e, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e,
|
||||
0x63, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73,
|
||||
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6e, 0x67, 0x61, 0x6c, 0x65, 0x72, 0x74,
|
||||
0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x49,
|
||||
0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63,
|
||||
0x65, 0x73, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61,
|
||||
0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x6e, 0x67,
|
||||
0x61, 0x6c, 0x65, 0x72, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x69, 0x72, 0x65, 0x64, 0x41, 0x74, 0x12, 0x52, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6e, 0x67,
|
||||
0x61, 0x6c, 0x65, 0x72, 0x74, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41,
|
||||
0x6c, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x41, 0x6e, 0x6e,
|
||||
0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x61,
|
||||
0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61,
|
||||
0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3e, 0x0a, 0x10, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4f, 0x0a, 0x0e, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x49, 0x6e,
|
||||
0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61,
|
||||
0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6e, 0x67, 0x61,
|
||||
0x6c, 0x65, 0x72, 0x74, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x6c,
|
||||
0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x09, 0x69, 0x6e, 0x73,
|
||||
0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61,
|
||||
0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||
0x73, 0x2f, 0x6e, 0x67, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
})
|
||||
|
||||
var (
|
||||
@@ -265,27 +282,29 @@ func file_alert_rule_state_proto_rawDescGZIP() []byte {
|
||||
return file_alert_rule_state_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_alert_rule_state_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_alert_rule_state_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_alert_rule_state_proto_goTypes = []any{
|
||||
(*AlertInstance)(nil), // 0: ngalert.store.v1.AlertInstance
|
||||
(*AlertInstances)(nil), // 1: ngalert.store.v1.AlertInstances
|
||||
nil, // 2: ngalert.store.v1.AlertInstance.LabelsEntry
|
||||
(*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp
|
||||
nil, // 3: ngalert.store.v1.AlertInstance.AnnotationsEntry
|
||||
(*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
|
||||
}
|
||||
var file_alert_rule_state_proto_depIdxs = []int32{
|
||||
2, // 0: ngalert.store.v1.AlertInstance.labels:type_name -> ngalert.store.v1.AlertInstance.LabelsEntry
|
||||
3, // 1: ngalert.store.v1.AlertInstance.current_state_since:type_name -> google.protobuf.Timestamp
|
||||
3, // 2: ngalert.store.v1.AlertInstance.current_state_end:type_name -> google.protobuf.Timestamp
|
||||
3, // 3: ngalert.store.v1.AlertInstance.last_eval_time:type_name -> google.protobuf.Timestamp
|
||||
3, // 4: ngalert.store.v1.AlertInstance.last_sent_at:type_name -> google.protobuf.Timestamp
|
||||
3, // 5: ngalert.store.v1.AlertInstance.resolved_at:type_name -> google.protobuf.Timestamp
|
||||
3, // 6: ngalert.store.v1.AlertInstance.fired_at:type_name -> google.protobuf.Timestamp
|
||||
0, // 7: ngalert.store.v1.AlertInstances.instances:type_name -> ngalert.store.v1.AlertInstance
|
||||
8, // [8:8] is the sub-list for method output_type
|
||||
8, // [8:8] is the sub-list for method input_type
|
||||
8, // [8:8] is the sub-list for extension type_name
|
||||
8, // [8:8] is the sub-list for extension extendee
|
||||
0, // [0:8] is the sub-list for field type_name
|
||||
4, // 1: ngalert.store.v1.AlertInstance.current_state_since:type_name -> google.protobuf.Timestamp
|
||||
4, // 2: ngalert.store.v1.AlertInstance.current_state_end:type_name -> google.protobuf.Timestamp
|
||||
4, // 3: ngalert.store.v1.AlertInstance.last_eval_time:type_name -> google.protobuf.Timestamp
|
||||
4, // 4: ngalert.store.v1.AlertInstance.last_sent_at:type_name -> google.protobuf.Timestamp
|
||||
4, // 5: ngalert.store.v1.AlertInstance.resolved_at:type_name -> google.protobuf.Timestamp
|
||||
4, // 6: ngalert.store.v1.AlertInstance.fired_at:type_name -> google.protobuf.Timestamp
|
||||
3, // 7: ngalert.store.v1.AlertInstance.annotations:type_name -> ngalert.store.v1.AlertInstance.AnnotationsEntry
|
||||
0, // 8: ngalert.store.v1.AlertInstances.instances:type_name -> ngalert.store.v1.AlertInstance
|
||||
9, // [9:9] is the sub-list for method output_type
|
||||
9, // [9:9] is the sub-list for method input_type
|
||||
9, // [9:9] is the sub-list for extension type_name
|
||||
9, // [9:9] is the sub-list for extension extendee
|
||||
0, // [0:9] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_alert_rule_state_proto_init() }
|
||||
@@ -299,7 +318,7 @@ func file_alert_rule_state_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_alert_rule_state_proto_rawDesc), len(file_alert_rule_state_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 3,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -18,6 +18,7 @@ message AlertInstance {
|
||||
google.protobuf.Timestamp resolved_at = 9;
|
||||
string result_fingerprint = 10;
|
||||
google.protobuf.Timestamp fired_at = 11;
|
||||
map<string, string> annotations = 12;
|
||||
}
|
||||
|
||||
message AlertInstances {
|
||||
|
||||
@@ -188,6 +188,7 @@ func alertInstanceModelToProto(modelInstance models.AlertInstance) *pb.AlertInst
|
||||
return &pb.AlertInstance{
|
||||
Labels: modelInstance.Labels,
|
||||
LabelsHash: modelInstance.LabelsHash,
|
||||
Annotations: modelInstance.Annotations,
|
||||
CurrentState: string(modelInstance.CurrentState),
|
||||
CurrentStateSince: timestamppb.New(modelInstance.CurrentStateSince),
|
||||
CurrentStateEnd: timestamppb.New(modelInstance.CurrentStateEnd),
|
||||
@@ -255,6 +256,7 @@ func alertInstanceProtoToModel(ruleUID string, ruleOrgID int64, protoInstance *p
|
||||
LabelsHash: protoInstance.LabelsHash,
|
||||
},
|
||||
Labels: protoInstance.Labels,
|
||||
Annotations: protoInstance.Annotations,
|
||||
CurrentState: models.InstanceStateType(protoInstance.CurrentState),
|
||||
CurrentStateSince: protoInstance.CurrentStateSince.AsTime(),
|
||||
CurrentStateEnd: protoInstance.CurrentStateEnd.AsTime(),
|
||||
|
||||
@@ -19,6 +19,7 @@ func TestAlertInstanceModelToProto(t *testing.T) {
|
||||
lastSentAt := currentStateSince.Add(-2 * time.Minute)
|
||||
firedAt := currentStateSince.Add(-2 * time.Minute)
|
||||
resolvedAt := currentStateSince.Add(-3 * time.Minute)
|
||||
annotations := map[string]string{"summary": "value", "team": "alerting"}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -28,7 +29,8 @@ func TestAlertInstanceModelToProto(t *testing.T) {
|
||||
{
|
||||
name: "valid instance",
|
||||
input: models.AlertInstance{
|
||||
Labels: map[string]string{"key": "value"},
|
||||
Labels: map[string]string{"key": "value"},
|
||||
Annotations: annotations,
|
||||
AlertInstanceKey: models.AlertInstanceKey{
|
||||
RuleUID: "rule-uid-1",
|
||||
RuleOrgID: 1,
|
||||
@@ -46,6 +48,7 @@ func TestAlertInstanceModelToProto(t *testing.T) {
|
||||
},
|
||||
expected: &pb.AlertInstance{
|
||||
Labels: map[string]string{"key": "value"},
|
||||
Annotations: annotations,
|
||||
LabelsHash: "hash123",
|
||||
CurrentState: "Alerting",
|
||||
CurrentStateSince: timestamppb.New(currentStateSince),
|
||||
@@ -75,6 +78,7 @@ func TestAlertInstanceProtoToModel(t *testing.T) {
|
||||
lastSentAt := currentStateSince.Add(-2 * time.Minute).UTC()
|
||||
firedAt := currentStateSince.Add(-2 * time.Minute).UTC()
|
||||
resolvedAt := currentStateSince.Add(-3 * time.Minute).UTC()
|
||||
annotations := map[string]string{"summary": "value", "team": "alerting"}
|
||||
ruleUID := "rule-uid-1"
|
||||
orgID := int64(1)
|
||||
|
||||
@@ -87,6 +91,7 @@ func TestAlertInstanceProtoToModel(t *testing.T) {
|
||||
name: "valid instance",
|
||||
input: &pb.AlertInstance{
|
||||
Labels: map[string]string{"key": "value"},
|
||||
Annotations: annotations,
|
||||
LabelsHash: "hash123",
|
||||
CurrentState: "Alerting",
|
||||
CurrentStateSince: timestamppb.New(currentStateSince),
|
||||
@@ -98,7 +103,8 @@ func TestAlertInstanceProtoToModel(t *testing.T) {
|
||||
ResultFingerprint: "fingerprint",
|
||||
},
|
||||
expected: &models.AlertInstance{
|
||||
Labels: map[string]string{"key": "value"},
|
||||
Labels: map[string]string{"key": "value"},
|
||||
Annotations: annotations,
|
||||
AlertInstanceKey: models.AlertInstanceKey{
|
||||
RuleUID: ruleUID,
|
||||
RuleOrgID: orgID,
|
||||
@@ -132,7 +138,7 @@ func TestModelAlertInstanceMatchesProtobuf(t *testing.T) {
|
||||
// and update them accordingly.
|
||||
t.Run("when AlertInstance model changes", func(t *testing.T) {
|
||||
modelType := reflect.TypeOf(models.AlertInstance{})
|
||||
require.Equal(t, 11, modelType.NumField(), "AlertInstance model has changed, update the protobuf")
|
||||
require.Equal(t, 12, modelType.NumField(), "AlertInstance model has changed, update the protobuf")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -142,6 +148,7 @@ func TestCompressAndDecompressAlertInstances(t *testing.T) {
|
||||
alertInstances := []*pb.AlertInstance{
|
||||
{
|
||||
Labels: map[string]string{"label-1": "value-1"},
|
||||
Annotations: map[string]string{"anno-1": "value-1"},
|
||||
LabelsHash: "hash-1",
|
||||
CurrentState: "normal",
|
||||
CurrentStateSince: timestamppb.New(now),
|
||||
@@ -154,6 +161,7 @@ func TestCompressAndDecompressAlertInstances(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Labels: map[string]string{"label-2": "value-2"},
|
||||
Annotations: map[string]string{"anno-2": "value-2"},
|
||||
LabelsHash: "hash-2",
|
||||
CurrentState: "firing",
|
||||
CurrentStateSince: timestamppb.New(now),
|
||||
@@ -185,6 +193,7 @@ func TestConvertAndCompressAlertInstances(t *testing.T) {
|
||||
LabelsHash: "hash-1",
|
||||
},
|
||||
Labels: map[string]string{"label-1": "value-1"},
|
||||
Annotations: map[string]string{"anno-1": "value-1"},
|
||||
CurrentState: models.InstanceStateFiring,
|
||||
CurrentStateSince: now,
|
||||
CurrentStateEnd: now.Add(time.Hour),
|
||||
@@ -202,6 +211,7 @@ func TestConvertAndCompressAlertInstances(t *testing.T) {
|
||||
LabelsHash: "hash-2",
|
||||
},
|
||||
Labels: map[string]string{"label-2": "value-2"},
|
||||
Annotations: map[string]string{"anno-2": "value-2"},
|
||||
CurrentState: models.InstanceStateNormal,
|
||||
CurrentStateSince: now,
|
||||
CurrentStateEnd: now.Add(time.Hour),
|
||||
@@ -227,6 +237,7 @@ func TestConvertAndCompressAlertInstances(t *testing.T) {
|
||||
for i, protoInstance := range decompressedInstances {
|
||||
modelInstance := alertInstanceProtoToModel("rule-uid-1", 1, protoInstance)
|
||||
require.Equal(t, modelInstances[i].Labels, modelInstance.Labels)
|
||||
require.Equal(t, modelInstances[i].Annotations, modelInstance.Annotations)
|
||||
require.Equal(t, modelInstances[i].CurrentState, modelInstance.CurrentState)
|
||||
require.Equal(t, modelInstances[i].LabelsHash, modelInstance.LabelsHash)
|
||||
require.Equal(t, modelInstances[i].ResultFingerprint, modelInstance.ResultFingerprint)
|
||||
|
||||
@@ -163,5 +163,7 @@ func (oss *OSSMigrations) AddMigration(mg *Migrator) {
|
||||
|
||||
ualert.AddAlertRuleGroupIndexMigration(mg)
|
||||
|
||||
ualert.AddStateAnnotationsColumn(mg)
|
||||
|
||||
ualert.CollateBinAlertRuleGroup(mg)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package ualert
|
||||
|
||||
import "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
|
||||
// AddStateAnnotationsColumn adds annotations column to alert_instance
|
||||
func AddStateAnnotationsColumn(mg *migrator.Migrator) {
|
||||
mg.AddMigration("add annotations column to alert_instance table", migrator.NewAddColumnMigration(migrator.Table{Name: "alert_instance"}, &migrator.Column{
|
||||
Name: "annotations",
|
||||
Type: migrator.DB_Text,
|
||||
Nullable: true,
|
||||
}))
|
||||
}
|
||||
@@ -388,7 +388,7 @@ func (x *ResourceSearchResponse) GetFacet() map[string]*ResourceSearchResponse_F
|
||||
|
||||
type RebuildIndexesRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Namespace (tenant)
|
||||
// Namespace (tenant) must be the same as all keys' namespace
|
||||
Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
|
||||
// List of ResourceKeys (Namespace + Group + Resource)
|
||||
Keys []*ResourceKey `protobuf:"bytes,2,rep,name=keys,proto3" json:"keys,omitempty"`
|
||||
|
||||
Reference in New Issue
Block a user