Skip to content

Commit

Permalink
[Web UI] add silence / unsilence to event details page (#2640)
Browse files Browse the repository at this point in the history
Signed-off-by: James Phillips <jamesdphillips@gmail.com>
  • Loading branch information
jamesdphillips committed Jan 30, 2019
1 parent a393173 commit 15b4e2e
Show file tree
Hide file tree
Showing 32 changed files with 904 additions and 290 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -15,6 +15,7 @@ system's CA store, either by explicitly trusting the signer, or by disabling
TLS hostname verification.
- Added a generic watcher in the store.
- Added `RemoveProvider` method to authenticator.
- Added ability to silence/unsilence from the event details page.

### Changed
- Removed unused workflow `rel_build_and_test` in CircleCI config.
Expand Down
69 changes: 69 additions & 0 deletions api/core/v2/event.go
Expand Up @@ -3,6 +3,7 @@ package v2
import (
"errors"
fmt "fmt"
stringsutil "github.com/sensu/sensu-go/util/strings"
"net/url"
"sort"
"time"
Expand Down Expand Up @@ -279,3 +280,71 @@ func (e *Event) URIPath() string {
}
return fmt.Sprintf("/api/core/v2/namespaces/%s/events/%s/%s", url.PathEscape(e.Entity.Namespace), url.PathEscape(e.Entity.Name), url.PathEscape(e.Check.Name))
}

// SilencedBy returns the subset of given silences, that silence the event.
func (e *Event) SilencedBy(entries []*Silenced) []*Silenced {
silencedBy := make([]*Silenced, 0, len(entries))
if !e.HasCheck() {
return silencedBy
}

// Loop through every silenced entries in order to determine if it applies to
// the given event
for _, entry := range entries {
if e.IsSilencedBy(entry) {
silencedBy = append(silencedBy, entry)
}
}

return silencedBy
}

// IsSilencedBy returns true if given silence will silence the event.
func (e *Event) IsSilencedBy(entry *Silenced) bool {
if !e.HasCheck() {
return false
}

// Make sure the silence has started
now := time.Now().Unix()
if !entry.StartSilence(now) {
return false
}

// Is this event silenced for all subscriptions? (e.g. *:check_cpu)
if entry.Name == fmt.Sprintf("*:%s", e.Check.Name) {
return true
}

// Is this event silenced by the entity subscription? (e.g. entity:id:*)
if entry.Name == fmt.Sprintf("%s:*", GetEntitySubscription(e.Entity.Name)) {
return true
}

// Is this event silenced for this particular entity? (e.g.
// entity:id:check_cpu)
if entry.Name == fmt.Sprintf("%s:%s", GetEntitySubscription(e.Entity.Name), e.Check.Name) {
return true
}

for _, subscription := range e.Check.Subscriptions {
// Make sure the entity is subscribed to this specific subscription
if !stringsutil.InArray(subscription, e.Entity.Subscriptions) {
continue
}

// Is this event silenced by one of the check subscription? (e.g.
// load-balancer:*)
if entry.Name == fmt.Sprintf("%s:*", subscription) {
return true
}

// Is this event silenced by one of the check subscription for this
// particular check? (e.g. load-balancer:check_cpu)
if entry.Name == fmt.Sprintf("%s:%s", subscription, e.Check.Name) {
return true
}
}

return false
}
251 changes: 251 additions & 0 deletions api/core/v2/event_test.go
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"sort"
"testing"
time "time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -324,3 +325,253 @@ func TestEventsByTimestamp(t *testing.T) {
})
}
}

func TestSilencedBy(t *testing.T) {
testCases := []struct {
name string
event *Event
entries []*Silenced
expectedEntries []*Silenced
}{
{
name: "no entries",
event: FixtureEvent("foo", "check_cpu"),
entries: []*Silenced{},
expectedEntries: []*Silenced{},
},
{
name: "not silenced",
event: FixtureEvent("foo", "check_cpu"),
entries: []*Silenced{
FixtureSilenced("entity:foo:check_mem"),
FixtureSilenced("entity:bar:*"),
FixtureSilenced("foo:check_cpu"),
FixtureSilenced("foo:*"),
FixtureSilenced("*:check_mem"),
},
expectedEntries: []*Silenced{},
},
{
name: "silenced by check",
event: FixtureEvent("foo", "check_cpu"),
entries: []*Silenced{
FixtureSilenced("*:check_cpu"),
},
expectedEntries: []*Silenced{
FixtureSilenced("*:check_cpu"),
},
},
{
name: "silenced by entity subscription",
event: FixtureEvent("foo", "check_cpu"),
entries: []*Silenced{
FixtureSilenced("entity:foo:*"),
},
expectedEntries: []*Silenced{
FixtureSilenced("entity:foo:*"),
},
},
{
name: "silenced by entity's check subscription",
event: FixtureEvent("foo", "check_cpu"),
entries: []*Silenced{
FixtureSilenced("entity:foo:check_cpu"),
},
expectedEntries: []*Silenced{
FixtureSilenced("entity:foo:check_cpu"),
},
},
{
name: "silenced by check subscription",
event: FixtureEvent("foo", "check_cpu"), // has a linux subscription
entries: []*Silenced{
FixtureSilenced("linux:*"),
},
expectedEntries: []*Silenced{
FixtureSilenced("linux:*"),
},
},
{
name: "silenced by subscription with check",
event: FixtureEvent("foo", "check_cpu"), // has a linux subscription
entries: []*Silenced{
FixtureSilenced("linux:check_cpu"),
},
expectedEntries: []*Silenced{
FixtureSilenced("linux:check_cpu"),
},
},
{
name: "silenced by multiple entries",
event: FixtureEvent("foo", "check_cpu"), // has a linux subscription
entries: []*Silenced{
FixtureSilenced("entity:foo:*"),
FixtureSilenced("linux:check_cpu"),
},
expectedEntries: []*Silenced{
FixtureSilenced("entity:foo:*"),
FixtureSilenced("linux:check_cpu"),
},
},
{
name: "not silenced, silenced & client don't have a common subscription",
event: &Event{
Check: &Check{
ObjectMeta: ObjectMeta{
Name: "check_cpu",
},
Subscriptions: []string{"linux", "windows"},
},
Entity: &Entity{
ObjectMeta: ObjectMeta{
Name: "foo",
},
Subscriptions: []string{"linux"},
},
},
entries: []*Silenced{
FixtureSilenced("windows:check_cpu"),
},
expectedEntries: []*Silenced{},
},
{
name: "silenced, silenced & client do have a common subscription",
event: &Event{
Check: &Check{
ObjectMeta: ObjectMeta{
Name: "check_cpu",
},
Subscriptions: []string{"linux", "windows"},
},
Entity: &Entity{
ObjectMeta: ObjectMeta{
Name: "foo",
},
Subscriptions: []string{"linux"},
},
},
entries: []*Silenced{
FixtureSilenced("linux:check_cpu"),
},
expectedEntries: []*Silenced{
FixtureSilenced("linux:check_cpu"),
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := tc.event.SilencedBy(tc.entries)
assert.EqualValues(t, tc.expectedEntries, result)
})
}
}

