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 }