...

Source file src/github.com/prometheus/alertmanager/silence/silence_test.go

Documentation: github.com/prometheus/alertmanager/silence

     1  // Copyright 2016 Prometheus Team
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package silence
    15  
    16  import (
    17  	"bytes"
    18  	"fmt"
    19  	"os"
    20  	"runtime"
    21  	"sort"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/benbjohnson/clock"
    26  	"github.com/go-kit/log"
    27  	"github.com/matttproud/golang_protobuf_extensions/pbutil"
    28  	"github.com/prometheus/client_golang/prometheus"
    29  	"github.com/prometheus/common/model"
    30  	"github.com/stretchr/testify/require"
    31  
    32  	pb "github.com/prometheus/alertmanager/silence/silencepb"
    33  	"github.com/prometheus/alertmanager/types"
    34  )
    35  
    36  func checkErr(t *testing.T, expected string, got error) {
    37  	t.Helper()
    38  
    39  	if expected == "" {
    40  		require.NoError(t, got)
    41  		return
    42  	}
    43  
    44  	if got == nil {
    45  		t.Errorf("expected error containing %q but got none", expected)
    46  		return
    47  	}
    48  
    49  	require.Contains(t, got.Error(), expected)
    50  }
    51  
    52  func TestOptionsValidate(t *testing.T) {
    53  	cases := []struct {
    54  		options *Options
    55  		err     string
    56  	}{
    57  		{
    58  			options: &Options{
    59  				SnapshotReader: &bytes.Buffer{},
    60  			},
    61  		},
    62  		{
    63  			options: &Options{
    64  				SnapshotFile: "test.bkp",
    65  			},
    66  		},
    67  		{
    68  			options: &Options{
    69  				SnapshotFile:   "test bkp",
    70  				SnapshotReader: &bytes.Buffer{},
    71  			},
    72  			err: "only one of SnapshotFile and SnapshotReader must be set",
    73  		},
    74  	}
    75  
    76  	for _, c := range cases {
    77  		checkErr(t, c.err, c.options.validate())
    78  	}
    79  }
    80  
    81  func TestSilencesGC(t *testing.T) {
    82  	s, err := New(Options{})
    83  	require.NoError(t, err)
    84  
    85  	s.clock = clock.NewMock()
    86  	now := s.nowUTC()
    87  
    88  	newSilence := func(exp time.Time) *pb.MeshSilence {
    89  		return &pb.MeshSilence{ExpiresAt: exp}
    90  	}
    91  	s.st = state{
    92  		"1": newSilence(now),
    93  		"2": newSilence(now.Add(-time.Second)),
    94  		"3": newSilence(now.Add(time.Second)),
    95  	}
    96  	want := state{
    97  		"3": newSilence(now.Add(time.Second)),
    98  	}
    99  
   100  	n, err := s.GC()
   101  	require.NoError(t, err)
   102  	require.Equal(t, 2, n)
   103  	require.Equal(t, want, s.st)
   104  }
   105  
   106  func TestSilencesSnapshot(t *testing.T) {
   107  	// Check whether storing and loading the snapshot is symmetric.
   108  	now := clock.NewMock().Now().UTC()
   109  
   110  	cases := []struct {
   111  		entries []*pb.MeshSilence
   112  	}{
   113  		{
   114  			entries: []*pb.MeshSilence{
   115  				{
   116  					Silence: &pb.Silence{
   117  						Id: "3be80475-e219-4ee7-b6fc-4b65114e362f",
   118  						Matchers: []*pb.Matcher{
   119  							{Name: "label1", Pattern: "val1", Type: pb.Matcher_EQUAL},
   120  							{Name: "label2", Pattern: "val.+", Type: pb.Matcher_REGEXP},
   121  						},
   122  						StartsAt:  now,
   123  						EndsAt:    now,
   124  						UpdatedAt: now,
   125  					},
   126  					ExpiresAt: now,
   127  				},
   128  				{
   129  					Silence: &pb.Silence{
   130  						Id: "3dfb2528-59ce-41eb-b465-f875a4e744a4",
   131  						Matchers: []*pb.Matcher{
   132  							{Name: "label1", Pattern: "val1", Type: pb.Matcher_NOT_EQUAL},
   133  							{Name: "label2", Pattern: "val.+", Type: pb.Matcher_NOT_REGEXP},
   134  						},
   135  						StartsAt:  now,
   136  						EndsAt:    now,
   137  						UpdatedAt: now,
   138  					},
   139  					ExpiresAt: now,
   140  				},
   141  				{
   142  					Silence: &pb.Silence{
   143  						Id: "4b1e760d-182c-4980-b873-c1a6827c9817",
   144  						Matchers: []*pb.Matcher{
   145  							{Name: "label1", Pattern: "val1", Type: pb.Matcher_EQUAL},
   146  						},
   147  						StartsAt:  now.Add(time.Hour),
   148  						EndsAt:    now.Add(2 * time.Hour),
   149  						UpdatedAt: now,
   150  					},
   151  					ExpiresAt: now.Add(24 * time.Hour),
   152  				},
   153  			},
   154  		},
   155  	}
   156  
   157  	for _, c := range cases {
   158  		f, err := os.CreateTemp("", "snapshot")
   159  		require.NoError(t, err, "creating temp file failed")
   160  
   161  		s1 := &Silences{st: state{}, metrics: newMetrics(nil, nil)}
   162  		// Setup internal state manually.
   163  		for _, e := range c.entries {
   164  			s1.st[e.Silence.Id] = e
   165  		}
   166  		_, err = s1.Snapshot(f)
   167  		require.NoError(t, err, "creating snapshot failed")
   168  
   169  		require.NoError(t, f.Close(), "closing snapshot file failed")
   170  
   171  		f, err = os.Open(f.Name())
   172  		require.NoError(t, err, "opening snapshot file failed")
   173  
   174  		// Check again against new nlog instance.
   175  		s2 := &Silences{mc: matcherCache{}, st: state{}}
   176  		err = s2.loadSnapshot(f)
   177  		require.NoError(t, err, "error loading snapshot")
   178  		require.Equal(t, s1.st, s2.st, "state after loading snapshot did not match snapshotted state")
   179  
   180  		require.NoError(t, f.Close(), "closing snapshot file failed")
   181  	}
   182  }
   183  
   184  // This tests a regression introduced by https://github.com/prometheus/alertmanager/pull/2689.
   185  func TestSilences_Maintenance_DefaultMaintenanceFuncDoesntCrash(t *testing.T) {
   186  	f, err := os.CreateTemp("", "snapshot")
   187  	require.NoError(t, err, "creating temp file failed")
   188  	clock := clock.NewMock()
   189  	s := &Silences{st: state{}, logger: log.NewNopLogger(), clock: clock, metrics: newMetrics(nil, nil)}
   190  	stopc := make(chan struct{})
   191  
   192  	done := make(chan struct{})
   193  	go func() {
   194  		s.Maintenance(100*time.Millisecond, f.Name(), stopc, nil)
   195  		close(done)
   196  	}()
   197  	runtime.Gosched()
   198  
   199  	clock.Add(100 * time.Millisecond)
   200  	close(stopc)
   201  
   202  	<-done
   203  }
   204  
   205  func TestSilences_Maintenance_SupportsCustomCallback(t *testing.T) {
   206  	f, err := os.CreateTemp("", "snapshot")
   207  	require.NoError(t, err, "creating temp file failed")
   208  	clock := clock.NewMock()
   209  	s := &Silences{st: state{}, logger: log.NewNopLogger(), clock: clock, metrics: newMetrics(nil, nil)}
   210  	stopc := make(chan struct{})
   211  
   212  	called := make(chan struct{}, 5)
   213  	go func() {
   214  		s.Maintenance(100*time.Millisecond, f.Name(), stopc, func() (int64, error) {
   215  			called <- struct{}{}
   216  			return 0, nil
   217  		})
   218  		close(called)
   219  	}()
   220  	runtime.Gosched()
   221  
   222  	clock.Add(100 * time.Millisecond)
   223  
   224  	// Stop the maintenance loop. We should get exactly one more execution of the maintenance func.
   225  	close(stopc)
   226  	calls := 0
   227  	for range called {
   228  		calls++
   229  	}
   230  
   231  	require.EqualValues(t, 2, calls)
   232  }
   233  
   234  func TestSilencesSetSilence(t *testing.T) {
   235  	s, err := New(Options{
   236  		Retention: time.Minute,
   237  	})
   238  	require.NoError(t, err)
   239  
   240  	clock := clock.NewMock()
   241  	s.clock = clock
   242  
   243  	nowpb := s.nowUTC()
   244  
   245  	sil := &pb.Silence{
   246  		Id:       "some_id",
   247  		Matchers: []*pb.Matcher{{Name: "abc", Pattern: "def"}},
   248  		StartsAt: nowpb,
   249  		EndsAt:   nowpb,
   250  	}
   251  
   252  	want := state{
   253  		"some_id": &pb.MeshSilence{
   254  			Silence:   sil,
   255  			ExpiresAt: nowpb.Add(time.Minute),
   256  		},
   257  	}
   258  
   259  	done := make(chan struct{})
   260  	s.broadcast = func(b []byte) {
   261  		var e pb.MeshSilence
   262  		r := bytes.NewReader(b)
   263  		_, err := pbutil.ReadDelimited(r, &e)
   264  		require.NoError(t, err)
   265  
   266  		require.Equal(t, want["some_id"], &e)
   267  		close(done)
   268  	}
   269  
   270  	// setSilence() is always called with s.mtx locked() in the application code
   271  	func() {
   272  		s.mtx.Lock()
   273  		defer s.mtx.Unlock()
   274  		require.NoError(t, s.setSilence(sil, nowpb))
   275  	}()
   276  
   277  	// Ensure broadcast was called.
   278  	if _, isOpen := <-done; isOpen {
   279  		t.Fatal("broadcast was not called")
   280  	}
   281  
   282  	require.Equal(t, want, s.st, "Unexpected silence state")
   283  }
   284  
   285  func TestSilenceSet(t *testing.T) {
   286  	s, err := New(Options{
   287  		Retention: time.Hour,
   288  	})
   289  	require.NoError(t, err)
   290  
   291  	clock := clock.NewMock()
   292  	s.clock = clock
   293  	start1 := s.nowUTC()
   294  
   295  	// Insert silence with fixed start time.
   296  	sil1 := &pb.Silence{
   297  		Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}},
   298  		StartsAt: start1.Add(2 * time.Minute),
   299  		EndsAt:   start1.Add(5 * time.Minute),
   300  	}
   301  	id1, err := s.Set(sil1)
   302  	require.NoError(t, err)
   303  	require.NotEqual(t, id1, "")
   304  
   305  	want := state{
   306  		id1: &pb.MeshSilence{
   307  			Silence: &pb.Silence{
   308  				Id:        id1,
   309  				Matchers:  []*pb.Matcher{{Name: "a", Pattern: "b"}},
   310  				StartsAt:  start1.Add(2 * time.Minute),
   311  				EndsAt:    start1.Add(5 * time.Minute),
   312  				UpdatedAt: start1,
   313  			},
   314  			ExpiresAt: start1.Add(5*time.Minute + s.retention),
   315  		},
   316  	}
   317  	require.Equal(t, want, s.st, "unexpected state after silence creation")
   318  
   319  	// Insert silence with unset start time. Must be set to now.
   320  	clock.Add(time.Minute)
   321  	start2 := s.nowUTC()
   322  
   323  	sil2 := &pb.Silence{
   324  		Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}},
   325  		EndsAt:   start2.Add(1 * time.Minute),
   326  	}
   327  	id2, err := s.Set(sil2)
   328  	require.NoError(t, err)
   329  	require.NotEqual(t, id2, "")
   330  
   331  	want = state{
   332  		id1: want[id1],
   333  		id2: &pb.MeshSilence{
   334  			Silence: &pb.Silence{
   335  				Id:        id2,
   336  				Matchers:  []*pb.Matcher{{Name: "a", Pattern: "b"}},
   337  				StartsAt:  start2,
   338  				EndsAt:    start2.Add(1 * time.Minute),
   339  				UpdatedAt: start2,
   340  			},
   341  			ExpiresAt: start2.Add(1*time.Minute + s.retention),
   342  		},
   343  	}
   344  	require.Equal(t, want, s.st, "unexpected state after silence creation")
   345  
   346  	// Overwrite silence 2 with new end time.
   347  	clock.Add(time.Minute)
   348  	start3 := s.nowUTC()
   349  
   350  	sil3 := cloneSilence(sil2)
   351  	sil3.EndsAt = start3.Add(100 * time.Minute)
   352  
   353  	id3, err := s.Set(sil3)
   354  	require.NoError(t, err)
   355  	require.Equal(t, id2, id3)
   356  
   357  	want = state{
   358  		id1: want[id1],
   359  		id2: &pb.MeshSilence{
   360  			Silence: &pb.Silence{
   361  				Id:        id2,
   362  				Matchers:  []*pb.Matcher{{Name: "a", Pattern: "b"}},
   363  				StartsAt:  start2,
   364  				EndsAt:    start3.Add(100 * time.Minute),
   365  				UpdatedAt: start3,
   366  			},
   367  			ExpiresAt: start3.Add(100*time.Minute + s.retention),
   368  		},
   369  	}
   370  	require.Equal(t, want, s.st, "unexpected state after silence creation")
   371  
   372  	// Update this silence again with new matcher. This expires it and creates a new one.
   373  	clock.Add(time.Minute)
   374  	start4 := s.nowUTC()
   375  
   376  	sil4 := cloneSilence(sil3)
   377  	sil4.Matchers = []*pb.Matcher{{Name: "a", Pattern: "c"}}
   378  
   379  	id4, err := s.Set(sil4)
   380  	require.NoError(t, err)
   381  	// This new silence gets a new id.
   382  	require.NotEqual(t, id2, id4)
   383  
   384  	want = state{
   385  		id1: want[id1],
   386  		id2: &pb.MeshSilence{
   387  			Silence: &pb.Silence{
   388  				Id:        id2,
   389  				Matchers:  []*pb.Matcher{{Name: "a", Pattern: "b"}},
   390  				StartsAt:  start2,
   391  				EndsAt:    start4, // Expired
   392  				UpdatedAt: start4,
   393  			},
   394  			ExpiresAt: start4.Add(s.retention),
   395  		},
   396  		id4: &pb.MeshSilence{
   397  			Silence: &pb.Silence{
   398  				Id:        id4,
   399  				Matchers:  []*pb.Matcher{{Name: "a", Pattern: "c"}},
   400  				StartsAt:  start4,
   401  				EndsAt:    start3.Add(100 * time.Minute),
   402  				UpdatedAt: start4,
   403  			},
   404  			ExpiresAt: start3.Add(100*time.Minute + s.retention),
   405  		},
   406  	}
   407  	require.Equal(t, want, s.st, "unexpected state after silence creation")
   408  
   409  	// Re-create the silence that just expired.
   410  	clock.Add(time.Minute)
   411  	start5 := s.nowUTC()
   412  
   413  	sil5 := cloneSilence(sil3)
   414  	sil5.StartsAt = start1
   415  	sil5.EndsAt = start1.Add(5 * time.Minute)
   416  
   417  	id5, err := s.Set(sil5)
   418  	require.NoError(t, err)
   419  	require.NotEqual(t, id2, id4)
   420  
   421  	want = state{
   422  		id1: want[id1],
   423  		id2: want[id2],
   424  		id4: want[id4],
   425  		id5: &pb.MeshSilence{
   426  			Silence: &pb.Silence{
   427  				Id:        id5,
   428  				Matchers:  []*pb.Matcher{{Name: "a", Pattern: "b"}},
   429  				StartsAt:  start5, // New silences have their start time set to "now" when created.
   430  				EndsAt:    start1.Add(5 * time.Minute),
   431  				UpdatedAt: start5,
   432  			},
   433  			ExpiresAt: start1.Add(5*time.Minute + s.retention),
   434  		},
   435  	}
   436  	require.Equal(t, want, s.st, "unexpected state after silence creation")
   437  }
   438  
   439  func TestSetActiveSilence(t *testing.T) {
   440  	s, err := New(Options{
   441  		Retention: time.Hour,
   442  	})
   443  	require.NoError(t, err)
   444  
   445  	clock := clock.NewMock()
   446  	s.clock = clock
   447  	now := clock.Now()
   448  
   449  	startsAt := now.Add(-1 * time.Minute)
   450  	endsAt := now.Add(5 * time.Minute)
   451  	// Insert silence with fixed start time.
   452  	sil1 := &pb.Silence{
   453  		Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}},
   454  		StartsAt: startsAt,
   455  		EndsAt:   endsAt,
   456  	}
   457  	id1, _ := s.Set(sil1)
   458  
   459  	// Update silence with 2 extra nanoseconds so the "seconds" part should not change
   460  
   461  	newStartsAt := now.Add(2 * time.Nanosecond)
   462  	newEndsAt := endsAt.Add(2 * time.Minute)
   463  
   464  	sil2 := cloneSilence(sil1)
   465  	sil2.Id = id1
   466  	sil2.StartsAt = newStartsAt
   467  	sil2.EndsAt = newEndsAt
   468  
   469  	clock.Add(time.Minute)
   470  	now = s.nowUTC()
   471  	id2, err := s.Set(sil2)
   472  	require.NoError(t, err)
   473  	require.Equal(t, id1, id2)
   474  
   475  	want := state{
   476  		id2: &pb.MeshSilence{
   477  			Silence: &pb.Silence{
   478  				Id:        id1,
   479  				Matchers:  []*pb.Matcher{{Name: "a", Pattern: "b"}},
   480  				StartsAt:  newStartsAt,
   481  				EndsAt:    newEndsAt,
   482  				UpdatedAt: now,
   483  			},
   484  			ExpiresAt: newEndsAt.Add(s.retention),
   485  		},
   486  	}
   487  	require.Equal(t, want, s.st, "unexpected state after silence creation")
   488  }
   489  
   490  func TestSilencesSetFail(t *testing.T) {
   491  	s, err := New(Options{})
   492  	require.NoError(t, err)
   493  
   494  	clock := clock.NewMock()
   495  	s.clock = clock
   496  
   497  	cases := []struct {
   498  		s   *pb.Silence
   499  		err string
   500  	}{
   501  		{
   502  			s:   &pb.Silence{Id: "some_id"},
   503  			err: ErrNotFound.Error(),
   504  		}, {
   505  			s:   &pb.Silence{}, // Silence without matcher.
   506  			err: "silence invalid",
   507  		},
   508  	}
   509  	for _, c := range cases {
   510  		_, err := s.Set(c.s)
   511  		checkErr(t, c.err, err)
   512  	}
   513  }
   514  
   515  func TestQState(t *testing.T) {
   516  	now := time.Now().UTC()
   517  
   518  	cases := []struct {
   519  		sil    *pb.Silence
   520  		states []types.SilenceState
   521  		keep   bool
   522  	}{
   523  		{
   524  			sil: &pb.Silence{
   525  				StartsAt: now.Add(time.Minute),
   526  				EndsAt:   now.Add(time.Hour),
   527  			},
   528  			states: []types.SilenceState{types.SilenceStateActive, types.SilenceStateExpired},
   529  			keep:   false,
   530  		},
   531  		{
   532  			sil: &pb.Silence{
   533  				StartsAt: now.Add(time.Minute),
   534  				EndsAt:   now.Add(time.Hour),
   535  			},
   536  			states: []types.SilenceState{types.SilenceStatePending},
   537  			keep:   true,
   538  		},
   539  		{
   540  			sil: &pb.Silence{
   541  				StartsAt: now.Add(time.Minute),
   542  				EndsAt:   now.Add(time.Hour),
   543  			},
   544  			states: []types.SilenceState{types.SilenceStateExpired, types.SilenceStatePending},
   545  			keep:   true,
   546  		},
   547  	}
   548  	for i, c := range cases {
   549  		q := &query{}
   550  		QState(c.states...)(q)
   551  		f := q.filters[0]
   552  
   553  		keep, err := f(c.sil, nil, now)
   554  		require.NoError(t, err)
   555  		require.Equal(t, c.keep, keep, "unexpected filter result for case %d", i)
   556  	}
   557  }
   558  
   559  func TestQMatches(t *testing.T) {
   560  	qp := QMatches(model.LabelSet{
   561  		"job":      "test",
   562  		"instance": "web-1",
   563  		"path":     "/user/profile",
   564  		"method":   "GET",
   565  	})
   566  
   567  	q := &query{}
   568  	qp(q)
   569  	f := q.filters[0]
   570  
   571  	cases := []struct {
   572  		sil  *pb.Silence
   573  		drop bool
   574  	}{
   575  		{
   576  			sil: &pb.Silence{
   577  				Matchers: []*pb.Matcher{
   578  					{Name: "job", Pattern: "test", Type: pb.Matcher_EQUAL},
   579  				},
   580  			},
   581  			drop: true,
   582  		},
   583  		{
   584  			sil: &pb.Silence{
   585  				Matchers: []*pb.Matcher{
   586  					{Name: "job", Pattern: "test", Type: pb.Matcher_NOT_EQUAL},
   587  				},
   588  			},
   589  			drop: false,
   590  		},
   591  		{
   592  			sil: &pb.Silence{
   593  				Matchers: []*pb.Matcher{
   594  					{Name: "job", Pattern: "test", Type: pb.Matcher_EQUAL},
   595  					{Name: "method", Pattern: "POST", Type: pb.Matcher_EQUAL},
   596  				},
   597  			},
   598  			drop: false,
   599  		},
   600  		{
   601  			sil: &pb.Silence{
   602  				Matchers: []*pb.Matcher{
   603  					{Name: "job", Pattern: "test", Type: pb.Matcher_EQUAL},
   604  					{Name: "method", Pattern: "POST", Type: pb.Matcher_NOT_EQUAL},
   605  				},
   606  			},
   607  			drop: true,
   608  		},
   609  		{
   610  			sil: &pb.Silence{
   611  				Matchers: []*pb.Matcher{
   612  					{Name: "path", Pattern: "/user/.+", Type: pb.Matcher_REGEXP},
   613  				},
   614  			},
   615  			drop: true,
   616  		},
   617  		{
   618  			sil: &pb.Silence{
   619  				Matchers: []*pb.Matcher{
   620  					{Name: "path", Pattern: "/user/.+", Type: pb.Matcher_NOT_REGEXP},
   621  				},
   622  			},
   623  			drop: false,
   624  		},
   625  		{
   626  			sil: &pb.Silence{
   627  				Matchers: []*pb.Matcher{
   628  					{Name: "path", Pattern: "/user/.+", Type: pb.Matcher_REGEXP},
   629  					{Name: "path", Pattern: "/nothing/.+", Type: pb.Matcher_REGEXP},
   630  				},
   631  			},
   632  			drop: false,
   633  		},
   634  	}
   635  	for _, c := range cases {
   636  		drop, err := f(c.sil, &Silences{mc: matcherCache{}, st: state{}}, time.Time{})
   637  		require.NoError(t, err)
   638  		require.Equal(t, c.drop, drop, "unexpected filter result")
   639  	}
   640  }
   641  
   642  func TestSilencesQuery(t *testing.T) {
   643  	s, err := New(Options{})
   644  	require.NoError(t, err)
   645  
   646  	s.st = state{
   647  		"1": &pb.MeshSilence{Silence: &pb.Silence{Id: "1"}},
   648  		"2": &pb.MeshSilence{Silence: &pb.Silence{Id: "2"}},
   649  		"3": &pb.MeshSilence{Silence: &pb.Silence{Id: "3"}},
   650  		"4": &pb.MeshSilence{Silence: &pb.Silence{Id: "4"}},
   651  		"5": &pb.MeshSilence{Silence: &pb.Silence{Id: "5"}},
   652  	}
   653  	cases := []struct {
   654  		q   *query
   655  		exp []*pb.Silence
   656  	}{
   657  		{
   658  			// Default query of retrieving all silences.
   659  			q: &query{},
   660  			exp: []*pb.Silence{
   661  				{Id: "1"},
   662  				{Id: "2"},
   663  				{Id: "3"},
   664  				{Id: "4"},
   665  				{Id: "5"},
   666  			},
   667  		},
   668  		{
   669  			// Retrieve by IDs.
   670  			q: &query{
   671  				ids: []string{"2", "5"},
   672  			},
   673  			exp: []*pb.Silence{
   674  				{Id: "2"},
   675  				{Id: "5"},
   676  			},
   677  		},
   678  		{
   679  			// Retrieve all and filter
   680  			q: &query{
   681  				filters: []silenceFilter{
   682  					func(sil *pb.Silence, _ *Silences, _ time.Time) (bool, error) {
   683  						return sil.Id == "1" || sil.Id == "2", nil
   684  					},
   685  				},
   686  			},
   687  			exp: []*pb.Silence{
   688  				{Id: "1"},
   689  				{Id: "2"},
   690  			},
   691  		},
   692  		{
   693  			// Retrieve by IDs and filter
   694  			q: &query{
   695  				ids: []string{"2", "5"},
   696  				filters: []silenceFilter{
   697  					func(sil *pb.Silence, _ *Silences, _ time.Time) (bool, error) {
   698  						return sil.Id == "1" || sil.Id == "2", nil
   699  					},
   700  				},
   701  			},
   702  			exp: []*pb.Silence{
   703  				{Id: "2"},
   704  			},
   705  		},
   706  	}
   707  
   708  	for _, c := range cases {
   709  		// Run default query of retrieving all silences.
   710  		res, _, err := s.query(c.q, time.Time{})
   711  		require.NoError(t, err, "unexpected error on querying")
   712  
   713  		// Currently there are no sorting guarantees in the querying API.
   714  		sort.Sort(silencesByID(c.exp))
   715  		sort.Sort(silencesByID(res))
   716  		require.Equal(t, c.exp, res, "unexpected silences in result")
   717  	}
   718  }
   719  
   720  type silencesByID []*pb.Silence
   721  
   722  func (s silencesByID) Len() int           { return len(s) }
   723  func (s silencesByID) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   724  func (s silencesByID) Less(i, j int) bool { return s[i].Id < s[j].Id }
   725  
   726  func TestSilenceCanUpdate(t *testing.T) {
   727  	now := time.Now().UTC()
   728  
   729  	cases := []struct {
   730  		a, b *pb.Silence
   731  		ok   bool
   732  	}{
   733  		// Bad arguments.
   734  		{
   735  			a: &pb.Silence{},
   736  			b: &pb.Silence{
   737  				StartsAt: now,
   738  				EndsAt:   now.Add(-time.Minute),
   739  			},
   740  			ok: false,
   741  		},
   742  		// Expired silence.
   743  		{
   744  			a: &pb.Silence{
   745  				StartsAt: now.Add(-time.Hour),
   746  				EndsAt:   now.Add(-time.Second),
   747  			},
   748  			b: &pb.Silence{
   749  				StartsAt: now,
   750  				EndsAt:   now,
   751  			},
   752  			ok: false,
   753  		},
   754  		// Pending silences.
   755  		{
   756  			a: &pb.Silence{
   757  				StartsAt:  now.Add(time.Hour),
   758  				EndsAt:    now.Add(2 * time.Hour),
   759  				UpdatedAt: now.Add(-time.Hour),
   760  			},
   761  			b: &pb.Silence{
   762  				StartsAt: now.Add(-time.Minute),
   763  				EndsAt:   now.Add(time.Hour),
   764  			},
   765  			ok: false,
   766  		},
   767  		{
   768  			a: &pb.Silence{
   769  				StartsAt:  now.Add(time.Hour),
   770  				EndsAt:    now.Add(2 * time.Hour),
   771  				UpdatedAt: now.Add(-time.Hour),
   772  			},
   773  			b: &pb.Silence{
   774  				StartsAt: now.Add(time.Minute),
   775  				EndsAt:   now.Add(time.Minute),
   776  			},
   777  			ok: true,
   778  		},
   779  		{
   780  			a: &pb.Silence{
   781  				StartsAt:  now.Add(time.Hour),
   782  				EndsAt:    now.Add(2 * time.Hour),
   783  				UpdatedAt: now.Add(-time.Hour),
   784  			},
   785  			b: &pb.Silence{
   786  				StartsAt: now, // set to exactly start now.
   787  				EndsAt:   now.Add(2 * time.Hour),
   788  			},
   789  			ok: true,
   790  		},
   791  		// Active silences.
   792  		{
   793  			a: &pb.Silence{
   794  				StartsAt:  now.Add(-time.Hour),
   795  				EndsAt:    now.Add(2 * time.Hour),
   796  				UpdatedAt: now.Add(-time.Hour),
   797  			},
   798  			b: &pb.Silence{
   799  				StartsAt: now.Add(-time.Minute),
   800  				EndsAt:   now.Add(2 * time.Hour),
   801  			},
   802  			ok: false,
   803  		},
   804  		{
   805  			a: &pb.Silence{
   806  				StartsAt:  now.Add(-time.Hour),
   807  				EndsAt:    now.Add(2 * time.Hour),
   808  				UpdatedAt: now.Add(-time.Hour),
   809  			},
   810  			b: &pb.Silence{
   811  				StartsAt: now.Add(-time.Hour),
   812  				EndsAt:   now.Add(-time.Second),
   813  			},
   814  			ok: false,
   815  		},
   816  		{
   817  			a: &pb.Silence{
   818  				StartsAt:  now.Add(-time.Hour),
   819  				EndsAt:    now.Add(2 * time.Hour),
   820  				UpdatedAt: now.Add(-time.Hour),
   821  			},
   822  			b: &pb.Silence{
   823  				StartsAt: now.Add(-time.Hour),
   824  				EndsAt:   now,
   825  			},
   826  			ok: true,
   827  		},
   828  		{
   829  			a: &pb.Silence{
   830  				StartsAt:  now.Add(-time.Hour),
   831  				EndsAt:    now.Add(2 * time.Hour),
   832  				UpdatedAt: now.Add(-time.Hour),
   833  			},
   834  			b: &pb.Silence{
   835  				StartsAt: now.Add(-time.Hour),
   836  				EndsAt:   now.Add(3 * time.Hour),
   837  			},
   838  			ok: true,
   839  		},
   840  	}
   841  	for _, c := range cases {
   842  		ok := canUpdate(c.a, c.b, now)
   843  		if ok && !c.ok {
   844  			t.Errorf("expected not-updateable but was: %v, %v", c.a, c.b)
   845  		}
   846  		if ok && !c.ok {
   847  			t.Errorf("expected updateable but was not: %v, %v", c.a, c.b)
   848  		}
   849  	}
   850  }
   851  
   852  func TestSilenceExpire(t *testing.T) {
   853  	s, err := New(Options{Retention: time.Hour})
   854  	require.NoError(t, err)
   855  
   856  	clock := clock.NewMock()
   857  	s.clock = clock
   858  	now := s.nowUTC()
   859  
   860  	m := &pb.Matcher{Type: pb.Matcher_EQUAL, Name: "a", Pattern: "b"}
   861  
   862  	s.st = state{
   863  		"pending": &pb.MeshSilence{Silence: &pb.Silence{
   864  			Id:        "pending",
   865  			Matchers:  []*pb.Matcher{m},
   866  			StartsAt:  now.Add(time.Minute),
   867  			EndsAt:    now.Add(time.Hour),
   868  			UpdatedAt: now.Add(-time.Hour),
   869  		}},
   870  		"active": &pb.MeshSilence{Silence: &pb.Silence{
   871  			Id:        "active",
   872  			Matchers:  []*pb.Matcher{m},
   873  			StartsAt:  now.Add(-time.Minute),
   874  			EndsAt:    now.Add(time.Hour),
   875  			UpdatedAt: now.Add(-time.Hour),
   876  		}},
   877  		"expired": &pb.MeshSilence{Silence: &pb.Silence{
   878  			Id:        "expired",
   879  			Matchers:  []*pb.Matcher{m},
   880  			StartsAt:  now.Add(-time.Hour),
   881  			EndsAt:    now.Add(-time.Minute),
   882  			UpdatedAt: now.Add(-time.Hour),
   883  		}},
   884  	}
   885  
   886  	count, err := s.CountState(types.SilenceStatePending)
   887  	require.NoError(t, err)
   888  	require.Equal(t, 1, count)
   889  
   890  	count, err = s.CountState(types.SilenceStateExpired)
   891  	require.NoError(t, err)
   892  	require.Equal(t, 1, count)
   893  
   894  	require.NoError(t, s.Expire("pending"))
   895  	require.NoError(t, s.Expire("active"))
   896  
   897  	require.NoError(t, s.Expire("expired"))
   898  
   899  	sil, err := s.QueryOne(QIDs("pending"))
   900  	require.NoError(t, err)
   901  	require.Equal(t, &pb.Silence{
   902  		Id:        "pending",
   903  		Matchers:  []*pb.Matcher{m},
   904  		StartsAt:  now,
   905  		EndsAt:    now,
   906  		UpdatedAt: now,
   907  	}, sil)
   908  
   909  	// Let time pass...
   910  	clock.Add(time.Second)
   911  
   912  	count, err = s.CountState(types.SilenceStatePending)
   913  	require.NoError(t, err)
   914  	require.Equal(t, 0, count)
   915  
   916  	count, err = s.CountState(types.SilenceStateExpired)
   917  	require.NoError(t, err)
   918  	require.Equal(t, 3, count)
   919  
   920  	// Expiring a pending Silence should make the API return the
   921  	// SilenceStateExpired Silence state.
   922  	silenceState := types.CalcSilenceState(sil.StartsAt, sil.EndsAt)
   923  	require.Equal(t, silenceState, types.SilenceStateExpired)
   924  
   925  	sil, err = s.QueryOne(QIDs("active"))
   926  	require.NoError(t, err)
   927  	require.Equal(t, &pb.Silence{
   928  		Id:        "active",
   929  		Matchers:  []*pb.Matcher{m},
   930  		StartsAt:  now.Add(-time.Minute),
   931  		EndsAt:    now,
   932  		UpdatedAt: now,
   933  	}, sil)
   934  
   935  	sil, err = s.QueryOne(QIDs("expired"))
   936  	require.NoError(t, err)
   937  	require.Equal(t, &pb.Silence{
   938  		Id:        "expired",
   939  		Matchers:  []*pb.Matcher{m},
   940  		StartsAt:  now.Add(-time.Hour),
   941  		EndsAt:    now.Add(-time.Minute),
   942  		UpdatedAt: now.Add(-time.Hour),
   943  	}, sil)
   944  }
   945  
   946  // TestSilenceExpireWithZeroRetention covers the problem that, with zero
   947  // retention time, a silence explicitly set to expired will also immediately
   948  // expire from the silence storage.
   949  func TestSilenceExpireWithZeroRetention(t *testing.T) {
   950  	s, err := New(Options{Retention: 0})
   951  	require.NoError(t, err)
   952  
   953  	clock := clock.NewMock()
   954  	s.clock = clock
   955  	now := s.nowUTC()
   956  
   957  	m := &pb.Matcher{Type: pb.Matcher_EQUAL, Name: "a", Pattern: "b"}
   958  
   959  	s.st = state{
   960  		"pending": &pb.MeshSilence{Silence: &pb.Silence{
   961  			Id:        "pending",
   962  			Matchers:  []*pb.Matcher{m},
   963  			StartsAt:  now.Add(time.Minute),
   964  			EndsAt:    now.Add(time.Hour),
   965  			UpdatedAt: now.Add(-time.Hour),
   966  		}},
   967  		"active": &pb.MeshSilence{Silence: &pb.Silence{
   968  			Id:        "active",
   969  			Matchers:  []*pb.Matcher{m},
   970  			StartsAt:  now.Add(-time.Minute),
   971  			EndsAt:    now.Add(time.Hour),
   972  			UpdatedAt: now.Add(-time.Hour),
   973  		}},
   974  		"expired": &pb.MeshSilence{Silence: &pb.Silence{
   975  			Id:        "expired",
   976  			Matchers:  []*pb.Matcher{m},
   977  			StartsAt:  now.Add(-time.Hour),
   978  			EndsAt:    now.Add(-time.Minute),
   979  			UpdatedAt: now.Add(-time.Hour),
   980  		}},
   981  	}
   982  
   983  	count, err := s.CountState(types.SilenceStatePending)
   984  	require.NoError(t, err)
   985  	require.Equal(t, 1, count)
   986  
   987  	count, err = s.CountState(types.SilenceStateActive)
   988  	require.NoError(t, err)
   989  	require.Equal(t, 1, count)
   990  
   991  	count, err = s.CountState(types.SilenceStateExpired)
   992  	require.NoError(t, err)
   993  	require.Equal(t, 1, count)
   994  
   995  	// Advance time. The silence state management code uses update time when
   996  	// merging, and the logic is "first write wins". So we must advance the clock
   997  	// one tick for updates to take effect.
   998  	clock.Add(1 * time.Millisecond)
   999  
  1000  	require.NoError(t, s.Expire("pending"))
  1001  	require.NoError(t, s.Expire("active"))
  1002  	require.NoError(t, s.Expire("expired"))
  1003  
  1004  	// Advance time again. Despite what the function name says, s.Expire() does
  1005  	// not expire a silence. It sets the silence to EndAt the current time. This
  1006  	// means that the silence is active immediately after calling Expire.
  1007  	clock.Add(1 * time.Millisecond)
  1008  
  1009  	// Verify all silences have expired.
  1010  	count, err = s.CountState(types.SilenceStatePending)
  1011  	require.NoError(t, err)
  1012  	require.Equal(t, 0, count)
  1013  
  1014  	count, err = s.CountState(types.SilenceStateActive)
  1015  	require.NoError(t, err)
  1016  	require.Equal(t, 0, count)
  1017  
  1018  	count, err = s.CountState(types.SilenceStateExpired)
  1019  	require.NoError(t, err)
  1020  	require.Equal(t, 3, count)
  1021  }
  1022  
  1023  func TestSilencer(t *testing.T) {
  1024  	ss, err := New(Options{Retention: time.Hour})
  1025  	require.NoError(t, err)
  1026  
  1027  	clock := clock.NewMock()
  1028  	ss.clock = clock
  1029  	now := ss.nowUTC()
  1030  
  1031  	m := types.NewMarker(prometheus.NewRegistry())
  1032  	s := NewSilencer(ss, m, log.NewNopLogger())
  1033  
  1034  	require.False(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert not silenced without any silences")
  1035  
  1036  	_, err = ss.Set(&pb.Silence{
  1037  		Matchers: []*pb.Matcher{{Name: "foo", Pattern: "baz"}},
  1038  		StartsAt: now.Add(-time.Hour),
  1039  		EndsAt:   now.Add(5 * time.Minute),
  1040  	})
  1041  	require.NoError(t, err)
  1042  
  1043  	require.False(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert not silenced by non-matching silence")
  1044  
  1045  	id, err := ss.Set(&pb.Silence{
  1046  		Matchers: []*pb.Matcher{{Name: "foo", Pattern: "bar"}},
  1047  		StartsAt: now.Add(-time.Hour),
  1048  		EndsAt:   now.Add(5 * time.Minute),
  1049  	})
  1050  	require.NoError(t, err)
  1051  	require.NotEmpty(t, id)
  1052  
  1053  	require.True(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert silenced by matching silence")
  1054  
  1055  	// One hour passes, silence expires.
  1056  	clock.Add(time.Hour)
  1057  	now = ss.nowUTC()
  1058  
  1059  	require.False(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert not silenced by expired silence")
  1060  
  1061  	// Update silence to start in the future.
  1062  	_, err = ss.Set(&pb.Silence{
  1063  		Id:       id,
  1064  		Matchers: []*pb.Matcher{{Name: "foo", Pattern: "bar"}},
  1065  		StartsAt: now.Add(time.Hour),
  1066  		EndsAt:   now.Add(3 * time.Hour),
  1067  	})
  1068  	require.NoError(t, err)
  1069  
  1070  	require.False(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert not silenced by future silence")
  1071  
  1072  	// Two hours pass, silence becomes active.
  1073  	clock.Add(2 * time.Hour)
  1074  	now = ss.nowUTC()
  1075  
  1076  	// Exposes issue #2426.
  1077  	require.True(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert silenced by activated silence")
  1078  
  1079  	_, err = ss.Set(&pb.Silence{
  1080  		Matchers: []*pb.Matcher{{Name: "foo", Pattern: "b..", Type: pb.Matcher_REGEXP}},
  1081  		StartsAt: now.Add(time.Hour),
  1082  		EndsAt:   now.Add(3 * time.Hour),
  1083  	})
  1084  	require.NoError(t, err)
  1085  
  1086  	// Note that issue #2426 doesn't apply anymore because we added a new silence.
  1087  	require.True(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert still silenced by activated silence")
  1088  
  1089  	// Two hours pass, first silence expires, overlapping second silence becomes active.
  1090  	clock.Add(2 * time.Hour)
  1091  
  1092  	// Another variant of issue #2426 (overlapping silences).
  1093  	require.True(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert silenced by activated second silence")
  1094  }
  1095  
  1096  func TestValidateMatcher(t *testing.T) {
  1097  	cases := []struct {
  1098  		m   *pb.Matcher
  1099  		err string
  1100  	}{
  1101  		{
  1102  			m: &pb.Matcher{
  1103  				Name:    "a",
  1104  				Pattern: "b",
  1105  				Type:    pb.Matcher_EQUAL,
  1106  			},
  1107  			err: "",
  1108  		}, {
  1109  			m: &pb.Matcher{
  1110  				Name:    "a",
  1111  				Pattern: "b",
  1112  				Type:    pb.Matcher_NOT_EQUAL,
  1113  			},
  1114  			err: "",
  1115  		}, {
  1116  			m: &pb.Matcher{
  1117  				Name:    "a",
  1118  				Pattern: "b",
  1119  				Type:    pb.Matcher_REGEXP,
  1120  			},
  1121  			err: "",
  1122  		}, {
  1123  			m: &pb.Matcher{
  1124  				Name:    "a",
  1125  				Pattern: "b",
  1126  				Type:    pb.Matcher_NOT_REGEXP,
  1127  			},
  1128  			err: "",
  1129  		}, {
  1130  			m: &pb.Matcher{
  1131  				Name:    "00",
  1132  				Pattern: "a",
  1133  				Type:    pb.Matcher_EQUAL,
  1134  			},
  1135  			err: "invalid label name",
  1136  		}, {
  1137  			m: &pb.Matcher{
  1138  				Name:    "a",
  1139  				Pattern: "((",
  1140  				Type:    pb.Matcher_REGEXP,
  1141  			},
  1142  			err: "invalid regular expression",
  1143  		}, {
  1144  			m: &pb.Matcher{
  1145  				Name:    "a",
  1146  				Pattern: "))",
  1147  				Type:    pb.Matcher_NOT_REGEXP,
  1148  			},
  1149  			err: "invalid regular expression",
  1150  		}, {
  1151  			m: &pb.Matcher{
  1152  				Name:    "a",
  1153  				Pattern: "\xff",
  1154  				Type:    pb.Matcher_EQUAL,
  1155  			},
  1156  			err: "invalid label value",
  1157  		}, {
  1158  			m: &pb.Matcher{
  1159  				Name:    "a",
  1160  				Pattern: "b",
  1161  				Type:    333,
  1162  			},
  1163  			err: "unknown matcher type",
  1164  		},
  1165  	}
  1166  
  1167  	for _, c := range cases {
  1168  		checkErr(t, c.err, ValidateMatcher(c.m))
  1169  	}
  1170  }
  1171  
  1172  func TestValidateSilence(t *testing.T) {
  1173  	var (
  1174  		now            = time.Now().UTC()
  1175  		zeroTimestamp  = time.Time{}
  1176  		validTimestamp = now
  1177  	)
  1178  	cases := []struct {
  1179  		s   *pb.Silence
  1180  		err string
  1181  	}{
  1182  		{
  1183  			s: &pb.Silence{
  1184  				Id: "some_id",
  1185  				Matchers: []*pb.Matcher{
  1186  					{Name: "a", Pattern: "b"},
  1187  				},
  1188  				StartsAt:  validTimestamp,
  1189  				EndsAt:    validTimestamp,
  1190  				UpdatedAt: validTimestamp,
  1191  			},
  1192  			err: "",
  1193  		},
  1194  		{
  1195  			s: &pb.Silence{
  1196  				Id: "",
  1197  				Matchers: []*pb.Matcher{
  1198  					{Name: "a", Pattern: "b"},
  1199  				},
  1200  				StartsAt:  validTimestamp,
  1201  				EndsAt:    validTimestamp,
  1202  				UpdatedAt: validTimestamp,
  1203  			},
  1204  			err: "ID missing",
  1205  		},
  1206  		{
  1207  			s: &pb.Silence{
  1208  				Id:        "some_id",
  1209  				Matchers:  []*pb.Matcher{},
  1210  				StartsAt:  validTimestamp,
  1211  				EndsAt:    validTimestamp,
  1212  				UpdatedAt: validTimestamp,
  1213  			},
  1214  			err: "at least one matcher required",
  1215  		},
  1216  		{
  1217  			s: &pb.Silence{
  1218  				Id: "some_id",
  1219  				Matchers: []*pb.Matcher{
  1220  					{Name: "a", Pattern: "b"},
  1221  					{Name: "00", Pattern: "b"},
  1222  				},
  1223  				StartsAt:  validTimestamp,
  1224  				EndsAt:    validTimestamp,
  1225  				UpdatedAt: validTimestamp,
  1226  			},
  1227  			err: "invalid label matcher",
  1228  		},
  1229  		{
  1230  			s: &pb.Silence{
  1231  				Id: "some_id",
  1232  				Matchers: []*pb.Matcher{
  1233  					{Name: "a", Pattern: ""},
  1234  					{Name: "b", Pattern: ".*", Type: pb.Matcher_REGEXP},
  1235  				},
  1236  				StartsAt:  validTimestamp,
  1237  				EndsAt:    validTimestamp,
  1238  				UpdatedAt: validTimestamp,
  1239  			},
  1240  			err: "at least one matcher must not match the empty string",
  1241  		},
  1242  		{
  1243  			s: &pb.Silence{
  1244  				Id: "some_id",
  1245  				Matchers: []*pb.Matcher{
  1246  					{Name: "a", Pattern: "b"},
  1247  				},
  1248  				StartsAt:  now,
  1249  				EndsAt:    now.Add(-time.Second),
  1250  				UpdatedAt: validTimestamp,
  1251  			},
  1252  			err: "end time must not be before start time",
  1253  		},
  1254  		{
  1255  			s: &pb.Silence{
  1256  				Id: "some_id",
  1257  				Matchers: []*pb.Matcher{
  1258  					{Name: "a", Pattern: "b"},
  1259  				},
  1260  				StartsAt:  zeroTimestamp,
  1261  				EndsAt:    validTimestamp,
  1262  				UpdatedAt: validTimestamp,
  1263  			},
  1264  			err: "invalid zero start timestamp",
  1265  		},
  1266  		{
  1267  			s: &pb.Silence{
  1268  				Id: "some_id",
  1269  				Matchers: []*pb.Matcher{
  1270  					{Name: "a", Pattern: "b"},
  1271  				},
  1272  				StartsAt:  validTimestamp,
  1273  				EndsAt:    zeroTimestamp,
  1274  				UpdatedAt: validTimestamp,
  1275  			},
  1276  			err: "invalid zero end timestamp",
  1277  		},
  1278  		{
  1279  			s: &pb.Silence{
  1280  				Id: "some_id",
  1281  				Matchers: []*pb.Matcher{
  1282  					{Name: "a", Pattern: "b"},
  1283  				},
  1284  				StartsAt:  validTimestamp,
  1285  				EndsAt:    validTimestamp,
  1286  				UpdatedAt: zeroTimestamp,
  1287  			},
  1288  			err: "invalid zero update timestamp",
  1289  		},
  1290  	}
  1291  	for _, c := range cases {
  1292  		checkErr(t, c.err, validateSilence(c.s))
  1293  	}
  1294  }
  1295  
  1296  func TestStateMerge(t *testing.T) {
  1297  	now := time.Now().UTC()
  1298  
  1299  	// We only care about key names and timestamps for the
  1300  	// merging logic.
  1301  	newSilence := func(id string, ts, exp time.Time) *pb.MeshSilence {
  1302  		return &pb.MeshSilence{
  1303  			Silence:   &pb.Silence{Id: id, UpdatedAt: ts},
  1304  			ExpiresAt: exp,
  1305  		}
  1306  	}
  1307  
  1308  	exp := now.Add(time.Minute)
  1309  
  1310  	cases := []struct {
  1311  		a, b  state
  1312  		final state
  1313  	}{
  1314  		{
  1315  			a: state{
  1316  				"a1": newSilence("a1", now, exp),
  1317  				"a2": newSilence("a2", now, exp),
  1318  				"a3": newSilence("a3", now, exp),
  1319  			},
  1320  			b: state{
  1321  				"b1": newSilence("b1", now, exp),                                          // new key, should be added
  1322  				"a2": newSilence("a2", now.Add(-time.Minute), exp),                        // older timestamp, should be dropped
  1323  				"a3": newSilence("a3", now.Add(time.Minute), exp),                         // newer timestamp, should overwrite
  1324  				"a4": newSilence("a4", now.Add(-time.Minute), now.Add(-time.Millisecond)), // new key, expired, should not be added
  1325  			},
  1326  			final: state{
  1327  				"a1": newSilence("a1", now, exp),
  1328  				"a2": newSilence("a2", now, exp),
  1329  				"a3": newSilence("a3", now.Add(time.Minute), exp),
  1330  				"b1": newSilence("b1", now, exp),
  1331  			},
  1332  		},
  1333  	}
  1334  
  1335  	for _, c := range cases {
  1336  		for _, e := range c.b {
  1337  			c.a.merge(e, now)
  1338  		}
  1339  
  1340  		require.Equal(t, c.final, c.a, "Merge result should match expectation")
  1341  	}
  1342  }
  1343  
  1344  func TestStateCoding(t *testing.T) {
  1345  	// Check whether encoding and decoding the data is symmetric.
  1346  	now := time.Now().UTC()
  1347  
  1348  	cases := []struct {
  1349  		entries []*pb.MeshSilence
  1350  	}{
  1351  		{
  1352  			entries: []*pb.MeshSilence{
  1353  				{
  1354  					Silence: &pb.Silence{
  1355  						Id: "3be80475-e219-4ee7-b6fc-4b65114e362f",
  1356  						Matchers: []*pb.Matcher{
  1357  							{Name: "label1", Pattern: "val1", Type: pb.Matcher_EQUAL},
  1358  							{Name: "label2", Pattern: "val.+", Type: pb.Matcher_REGEXP},
  1359  						},
  1360  						StartsAt:  now,
  1361  						EndsAt:    now,
  1362  						UpdatedAt: now,
  1363  					},
  1364  					ExpiresAt: now,
  1365  				},
  1366  				{
  1367  					Silence: &pb.Silence{
  1368  						Id: "4b1e760d-182c-4980-b873-c1a6827c9817",
  1369  						Matchers: []*pb.Matcher{
  1370  							{Name: "label1", Pattern: "val1", Type: pb.Matcher_EQUAL},
  1371  						},
  1372  						StartsAt:  now.Add(time.Hour),
  1373  						EndsAt:    now.Add(2 * time.Hour),
  1374  						UpdatedAt: now,
  1375  					},
  1376  					ExpiresAt: now.Add(24 * time.Hour),
  1377  				},
  1378  				{
  1379  					Silence: &pb.Silence{
  1380  						Id: "3dfb2528-59ce-41eb-b465-f875a4e744a4",
  1381  						Matchers: []*pb.Matcher{
  1382  							{Name: "label1", Pattern: "val1", Type: pb.Matcher_NOT_EQUAL},
  1383  							{Name: "label2", Pattern: "val.+", Type: pb.Matcher_NOT_REGEXP},
  1384  						},
  1385  						StartsAt:  now,
  1386  						EndsAt:    now,
  1387  						UpdatedAt: now,
  1388  					},
  1389  					ExpiresAt: now,
  1390  				},
  1391  			},
  1392  		},
  1393  	}
  1394  
  1395  	for _, c := range cases {
  1396  		// Create gossip data from input.
  1397  		in := state{}
  1398  		for _, e := range c.entries {
  1399  			in[e.Silence.Id] = e
  1400  		}
  1401  		msg, err := in.MarshalBinary()
  1402  		require.NoError(t, err)
  1403  
  1404  		out, err := decodeState(bytes.NewReader(msg))
  1405  		require.NoError(t, err, "decoding message failed")
  1406  
  1407  		require.Equal(t, in, out, "decoded data doesn't match encoded data")
  1408  	}
  1409  }
  1410  
  1411  func TestStateDecodingError(t *testing.T) {
  1412  	// Check whether decoding copes with erroneous data.
  1413  	s := state{"": &pb.MeshSilence{}}
  1414  
  1415  	msg, err := s.MarshalBinary()
  1416  	require.NoError(t, err)
  1417  
  1418  	_, err = decodeState(bytes.NewReader(msg))
  1419  	require.Equal(t, ErrInvalidState, err)
  1420  }
  1421  
  1422  func benchmarkSilencesQuery(b *testing.B, numSilences int) {
  1423  	s, err := New(Options{})
  1424  	require.NoError(b, err)
  1425  
  1426  	clock := clock.NewMock()
  1427  	s.clock = clock
  1428  	now := clock.Now()
  1429  
  1430  	lset := model.LabelSet{"aaaa": "AAAA", "bbbb": "BBBB", "cccc": "CCCC"}
  1431  
  1432  	s.st = state{}
  1433  	for i := 0; i < numSilences; i++ {
  1434  		id := fmt.Sprint("ID", i)
  1435  		// Patterns also contain the ID to bust any caches that might be used under the hood.
  1436  		patA := "A{4}|" + id
  1437  		patB := id // Does not match.
  1438  		if i%10 == 0 {
  1439  			// Every 10th time, have an actually matching pattern.
  1440  			patB = "B(B|C)B.|" + id
  1441  		}
  1442  
  1443  		s.st[id] = &pb.MeshSilence{Silence: &pb.Silence{
  1444  			Id: id,
  1445  			Matchers: []*pb.Matcher{
  1446  				{Type: pb.Matcher_REGEXP, Name: "aaaa", Pattern: patA},
  1447  				{Type: pb.Matcher_REGEXP, Name: "bbbb", Pattern: patB},
  1448  			},
  1449  			StartsAt:  now.Add(-time.Minute),
  1450  			EndsAt:    now.Add(time.Hour),
  1451  			UpdatedAt: now.Add(-time.Hour),
  1452  		}}
  1453  	}
  1454  
  1455  	// Run things once to populate the matcherCache.
  1456  	sils, _, err := s.Query(
  1457  		QState(types.SilenceStateActive),
  1458  		QMatches(lset),
  1459  	)
  1460  	require.NoError(b, err)
  1461  	require.Equal(b, numSilences/10, len(sils))
  1462  
  1463  	b.ResetTimer()
  1464  	for i := 0; i < b.N; i++ {
  1465  		sils, _, err := s.Query(
  1466  			QState(types.SilenceStateActive),
  1467  			QMatches(lset),
  1468  		)
  1469  		require.NoError(b, err)
  1470  		require.Equal(b, numSilences/10, len(sils))
  1471  	}
  1472  }
  1473  
  1474  func Benchmark100SilencesQuery(b *testing.B) {
  1475  	benchmarkSilencesQuery(b, 100)
  1476  }
  1477  
  1478  func Benchmark1000SilencesQuery(b *testing.B) {
  1479  	benchmarkSilencesQuery(b, 1000)
  1480  }
  1481  
  1482  func Benchmark10000SilencesQuery(b *testing.B) {
  1483  	benchmarkSilencesQuery(b, 10000)
  1484  }
  1485  

View as plain text