func TestIsSilencedBy(t *testing.T) {
testCases := []struct {
name string
event *Event
silence *Silenced
expectedResult bool
}{
{
name: "silence has not started",
event: FixtureEvent("foo", "check_cpu"),
silence: &Silenced{
ObjectMeta: ObjectMeta{
Name: "*:check_cpu",
},
Begin: time.Now().Add(1 * time.Hour).Unix(),
},
expectedResult: false,
},
{
name: "check matches w/ wildcard subscription",
event: FixtureEvent("foo", "check_cpu"),
silence: FixtureSilenced("*:check_cpu"),
expectedResult: true,
},
{
name: "entity subscription matches w/ wildcard check",
event: FixtureEvent("foo", "check_cpu"),
silence: FixtureSilenced("entity:foo:*"),
expectedResult: true,
},
{
name: "entity subscription and check match",
event: FixtureEvent("foo", "check_cpu"),
silence: FixtureSilenced("entity:foo:check_cpu"),
expectedResult: true,
},
{
name: "subscription matches",
event: &Event{
Check: &Check{
ObjectMeta: ObjectMeta{
Name: "check_cpu",
},
Subscriptions: []string{"unix"},
},
Entity: &Entity{
ObjectMeta: ObjectMeta{
Name: "foo",
},
Subscriptions: []string{"unix"},
},
},
silence: FixtureSilenced("unix:check_cpu"),
expectedResult: true,
},
{
name: "subscription does not match",
event: &Event{
Check: &Check{
ObjectMeta: ObjectMeta{
Name: "check_cpu",
},
Subscriptions: []string{"unix"},
},
Entity: &Entity{
ObjectMeta: ObjectMeta{
Name: "foo",
},
Subscriptions: []string{"unix"},
},
},
silence: FixtureSilenced("windows:check_cpu"),
expectedResult: false,
},
{
name: "entity subscription doesn't match",
event: &Event{
Check: &Check{
ObjectMeta: ObjectMeta{
Name: "check_cpu",
},
Subscriptions: []string{"unix"},
},
Entity: &Entity{
ObjectMeta: ObjectMeta{
Name: "foo",
},
Subscriptions: []string{"windows"},
},
},
silence: FixtureSilenced("check:check_cpu"),
expectedResult: false,
},
{
name: "check does not match",
event: FixtureEvent("foo", "check_mem"),
silence: FixtureSilenced("*:check_cpu"),
expectedResult: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := tc.event.IsSilencedBy(tc.silence)
assert.EqualValues(t, tc.expectedResult, result)
})
}
}
21 changes: 19 additions & 2 deletions backend/apid/graphql/event.go
Expand Up @@ -66,12 +66,29 @@ func (r *eventImpl) IsResolution(p graphql.ResolveParams) (bool, error) {
return event.IsResolution(), nil
}

// IsSilenced implements response to request for 'isSilenced' field.
func (r *eventImpl) IsSilenced(p graphql.ResolveParams) (bool, error) {
// WasSilenced implements response to request for 'wasSilenced' field.
func (r *eventImpl) WasSilenced(p graphql.ResolveParams) (bool, error) {
event := p.Source.(*types.Event)
return event.IsSilenced(), nil
}

// IsSilenced implements response to request for 'isSilenced' field.
func (r *eventImpl) IsSilenced(p graphql.ResolveParams) (bool, error) {
src := p.Source.(*types.Event)
results, err := loadSilenceds(p.Context, src.Namespace)
records := filterSilenceds(results, src.IsSilencedBy)
return len(records) > 0, err
}

// Silences implements response to request for 'silences' field.
func (r *eventImpl) Silences(p graphql.ResolveParams) (interface{}, error) {
src := p.Source.(*types.Event)
results, err := loadSilenceds(p.Context, src.Namespace)
records := filterSilenceds(results, src.IsSilencedBy)

return records, err
}

// IsTypeOf is used to determine if a given value is associated with the type
func (r *eventImpl) IsTypeOf(s interface{}, p graphql.IsTypeOfParams) bool {
_, ok := s.(*types.Event)
Expand Down

0 comments on commit 15b4e2e

Please sign in to comment.