...

Source file src/github.com/prometheus/alertmanager/api/v1/api_test.go

Documentation: github.com/prometheus/alertmanager/api/v1

     1  // Copyright 2018 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 v1
    15  
    16  import (
    17  	"bytes"
    18  	"encoding/json"
    19  	"errors"
    20  	"fmt"
    21  	"io"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"regexp"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/prometheus/common/model"
    29  	"github.com/stretchr/testify/require"
    30  
    31  	"github.com/prometheus/alertmanager/config"
    32  	"github.com/prometheus/alertmanager/dispatch"
    33  	"github.com/prometheus/alertmanager/pkg/labels"
    34  	"github.com/prometheus/alertmanager/provider"
    35  	"github.com/prometheus/alertmanager/types"
    36  )
    37  
    38  // fakeAlerts is a struct implementing the provider.Alerts interface for tests.
    39  type fakeAlerts struct {
    40  	fps    map[model.Fingerprint]int
    41  	alerts []*types.Alert
    42  	err    error
    43  }
    44  
    45  func newFakeAlerts(alerts []*types.Alert, withErr bool) *fakeAlerts {
    46  	fps := make(map[model.Fingerprint]int)
    47  	for i, a := range alerts {
    48  		fps[a.Fingerprint()] = i
    49  	}
    50  	f := &fakeAlerts{
    51  		alerts: alerts,
    52  		fps:    fps,
    53  	}
    54  	if withErr {
    55  		f.err = errors.New("error occurred")
    56  	}
    57  	return f
    58  }
    59  
    60  func (f *fakeAlerts) Subscribe() provider.AlertIterator           { return nil }
    61  func (f *fakeAlerts) Get(model.Fingerprint) (*types.Alert, error) { return nil, nil }
    62  func (f *fakeAlerts) Put(alerts ...*types.Alert) error {
    63  	return f.err
    64  }
    65  
    66  func (f *fakeAlerts) GetPending() provider.AlertIterator {
    67  	ch := make(chan *types.Alert)
    68  	done := make(chan struct{})
    69  	go func() {
    70  		defer close(ch)
    71  		for _, a := range f.alerts {
    72  			ch <- a
    73  		}
    74  	}()
    75  	return provider.NewAlertIterator(ch, done, f.err)
    76  }
    77  
    78  func newGetAlertStatus(f *fakeAlerts) func(model.Fingerprint) types.AlertStatus {
    79  	return func(fp model.Fingerprint) types.AlertStatus {
    80  		status := types.AlertStatus{SilencedBy: []string{}, InhibitedBy: []string{}}
    81  
    82  		i, ok := f.fps[fp]
    83  		if !ok {
    84  			return status
    85  		}
    86  		alert := f.alerts[i]
    87  		switch alert.Labels["state"] {
    88  		case "active":
    89  			status.State = types.AlertStateActive
    90  		case "unprocessed":
    91  			status.State = types.AlertStateUnprocessed
    92  		case "suppressed":
    93  			status.State = types.AlertStateSuppressed
    94  		}
    95  		if alert.Labels["silenced_by"] != "" {
    96  			status.SilencedBy = append(status.SilencedBy, string(alert.Labels["silenced_by"]))
    97  		}
    98  		if alert.Labels["inhibited_by"] != "" {
    99  			status.InhibitedBy = append(status.InhibitedBy, string(alert.Labels["inhibited_by"]))
   100  		}
   101  		return status
   102  	}
   103  }
   104  
   105  func TestAddAlerts(t *testing.T) {
   106  	now := func(offset int) time.Time {
   107  		return time.Now().Add(time.Duration(offset) * time.Second)
   108  	}
   109  
   110  	for i, tc := range []struct {
   111  		start, end time.Time
   112  		err        bool
   113  		code       int
   114  	}{
   115  		{time.Time{}, time.Time{}, false, 200},
   116  		{now(0), time.Time{}, false, 200},
   117  		{time.Time{}, now(-1), false, 200},
   118  		{time.Time{}, now(0), false, 200},
   119  		{time.Time{}, now(1), false, 200},
   120  		{now(-2), now(-1), false, 200},
   121  		{now(1), now(2), false, 200},
   122  		{now(1), now(0), false, 400},
   123  		{now(0), time.Time{}, true, 500},
   124  	} {
   125  		alerts := []model.Alert{{
   126  			StartsAt:    tc.start,
   127  			EndsAt:      tc.end,
   128  			Labels:      model.LabelSet{"label1": "test1"},
   129  			Annotations: model.LabelSet{"annotation1": "some text"},
   130  		}}
   131  		b, err := json.Marshal(&alerts)
   132  		if err != nil {
   133  			t.Errorf("Unexpected error %v", err)
   134  		}
   135  
   136  		alertsProvider := newFakeAlerts([]*types.Alert{}, tc.err)
   137  		api := New(alertsProvider, nil, newGetAlertStatus(alertsProvider), nil, nil, nil)
   138  		defaultGlobalConfig := config.DefaultGlobalConfig()
   139  		route := config.Route{}
   140  		api.Update(&config.Config{
   141  			Global: &defaultGlobalConfig,
   142  			Route:  &route,
   143  		})
   144  
   145  		r, err := http.NewRequest("POST", "/api/v1/alerts", bytes.NewReader(b))
   146  		w := httptest.NewRecorder()
   147  		if err != nil {
   148  			t.Errorf("Unexpected error %v", err)
   149  		}
   150  
   151  		api.addAlerts(w, r)
   152  		res := w.Result()
   153  		body, _ := io.ReadAll(res.Body)
   154  
   155  		require.Equal(t, tc.code, w.Code, fmt.Sprintf("test case: %d, StartsAt %v, EndsAt %v, Response: %s", i, tc.start, tc.end, string(body)))
   156  	}
   157  }
   158  
   159  func TestListAlerts(t *testing.T) {
   160  	now := time.Now()
   161  	alerts := []*types.Alert{
   162  		{
   163  			Alert: model.Alert{
   164  				Labels:   model.LabelSet{"state": "active", "alertname": "alert1"},
   165  				StartsAt: now.Add(-time.Minute),
   166  			},
   167  		},
   168  		{
   169  			Alert: model.Alert{
   170  				Labels:   model.LabelSet{"state": "unprocessed", "alertname": "alert2"},
   171  				StartsAt: now.Add(-time.Minute),
   172  			},
   173  		},
   174  		{
   175  			Alert: model.Alert{
   176  				Labels:   model.LabelSet{"state": "suppressed", "silenced_by": "abc", "alertname": "alert3"},
   177  				StartsAt: now.Add(-time.Minute),
   178  			},
   179  		},
   180  		{
   181  			Alert: model.Alert{
   182  				Labels:   model.LabelSet{"state": "suppressed", "inhibited_by": "abc", "alertname": "alert4"},
   183  				StartsAt: now.Add(-time.Minute),
   184  			},
   185  		},
   186  		{
   187  			Alert: model.Alert{
   188  				Labels:   model.LabelSet{"alertname": "alert5"},
   189  				StartsAt: now.Add(-2 * time.Minute),
   190  				EndsAt:   now.Add(-time.Minute),
   191  			},
   192  		},
   193  	}
   194  
   195  	for i, tc := range []struct {
   196  		err    bool
   197  		params map[string]string
   198  
   199  		code   int
   200  		anames []string
   201  	}{
   202  		{
   203  			false,
   204  			map[string]string{},
   205  			200,
   206  			[]string{"alert1", "alert2", "alert3", "alert4"},
   207  		},
   208  		{
   209  			false,
   210  			map[string]string{"active": "true", "unprocessed": "true", "silenced": "true", "inhibited": "true"},
   211  			200,
   212  			[]string{"alert1", "alert2", "alert3", "alert4"},
   213  		},
   214  		{
   215  			false,
   216  			map[string]string{"active": "false", "unprocessed": "true", "silenced": "true", "inhibited": "true"},
   217  			200,
   218  			[]string{"alert2", "alert3", "alert4"},
   219  		},
   220  		{
   221  			false,
   222  			map[string]string{"active": "true", "unprocessed": "false", "silenced": "true", "inhibited": "true"},
   223  			200,
   224  			[]string{"alert1", "alert3", "alert4"},
   225  		},
   226  		{
   227  			false,
   228  			map[string]string{"active": "true", "unprocessed": "true", "silenced": "false", "inhibited": "true"},
   229  			200,
   230  			[]string{"alert1", "alert2", "alert4"},
   231  		},
   232  		{
   233  			false,
   234  			map[string]string{"active": "true", "unprocessed": "true", "silenced": "true", "inhibited": "false"},
   235  			200,
   236  			[]string{"alert1", "alert2", "alert3"},
   237  		},
   238  		{
   239  			false,
   240  			map[string]string{"filter": "{alertname=\"alert3\""},
   241  			200,
   242  			[]string{"alert3"},
   243  		},
   244  		{
   245  			false,
   246  			map[string]string{"filter": "{alertname"},
   247  			400,
   248  			[]string{},
   249  		},
   250  		{
   251  			false,
   252  			map[string]string{"receiver": "other"},
   253  			200,
   254  			[]string{},
   255  		},
   256  		{
   257  			false,
   258  			map[string]string{"active": "invalid"},
   259  			400,
   260  			[]string{},
   261  		},
   262  		{
   263  			true,
   264  			map[string]string{},
   265  			500,
   266  			[]string{},
   267  		},
   268  	} {
   269  		alertsProvider := newFakeAlerts(alerts, tc.err)
   270  		api := New(alertsProvider, nil, newGetAlertStatus(alertsProvider), nil, nil, nil)
   271  		api.route = dispatch.NewRoute(&config.Route{Receiver: "def-receiver"}, nil)
   272  
   273  		r, err := http.NewRequest("GET", "/api/v1/alerts", nil)
   274  		if err != nil {
   275  			t.Fatalf("Unexpected error %v", err)
   276  		}
   277  		q := r.URL.Query()
   278  		for k, v := range tc.params {
   279  			q.Add(k, v)
   280  		}
   281  		r.URL.RawQuery = q.Encode()
   282  		w := httptest.NewRecorder()
   283  
   284  		api.listAlerts(w, r)
   285  		body, _ := io.ReadAll(w.Result().Body)
   286  
   287  		var res response
   288  		err = json.Unmarshal(body, &res)
   289  		if err != nil {
   290  			t.Fatalf("Unexpected error %v", err)
   291  		}
   292  
   293  		require.Equal(t, tc.code, w.Code, fmt.Sprintf("test case: %d, response: %s", i, string(body)))
   294  		if w.Code != 200 {
   295  			continue
   296  		}
   297  
   298  		// Data needs to be serialized/deserialized to be converted to the real type.
   299  		b, err := json.Marshal(res.Data)
   300  		if err != nil {
   301  			t.Fatalf("Unexpected error %v", err)
   302  		}
   303  		retAlerts := []*Alert{}
   304  		err = json.Unmarshal(b, &retAlerts)
   305  		if err != nil {
   306  			t.Fatalf("Unexpected error %v", err)
   307  		}
   308  
   309  		anames := []string{}
   310  		for _, a := range retAlerts {
   311  			name, ok := a.Labels["alertname"]
   312  			if ok {
   313  				anames = append(anames, string(name))
   314  			}
   315  		}
   316  		require.Equal(t, tc.anames, anames, fmt.Sprintf("test case: %d, alert names are not equal", i))
   317  	}
   318  }
   319  
   320  func TestAlertFiltering(t *testing.T) {
   321  	type test struct {
   322  		alert    *model.Alert
   323  		msg      string
   324  		expected bool
   325  	}
   326  
   327  	// Equal
   328  	equal, err := labels.NewMatcher(labels.MatchEqual, "label1", "test1")
   329  	if err != nil {
   330  		t.Errorf("Unexpected error %v", err)
   331  	}
   332  
   333  	tests := []test{
   334  		{&model.Alert{Labels: model.LabelSet{"label1": "test1"}}, "label1=test1", true},
   335  		{&model.Alert{Labels: model.LabelSet{"label1": "test2"}}, "label1=test2", false},
   336  		{&model.Alert{Labels: model.LabelSet{"label2": "test2"}}, "label2=test2", false},
   337  	}
   338  
   339  	for _, test := range tests {
   340  		actual := alertMatchesFilterLabels(test.alert, []*labels.Matcher{equal})
   341  		msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
   342  		require.Equal(t, test.expected, actual, msg)
   343  	}
   344  
   345  	// Not Equal
   346  	notEqual, err := labels.NewMatcher(labels.MatchNotEqual, "label1", "test1")
   347  	if err != nil {
   348  		t.Errorf("Unexpected error %v", err)
   349  	}
   350  
   351  	tests = []test{
   352  		{&model.Alert{Labels: model.LabelSet{"label1": "test1"}}, "label1!=test1", false},
   353  		{&model.Alert{Labels: model.LabelSet{"label1": "test2"}}, "label1!=test2", true},
   354  		{&model.Alert{Labels: model.LabelSet{"label2": "test2"}}, "label2!=test2", true},
   355  	}
   356  
   357  	for _, test := range tests {
   358  		actual := alertMatchesFilterLabels(test.alert, []*labels.Matcher{notEqual})
   359  		msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
   360  		require.Equal(t, test.expected, actual, msg)
   361  	}
   362  
   363  	// Regexp Equal
   364  	regexpEqual, err := labels.NewMatcher(labels.MatchRegexp, "label1", "tes.*")
   365  	if err != nil {
   366  		t.Errorf("Unexpected error %v", err)
   367  	}
   368  
   369  	tests = []test{
   370  		{&model.Alert{Labels: model.LabelSet{"label1": "test1"}}, "label1=~test1", true},
   371  		{&model.Alert{Labels: model.LabelSet{"label1": "test2"}}, "label1=~test2", true},
   372  		{&model.Alert{Labels: model.LabelSet{"label2": "test2"}}, "label2=~test2", false},
   373  	}
   374  
   375  	for _, test := range tests {
   376  		actual := alertMatchesFilterLabels(test.alert, []*labels.Matcher{regexpEqual})
   377  		msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
   378  		require.Equal(t, test.expected, actual, msg)
   379  	}
   380  
   381  	// Regexp Not Equal
   382  	regexpNotEqual, err := labels.NewMatcher(labels.MatchNotRegexp, "label1", "tes.*")
   383  	if err != nil {
   384  		t.Errorf("Unexpected error %v", err)
   385  	}
   386  
   387  	tests = []test{
   388  		{&model.Alert{Labels: model.LabelSet{"label1": "test1"}}, "label1!~test1", false},
   389  		{&model.Alert{Labels: model.LabelSet{"label1": "test2"}}, "label1!~test2", false},
   390  		{&model.Alert{Labels: model.LabelSet{"label2": "test2"}}, "label2!~test2", true},
   391  	}
   392  
   393  	for _, test := range tests {
   394  		actual := alertMatchesFilterLabels(test.alert, []*labels.Matcher{regexpNotEqual})
   395  		msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
   396  		require.Equal(t, test.expected, actual, msg)
   397  	}
   398  }
   399  
   400  func TestSilenceFiltering(t *testing.T) {
   401  	type test struct {
   402  		silence  *types.Silence
   403  		msg      string
   404  		expected bool
   405  	}
   406  
   407  	// Equal
   408  	equal, err := labels.NewMatcher(labels.MatchEqual, "label1", "test1")
   409  	if err != nil {
   410  		t.Errorf("Unexpected error %v", err)
   411  	}
   412  
   413  	tests := []test{
   414  		{
   415  			&types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test1"})},
   416  			"label1=test1",
   417  			true,
   418  		},
   419  		{
   420  			&types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test2"})},
   421  			"label1=test2",
   422  			false,
   423  		},
   424  		{
   425  			&types.Silence{Matchers: newMatcher(model.LabelSet{"label2": "test2"})},
   426  			"label2=test2",
   427  			false,
   428  		},
   429  	}
   430  
   431  	for _, test := range tests {
   432  		actual := silenceMatchesFilterLabels(test.silence, []*labels.Matcher{equal})
   433  		msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
   434  		require.Equal(t, test.expected, actual, msg)
   435  	}
   436  
   437  	// Not Equal
   438  	notEqual, err := labels.NewMatcher(labels.MatchNotEqual, "label1", "test1")
   439  	if err != nil {
   440  		t.Errorf("Unexpected error %v", err)
   441  	}
   442  
   443  	tests = []test{
   444  		{
   445  			&types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test1"})},
   446  			"label1!=test1",
   447  			false,
   448  		},
   449  		{
   450  			&types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test2"})},
   451  			"label1!=test2",
   452  			true,
   453  		},
   454  		{
   455  			&types.Silence{Matchers: newMatcher(model.LabelSet{"label2": "test2"})},
   456  			"label2!=test2",
   457  			true,
   458  		},
   459  	}
   460  
   461  	for _, test := range tests {
   462  		actual := silenceMatchesFilterLabels(test.silence, []*labels.Matcher{notEqual})
   463  		msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
   464  		require.Equal(t, test.expected, actual, msg)
   465  	}
   466  
   467  	// Regexp Equal
   468  	regexpEqual, err := labels.NewMatcher(labels.MatchRegexp, "label1", "tes.*")
   469  	if err != nil {
   470  		t.Errorf("Unexpected error %v", err)
   471  	}
   472  
   473  	tests = []test{
   474  		{
   475  			&types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test1"})},
   476  			"label1=~test1",
   477  			true,
   478  		},
   479  		{
   480  			&types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test2"})},
   481  			"label1=~test2",
   482  			true,
   483  		},
   484  		{
   485  			&types.Silence{Matchers: newMatcher(model.LabelSet{"label2": "test2"})},
   486  			"label2=~test2",
   487  			false,
   488  		},
   489  	}
   490  
   491  	for _, test := range tests {
   492  		actual := silenceMatchesFilterLabels(test.silence, []*labels.Matcher{regexpEqual})
   493  		msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
   494  		require.Equal(t, test.expected, actual, msg)
   495  	}
   496  
   497  	// Regexp Not Equal
   498  	regexpNotEqual, err := labels.NewMatcher(labels.MatchNotRegexp, "label1", "tes.*")
   499  	if err != nil {
   500  		t.Errorf("Unexpected error %v", err)
   501  	}
   502  
   503  	tests = []test{
   504  		{
   505  			&types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test1"})},
   506  			"label1!~test1",
   507  			false,
   508  		},
   509  		{
   510  			&types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test2"})},
   511  			"label1!~test2",
   512  			false,
   513  		},
   514  		{
   515  			&types.Silence{Matchers: newMatcher(model.LabelSet{"label2": "test2"})},
   516  			"label2!~test2",
   517  			true,
   518  		},
   519  	}
   520  
   521  	for _, test := range tests {
   522  		actual := silenceMatchesFilterLabels(test.silence, []*labels.Matcher{regexpNotEqual})
   523  		msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
   524  		require.Equal(t, test.expected, actual, msg)
   525  	}
   526  }
   527  
   528  func TestReceiversMatchFilter(t *testing.T) {
   529  	receivers := []string{"pagerduty", "slack", "pushover"}
   530  
   531  	filter, err := regexp.Compile(fmt.Sprintf("^(?:%s)$", "push.*"))
   532  	if err != nil {
   533  		t.Errorf("Unexpected error %v", err)
   534  	}
   535  	require.True(t, receiversMatchFilter(receivers, filter))
   536  
   537  	filter, err = regexp.Compile(fmt.Sprintf("^(?:%s)$", "push"))
   538  	if err != nil {
   539  		t.Errorf("Unexpected error %v", err)
   540  	}
   541  	require.False(t, receiversMatchFilter(receivers, filter))
   542  }
   543  
   544  func TestMatchFilterLabels(t *testing.T) {
   545  	testCases := []struct {
   546  		matcher  labels.MatchType
   547  		expected bool
   548  	}{
   549  		{labels.MatchEqual, true},
   550  		{labels.MatchRegexp, true},
   551  		{labels.MatchNotEqual, false},
   552  		{labels.MatchNotRegexp, false},
   553  	}
   554  
   555  	for _, tc := range testCases {
   556  		l, err := labels.NewMatcher(tc.matcher, "foo", "")
   557  		require.NoError(t, err)
   558  		sms := map[string]string{
   559  			"baz": "bar",
   560  		}
   561  		ls := []*labels.Matcher{l}
   562  
   563  		require.Equal(t, tc.expected, matchFilterLabels(ls, sms))
   564  
   565  		l, err = labels.NewMatcher(tc.matcher, "foo", "")
   566  		require.NoError(t, err)
   567  		sms = map[string]string{
   568  			"baz": "bar",
   569  			"foo": "quux",
   570  		}
   571  		ls = []*labels.Matcher{l}
   572  		require.NotEqual(t, tc.expected, matchFilterLabels(ls, sms))
   573  	}
   574  }
   575  
   576  func newMatcher(labelSet model.LabelSet) labels.Matchers {
   577  	matchers := make([]*labels.Matcher, 0, len(labelSet))
   578  	for key, val := range labelSet {
   579  		matchers = append(matchers, &labels.Matcher{
   580  			Type:  labels.MatchEqual,
   581  			Name:  string(key),
   582  			Value: string(val),
   583  		})
   584  	}
   585  	return matchers
   586  }
   587  

View as plain text