Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions alerter/rules/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import (
)

type StoreOpts struct {
Region string
CtrlCli client.Client
Region string
Namespace string
CtrlCli client.Client
}

type Store struct {
Expand Down Expand Up @@ -108,7 +109,11 @@ func (s *Store) reloadRules() ([]*Rule, error) {
defer cancel()

ruleList := &alertrulev1.AlertRuleList{}
if err := s.ctrlCli.List(ctx, ruleList); err != nil {
var listOpts []client.ListOption
if s.opts.Namespace != "" {
listOpts = append(listOpts, client.InNamespace(s.opts.Namespace))
}
if err := s.ctrlCli.List(ctx, ruleList, listOpts...); err != nil {
return nil, fmt.Errorf("failed to list alert rules: %w", err)
}

Expand Down
113 changes: 113 additions & 0 deletions alerter/rules/store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package rules

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"

alertrulev1 "github.com/Azure/adx-mon/api/v1"
)

func newAlertRule(name, namespace, database string) *alertrulev1.AlertRule {
return &alertrulev1.AlertRule{
TypeMeta: metav1.TypeMeta{
APIVersion: "adx-mon.azure.com/v1",
Kind: "AlertRule",
},
Comment on lines +18 to +21
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: alertrulev1.AlertRuleSpec{
Database: database,
Interval: metav1.Duration{Duration: time.Minute},
Query: "TestTable | count",
Destination: "test-dest",
},
}
}

func TestStore_ReloadRules_AllNamespaces(t *testing.T) {
scheme := runtime.NewScheme()
require.NoError(t, alertrulev1.AddToScheme(scheme))

rule1 := newAlertRule("rule-a", "ns-one", "DB1")
rule2 := newAlertRule("rule-b", "ns-two", "DB2")

cli := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(rule1, rule2).
Build()

store := NewStore(StoreOpts{
Region: "eastus",
CtrlCli: cli,
})

ctx := context.Background()
require.NoError(t, store.Open(ctx))
defer store.Close()

rules := store.Rules()
require.Len(t, rules, 2)
}

func TestStore_ReloadRules_FilteredNamespace(t *testing.T) {
scheme := runtime.NewScheme()
require.NoError(t, alertrulev1.AddToScheme(scheme))

rule1 := newAlertRule("rule-a", "ns-one", "DB1")
rule2 := newAlertRule("rule-b", "ns-two", "DB2")
rule3 := newAlertRule("rule-c", "ns-one", "DB3")

cli := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(rule1, rule2, rule3).
Build()

store := NewStore(StoreOpts{
Region: "eastus",
Namespace: "ns-one",
CtrlCli: cli,
})

ctx := context.Background()
require.NoError(t, store.Open(ctx))
defer store.Close()

rules := store.Rules()
require.Len(t, rules, 2)
for _, r := range rules {
require.Equal(t, "ns-one", r.Namespace)
}
}

func TestStore_ReloadRules_FilteredNamespace_NoMatches(t *testing.T) {
scheme := runtime.NewScheme()
require.NoError(t, alertrulev1.AddToScheme(scheme))

rule1 := newAlertRule("rule-a", "ns-one", "DB1")

cli := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(rule1).
Build()

store := NewStore(StoreOpts{
Region: "eastus",
Namespace: "ns-nonexistent",
CtrlCli: cli,
})

ctx := context.Background()
require.NoError(t, store.Open(ctx))
defer store.Close()

rules := store.Rules()
require.Empty(t, rules)
}
9 changes: 7 additions & 2 deletions alerter/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ type AlerterOpts struct {
// MaxNotifications is the maximum number of notifications to send per rule.
MaxNotifications int

// Namespace is the Kubernetes namespace to watch for AlertRules.
// If empty, all namespaces are watched.
Namespace string

// Managed Identity options
MSIID string
MSIResource string
Expand Down Expand Up @@ -72,8 +76,9 @@ type KustoClient interface {

func NewService(opts *AlerterOpts) (*Alerter, error) {
ruleStore := rules.NewStore(rules.StoreOpts{
Region: opts.Region,
CtrlCli: opts.CtrlCli,
Region: opts.Region,
Namespace: opts.Namespace,
CtrlCli: opts.CtrlCli,
})

l2m := &Alerter{
Expand Down
2 changes: 2 additions & 0 deletions cmd/alerter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func main() {
&cli.StringFlag{Name: "alerter-address", Usage: "Address of the alert notification service"},
&cli.IntFlag{Name: "concurrency", Value: 10, Usage: "Number of concurrent queries to run"},
&cli.IntFlag{Name: "max-notifications", Value: 25, Usage: "Maximum number of notifications to send per rule"},
&cli.StringFlag{Name: "namespace", Usage: "Kubernetes namespace to watch for AlertRules. If not set, all namespaces are watched"},
&cli.StringSliceFlag{Name: "tag", Usage: "Tag in the format of <key>=<value> that applies to execution context"},
},
Action: realMain,
Expand Down Expand Up @@ -102,6 +103,7 @@ func realMain(ctx *cli.Context) error {
AlertAddr: ctx.String("alerter-address"),
Concurrency: ctx.Int("concurrency"),
MaxNotifications: ctx.Int("max-notifications"),
Namespace: ctx.String("namespace"),
MSIID: ctx.String("auth-msi-id"),
KustoToken: ctx.String("auth-token"),
Tags: tags,
Expand Down
Loading