Skip to content

Commit

Permalink
feat: filter on notification silence
Browse files Browse the repository at this point in the history
  • Loading branch information
adityathebe committed Nov 7, 2024
1 parent d6515d9 commit d9211c5
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 32 deletions.
14 changes: 6 additions & 8 deletions db/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,12 @@ func NotificationSendSummary(ctx context.Context, id string, window time.Duratio
return earliest.Time, count, err
}

func GetMatchingNotificationSilencesCount(ctx context.Context, resources models.NotificationSilenceResource) (int64, error) {
func GetMatchingNotificationSilences(ctx context.Context, resources models.NotificationSilenceResource) ([]models.NotificationSilence, error) {
_ = ctx.DB().Use(extraClausePlugin.New())

query := ctx.DB().Model(&models.NotificationSilence{})

// Initialize with a false condition,
// if no resources are provided, the query won't return all records
orClauses := ctx.DB().Where("1 = 0")
orClauses := ctx.DB().Where("filter != ''")

if resources.ConfigID != nil {
orClauses = orClauses.Or("config_id = ?", *resources.ConfigID)
Expand Down Expand Up @@ -168,13 +166,13 @@ func GetMatchingNotificationSilencesCount(ctx context.Context, resources models.

query = query.Where(orClauses)

var count int64
err := query.Count(&count).Where(`"from" <= NOW()`).Where("until >= NOW()").Where("deleted_at IS NULL").Error
var silences []models.NotificationSilence
err := query.Where(`"from" <= NOW()`).Where("until >= NOW()").Where("deleted_at IS NULL").Find(&silences).Error
if err != nil {
return 0, err
return nil, err
}

return count, nil
return silences, nil
}

func SaveUnsentNotificationToHistory(ctx context.Context, sendHistory models.NotificationSendHistory, window time.Duration) error {
Expand Down
32 changes: 16 additions & 16 deletions db/notifications_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,55 +52,55 @@ var _ = ginkgo.Describe("Notification Silence", ginkgo.Ordered, func() {

ginkgo.Context("non recursive match", func() {
ginkgo.It("should match", func() {
matched, err := GetMatchingNotificationSilencesCount(DefaultContext, models.NotificationSilenceResource{ConfigID: lo.ToPtr(dummy.EKSCluster.ID.String())})
matched, err := GetMatchingNotificationSilences(DefaultContext, models.NotificationSilenceResource{ConfigID: lo.ToPtr(dummy.EKSCluster.ID.String())})
Expect(err).To(BeNil())
Expect(matched).To(Equal(int64(1)))
Expect(len(matched)).To(Equal(int64(1)))

Check failure on line 57 in db/notifications_test.go

View workflow job for this annotation

GitHub Actions / test

It 11/07/24 09:03:08.551
})

ginkgo.It("should not match", func() {
matched, err := GetMatchingNotificationSilencesCount(DefaultContext, models.NotificationSilenceResource{ConfigID: lo.ToPtr(dummy.KubernetesCluster.ID.String())})
matched, err := GetMatchingNotificationSilences(DefaultContext, models.NotificationSilenceResource{ConfigID: lo.ToPtr(dummy.KubernetesCluster.ID.String())})
Expect(err).To(BeNil())
Expect(matched).To(Equal(int64(0)))
Expect(len(matched)).To(Equal(int64(0)))
})
})

ginkgo.Context("config recursive match", func() {
ginkgo.It("should match a child", func() {
matched, err := GetMatchingNotificationSilencesCount(DefaultContext, models.NotificationSilenceResource{ConfigID: lo.ToPtr(dummy.LogisticsAPIReplicaSet.ID.String())})
matched, err := GetMatchingNotificationSilences(DefaultContext, models.NotificationSilenceResource{ConfigID: lo.ToPtr(dummy.LogisticsAPIReplicaSet.ID.String())})
Expect(err).To(BeNil())
Expect(matched).To(Equal(int64(1)))
Expect(len(matched)).To(Equal(int64(1)))
})

ginkgo.It("should match a grand child", func() {
matched, err := GetMatchingNotificationSilencesCount(DefaultContext, models.NotificationSilenceResource{ConfigID: lo.ToPtr(dummy.LogisticsAPIPodConfig.ID.String())})
matched, err := GetMatchingNotificationSilences(DefaultContext, models.NotificationSilenceResource{ConfigID: lo.ToPtr(dummy.LogisticsAPIPodConfig.ID.String())})
Expect(err).To(BeNil())
Expect(matched).To(Equal(int64(1)))
Expect(len(matched)).To(Equal(int64(1)))
})

ginkgo.It("should not match", func() {
matched, err := GetMatchingNotificationSilencesCount(DefaultContext, models.NotificationSilenceResource{ConfigID: lo.ToPtr(dummy.LogisticsUIDeployment.ID.String())})
matched, err := GetMatchingNotificationSilences(DefaultContext, models.NotificationSilenceResource{ConfigID: lo.ToPtr(dummy.LogisticsUIDeployment.ID.String())})
Expect(err).To(BeNil())
Expect(matched).To(Equal(int64(0)))
Expect(len(matched)).To(Equal(int64(0)))
})
})

ginkgo.Context("component recursive match", func() {
ginkgo.It("should match a child", func() {
matched, err := GetMatchingNotificationSilencesCount(DefaultContext, models.NotificationSilenceResource{ComponentID: lo.ToPtr(dummy.LogisticsAPI.ID.String())})
matched, err := GetMatchingNotificationSilences(DefaultContext, models.NotificationSilenceResource{ComponentID: lo.ToPtr(dummy.LogisticsAPI.ID.String())})
Expect(err).To(BeNil())
Expect(matched).To(Equal(int64(1)))
Expect(len(matched)).To(Equal(int64(1)))
})

ginkgo.It("should match a grand child", func() {
matched, err := GetMatchingNotificationSilencesCount(DefaultContext, models.NotificationSilenceResource{ComponentID: lo.ToPtr(dummy.LogisticsWorker.ID.String())})
matched, err := GetMatchingNotificationSilences(DefaultContext, models.NotificationSilenceResource{ComponentID: lo.ToPtr(dummy.LogisticsWorker.ID.String())})
Expect(err).To(BeNil())
Expect(matched).To(Equal(int64(1)))
Expect(len(matched)).To(Equal(int64(1)))
})

ginkgo.It("should not match", func() {
matched, err := GetMatchingNotificationSilencesCount(DefaultContext, models.NotificationSilenceResource{ComponentID: lo.ToPtr(dummy.ClusterComponent.ID.String())})
matched, err := GetMatchingNotificationSilences(DefaultContext, models.NotificationSilenceResource{ComponentID: lo.ToPtr(dummy.ClusterComponent.ID.String())})
Expect(err).To(BeNil())
Expect(matched).To(Equal(int64(0)))
Expect(len(matched)).To(Equal(int64(0)))
})
})
})
31 changes: 27 additions & 4 deletions notification/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func (t *notificationHandler) addNotificationEvent(ctx context.Context, event mo
t.Ring.Add(event, celEnv.AsMap())

silencedResource := getSilencedResourceFromCelEnv(celEnv)
matchingSilences, err := db.GetMatchingNotificationSilencesCount(ctx, silencedResource)
matchingSilences, err := db.GetMatchingNotificationSilences(ctx, silencedResource)
if err != nil {
return err
}
Expand All @@ -147,7 +147,7 @@ func (t *notificationHandler) addNotificationEvent(ctx context.Context, event mo
return nil
}

func addNotificationEvent(ctx context.Context, id string, celEnv map[string]any, event models.Event, matchingSilences int64) error {
func addNotificationEvent(ctx context.Context, id string, celEnv map[string]any, event models.Event, matchingSilences []models.NotificationSilence) error {
n, err := GetNotification(ctx, id)
if err != nil {
return fmt.Errorf("failed to get notification %s: %w", id, err)
Expand Down Expand Up @@ -191,7 +191,9 @@ func addNotificationEvent(ctx context.Context, id string, celEnv map[string]any,
}

for _, payload := range payloads {
if matchingSilences > 0 {
if ok, err := shouldSilence(celEnv, matchingSilences); err != nil {
return err
} else if ok {
ctx.Logger.V(6).Infof("silencing notification for event %s due to %d matching silences", event.ID, matchingSilences)
ctx.Counter("notification_silenced", "id", id, "resource", payload.ID.String()).Add(1)

Expand Down Expand Up @@ -527,7 +529,12 @@ func GetEnvForEvent(ctx context.Context, event models.Event) (*celVariables, err
}

eventSuffix := strings.TrimPrefix(event.Name, "config.")
isStateUpdateEvent := slices.Contains([]string{api.EventConfigCreated, api.EventConfigChanged, api.EventConfigUpdated, api.EventConfigDeleted}, event.Name)
isStateUpdateEvent := slices.Contains([]string{
api.EventConfigChanged,
api.EventConfigCreated,
api.EventConfigDeleted,
api.EventConfigUpdated,
}, event.Name)
if isStateUpdateEvent {
env.NewState = eventSuffix
} else {
Expand All @@ -544,3 +551,19 @@ func GetEnvForEvent(ctx context.Context, event models.Event) (*celVariables, err
env.SetSilenceURL(api.FrontendURL)
return &env, nil
}

func shouldSilence(celEnv map[string]any, matchingSilences []models.NotificationSilence) (bool, error) {
for _, silence := range matchingSilences {
if silence.Filter != "" {
if ok, err := gomplate.RunTemplateBool(celEnv, gomplate.Template{Expression: string(silence.Filter)}); err != nil {
return false, fmt.Errorf("failed to run filter expression(%s): %w", silence.Filter, err)
} else if !ok {
continue
}
}

return true, nil
}

return false, nil
}
11 changes: 7 additions & 4 deletions notification/silence.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ import (
"github.com/flanksource/duty/context"
"github.com/flanksource/duty/db"
"github.com/flanksource/duty/models"
"github.com/flanksource/duty/types"
"github.com/samber/lo"
"github.com/timberio/go-datemath"
)

type SilenceSaveRequest struct {
models.NotificationSilenceResource
From string `json:"from"`
Until string `json:"until"`
Description string `json:"description"`
Recursive bool `json:"recursive"`
From string `json:"from"`
Until string `json:"until"`
Description string `json:"description"`
Recursive bool `json:"recursive"`
Filter types.CelExpression `json:"filter"`

from time.Time
until time.Time
Expand Down Expand Up @@ -63,6 +65,7 @@ func SaveNotificationSilence(ctx context.Context, req SilenceSaveRequest) error
silence := models.NotificationSilence{
NotificationSilenceResource: req.NotificationSilenceResource,
From: req.from,
Filter: req.Filter,
Until: req.until,
Description: req.Description,
Recursive: req.Recursive,
Expand Down

0 comments on commit d9211c5

Please sign in to comment.