...

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

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

     1  // Copyright 2019 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 v2
    15  
    16  import (
    17  	"bytes"
    18  	"fmt"
    19  	"io"
    20  	"net/http"
    21  	"net/http/httptest"
    22  	"strconv"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/go-openapi/runtime"
    27  	"github.com/go-openapi/strfmt"
    28  	"github.com/prometheus/common/model"
    29  	"github.com/stretchr/testify/require"
    30  
    31  	open_api_models "github.com/prometheus/alertmanager/api/v2/models"
    32  	general_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/general"
    33  	silence_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/silence"
    34  	"github.com/prometheus/alertmanager/config"
    35  	"github.com/prometheus/alertmanager/pkg/labels"
    36  	"github.com/prometheus/alertmanager/silence"
    37  	"github.com/prometheus/alertmanager/silence/silencepb"
    38  	"github.com/prometheus/alertmanager/types"
    39  
    40  	"github.com/go-kit/log"
    41  )
    42  
    43  // If api.peers == nil, Alertmanager cluster feature is disabled. Make sure to
    44  // not try to access properties of peer, which would trigger a nil pointer
    45  // dereference.
    46  func TestGetStatusHandlerWithNilPeer(t *testing.T) {
    47  	api := API{
    48  		uptime:             time.Now(),
    49  		peer:               nil,
    50  		alertmanagerConfig: &config.Config{},
    51  	}
    52  
    53  	// Test ensures this method call does not panic.
    54  	status := api.getStatusHandler(general_ops.GetStatusParams{}).(*general_ops.GetStatusOK)
    55  
    56  	c := status.Payload.Cluster
    57  
    58  	if c == nil || c.Status == nil {
    59  		t.Fatal("expected cluster status not to be nil, violating the openapi specification")
    60  	}
    61  
    62  	if c.Peers == nil {
    63  		t.Fatal("expected cluster peers to be not nil when api.peer is nil, violating the openapi specification")
    64  	}
    65  	if len(c.Peers) != 0 {
    66  		t.Fatal("expected cluster peers to be empty when api.peer is nil, violating the openapi specification")
    67  	}
    68  
    69  	if c.Name != "" {
    70  		t.Fatal("expected cluster name to be empty, violating the openapi specification")
    71  	}
    72  }
    73  
    74  func assertEqualStrings(t *testing.T, expected, actual string) {
    75  	if expected != actual {
    76  		t.Fatal("expected: ", expected, ", actual: ", actual)
    77  	}
    78  }
    79  
    80  var (
    81  	testComment = "comment"
    82  	createdBy   = "test"
    83  )
    84  
    85  func newSilences(t *testing.T) *silence.Silences {
    86  	silences, err := silence.New(silence.Options{})
    87  	require.NoError(t, err)
    88  
    89  	return silences
    90  }
    91  
    92  func gettableSilence(id, state string,
    93  	updatedAt, start, end string,
    94  ) *open_api_models.GettableSilence {
    95  	updAt, err := strfmt.ParseDateTime(updatedAt)
    96  	if err != nil {
    97  		panic(err)
    98  	}
    99  	strAt, err := strfmt.ParseDateTime(start)
   100  	if err != nil {
   101  		panic(err)
   102  	}
   103  	endAt, err := strfmt.ParseDateTime(end)
   104  	if err != nil {
   105  		panic(err)
   106  	}
   107  	return &open_api_models.GettableSilence{
   108  		Silence: open_api_models.Silence{
   109  			StartsAt:  &strAt,
   110  			EndsAt:    &endAt,
   111  			Comment:   &testComment,
   112  			CreatedBy: &createdBy,
   113  		},
   114  		ID:        &id,
   115  		UpdatedAt: &updAt,
   116  		Status: &open_api_models.SilenceStatus{
   117  			State: &state,
   118  		},
   119  	}
   120  }
   121  
   122  func TestGetSilencesHandler(t *testing.T) {
   123  	updateTime := "2019-01-01T12:00:00+00:00"
   124  	silences := []*open_api_models.GettableSilence{
   125  		gettableSilence("silence-6-expired", "expired", updateTime,
   126  			"2019-01-01T12:00:00+00:00", "2019-01-01T11:00:00+00:00"),
   127  		gettableSilence("silence-1-active", "active", updateTime,
   128  			"2019-01-01T12:00:00+00:00", "2019-01-01T13:00:00+00:00"),
   129  		gettableSilence("silence-7-expired", "expired", updateTime,
   130  			"2019-01-01T12:00:00+00:00", "2019-01-01T10:00:00+00:00"),
   131  		gettableSilence("silence-5-expired", "expired", updateTime,
   132  			"2019-01-01T12:00:00+00:00", "2019-01-01T12:00:00+00:00"),
   133  		gettableSilence("silence-0-active", "active", updateTime,
   134  			"2019-01-01T12:00:00+00:00", "2019-01-01T12:00:00+00:00"),
   135  		gettableSilence("silence-4-pending", "pending", updateTime,
   136  			"2019-01-01T13:00:00+00:00", "2019-01-01T12:00:00+00:00"),
   137  		gettableSilence("silence-3-pending", "pending", updateTime,
   138  			"2019-01-01T12:00:00+00:00", "2019-01-01T12:00:00+00:00"),
   139  		gettableSilence("silence-2-active", "active", updateTime,
   140  			"2019-01-01T12:00:00+00:00", "2019-01-01T14:00:00+00:00"),
   141  	}
   142  	SortSilences(open_api_models.GettableSilences(silences))
   143  
   144  	for i, sil := range silences {
   145  		assertEqualStrings(t, "silence-"+strconv.Itoa(i)+"-"+*sil.Status.State, *sil.ID)
   146  	}
   147  }
   148  
   149  func TestDeleteSilenceHandler(t *testing.T) {
   150  	now := time.Now()
   151  	silences := newSilences(t)
   152  
   153  	m := &silencepb.Matcher{Type: silencepb.Matcher_EQUAL, Name: "a", Pattern: "b"}
   154  
   155  	unexpiredSil := &silencepb.Silence{
   156  		Matchers:  []*silencepb.Matcher{m},
   157  		StartsAt:  now,
   158  		EndsAt:    now.Add(time.Hour),
   159  		UpdatedAt: now,
   160  	}
   161  	unexpiredSid, err := silences.Set(unexpiredSil)
   162  	require.NoError(t, err)
   163  
   164  	expiredSil := &silencepb.Silence{
   165  		Matchers:  []*silencepb.Matcher{m},
   166  		StartsAt:  now.Add(-time.Hour),
   167  		EndsAt:    now.Add(time.Hour),
   168  		UpdatedAt: now,
   169  	}
   170  	expiredSid, err := silences.Set(expiredSil)
   171  	require.NoError(t, err)
   172  	require.NoError(t, silences.Expire(expiredSid))
   173  
   174  	for i, tc := range []struct {
   175  		sid          string
   176  		expectedCode int
   177  	}{
   178  		{
   179  			"unknownSid",
   180  			500,
   181  		},
   182  		{
   183  			unexpiredSid,
   184  			200,
   185  		},
   186  		{
   187  			expiredSid,
   188  			200,
   189  		},
   190  	} {
   191  		api := API{
   192  			uptime:   time.Now(),
   193  			silences: silences,
   194  			logger:   log.NewNopLogger(),
   195  		}
   196  
   197  		r, err := http.NewRequest("DELETE", "/api/v2/silence/${tc.sid}", nil)
   198  		require.NoError(t, err)
   199  
   200  		w := httptest.NewRecorder()
   201  		p := runtime.TextProducer()
   202  		responder := api.deleteSilenceHandler(silence_ops.DeleteSilenceParams{
   203  			SilenceID:   strfmt.UUID(tc.sid),
   204  			HTTPRequest: r,
   205  		})
   206  		responder.WriteResponse(w, p)
   207  		body, _ := io.ReadAll(w.Result().Body)
   208  
   209  		require.Equal(t, tc.expectedCode, w.Code, fmt.Sprintf("test case: %d, response: %s", i, string(body)))
   210  	}
   211  }
   212  
   213  func TestPostSilencesHandler(t *testing.T) {
   214  	now := time.Now()
   215  	silences := newSilences(t)
   216  
   217  	m := &silencepb.Matcher{Type: silencepb.Matcher_EQUAL, Name: "a", Pattern: "b"}
   218  
   219  	unexpiredSil := &silencepb.Silence{
   220  		Matchers:  []*silencepb.Matcher{m},
   221  		StartsAt:  now,
   222  		EndsAt:    now.Add(time.Hour),
   223  		UpdatedAt: now,
   224  	}
   225  	unexpiredSid, err := silences.Set(unexpiredSil)
   226  	require.NoError(t, err)
   227  
   228  	expiredSil := &silencepb.Silence{
   229  		Matchers:  []*silencepb.Matcher{m},
   230  		StartsAt:  now.Add(-time.Hour),
   231  		EndsAt:    now.Add(time.Hour),
   232  		UpdatedAt: now,
   233  	}
   234  	expiredSid, err := silences.Set(expiredSil)
   235  	require.NoError(t, err)
   236  	require.NoError(t, silences.Expire(expiredSid))
   237  
   238  	t.Run("Silences CRUD", func(t *testing.T) {
   239  		for i, tc := range []struct {
   240  			name         string
   241  			sid          string
   242  			start, end   time.Time
   243  			expectedCode int
   244  		}{
   245  			{
   246  				"with an non-existent silence ID - it returns 404",
   247  				"unknownSid",
   248  				now.Add(time.Hour),
   249  				now.Add(time.Hour * 2),
   250  				404,
   251  			},
   252  			{
   253  				"with no silence ID - it creates the silence",
   254  				"",
   255  				now.Add(time.Hour),
   256  				now.Add(time.Hour * 2),
   257  				200,
   258  			},
   259  			{
   260  				"with an active silence ID - it extends the silence",
   261  				unexpiredSid,
   262  				now.Add(time.Hour),
   263  				now.Add(time.Hour * 2),
   264  				200,
   265  			},
   266  			{
   267  				"with an expired silence ID - it re-creates the silence",
   268  				expiredSid,
   269  				now.Add(time.Hour),
   270  				now.Add(time.Hour * 2),
   271  				200,
   272  			},
   273  		} {
   274  			t.Run(tc.name, func(t *testing.T) {
   275  				silence, silenceBytes := createSilence(t, tc.sid, "silenceCreator", tc.start, tc.end)
   276  
   277  				api := API{
   278  					uptime:   time.Now(),
   279  					silences: silences,
   280  					logger:   log.NewNopLogger(),
   281  				}
   282  
   283  				r, err := http.NewRequest("POST", "/api/v2/silence/${tc.sid}", bytes.NewReader(silenceBytes))
   284  				require.NoError(t, err)
   285  
   286  				w := httptest.NewRecorder()
   287  				p := runtime.TextProducer()
   288  				responder := api.postSilencesHandler(silence_ops.PostSilencesParams{
   289  					HTTPRequest: r,
   290  					Silence:     &silence,
   291  				})
   292  				responder.WriteResponse(w, p)
   293  				body, _ := io.ReadAll(w.Result().Body)
   294  
   295  				require.Equal(t, tc.expectedCode, w.Code, fmt.Sprintf("test case: %d, response: %s", i, string(body)))
   296  			})
   297  		}
   298  	})
   299  }
   300  
   301  func TestCheckSilenceMatchesFilterLabels(t *testing.T) {
   302  	type test struct {
   303  		silenceMatchers []*silencepb.Matcher
   304  		filterMatchers  []*labels.Matcher
   305  		expected        bool
   306  	}
   307  
   308  	tests := []test{
   309  		{
   310  			[]*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_EQUAL)},
   311  			[]*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchEqual)},
   312  			true,
   313  		},
   314  		{
   315  			[]*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_EQUAL)},
   316  			[]*labels.Matcher{createLabelMatcher(t, "label", "novalue", labels.MatchEqual)},
   317  			false,
   318  		},
   319  		{
   320  			[]*silencepb.Matcher{createSilenceMatcher(t, "label", "(foo|bar)", silencepb.Matcher_REGEXP)},
   321  			[]*labels.Matcher{createLabelMatcher(t, "label", "(foo|bar)", labels.MatchRegexp)},
   322  			true,
   323  		},
   324  		{
   325  			[]*silencepb.Matcher{createSilenceMatcher(t, "label", "foo", silencepb.Matcher_REGEXP)},
   326  			[]*labels.Matcher{createLabelMatcher(t, "label", "(foo|bar)", labels.MatchRegexp)},
   327  			false,
   328  		},
   329  
   330  		{
   331  			[]*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_EQUAL)},
   332  			[]*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchRegexp)},
   333  			false,
   334  		},
   335  		{
   336  			[]*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_REGEXP)},
   337  			[]*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchEqual)},
   338  			false,
   339  		},
   340  		{
   341  			[]*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_NOT_EQUAL)},
   342  			[]*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchNotEqual)},
   343  			true,
   344  		},
   345  		{
   346  			[]*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_NOT_REGEXP)},
   347  			[]*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchNotRegexp)},
   348  			true,
   349  		},
   350  		{
   351  			[]*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_EQUAL)},
   352  			[]*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchNotEqual)},
   353  			false,
   354  		},
   355  		{
   356  			[]*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_REGEXP)},
   357  			[]*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchNotRegexp)},
   358  			false,
   359  		},
   360  		{
   361  			[]*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_NOT_EQUAL)},
   362  			[]*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchNotRegexp)},
   363  			false,
   364  		},
   365  		{
   366  			[]*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_NOT_REGEXP)},
   367  			[]*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchNotEqual)},
   368  			false,
   369  		},
   370  		{
   371  			[]*silencepb.Matcher{
   372  				createSilenceMatcher(t, "label", "(foo|bar)", silencepb.Matcher_REGEXP),
   373  				createSilenceMatcher(t, "label", "value", silencepb.Matcher_EQUAL),
   374  			},
   375  			[]*labels.Matcher{createLabelMatcher(t, "label", "(foo|bar)", labels.MatchRegexp)},
   376  			true,
   377  		},
   378  	}
   379  
   380  	for _, test := range tests {
   381  		silence := silencepb.Silence{
   382  			Matchers: test.silenceMatchers,
   383  		}
   384  		actual := CheckSilenceMatchesFilterLabels(&silence, test.filterMatchers)
   385  		if test.expected != actual {
   386  			t.Fatal("unexpected match result between silence and filter. expected:", test.expected, ", actual:", actual)
   387  		}
   388  	}
   389  }
   390  
   391  func convertDateTime(ts time.Time) *strfmt.DateTime {
   392  	dt := strfmt.DateTime(ts)
   393  	return &dt
   394  }
   395  
   396  func TestAlertToOpenAPIAlert(t *testing.T) {
   397  	var (
   398  		start     = time.Now().Add(-time.Minute)
   399  		updated   = time.Now()
   400  		active    = "active"
   401  		fp        = "0223b772b51c29e1"
   402  		receivers = []string{"receiver1", "receiver2"}
   403  
   404  		alert = &types.Alert{
   405  			Alert: model.Alert{
   406  				Labels:   model.LabelSet{"severity": "critical", "alertname": "alert1"},
   407  				StartsAt: start,
   408  			},
   409  			UpdatedAt: updated,
   410  		}
   411  	)
   412  	openAPIAlert := AlertToOpenAPIAlert(alert, types.AlertStatus{State: types.AlertStateActive}, receivers)
   413  	require.Equal(t, &open_api_models.GettableAlert{
   414  		Annotations: open_api_models.LabelSet{},
   415  		Alert: open_api_models.Alert{
   416  			Labels: open_api_models.LabelSet{"severity": "critical", "alertname": "alert1"},
   417  		},
   418  		Status: &open_api_models.AlertStatus{
   419  			State:       &active,
   420  			InhibitedBy: []string{},
   421  			SilencedBy:  []string{},
   422  		},
   423  		StartsAt:    convertDateTime(start),
   424  		EndsAt:      convertDateTime(time.Time{}),
   425  		UpdatedAt:   convertDateTime(updated),
   426  		Fingerprint: &fp,
   427  		Receivers: []*open_api_models.Receiver{
   428  			{Name: &receivers[0]},
   429  			{Name: &receivers[1]},
   430  		},
   431  	}, openAPIAlert)
   432  }
   433  
   434  func TestMatchFilterLabels(t *testing.T) {
   435  	sms := map[string]string{
   436  		"foo": "bar",
   437  	}
   438  
   439  	testCases := []struct {
   440  		matcher  labels.MatchType
   441  		name     string
   442  		val      string
   443  		expected bool
   444  	}{
   445  		{labels.MatchEqual, "foo", "bar", true},
   446  		{labels.MatchEqual, "baz", "", true},
   447  		{labels.MatchEqual, "baz", "qux", false},
   448  		{labels.MatchEqual, "baz", "qux|", false},
   449  		{labels.MatchRegexp, "foo", "bar", true},
   450  		{labels.MatchRegexp, "baz", "", true},
   451  		{labels.MatchRegexp, "baz", "qux", false},
   452  		{labels.MatchRegexp, "baz", "qux|", true},
   453  		{labels.MatchNotEqual, "foo", "bar", false},
   454  		{labels.MatchNotEqual, "baz", "", false},
   455  		{labels.MatchNotEqual, "baz", "qux", true},
   456  		{labels.MatchNotEqual, "baz", "qux|", true},
   457  		{labels.MatchNotRegexp, "foo", "bar", false},
   458  		{labels.MatchNotRegexp, "baz", "", false},
   459  		{labels.MatchNotRegexp, "baz", "qux", true},
   460  		{labels.MatchNotRegexp, "baz", "qux|", false},
   461  	}
   462  
   463  	for _, tc := range testCases {
   464  		m, err := labels.NewMatcher(tc.matcher, tc.name, tc.val)
   465  		require.NoError(t, err)
   466  
   467  		ms := []*labels.Matcher{m}
   468  		require.Equal(t, tc.expected, matchFilterLabels(ms, sms))
   469  	}
   470  }
   471  

View as plain text