Rule alerts go to the rule's own org

SendAlerts addressed every alert to `SELECT id FROM organizations LIMIT 1`,
which is whichever org sorts first, not the rule's. That org's
alertmanager has none of the rule's channels or route policies, so the
alert sat on its default receiver and reached nobody, and that org's
members could see it. Alerts now carry the rule's own org id.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
zooqueen
2026-09-24 16:40:51 -07:00
co-authored by Hanzo Dev
parent 1fcc796c73
commit 62b4ca36dc
3 changed files with 40 additions and 31 deletions
+4 -15
View File
@@ -358,21 +358,10 @@ func (r *BaseRule) ActiveAlerts() []*ruletypes.Alert {
return res
}
// SendAlerts hands the rule's alerts that are due to notifyFunc, addressed to
// the rule's own org: its alertmanager holds the channels and route policies
// the rule's thresholds name.
func (r *BaseRule) SendAlerts(ctx context.Context, ts time.Time, resendDelay time.Duration, interval time.Duration, notifyFunc NotifyFunc) {
var orgID string
err := r.
sqlstore.
BunDB().
NewSelect().
Table("organizations").
ColumnExpr("id").
Limit(1).
Scan(ctx, &orgID)
if err != nil {
r.logger.ErrorContext(ctx, "failed to get org ids", errors.Attr(err))
return
}
alerts := []*ruletypes.Alert{}
r.ForEachActiveAlert(func(alert *ruletypes.Alert) {
if alert.NeedsSending(ts, resendDelay) {
@@ -386,7 +375,7 @@ func (r *BaseRule) SendAlerts(ctx context.Context, ts time.Time, resendDelay tim
alerts = append(alerts, &anew)
}
})
notifyFunc(ctx, orgID, alerts...)
notifyFunc(ctx, r.orgID.StringValue(), alerts...)
}
// withTenant scopes ctx to the rule's org when req reads event.log or
-16
View File
@@ -14,8 +14,6 @@ import (
"github.com/hanzoai/o11y/pkg/instrumentation/instrumentationtest"
"github.com/hanzoai/o11y/pkg/prometheus"
"github.com/hanzoai/o11y/pkg/prometheus/prometheustest"
"github.com/hanzoai/o11y/pkg/sqlstore"
"github.com/hanzoai/o11y/pkg/sqlstore/sqlstoretest"
"github.com/hanzoai/o11y/pkg/telemetrystore"
"github.com/hanzoai/o11y/pkg/telemetrystore/telemetrystoretest"
"github.com/hanzoai/o11y/pkg/types/alertmanagertypes"
@@ -59,14 +57,6 @@ func TestManager_TestNotification_SendUnmatched_ThresholdRule(t *testing.T) {
}).Return(nil).Times(tc.ExpectAlerts)
}
},
SqlStoreHook: func(store sqlstore.SQLStore) {
mockStore := store.(*sqlstoretest.Provider)
// Mock the organizations query that SendAlerts makes
// Bun generates: SELECT id FROM organizations LIMIT 1 (or SELECT "id" FROM "organizations" LIMIT 1)
orgRows := mockStore.Mock().NewRows([]string{"id"}).AddRow(orgID.StringValue())
// Match bun's generated query pattern - bun may quote identifiers
mockStore.Mock().ExpectQuery("SELECT (.+) FROM (.+)organizations(.+) LIMIT (.+)").WillReturnRows(orgRows)
},
TelemetryStoreHook: func(store telemetrystore.TelemetryStore) {
mockStore := store.(*telemetrystoretest.Provider)
// Set up mock data for telemetry store
@@ -172,12 +162,6 @@ func TestManager_TestNotification_SendUnmatched_PromRule(t *testing.T) {
}).Return(nil).Times(tc.ExpectAlerts)
}
},
SqlStoreHook: func(store sqlstore.SQLStore) {
mockStore := store.(*sqlstoretest.Provider)
// Mock the organizations query that SendAlerts makes
orgRows := mockStore.Mock().NewRows([]string{"id"}).AddRow(orgID.StringValue())
mockStore.Mock().ExpectQuery("SELECT (.+) FROM (.+)organizations(.+) LIMIT (.+)").WillReturnRows(orgRows)
},
TelemetryStoreHook: func(store telemetrystore.TelemetryStore) {
mockStore := store.(*telemetrystoretest.Provider)
@@ -0,0 +1,36 @@
package rules
import (
"context"
"testing"
"time"
"github.com/hanzoai/o11y/pkg/instrumentation/instrumentationtest"
"github.com/hanzoai/o11y/pkg/types/ruletypes"
"github.com/hanzoai/o11y/pkg/valuer"
"github.com/stretchr/testify/require"
)
// A rule's alerts are addressed to the rule's own org, whichever org the
// organizations table happens to list first.
func TestSendAlertsAddressesTheRulesOrg(t *testing.T) {
orgID := valuer.GenerateUUID()
now := time.Now()
r := &BaseRule{
id: "rule-1",
orgID: orgID,
logger: instrumentationtest.New().Logger(),
Active: map[uint64]*ruletypes.Alert{
1: {State: ruletypes.StateFiring, FiredAt: now.Add(-time.Minute)},
},
}
var got string
var sent int
r.SendAlerts(context.Background(), now, time.Minute, time.Minute, func(_ context.Context, org string, alerts ...*ruletypes.Alert) {
got, sent = org, len(alerts)
})
require.Equal(t, 1, sent)
require.Equal(t, orgID.StringValue(), got)
}