github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ruler/base/api_test.go (about) 1 package base 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "net/http/httptest" 11 "strings" 12 "testing" 13 14 "github.com/go-kit/log" 15 "github.com/gorilla/mux" 16 "github.com/grafana/dskit/services" 17 "github.com/stretchr/testify/require" 18 "github.com/weaveworks/common/user" 19 20 "github.com/grafana/loki/pkg/ruler/rulespb" 21 ) 22 23 func TestRuler_rules(t *testing.T) { 24 cfg := defaultRulerConfig(t, newMockRuleStore(mockRules)) 25 26 r := newTestRuler(t, cfg) 27 defer services.StopAndAwaitTerminated(context.Background(), r) //nolint:errcheck 28 29 a := NewAPI(r, r.store, log.NewNopLogger()) 30 31 req := requestFor(t, "GET", "https://localhost:8080/api/prom/api/v1/rules", nil, "user1") 32 w := httptest.NewRecorder() 33 a.PrometheusRules(w, req) 34 35 resp := w.Result() 36 body, _ := ioutil.ReadAll(resp.Body) 37 38 // Check status code and status response 39 responseJSON := response{} 40 err := json.Unmarshal(body, &responseJSON) 41 require.NoError(t, err) 42 require.Equal(t, http.StatusOK, resp.StatusCode) 43 require.Equal(t, responseJSON.Status, "success") 44 45 // Testing the running rules for user1 in the mock store 46 expectedResponse, _ := json.Marshal(response{ 47 Status: "success", 48 Data: &RuleDiscovery{ 49 RuleGroups: []*RuleGroup{ 50 { 51 Name: "group1", 52 File: "namespace1", 53 Rules: []rule{ 54 &recordingRule{ 55 Name: "UP_RULE", 56 Query: "up", 57 Health: "unknown", 58 Type: "recording", 59 }, 60 &alertingRule{ 61 Name: "UP_ALERT", 62 Query: "up < 1", 63 State: "inactive", 64 Health: "unknown", 65 Type: "alerting", 66 Alerts: []*Alert{}, 67 }, 68 }, 69 Interval: 60, 70 }, 71 }, 72 }, 73 }) 74 75 require.Equal(t, string(expectedResponse), string(body)) 76 } 77 78 func TestRuler_rules_special_characters(t *testing.T) { 79 cfg := defaultRulerConfig(t, newMockRuleStore(mockSpecialCharRules)) 80 81 r := newTestRuler(t, cfg) 82 defer services.StopAndAwaitTerminated(context.Background(), r) //nolint:errcheck 83 84 a := NewAPI(r, r.store, log.NewNopLogger()) 85 86 req := requestFor(t, http.MethodGet, "https://localhost:8080/api/prom/api/v1/rules", nil, "user1") 87 w := httptest.NewRecorder() 88 a.PrometheusRules(w, req) 89 90 resp := w.Result() 91 body, _ := ioutil.ReadAll(resp.Body) 92 93 // Check status code and status response 94 responseJSON := response{} 95 err := json.Unmarshal(body, &responseJSON) 96 require.NoError(t, err) 97 require.Equal(t, http.StatusOK, resp.StatusCode) 98 require.Equal(t, responseJSON.Status, "success") 99 100 // Testing the running rules for user1 in the mock store 101 expectedResponse, _ := json.Marshal(response{ 102 Status: "success", 103 Data: &RuleDiscovery{ 104 RuleGroups: []*RuleGroup{ 105 { 106 Name: ")(_+?/|group1+/?", 107 File: ")(_+?/|namespace1+/?", 108 Rules: []rule{ 109 &recordingRule{ 110 Name: "UP_RULE", 111 Query: "up", 112 Health: "unknown", 113 Type: "recording", 114 }, 115 &alertingRule{ 116 Name: "UP_ALERT", 117 Query: "up < 1", 118 State: "inactive", 119 Health: "unknown", 120 Type: "alerting", 121 Alerts: []*Alert{}, 122 }, 123 }, 124 Interval: 60, 125 }, 126 }, 127 }, 128 }) 129 130 require.Equal(t, string(expectedResponse), string(body)) 131 } 132 133 func TestRuler_alerts(t *testing.T) { 134 cfg := defaultRulerConfig(t, newMockRuleStore(mockRules)) 135 136 r := newTestRuler(t, cfg) 137 defer r.StopAsync() 138 139 a := NewAPI(r, r.store, log.NewNopLogger()) 140 141 req := requestFor(t, http.MethodGet, "https://localhost:8080/api/prom/api/v1/alerts", nil, "user1") 142 w := httptest.NewRecorder() 143 a.PrometheusAlerts(w, req) 144 145 resp := w.Result() 146 body, _ := ioutil.ReadAll(resp.Body) 147 148 // Check status code and status response 149 responseJSON := response{} 150 err := json.Unmarshal(body, &responseJSON) 151 require.NoError(t, err) 152 require.Equal(t, http.StatusOK, resp.StatusCode) 153 require.Equal(t, responseJSON.Status, "success") 154 155 // Currently there is not an easy way to mock firing alerts. The empty 156 // response case is tested instead. 157 expectedResponse, _ := json.Marshal(response{ 158 Status: "success", 159 Data: &AlertDiscovery{ 160 Alerts: []*Alert{}, 161 }, 162 }) 163 164 require.Equal(t, string(expectedResponse), string(body)) 165 } 166 167 func TestRuler_Create(t *testing.T) { 168 cfg := defaultRulerConfig(t, newMockRuleStore(make(map[string]rulespb.RuleGroupList))) 169 170 r := newTestRuler(t, cfg) 171 defer services.StopAndAwaitTerminated(context.Background(), r) //nolint:errcheck 172 173 a := NewAPI(r, r.store, log.NewNopLogger()) 174 175 tc := []struct { 176 name string 177 input string 178 output string 179 err error 180 status int 181 }{ 182 { 183 name: "with an empty payload", 184 input: "", 185 status: 400, 186 err: errors.New("invalid rules config: rule group name must not be empty"), 187 }, 188 { 189 name: "with no rule group name", 190 input: ` 191 interval: 15s 192 rules: 193 - record: up_rule 194 expr: up 195 `, 196 status: 400, 197 err: errors.New("invalid rules config: rule group name must not be empty"), 198 }, 199 { 200 name: "with no rules", 201 input: ` 202 name: rg_name 203 interval: 15s 204 `, 205 status: 400, 206 err: errors.New("invalid rules config: rule group 'rg_name' has no rules"), 207 }, 208 { 209 name: "with a a valid rules file", 210 status: 202, 211 input: ` 212 name: test 213 interval: 15s 214 rules: 215 - record: up_rule 216 expr: up{} 217 - alert: up_alert 218 expr: sum(up{}) > 1 219 for: 30s 220 annotations: 221 test: test 222 labels: 223 test: test 224 `, 225 output: "name: test\ninterval: 15s\nrules:\n - record: up_rule\n expr: up{}\n - alert: up_alert\n expr: sum(up{}) > 1\n for: 30s\n labels:\n test: test\n annotations:\n test: test\n", 226 }, 227 } 228 229 for _, tt := range tc { 230 t.Run(tt.name, func(t *testing.T) { 231 router := mux.NewRouter() 232 router.Path("/api/v1/rules/{namespace}").Methods("POST").HandlerFunc(a.CreateRuleGroup) 233 router.Path("/api/v1/rules/{namespace}/{groupName}").Methods("GET").HandlerFunc(a.GetRuleGroup) 234 // POST 235 req := requestFor(t, http.MethodPost, "https://localhost:8080/api/v1/rules/namespace", strings.NewReader(tt.input), "user1") 236 w := httptest.NewRecorder() 237 238 router.ServeHTTP(w, req) 239 require.Equal(t, tt.status, w.Code) 240 241 if tt.err == nil { 242 // GET 243 req = requestFor(t, http.MethodGet, "https://localhost:8080/api/v1/rules/namespace/test", nil, "user1") 244 w = httptest.NewRecorder() 245 246 router.ServeHTTP(w, req) 247 require.Equal(t, 200, w.Code) 248 require.Equal(t, tt.output, w.Body.String()) 249 } else { 250 require.Equal(t, tt.err.Error()+"\n", w.Body.String()) 251 } 252 }) 253 } 254 } 255 256 func TestRuler_DeleteNamespace(t *testing.T) { 257 cfg := defaultRulerConfig(t, newMockRuleStore(mockRulesNamespaces)) 258 259 r := newTestRuler(t, cfg) 260 defer services.StopAndAwaitTerminated(context.Background(), r) //nolint:errcheck 261 262 a := NewAPI(r, r.store, log.NewNopLogger()) 263 264 router := mux.NewRouter() 265 router.Path("/api/v1/rules/{namespace}").Methods(http.MethodDelete).HandlerFunc(a.DeleteNamespace) 266 router.Path("/api/v1/rules/{namespace}/{groupName}").Methods(http.MethodGet).HandlerFunc(a.GetRuleGroup) 267 268 // Verify namespace1 rules are there. 269 req := requestFor(t, http.MethodGet, "https://localhost:8080/api/v1/rules/namespace1/group1", nil, "user1") 270 w := httptest.NewRecorder() 271 272 router.ServeHTTP(w, req) 273 require.Equal(t, http.StatusOK, w.Code) 274 require.Equal(t, "name: group1\ninterval: 1m\nrules:\n - record: UP_RULE\n expr: up\n - alert: UP_ALERT\n expr: up < 1\n", w.Body.String()) 275 276 // Delete namespace1 277 req = requestFor(t, http.MethodDelete, "https://localhost:8080/api/v1/rules/namespace1", nil, "user1") 278 w = httptest.NewRecorder() 279 280 router.ServeHTTP(w, req) 281 require.Equal(t, http.StatusAccepted, w.Code) 282 require.Equal(t, "{\"status\":\"success\",\"data\":null,\"errorType\":\"\",\"error\":\"\"}", w.Body.String()) 283 284 // On Partial failures 285 req = requestFor(t, http.MethodDelete, "https://localhost:8080/api/v1/rules/namespace2", nil, "user1") 286 w = httptest.NewRecorder() 287 288 router.ServeHTTP(w, req) 289 require.Equal(t, http.StatusInternalServerError, w.Code) 290 require.Equal(t, "{\"status\":\"error\",\"data\":null,\"errorType\":\"server_error\",\"error\":\"unable to delete rg\"}", w.Body.String()) 291 } 292 293 func TestRuler_LimitsPerGroup(t *testing.T) { 294 cfg := defaultRulerConfig(t, newMockRuleStore(make(map[string]rulespb.RuleGroupList))) 295 296 r := newTestRuler(t, cfg) 297 defer services.StopAndAwaitTerminated(context.Background(), r) //nolint:errcheck 298 299 r.limits = ruleLimits{maxRuleGroups: 1, maxRulesPerRuleGroup: 1} 300 301 a := NewAPI(r, r.store, log.NewNopLogger()) 302 303 tc := []struct { 304 name string 305 input string 306 output string 307 err error 308 status int 309 }{ 310 { 311 name: "when exceeding the rules per rule group limit", 312 status: 400, 313 input: ` 314 name: test 315 interval: 15s 316 rules: 317 - record: up_rule 318 expr: up{} 319 - alert: up_alert 320 expr: sum(up{}) > 1 321 for: 30s 322 annotations: 323 test: test 324 labels: 325 test: test 326 `, 327 output: "per-user rules per rule group limit (limit: 1 actual: 2) exceeded\n", 328 }, 329 } 330 331 for _, tt := range tc { 332 t.Run(tt.name, func(t *testing.T) { 333 router := mux.NewRouter() 334 router.Path("/api/v1/rules/{namespace}").Methods("POST").HandlerFunc(a.CreateRuleGroup) 335 // POST 336 req := requestFor(t, http.MethodPost, "https://localhost:8080/api/v1/rules/namespace", strings.NewReader(tt.input), "user1") 337 w := httptest.NewRecorder() 338 339 router.ServeHTTP(w, req) 340 require.Equal(t, tt.status, w.Code) 341 require.Equal(t, tt.output, w.Body.String()) 342 }) 343 } 344 } 345 346 func TestRuler_RulerGroupLimits(t *testing.T) { 347 cfg := defaultRulerConfig(t, newMockRuleStore(make(map[string]rulespb.RuleGroupList))) 348 349 r := newTestRuler(t, cfg) 350 defer services.StopAndAwaitTerminated(context.Background(), r) //nolint:errcheck 351 352 r.limits = ruleLimits{maxRuleGroups: 1, maxRulesPerRuleGroup: 1} 353 354 a := NewAPI(r, r.store, log.NewNopLogger()) 355 356 tc := []struct { 357 name string 358 input string 359 output string 360 err error 361 status int 362 }{ 363 { 364 name: "when pushing the first group within bounds of the limit", 365 status: 202, 366 input: ` 367 name: test_first_group_will_succeed 368 interval: 15s 369 rules: 370 - record: up_rule 371 expr: up{} 372 `, 373 output: "{\"status\":\"success\",\"data\":null,\"errorType\":\"\",\"error\":\"\"}", 374 }, 375 { 376 name: "when exceeding the rule group limit after sending the first group", 377 status: 400, 378 input: ` 379 name: test_second_group_will_fail 380 interval: 15s 381 rules: 382 - record: up_rule 383 expr: up{} 384 `, 385 output: "per-user rule groups limit (limit: 1 actual: 2) exceeded\n", 386 }, 387 } 388 389 // define once so the requests build on each other so the number of rules can be tested 390 router := mux.NewRouter() 391 router.Path("/api/v1/rules/{namespace}").Methods("POST").HandlerFunc(a.CreateRuleGroup) 392 393 for _, tt := range tc { 394 t.Run(tt.name, func(t *testing.T) { 395 // POST 396 req := requestFor(t, http.MethodPost, "https://localhost:8080/api/v1/rules/namespace", strings.NewReader(tt.input), "user1") 397 w := httptest.NewRecorder() 398 399 router.ServeHTTP(w, req) 400 require.Equal(t, tt.status, w.Code) 401 require.Equal(t, tt.output, w.Body.String()) 402 }) 403 } 404 } 405 406 func requestFor(t *testing.T, method string, url string, body io.Reader, userID string) *http.Request { 407 t.Helper() 408 409 req := httptest.NewRequest(method, url, body) 410 ctx := user.InjectOrgID(req.Context(), userID) 411 412 return req.WithContext(ctx) 413 }