prometheus/handlers/handlers.go (262 lines of code) (raw):
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package handlers
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/facebookincubator/prometheus-configmanager/prometheus/alert"
"github.com/golang/glog"
"github.com/labstack/echo"
"github.com/prometheus/prometheus/pkg/rulefmt"
)
const (
v0rootPath = "/:tenant_id"
v0alertPath = "/alert"
v0alertUpdatePath = v0alertPath + "/:" + ruleNameParam
v0alertBulkPath = v0alertPath + "/bulk"
ruleNameParam = "alert_name"
tenantIDParam = "tenant_id"
v1rootPath = "/v1"
v1TenantRootPath = v1rootPath + "/:tenant_id"
v1alertPath = "/alert"
v1alertBulkPath = v1alertPath + "/bulk"
v1alertNamePath = v1alertPath + "/:" + ruleNameParam
v1TenancyPath = "/tenancy"
)
func statusHandler(c echo.Context) error {
return c.String(http.StatusOK, "Prometheus Config server")
}
func RegisterBaseHandlers(e *echo.Echo) {
e.GET("/", statusHandler)
}
func RegisterV0Handlers(e *echo.Echo, alertClient alert.PrometheusAlertClient) {
v0 := e.Group(v0rootPath)
v0.Use(tenancyMiddlewareProvider(pathTenantProvider))
v0.POST(v0alertPath, GetConfigureAlertHandler(alertClient))
v0.GET(v0alertPath, GetRetrieveAlertHandler(alertClient))
v0.DELETE(v0alertPath, GetDeleteAlertHandler(alertClient, queryAlertNameProvider))
v0.PUT(v0alertUpdatePath, GetUpdateAlertHandler(alertClient))
v0.PUT(v0alertBulkPath, GetBulkAlertUpdateHandler(alertClient))
}
func RegisterV1Handlers(e *echo.Echo, alertClient alert.PrometheusAlertClient) {
v1 := e.Group(v1rootPath)
v1.GET(v1TenancyPath, GetGetTenancyHandler(alertClient))
v1Tenant := e.Group(v1TenantRootPath)
v1Tenant.Use(tenancyMiddlewareProvider(pathTenantProvider))
v1Tenant.POST(v1alertPath, GetConfigureAlertHandler(alertClient))
v1Tenant.GET(v1alertPath, GetRetrieveAlertHandler(alertClient))
v1Tenant.DELETE(v1alertNamePath, GetDeleteAlertHandler(alertClient, pathAlertNameProvider))
v1Tenant.PUT(v1alertNamePath, GetUpdateAlertHandler(alertClient))
v1Tenant.GET(v1alertNamePath, GetRetrieveAlertHandler(alertClient))
v1Tenant.POST(v1alertBulkPath, GetBulkAlertUpdateHandler(alertClient))
}
// Returns middleware func to check for tenant_id
func tenancyMiddlewareProvider(getTenantID paramProvider) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
providedTenantID := getTenantID(c)
if providedTenantID == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Must provide tenant_id parameter")
}
c.Set(tenantIDParam, providedTenantID)
return next(c)
}
}
}
type paramProvider func(c echo.Context) string
// V0 tenantID is a path parameter
var pathTenantProvider = func(c echo.Context) string {
return c.Param(tenantIDParam)
}
var pathAlertNameProvider = func(c echo.Context) string {
return c.Param(ruleNameParam)
}
var queryAlertNameProvider = func(c echo.Context) string {
return c.QueryParam(ruleNameParam)
}
// GetConfigureAlertHandler returns a handler that calls the client method WriteAlert() to
// write the alert configuration from the body of this request
func GetConfigureAlertHandler(client alert.PrometheusAlertClient) func(c echo.Context) error {
return func(c echo.Context) error {
defer glog.Flush()
rule, err := decodeRulePostRequest(c)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
tenantID := c.Get(tenantIDParam).(string)
glog.Infof("Configure Alert: Tenant: %s, %+v", tenantID, rule)
err = alert.ValidateRule(rule)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if client.RuleExists(tenantID, rule.Alert) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Rule '%s' already exists", rule.Alert))
}
err = client.WriteRule(tenantID, rule)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
err = client.ReloadPrometheus()
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return c.NoContent(http.StatusOK)
}
}
func GetRetrieveAlertHandler(client alert.PrometheusAlertClient) func(c echo.Context) error {
return func(c echo.Context) error {
defer glog.Flush()
ruleName := c.QueryParam(ruleNameParam)
tenantID := c.Get(tenantIDParam).(string)
glog.Infof("Get Rule: Tenant: %s, rule: %s", tenantID, ruleName)
rules, err := client.ReadRules(tenantID, ruleName)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, rulesToJSON(rules))
}
}
func GetDeleteAlertHandler(client alert.PrometheusAlertClient, getRuleName paramProvider) func(c echo.Context) error {
return func(c echo.Context) error {
defer glog.Flush()
ruleName := getRuleName(c)
tenantID := c.Get(tenantIDParam).(string)
glog.Infof("Delete Rule: Tenant: %s, rule: %+v", tenantID, ruleName)
if ruleName == "" {
return echo.NewHTTPError(http.StatusBadRequest, "No rule name provided")
}
err := client.DeleteRule(tenantID, ruleName)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
err = client.ReloadPrometheus()
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return c.String(http.StatusNoContent, fmt.Sprintf("rule %s deleted", ruleName))
}
}
func GetUpdateAlertHandler(client alert.PrometheusAlertClient) func(c echo.Context) error {
return func(c echo.Context) error {
defer glog.Flush()
ruleName := c.Param(ruleNameParam)
tenantID := c.Get(tenantIDParam).(string)
glog.Infof("Update Rule: Tenant: %s, rule: %s", tenantID, ruleName)
if ruleName == "" {
return echo.NewHTTPError(http.StatusBadRequest, "No rule name provided")
}
if !client.RuleExists(tenantID, ruleName) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Rule '%s' does not exist", ruleName))
}
rule, err := decodeRulePostRequest(c)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
err = alert.ValidateRule(rule)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
err = client.UpdateRule(tenantID, rule)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
err = client.ReloadPrometheus()
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return c.NoContent(http.StatusNoContent)
}
}
func GetBulkAlertUpdateHandler(client alert.PrometheusAlertClient) func(c echo.Context) error {
return func(c echo.Context) error {
defer glog.Flush()
tenantID := c.Get(tenantIDParam).(string)
rules, err := decodeBulkRulesPostRequest(c)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
glog.Infof("Bulk Update Rules: Tenant: %s, rules: %d", tenantID, len(rules))
for _, rule := range rules {
err = alert.ValidateRule(rule)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
}
results, err := client.BulkUpdateRules(tenantID, rules)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
err = client.ReloadPrometheus()
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, results)
}
}
func GetGetTenancyHandler(client alert.PrometheusAlertClient) func(c echo.Context) error {
return func(c echo.Context) error {
return c.JSON(http.StatusOK, client.Tenancy())
}
}
func decodeRulePostRequest(c echo.Context) (rulefmt.Rule, error) {
body, err := ioutil.ReadAll(c.Request().Body)
if err != nil {
glog.Errorf("Error reading rule payload: %v", err)
return rulefmt.Rule{}, fmt.Errorf("error reading request body: %v", err)
}
// First try unmarshaling into prometheus rulefmt.Rule{}
payload := rulefmt.Rule{}
err = json.Unmarshal(body, &payload)
if err == nil {
return payload, nil
}
// Try to unmarshal into the RuleJSONWrapper struct if prometheus struct doesn't work
jsonPayload := alert.RuleJSONWrapper{}
err = json.Unmarshal(body, &jsonPayload)
if err != nil {
glog.Errorf("Error unmarshaling rule payload: %v", err)
return payload, fmt.Errorf("error unmarshalling payload: %v", err)
}
return jsonPayload.ToRuleFmt()
}
func decodeBulkRulesPostRequest(c echo.Context) ([]rulefmt.Rule, error) {
body, err := ioutil.ReadAll(c.Request().Body)
if err != nil {
glog.Errorf("Error reading bulk rules payload: %v", err)
return []rulefmt.Rule{}, fmt.Errorf("error reading request body: %v", err)
}
var payload []rulefmt.Rule
err = json.Unmarshal(body, &payload)
if err == nil {
return payload, nil
}
// Try to unmarshal into the RuleJSONWrapper struct if prometheus struct doesn't work
jsonPayload := []alert.RuleJSONWrapper{}
err = json.Unmarshal(body, &jsonPayload)
if err != nil {
glog.Errorf("Error unmarshaling bulk rules: %v", err)
return []rulefmt.Rule{}, fmt.Errorf("error unmarshalling payload: %v", err)
}
return rulesFromJSON(jsonPayload)
}
func rulesToJSON(rules []rulefmt.Rule) []alert.RuleJSONWrapper {
ret := make([]alert.RuleJSONWrapper, 0)
for _, rule := range rules {
ret = append(ret, *rulefmtToJSON(rule))
}
return ret
}
func rulesFromJSON(rules []alert.RuleJSONWrapper) ([]rulefmt.Rule, error) {
ret := make([]rulefmt.Rule, 0)
for _, rule := range rules {
jsonRule, err := rule.ToRuleFmt()
if err != nil {
return ret, err
}
ret = append(ret, jsonRule)
}
return ret, nil
}
func rulefmtToJSON(rule rulefmt.Rule) *alert.RuleJSONWrapper {
return &alert.RuleJSONWrapper{
Record: rule.Record,
Alert: rule.Alert,
Expr: rule.Expr,
For: rule.For.String(),
Labels: rule.Labels,
Annotations: rule.Annotations,
}
}