Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new silenced setting attributes addition #5049

Open
wants to merge 5 commits into
base: develop/6
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG-6.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased

### Added
- Added `max-silenced-expiry-time-allowed` (in minutes) backend configuration variable to control maximum time an alert can be silenced.
- Added `default-silenced-expiry-time` (in minutes) backend configuration variable to create silenced with a default expiry time if user doesn't set expiry time while creating an silence.


### Changed
- Upgraded CI Go version to 1.21.3
- Upgraded jwt version to 4.4.3
Expand Down
4 changes: 4 additions & 0 deletions backend/apid/actions/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ const (
// the operation has completed successfully. For example, a successful
// response from a server could have been delayed long
DeadlineExceeded

// Threshold set in config reached. These settings are done in config of backend.yaml
ThresholdReached
)

// Default error messages if not message is provided.
Expand All @@ -65,6 +68,7 @@ var standardErrorMessages = map[ErrCode]string{
PaymentRequired: "license required",
PreconditionFailed: "precondition failed",
DeadlineExceeded: "deadline exceeded",
ThresholdReached: "Threshold reached",
}

// Error describes an issue that ocurred while performing the action.
Expand Down
2 changes: 2 additions & 0 deletions backend/apid/graphql/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ func newStdErr(input string, err error) stdErr {
out.code = schema.ErrCodes.ERR_ALREADY_EXISTS
case (*store.ErrNotFound):
out.code = schema.ErrCodes.ERR_NOT_FOUND
case (*store.ErrThreshold):
out.code = schema.ErrCodes.ERR_THRESHOLD_REACHED
}
return out
}
Expand Down
8 changes: 8 additions & 0 deletions backend/apid/graphql/schema/errors.gql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions backend/apid/graphql/schema/errors.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,10 @@ enum ErrCode {
permissions.
"""
ERR_PERMISSION_DENIED

"""
Indicates that set thresholds in configured have reached
"""
ERR_THRESHOLD_REACHED

}
7 changes: 7 additions & 0 deletions backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,13 @@ func Initialize(ctx context.Context, config *Config) (*Backend, error) {

// Create the store, which lives on top of etcd
stor := etcdstore.NewStore(b.Client, config.EtcdName)

// set config details
scfg := etcdstore.Config{}
scfg.DefaultSilencedExpiryTime = config.DefaultSilencedExpiryTime
scfg.MaxSilencedExpiryTimeAllowed = config.MaxSilencedExpiryTimeAllowed
etcdstore.SetConfig(scfg, stor)

b.Store = stor
storv2 := etcdstorev2.NewStore(b.Client)
var storev2Proxy storev2.Proxy
Expand Down
15 changes: 15 additions & 0 deletions backend/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ const (
flagLabels = "labels"
flagAnnotations = "annotations"

// silenced expiry flags
flagMaxSilencedExpiryTimeAllowed = "max-silenced-expiry-time-allowed"
flagDefaultSilencedExpiryTime = "default-silenced-expiry-time"

// Etcd flag constants
flagEtcdClientURLs = "etcd-client-urls"
flagEtcdListenClientURLs = "etcd-listen-client-urls"
Expand Down Expand Up @@ -255,6 +259,9 @@ func StartCommand(initialize InitializeFunc) *cobra.Command {
CacheDir: viper.GetString(flagCacheDir),
StateDir: viper.GetString(flagStateDir),

DefaultSilencedExpiryTime: viper.GetDuration(flagDefaultSilencedExpiryTime),
MaxSilencedExpiryTimeAllowed: viper.GetDuration(flagMaxSilencedExpiryTimeAllowed),

EtcdAdvertiseClientURLs: viper.GetStringSlice(flagEtcdAdvertiseClientURLs),
EtcdListenClientURLs: viper.GetStringSlice(flagEtcdListenClientURLs),
EtcdClientURLs: fallbackStringSlice(flagEtcdClientURLs, flagEtcdAdvertiseClientURLs),
Expand Down Expand Up @@ -448,6 +455,10 @@ func handleConfig(cmd *cobra.Command, arguments []string, server bool) error {
viper.SetDefault(flagEventLogBufferSize, 100000)
viper.SetDefault(flagEventLogFile, "")
viper.SetDefault(flagEventLogParallelEncoders, false)

// default silenced value are set for 1 day = 1440m
viper.SetDefault(flagMaxSilencedExpiryTimeAllowed, "1440m")
viper.SetDefault(flagDefaultSilencedExpiryTime, "1440m")
}

// Etcd defaults
Expand Down Expand Up @@ -583,6 +594,10 @@ func flagSet(server bool) *pflag.FlagSet {
flagSet.Duration(flagPlatformMetricsLoggingInterval, viper.GetDuration(flagPlatformMetricsLoggingInterval), "platform metrics logging interval")
flagSet.String(flagPlatformMetricsLogFile, viper.GetString(flagPlatformMetricsLogFile), "platform metrics log file path")

// silenced configuration flags
flagSet.Duration(flagDefaultSilencedExpiryTime, viper.GetDuration(flagDefaultSilencedExpiryTime), "Default expiry time for silenced if not set in minutes")
flagSet.Duration(flagMaxSilencedExpiryTimeAllowed, viper.GetDuration(flagMaxSilencedExpiryTimeAllowed), "Maximum expiry time allowed for silenced in minutes")

// Etcd server flags
flagSet.StringSlice(flagEtcdPeerURLs, viper.GetStringSlice(flagEtcdPeerURLs), "list of URLs to listen on for peer traffic")
_ = flagSet.SetAnnotation(flagEtcdPeerURLs, "categories", []string{"store"})
Expand Down
4 changes: 4 additions & 0 deletions backend/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,8 @@ type Config struct {
EventLogBufferWait time.Duration
EventLogFile string
EventLogParallelEncoders bool

// expiry setting for silences
DefaultSilencedExpiryTime time.Duration
MaxSilencedExpiryTimeAllowed time.Duration
}
23 changes: 23 additions & 0 deletions backend/store/etcd/silenced_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
const (
silencedPathPrefix = "silenced"
maxTxnOps = 64 // this is half of the etcd default maximum
silencedLimitError = "silenced crossed maximum duration allowed"
)

var (
Expand Down Expand Up @@ -207,13 +208,35 @@ func (s *Store) UpdateSilencedEntry(ctx context.Context, silenced *corev2.Silenc
if err := silenced.Validate(); err != nil {
return &store.ErrNotValid{Err: err}
}
allowedMaxTime := time.Now().Add(s.cfg.MaxSilencedExpiryTimeAllowed).Unix()

// check for maximum allowed duration for silenced allowed
if silenced.ExpireAt > 0 && (silenced.ExpireAt > allowedMaxTime) {
err := errors.New(silencedLimitError)
return &store.ErrThreshold{Err: err}
}

if silenced.ExpireAt == 0 && silenced.Expire > 0 {
start := time.Now()
if silenced.Begin > 0 {
start = time.Unix(silenced.Begin, 0)
}
silenced.ExpireAt = start.Add(time.Duration(silenced.Expire) * time.Second).Unix()

// check for maximum allowed duration for silenced allowed
if silenced.ExpireAt > allowedMaxTime {
err := errors.New(silencedLimitError)
return &store.ErrThreshold{Err: err}
}
}

// set default silenced expiry time configured in backend yaml file
if silenced.Expire <= 0 && silenced.ExpireAt == 0 {
start := time.Now()
if silenced.Begin > 0 {
start = time.Unix(silenced.Begin, 0)
}
silenced.ExpireAt = start.Add(s.cfg.DefaultSilencedExpiryTime).Unix()
}

silencedBytes, err := proto.Marshal(silenced)
Expand Down
87 changes: 81 additions & 6 deletions backend/store/etcd/silenced_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,17 @@ func TestSilencedStorage(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, entry)
assert.Equal(t, "subscription:*", entry.Name)
// Entries without expirations should return -1
assert.Equal(t, int64(-1), entry.Expire)

// check entries without -1 expiry
assert.NotEqual(t, int64(-1), entry.Expire)

// Delete silenced entry by name
err = store.DeleteSilencedEntryByName(ctx, silenced.Name)
assert.NoError(t, err)

// Update a silenced entry's expire time
silenced.Expire = 2
silenced.ExpireAt = 0
err = store.UpdateSilencedEntry(ctx, silenced)
assert.NoError(t, err)

Expand All @@ -100,6 +102,7 @@ func TestSilencedStorageWithExpire(t *testing.T) {
silenced := types.FixtureSilenced("subscription:checkname")
silenced.Namespace = "default"
silenced.Expire = 15
silenced.ExpireAt = 0
ctx := context.WithValue(context.Background(), types.NamespaceKey, silenced.Namespace)

err := store.UpdateSilencedEntry(ctx, silenced)
Expand All @@ -120,9 +123,9 @@ func TestSilencedStorageWithBegin(t *testing.T) {
silenced := types.FixtureSilenced("subscription:checkname")
silenced.Namespace = "default"
// set a begin time in the future
silenced.Begin = time.Date(1970, 01, 01, 01, 00, 00, 00, time.UTC).Unix()
silenced.Begin = time.Now().Add(time.Duration(1) * time.Second).Unix()
// current time is before the start time
currentTime := time.Date(1970, 01, 01, 00, 00, 00, 00, time.UTC).Unix()
currentTime := time.Now().Unix()
ctx := context.WithValue(context.Background(), types.NamespaceKey, silenced.Namespace)

err := store.UpdateSilencedEntry(ctx, silenced)
Expand All @@ -137,8 +140,11 @@ func TestSilencedStorageWithBegin(t *testing.T) {
require.NotNil(t, entry)
assert.False(t, entry.Begin < currentTime)

// Wait for begin time to elapse current time. i.e let silencing begin
time.Sleep(3 * time.Second)

// reset current time to be ahead of begin time
currentTime = time.Date(1970, 01, 01, 02, 00, 00, 00, time.UTC).Unix()
currentTime = time.Now().Unix()
assert.True(t, entry.Begin < currentTime)
})
}
Expand All @@ -150,7 +156,7 @@ func TestSilencedStorageWithBeginAndExpire(t *testing.T) {
silenced.Expire = 15
currentTime := time.Now().UTC().Unix()
// set a begin time in the future
silenced.Begin = currentTime + 3600
silenced.Begin = currentTime + 100
// current time is before the start time
ctx := context.WithValue(context.Background(), types.NamespaceKey, silenced.Namespace)

Expand All @@ -168,3 +174,72 @@ func TestSilencedStorageWithBeginAndExpire(t *testing.T) {
assert.Equal(t, entry.Expire, int64(15))
})
}

func TestSilencedStorageWithMaxAllowedThresholdExpiry(t *testing.T) {
testWithEtcd(t, func(store store.Store) {
silenced := types.FixtureSilenced("subscription:checkname")
silenced.Namespace = "default"
silenced.ExpireAt = time.Now().Add(time.Duration(30000) * time.Second).Unix()
// set a begin time
silenced.Begin = time.Now().Unix()
ctx := context.WithValue(context.Background(), types.NamespaceKey, silenced.Namespace)

err := store.UpdateSilencedEntry(ctx, silenced)

// assert that error is thrown for breaching max expiry time allowed
assert.Error(t, err)

entry, err := store.GetSilencedEntryByName(ctx, silenced.Name)

// assert that entry is nil
assert.NoError(t, err)
assert.Nil(t, entry)

})
}

func TestSilencedStorageWithMaxAllowedThresholdExpiryWithError(t *testing.T) {
testWithEtcd(t, func(store store.Store) {
silenced := types.FixtureSilenced("subscription:checkname")
silenced.Namespace = "default"
silenced.ExpireAt = 0
silenced.Expire = 3001
silenced.Begin = time.Now().Unix()
ctx := context.WithValue(context.Background(), types.NamespaceKey, silenced.Namespace)

err := store.UpdateSilencedEntry(ctx, silenced)

// assert that error is thrown for breaching max expiry time allowed
assert.Error(t, err)

entry, err := store.GetSilencedEntryByName(ctx, silenced.Name)

// assert that entry is nil
assert.NoError(t, err)
assert.Nil(t, entry)

})
}

func TestSilencedStorageWithMaxAllowedThresholdExpiryAndWithoutError(t *testing.T) {
testWithEtcd(t, func(store store.Store) {
silenced := types.FixtureSilenced("subscription:checkname")
silenced.Namespace = "default"
silenced.ExpireAt = 0
silenced.Expire = 100
silenced.Begin = time.Now().Unix()
ctx := context.WithValue(context.Background(), types.NamespaceKey, silenced.Namespace)

err := store.UpdateSilencedEntry(ctx, silenced)

// assert that error is nil
assert.Nil(t, err)

entry, err := store.GetSilencedEntryByName(ctx, silenced.Name)

// assert that entry is not nil
assert.NoError(t, err)
assert.NotNil(t, entry)

})
}
12 changes: 12 additions & 0 deletions backend/store/etcd/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"path"
"reflect"
"strings"
"time"

"github.com/gogo/protobuf/proto"
"github.com/sensu/sensu-go/backend/store"
Expand All @@ -22,10 +23,16 @@ const (
EtcdRoot = "/sensu.io"
)

type Config struct {
DefaultSilencedExpiryTime time.Duration
MaxSilencedExpiryTimeAllowed time.Duration
}

// Store is an implementation of the sensu-go/backend/store.Store iface.
type Store struct {
client *clientv3.Client
keepalivesPath string
cfg Config
Copy link
Contributor

@fguimond fguimond Mar 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if this is the right place to have this config property. It seems to be used exclusively in silenced_store.go so why not have it there instead?
Hmmm I see why. I know some of the validation logic was there already but I wonder if it could be performed elsewhere.

}

// NewStore creates a new Store.
Expand All @@ -38,6 +45,11 @@ func NewStore(client *clientv3.Client, name string) *Store {
return store
}

// SetConfig adds Store configurations
func SetConfig(cfg Config, store *Store) {
store.cfg = cfg
}

// Create the given key with the serialized object.
func Create(ctx context.Context, client *clientv3.Client, key, namespace string, object interface{}) error {
bytes, err := marshal(object)
Expand Down
10 changes: 10 additions & 0 deletions backend/store/etcd/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"testing"
"time"

"github.com/gogo/protobuf/proto"
corev2 "github.com/sensu/core/v2"
Expand All @@ -28,6 +29,9 @@ func testWithEtcd(t *testing.T, f func(store.Store)) {

s := NewStore(client, e.Name())

s.cfg.MaxSilencedExpiryTimeAllowed = time.Duration(3000 * time.Second)
s.cfg.DefaultSilencedExpiryTime = time.Duration(3000 * time.Second)

// Mock a default namespace
require.NoError(t, s.CreateNamespace(context.Background(), types.FixtureNamespace("default")))

Expand All @@ -42,6 +46,9 @@ func testWithEtcdStore(t *testing.T, f func(*Store)) {

s := NewStore(client, e.Name())

s.cfg.MaxSilencedExpiryTimeAllowed = time.Duration(3000 * time.Second)
s.cfg.DefaultSilencedExpiryTime = time.Duration(3000 * time.Second)

// Mock a default namespace
require.NoError(t, s.CreateNamespace(context.Background(), types.FixtureNamespace("default")))

Expand All @@ -56,6 +63,9 @@ func testWithEtcdClient(t *testing.T, f func(store.Store, *clientv3.Client)) {

s := NewStore(client, e.Name())

s.cfg.MaxSilencedExpiryTimeAllowed = time.Duration(3000 * time.Second)
s.cfg.DefaultSilencedExpiryTime = time.Duration(3000 * time.Second)

// Mock a default namespace
require.NoError(t, s.CreateNamespace(context.Background(), types.FixtureNamespace("default")))

Expand Down