github.com/thanos-io/thanos@v0.32.5/pkg/rules/rulespb/custom_test.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package rulespb
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/pkg/errors"
    13  	"github.com/prometheus/prometheus/model/labels"
    14  
    15  	"github.com/efficientgo/core/testutil"
    16  	"github.com/thanos-io/thanos/pkg/store/labelpb"
    17  	"github.com/thanos-io/thanos/pkg/store/storepb"
    18  	"github.com/thanos-io/thanos/pkg/testutil/testpromcompatibility"
    19  )
    20  
    21  func TestJSONUnmarshalMarshal(t *testing.T) {
    22  	now := time.Now()
    23  	twoHoursAgo := now.Add(2 * time.Hour)
    24  
    25  	for _, tcase := range []struct {
    26  		name  string
    27  		input *testpromcompatibility.RuleDiscovery
    28  
    29  		expectedProto      *RuleGroups
    30  		expectedErr        error
    31  		expectedJSONOutput string // If empty, expected same one as marshaled input.
    32  	}{
    33  		{
    34  			name:               "Empty JSON",
    35  			input:              &testpromcompatibility.RuleDiscovery{},
    36  			expectedProto:      &RuleGroups{},
    37  			expectedJSONOutput: `{"groups":[]}`,
    38  		},
    39  		{
    40  			name: "one empty group",
    41  			input: &testpromcompatibility.RuleDiscovery{
    42  				RuleGroups: []*testpromcompatibility.RuleGroup{
    43  					{
    44  						Name:                    "group1",
    45  						File:                    "file1.yml",
    46  						Interval:                2442,
    47  						LastEvaluation:          now,
    48  						EvaluationTime:          2.1,
    49  						PartialResponseStrategy: "ABORT",
    50  					},
    51  				},
    52  			},
    53  			expectedProto: &RuleGroups{
    54  				Groups: []*RuleGroup{
    55  					{
    56  						Name:                      "group1",
    57  						File:                      "file1.yml",
    58  						Interval:                  2442,
    59  						LastEvaluation:            now,
    60  						EvaluationDurationSeconds: 2.1,
    61  						Limit:                     0,
    62  						PartialResponseStrategy:   storepb.PartialResponseStrategy_ABORT,
    63  						Rules:                     []*Rule{},
    64  					},
    65  				},
    66  			},
    67  		},
    68  		{
    69  			name: "one group with one empty group",
    70  			input: &testpromcompatibility.RuleDiscovery{
    71  				RuleGroups: []*testpromcompatibility.RuleGroup{
    72  					{},
    73  				},
    74  			},
    75  			expectedProto: &RuleGroups{
    76  				Groups: []*RuleGroup{
    77  					{
    78  						PartialResponseStrategy: storepb.PartialResponseStrategy_ABORT,
    79  					},
    80  				},
    81  			},
    82  			// Different than input due to default enum fields.
    83  			expectedJSONOutput: `{"groups":[{"name":"","file":"","rules":[],"interval":0,"evaluationTime":0,"lastEvaluation":"0001-01-01T00:00:00Z","limit":0,"partialResponseStrategy":"ABORT"}]}`,
    84  		},
    85  		{
    86  			name: "one valid group, with 1 with no rule type",
    87  			input: &testpromcompatibility.RuleDiscovery{
    88  				RuleGroups: []*testpromcompatibility.RuleGroup{
    89  					{
    90  						Name: "group1",
    91  						Rules: []testpromcompatibility.Rule{
    92  							testpromcompatibility.RecordingRule{
    93  								Name: "recording1",
    94  							},
    95  						},
    96  						File:                    "file1.yml",
    97  						Interval:                2442,
    98  						LastEvaluation:          now,
    99  						EvaluationTime:          2.1,
   100  						PartialResponseStrategy: "ABORT",
   101  					},
   102  				},
   103  			},
   104  			expectedErr: errors.New("rule: no type field provided: {\"name\":\"recording1\",\"query\":\"\",\"labels\":{},\"health\":\"\",\"evaluationTime\":0,\"lastEvaluation\":\"0001-01-01T00:00:00Z\",\"type\":\"\"}"),
   105  		},
   106  		{
   107  			name: "one valid group, with 1 rule with invalid rule type",
   108  			input: &testpromcompatibility.RuleDiscovery{
   109  				RuleGroups: []*testpromcompatibility.RuleGroup{
   110  					{
   111  						Name: "group1",
   112  						Rules: []testpromcompatibility.Rule{
   113  							testpromcompatibility.RecordingRule{
   114  								Name: "recording1",
   115  								Type: "wrong",
   116  							},
   117  						},
   118  						File:                    "file1.yml",
   119  						Interval:                2442,
   120  						LastEvaluation:          now,
   121  						EvaluationTime:          2.1,
   122  						PartialResponseStrategy: "ABORT",
   123  					},
   124  				},
   125  			},
   126  			expectedErr: errors.New("rule: unknown type field provided wrong; {\"name\":\"recording1\",\"query\":\"\",\"labels\":{},\"health\":\"\",\"evaluationTime\":0,\"lastEvaluation\":\"0001-01-01T00:00:00Z\",\"type\":\"wrong\"}"),
   127  		},
   128  		{
   129  			name: "one valid group, with 1 rule with invalid alert state",
   130  			input: &testpromcompatibility.RuleDiscovery{
   131  				RuleGroups: []*testpromcompatibility.RuleGroup{
   132  					{
   133  						Name: "group1",
   134  						Rules: []testpromcompatibility.Rule{
   135  							testpromcompatibility.AlertingRule{
   136  								Name:  "alert1",
   137  								Type:  RuleAlertingType,
   138  								State: "sdfsdf",
   139  							},
   140  						},
   141  						File:                    "file1.yml",
   142  						Interval:                2442,
   143  						LastEvaluation:          now,
   144  						EvaluationTime:          2.1,
   145  						PartialResponseStrategy: "ABORT",
   146  					},
   147  				},
   148  			},
   149  			expectedErr: errors.New("rule: alerting rule unmarshal: {\"state\":\"sdfsdf\",\"name\":\"alert1\",\"query\":\"\",\"duration\":0,\"labels\":{},\"annotations\":{},\"alerts\":null,\"health\":\"\",\"evaluationTime\":0,\"lastEvaluation\":\"0001-01-01T00:00:00Z\",\"type\":\"alerting\"}: unknown alertState: \"sdfsdf\""),
   150  		},
   151  		{
   152  			name: "one group with WRONG partial response fields",
   153  			input: &testpromcompatibility.RuleDiscovery{
   154  				RuleGroups: []*testpromcompatibility.RuleGroup{
   155  					{
   156  						Name:                    "group1",
   157  						File:                    "file1.yml",
   158  						Interval:                2442,
   159  						LastEvaluation:          now,
   160  						EvaluationTime:          2.1,
   161  						PartialResponseStrategy: "asdfsdfsdfsd",
   162  					},
   163  				},
   164  			},
   165  			expectedErr: errors.New("failed to unmarshal \"asdfsdfsdfsd\" as 'partial_response_strategy'. Possible values are ABORT,WARN"),
   166  		},
   167  		{
   168  			name: "one valid group with 1 alerting rule containing no alerts.",
   169  			input: &testpromcompatibility.RuleDiscovery{
   170  				RuleGroups: []*testpromcompatibility.RuleGroup{
   171  					{
   172  						Name: "group1",
   173  						Rules: []testpromcompatibility.Rule{
   174  							testpromcompatibility.AlertingRule{
   175  								Type:  RuleAlertingType,
   176  								Name:  "alert1",
   177  								Query: "up == 0",
   178  								Labels: labels.Labels{
   179  									{Name: "a2", Value: "b2"},
   180  									{Name: "c2", Value: "d2"},
   181  								},
   182  								Annotations: labels.Labels{
   183  									{Name: "ann1", Value: "ann44"},
   184  									{Name: "ann2", Value: "ann33"},
   185  								},
   186  								Health:         "health2",
   187  								LastError:      "1",
   188  								Duration:       60,
   189  								State:          "pending",
   190  								EvaluationTime: 1.1,
   191  							},
   192  						},
   193  						File:                    "file1.yml",
   194  						Interval:                2442,
   195  						EvaluationTime:          2.1,
   196  						PartialResponseStrategy: "ABORT",
   197  					},
   198  				},
   199  			},
   200  			expectedProto: &RuleGroups{
   201  				Groups: []*RuleGroup{
   202  					{
   203  						Name: "group1",
   204  						Rules: []*Rule{
   205  							NewAlertingRule(&Alert{
   206  								Name:  "alert1",
   207  								Query: "up == 0",
   208  								Labels: labelpb.ZLabelSet{
   209  									Labels: []labelpb.ZLabel{
   210  										{Name: "a2", Value: "b2"},
   211  										{Name: "c2", Value: "d2"},
   212  									},
   213  								},
   214  								Annotations: labelpb.ZLabelSet{
   215  									Labels: []labelpb.ZLabel{
   216  										{Name: "ann1", Value: "ann44"},
   217  										{Name: "ann2", Value: "ann33"},
   218  									},
   219  								},
   220  								DurationSeconds:           60,
   221  								State:                     AlertState_PENDING,
   222  								LastError:                 "1",
   223  								Health:                    "health2",
   224  								EvaluationDurationSeconds: 1.1,
   225  							}),
   226  						},
   227  						File:                      "file1.yml",
   228  						Interval:                  2442,
   229  						EvaluationDurationSeconds: 2.1,
   230  						PartialResponseStrategy:   storepb.PartialResponseStrategy_ABORT,
   231  					},
   232  				},
   233  			},
   234  			// Different than input due to the alerts slice being initialized to a zero-length slice instead of nil.
   235  			expectedJSONOutput: `{"groups":[{"name":"group1","file":"file1.yml","rules":[{"state":"pending","name":"alert1","query":"up == 0","duration":60,"labels":{"a2":"b2","c2":"d2"},"annotations":{"ann1":"ann44","ann2":"ann33"},"alerts":[],"health":"health2","lastError":"1","evaluationTime":1.1,"lastEvaluation":"0001-01-01T00:00:00Z","type":"alerting"}],"interval":2442,"evaluationTime":2.1,"lastEvaluation":"0001-01-01T00:00:00Z","limit":0,"partialResponseStrategy":"ABORT"}]}`,
   236  		},
   237  		{
   238  			name: "one valid group, with 1 rule and alert each and second empty group.",
   239  			input: &testpromcompatibility.RuleDiscovery{
   240  				RuleGroups: []*testpromcompatibility.RuleGroup{
   241  					{
   242  						Name: "group1",
   243  						Rules: []testpromcompatibility.Rule{
   244  							testpromcompatibility.RecordingRule{
   245  								Type:  RuleRecordingType,
   246  								Query: "up",
   247  								Name:  "recording1",
   248  								Labels: labels.Labels{
   249  									{Name: "a", Value: "b"},
   250  									{Name: "c", Value: "d"},
   251  									{Name: "a", Value: "b"}, // Kind of invalid, but random one will be chosen.
   252  								},
   253  								LastError:      "2",
   254  								Health:         "health",
   255  								LastEvaluation: now.Add(-2 * time.Minute),
   256  								EvaluationTime: 2.6,
   257  							},
   258  							testpromcompatibility.AlertingRule{
   259  								Type:  RuleAlertingType,
   260  								Name:  "alert1",
   261  								Query: "up == 0",
   262  								Labels: labels.Labels{
   263  									{Name: "a2", Value: "b2"},
   264  									{Name: "c2", Value: "d2"},
   265  								},
   266  								Annotations: labels.Labels{
   267  									{Name: "ann1", Value: "ann44"},
   268  									{Name: "ann2", Value: "ann33"},
   269  								},
   270  								Health: "health2",
   271  								Alerts: []*testpromcompatibility.Alert{
   272  									{
   273  										Labels: labels.Labels{
   274  											{Name: "instance1", Value: "1"},
   275  										},
   276  										Annotations: labels.Labels{
   277  											{Name: "annotation1", Value: "2"},
   278  										},
   279  										State:                   "inactive",
   280  										ActiveAt:                nil,
   281  										Value:                   "1",
   282  										PartialResponseStrategy: "WARN",
   283  									},
   284  									{
   285  										Labels:                  nil,
   286  										Annotations:             nil,
   287  										State:                   "firing",
   288  										ActiveAt:                &twoHoursAgo,
   289  										Value:                   "2143",
   290  										PartialResponseStrategy: "ABORT",
   291  									},
   292  								},
   293  								LastError:      "1",
   294  								Duration:       60,
   295  								State:          "pending",
   296  								LastEvaluation: now.Add(-1 * time.Minute),
   297  								EvaluationTime: 1.1,
   298  							},
   299  						},
   300  						File:                    "file1.yml",
   301  						Interval:                2442,
   302  						LastEvaluation:          now,
   303  						EvaluationTime:          2.1,
   304  						PartialResponseStrategy: "ABORT",
   305  					},
   306  					{
   307  						Name:                    "group2",
   308  						File:                    "file2.yml",
   309  						Interval:                242342442,
   310  						LastEvaluation:          now.Add(40 * time.Hour),
   311  						EvaluationTime:          21244.1,
   312  						PartialResponseStrategy: "ABORT",
   313  					},
   314  				},
   315  			},
   316  			expectedProto: &RuleGroups{
   317  				Groups: []*RuleGroup{
   318  					{
   319  						Name: "group1",
   320  						Rules: []*Rule{
   321  							NewRecordingRule(&RecordingRule{
   322  								Query: "up",
   323  								Name:  "recording1",
   324  								Labels: labelpb.ZLabelSet{
   325  									Labels: []labelpb.ZLabel{
   326  										{Name: "a", Value: "b"},
   327  										{Name: "c", Value: "d"},
   328  									},
   329  								},
   330  								LastError:                 "2",
   331  								Health:                    "health",
   332  								LastEvaluation:            now.Add(-2 * time.Minute),
   333  								EvaluationDurationSeconds: 2.6,
   334  							}),
   335  							NewAlertingRule(&Alert{
   336  								Name:  "alert1",
   337  								Query: "up == 0",
   338  								Labels: labelpb.ZLabelSet{
   339  									Labels: []labelpb.ZLabel{
   340  										{Name: "a2", Value: "b2"},
   341  										{Name: "c2", Value: "d2"},
   342  									},
   343  								},
   344  								Annotations: labelpb.ZLabelSet{
   345  									Labels: []labelpb.ZLabel{
   346  										{Name: "ann1", Value: "ann44"},
   347  										{Name: "ann2", Value: "ann33"},
   348  									},
   349  								},
   350  								Alerts: []*AlertInstance{
   351  									{
   352  										Labels: labelpb.ZLabelSet{
   353  											Labels: []labelpb.ZLabel{
   354  												{Name: "instance1", Value: "1"},
   355  											},
   356  										},
   357  										Annotations: labelpb.ZLabelSet{
   358  											Labels: []labelpb.ZLabel{
   359  												{Name: "annotation1", Value: "2"},
   360  											},
   361  										},
   362  										State:                   AlertState_INACTIVE,
   363  										ActiveAt:                nil,
   364  										Value:                   "1",
   365  										PartialResponseStrategy: storepb.PartialResponseStrategy_WARN,
   366  									},
   367  									{
   368  										State:                   AlertState_FIRING,
   369  										ActiveAt:                &twoHoursAgo,
   370  										Value:                   "2143",
   371  										PartialResponseStrategy: storepb.PartialResponseStrategy_ABORT,
   372  									},
   373  								},
   374  								DurationSeconds:           60,
   375  								State:                     AlertState_PENDING,
   376  								LastError:                 "1",
   377  								Health:                    "health2",
   378  								LastEvaluation:            now.Add(-1 * time.Minute),
   379  								EvaluationDurationSeconds: 1.1,
   380  							}),
   381  						},
   382  						File:                      "file1.yml",
   383  						Interval:                  2442,
   384  						LastEvaluation:            now,
   385  						EvaluationDurationSeconds: 2.1,
   386  						PartialResponseStrategy:   storepb.PartialResponseStrategy_ABORT,
   387  					},
   388  					{
   389  						Name:                      "group2",
   390  						File:                      "file2.yml",
   391  						Interval:                  242342442,
   392  						LastEvaluation:            now.Add(40 * time.Hour),
   393  						EvaluationDurationSeconds: 21244.1,
   394  						PartialResponseStrategy:   storepb.PartialResponseStrategy_ABORT,
   395  						Rules:                     []*Rule{},
   396  					},
   397  				},
   398  			},
   399  		},
   400  	} {
   401  		t.Run(tcase.name, func(t *testing.T) {
   402  			jsonInput, err := json.Marshal(tcase.input)
   403  			testutil.Ok(t, err)
   404  
   405  			proto := &RuleGroups{}
   406  			err = json.Unmarshal(jsonInput, proto)
   407  			if tcase.expectedErr != nil {
   408  				testutil.NotOk(t, err)
   409  				testutil.Equals(t, tcase.expectedErr.Error(), err.Error())
   410  				return
   411  			}
   412  			testutil.Ok(t, err)
   413  			fmt.Println(proto.String())
   414  			testutil.Equals(t, tcase.expectedProto.String(), proto.String())
   415  
   416  			jsonProto, err := json.Marshal(proto)
   417  			testutil.Ok(t, err)
   418  			if tcase.expectedJSONOutput != "" {
   419  				testutil.Equals(t, tcase.expectedJSONOutput, string(jsonProto))
   420  				return
   421  			}
   422  			testutil.Equals(t, string(jsonInput), string(jsonProto))
   423  		})
   424  	}
   425  }
   426  
   427  func TestRulesComparator(t *testing.T) {
   428  	for _, tc := range []struct {
   429  		name   string
   430  		r1, r2 *Rule
   431  		want   int
   432  	}{
   433  		{
   434  			name: "same recording rule",
   435  			r1:   NewRecordingRule(&RecordingRule{Name: "a"}),
   436  			r2:   NewRecordingRule(&RecordingRule{Name: "a"}),
   437  			want: 0,
   438  		},
   439  		{
   440  			name: "same alerting rule",
   441  			r1:   NewAlertingRule(&Alert{Name: "a"}),
   442  			r2:   NewAlertingRule(&Alert{Name: "a"}),
   443  			want: 0,
   444  		},
   445  		{
   446  			name: "different types",
   447  			r1:   NewAlertingRule(&Alert{Name: "a"}),
   448  			r2:   NewRecordingRule(&RecordingRule{Name: "a"}),
   449  			want: -1,
   450  		},
   451  		{
   452  			name: "different names",
   453  			r1:   NewAlertingRule(&Alert{Name: "a"}),
   454  			r2:   NewAlertingRule(&Alert{Name: "b"}),
   455  			want: -1,
   456  		},
   457  		{
   458  			name: "no label before label",
   459  			r1:   NewAlertingRule(&Alert{Name: "a"}),
   460  			r2: NewAlertingRule(&Alert{
   461  				Name: "a",
   462  				Labels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{
   463  					{Name: "a", Value: "1"},
   464  				}}}),
   465  			want: -1,
   466  		},
   467  		{
   468  			name: "label ordering",
   469  			r1: NewAlertingRule(&Alert{
   470  				Name: "a",
   471  				Labels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{
   472  					{Name: "a", Value: "1"},
   473  				}}}),
   474  			r2: NewAlertingRule(&Alert{
   475  				Name: "a",
   476  				Labels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{
   477  					{Name: "a", Value: "2"},
   478  				}}}),
   479  			want: -1,
   480  		},
   481  		{
   482  			name: "multiple label ordering",
   483  			r1: NewAlertingRule(&Alert{
   484  				Name: "a",
   485  				Labels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{
   486  					{Name: "a", Value: "1"},
   487  				}}}),
   488  			r2: NewAlertingRule(&Alert{
   489  				Name: "a",
   490  				Labels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{
   491  					{Name: "a", Value: "1"},
   492  					{Name: "b", Value: "1"},
   493  				}}}),
   494  			want: -1,
   495  		},
   496  		{
   497  			name: "different durations",
   498  			r1: NewAlertingRule(&Alert{
   499  				Name:            "a",
   500  				DurationSeconds: 0.0,
   501  				Labels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{
   502  					{Name: "a", Value: "1"},
   503  				}}}),
   504  			r2: NewAlertingRule(&Alert{
   505  				Name:            "a",
   506  				DurationSeconds: 1.0,
   507  				Labels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{
   508  					{Name: "a", Value: "1"},
   509  				}}}),
   510  			want: -1,
   511  		},
   512  	} {
   513  		t.Run(tc.name, func(t *testing.T) {
   514  			testutil.Equals(t, tc.want, tc.r1.Compare(tc.r2))
   515  		})
   516  	}
   517  }