Skip to content

Commit

Permalink
Alerting: Implement /status for the notification system
Browse files Browse the repository at this point in the history
Implements the necessary plumbing to have a /status endpoint on the
notification system.
  • Loading branch information
gotjosh committed Apr 21, 2021
1 parent de0802c commit d76f08f
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 18 deletions.
1 change: 1 addition & 0 deletions pkg/services/ngalert/api/api.go
Expand Up @@ -26,6 +26,7 @@ var timeNow = time.Now
type Alertmanager interface {
// Configuration
SaveAndApplyConfig(config *apimodels.PostableUserConfig) error
GetStatus() apimodels.GettableStatus

// Silences
CreateSilence(ps *apimodels.PostableSilence) (string, error)
Expand Down
4 changes: 4 additions & 0 deletions pkg/services/ngalert/api/api_alertmanager.go
Expand Up @@ -20,6 +20,10 @@ type AlertmanagerSrv struct {
log log.Logger
}

func (srv AlertmanagerSrv) RouteGetAMStatus(c *models.ReqContext) response.Response {
return response.JSON(http.StatusOK, srv.am.GetStatus())
}

func (srv AlertmanagerSrv) RouteCreateSilence(c *models.ReqContext, postableSilence apimodels.PostableSilence) response.Response {
silenceID, err := srv.am.CreateSilence(&postableSilence)
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions pkg/services/ngalert/api/forked_am.go
Expand Up @@ -39,6 +39,15 @@ func (am *ForkedAMSvc) getService(ctx *models.ReqContext) (AlertmanagerApiServic
}
}

func (am *ForkedAMSvc) RouteGetAMStatus(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx)
if err != nil {
return response.Error(400, err.Error(), nil)
}

return s.RouteGetAMStatus(ctx)
}

func (am *ForkedAMSvc) RouteCreateSilence(ctx *models.ReqContext, body apimodels.PostableSilence) response.Response {
s, err := am.getService(ctx)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions pkg/services/ngalert/api/generated_base_api_alertmanager.go
Expand Up @@ -23,6 +23,7 @@ type AlertmanagerApiService interface {
RouteDeleteSilence(*models.ReqContext) response.Response
RouteGetAMAlertGroups(*models.ReqContext) response.Response
RouteGetAMAlerts(*models.ReqContext) response.Response
RouteGetAMStatus(*models.ReqContext) response.Response
RouteGetAlertingConfig(*models.ReqContext) response.Response
RouteGetSilence(*models.ReqContext) response.Response
RouteGetSilences(*models.ReqContext) response.Response
Expand All @@ -37,6 +38,7 @@ func (api *API) RegisterAlertmanagerApiEndpoints(srv AlertmanagerApiService) {
group.Delete(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), routing.Wrap(srv.RouteDeleteSilence))
group.Get(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/alerts/groups"), routing.Wrap(srv.RouteGetAMAlertGroups))
group.Get(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/alerts"), routing.Wrap(srv.RouteGetAMAlerts))
group.Get(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/status"), routing.Wrap(srv.RouteGetAMStatus))
group.Get(toMacaronPath("/api/alertmanager/{Recipient}/config/api/v1/alerts"), routing.Wrap(srv.RouteGetAlertingConfig))
group.Get(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), routing.Wrap(srv.RouteGetSilence))
group.Get(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silences"), routing.Wrap(srv.RouteGetSilences))
Expand Down
15 changes: 15 additions & 0 deletions pkg/services/ngalert/api/lotex_am.go
Expand Up @@ -16,6 +16,7 @@ import (
const (
amSilencesPath = "/alertmanager/api/v2/silences"
amSilencePath = "/alertmanager/api/v2/silence/%s"
amStatusPath = "/alertmanager/api/v2/status"
amAlertGroupsPath = "/alertmanager/api/v2/alerts/groups"
amAlertsPath = "/alertmanager/api/v2/alerts"
amConfigPath = "/api/v1/alerts"
Expand All @@ -33,6 +34,20 @@ func NewLotexAM(proxy *AlertingProxy, log log.Logger) *LotexAM {
}
}

func (am *LotexAM) RouteGetAMStatus(ctx *models.ReqContext) response.Response {
return am.withReq(
ctx,
http.MethodGet,
withPath(
*ctx.Req.URL,
amStatusPath,
),
nil,
jsonExtractor(&apimodels.GettableStatus{}),
nil,
)
}

func (am *LotexAM) RouteCreateSilence(ctx *models.ReqContext, silenceBody apimodels.PostableSilence) response.Response {
blob, err := json.Marshal(silenceBody)
if err != nil {
Expand Down
53 changes: 52 additions & 1 deletion pkg/services/ngalert/api/tooling/definitions/alertmanager.go
Expand Up @@ -4,6 +4,8 @@ import (
"encoding/json"
"fmt"

"github.com/go-openapi/strfmt"

"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models"
amv2 "github.com/prometheus/alertmanager/api/v2/models"
Expand Down Expand Up @@ -35,6 +37,14 @@ import (
// 200: Ack
// 400: ValidationError

// swagger:route GET /api/alertmanager/{Recipient}/api/v2/status alertmanager RouteGetAMStatus
//
// get alertmanager status and configuration
//
// Responses:
// 200: GettableStatus
// 400: ValidationError

// swagger:route GET /api/alertmanager/{Recipient}/api/v2/alerts alertmanager RouteGetAMAlerts
//
// get alertmanager alerts
Expand Down Expand Up @@ -109,6 +119,47 @@ type GetSilencesParams struct {
Filter []string `json:"filter"`
}

// swagger:model
type GettableStatus struct {
// cluster
// Required: true
Cluster *amv2.ClusterStatus `json:"cluster"`

// config
// Required: true
Config *PostableApiAlertingConfig `json:"config"`

// uptime
// Required: true
// Format: date-time
Uptime *strfmt.DateTime `json:"uptime"`

// version info
// Required: true
VersionInfo *amv2.VersionInfo `json:"versionInfo"`
}

func NewGettableStatus(cfg *PostableApiAlertingConfig) *GettableStatus {
// In Grafana, the only field we support is Config.
cs := amv2.ClusterStatusStatusDisabled
na := "N/A"
return &GettableStatus{
Cluster: &amv2.ClusterStatus{
Status: &cs,
Peers: []*amv2.PeerStatus{},
},
VersionInfo: &amv2.VersionInfo{
Branch: &na,
BuildDate: &na,
BuildUser: &na,
GoVersion: &na,
Revision: &na,
Version: &na,
},
Config: cfg,
}
}

// swagger:model
type PostableSilence = amv2.PostableSilence

Expand Down Expand Up @@ -178,7 +229,7 @@ type BodyAlertingConfig struct {
}

// alertmanager routes
// swagger:parameters RoutePostAlertingConfig RouteGetAlertingConfig RouteDeleteAlertingConfig RouteGetAMAlerts RoutePostAMAlerts RouteGetAMAlertGroups RouteGetSilences RouteCreateSilence RouteGetSilence RouteDeleteSilence RoutePostAlertingConfig
// swagger:parameters RoutePostAlertingConfig RouteGetAlertingConfig RouteDeleteAlertingConfig RouteGetAMStatus RouteGetAMAlerts RoutePostAMAlerts RouteGetAMAlertGroups RouteGetSilences RouteCreateSilence RouteGetSilence RouteDeleteSilence RoutePostAlertingConfig
// ruler routes
// swagger:parameters RouteGetRulesConfig RoutePostNameRulesConfig RouteGetNamespaceRulesConfig RouteDeleteNamespaceRulesConfig RouteGetRulegGroupConfig RouteDeleteRuleGroupConfig
// prom routes
Expand Down
43 changes: 35 additions & 8 deletions pkg/services/ngalert/api/tooling/post.json
Expand Up @@ -828,7 +828,10 @@
"GettableSilence": {
"$ref": "#/definitions/gettableSilence"
},
"GettableSilences": {},
"GettableSilences": {
"$ref": "#/definitions/gettableSilences"
},
"GettableStatus": {},
"GettableUserConfig": {
"properties": {
"alertmanager_config": {
Expand Down Expand Up @@ -1589,7 +1592,6 @@
"x-go-package": "github.com/prometheus/alertmanager/config"
},
"Receiver": {
"$ref": "#/definitions/receiver",
"properties": {
"email_configs": {
"items": {
Expand Down Expand Up @@ -2176,7 +2178,6 @@
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
},
"URL": {
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.",
"properties": {
"ForceQuery": {
"type": "boolean"
Expand Down Expand Up @@ -2209,9 +2210,9 @@
"$ref": "#/definitions/Userinfo"
}
},
"title": "A URL represents a parsed URL (technically, a URI reference).",
"title": "URL is a custom URL type that allows validation at configuration load time.",
"type": "object",
"x-go-package": "net/url"
"x-go-package": "github.com/prometheus/common/config"
},
"Userinfo": {
"description": "The Userinfo type is an immutable encapsulation of username and\npassword details for a URL. An existing Userinfo value is guaranteed\nto have a username set (potentially empty, as allowed by RFC 2396),\nand optionally a password.",
Expand Down Expand Up @@ -2380,7 +2381,7 @@
"alerts": {
"description": "alerts",
"items": {
"$ref": "#/definitions/gettableAlert"
"$ref": "#/definitions/GettableAlert"
},
"type": "array",
"x-go-name": "Alerts"
Expand All @@ -2404,7 +2405,7 @@
"alertGroups": {
"description": "AlertGroups alert groups",
"items": {
"$ref": "#/definitions/alertGroup"
"$ref": "#/definitions/AlertGroup"
},
"type": "array",
"x-go-name": "AlertGroups",
Expand Down Expand Up @@ -3273,7 +3274,7 @@
"in": "body",
"name": "Silence",
"schema": {
"$ref": "#/definitions/postableSilence"
"$ref": "#/definitions/PostableSilence"
}
},
{
Expand Down Expand Up @@ -3303,6 +3304,32 @@
]
}
},
"/api/alertmanager/{Recipient}/api/v2/status": {
"get": {
"description": "get alertmanager status and configuration",
"operationId": "RouteGetAMStatus",
"parameters": [
{
"description": "Recipient should be \"grafana\" for requests to be handled by grafana\nand the numeric datasource id for requests to be forwarded to a datasource",
"in": "path",
"name": "Recipient",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "GettableStatus",
"schema": {
"$ref": "#/definitions/GettableStatus"
}
}
},
"tags": [
"alertmanager"
]
}
},
"/api/alertmanager/{Recipient}/config/api/v1/alerts": {
"delete": {
"description": "deletes the Alerting config for a tenant",
Expand Down
44 changes: 36 additions & 8 deletions pkg/services/ngalert/api/tooling/spec.json
Expand Up @@ -327,7 +327,7 @@
"name": "Silence",
"in": "body",
"schema": {
"$ref": "#/definitions/postableSilence"
"$ref": "#/definitions/PostableSilence"
}
},
{
Expand All @@ -354,6 +354,32 @@
}
}
},
"/api/alertmanager/{Recipient}/api/v2/status": {
"get": {
"description": "get alertmanager status and configuration",
"tags": [
"alertmanager"
],
"operationId": "RouteGetAMStatus",
"parameters": [
{
"type": "string",
"description": "Recipient should be \"grafana\" for requests to be handled by grafana\nand the numeric datasource id for requests to be forwarded to a datasource",
"name": "Recipient",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "GettableStatus",
"schema": {
"$ref": "#/definitions/GettableStatus"
}
}
}
}
},
"/api/alertmanager/{Recipient}/config/api/v1/alerts": {
"get": {
"description": "gets an Alerting config",
Expand Down Expand Up @@ -1641,7 +1667,10 @@
"$ref": "#/definitions/gettableSilence"
},
"GettableSilences": {
"$ref": "#/definitions/GettableSilences"
"$ref": "#/definitions/gettableSilences"
},
"GettableStatus": {
"$ref": "#/definitions/GettableStatus"
},
"GettableUserConfig": {
"type": "object",
Expand Down Expand Up @@ -2471,7 +2500,7 @@
"x-go-name": "WechatConfigs"
}
},
"$ref": "#/definitions/receiver"
"$ref": "#/definitions/Receiver"
},
"Regexp": {
"description": "A Regexp is safe for concurrent use by multiple goroutines,\nexcept for configuration methods, such as Longest.",
Expand Down Expand Up @@ -2993,9 +3022,8 @@
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
},
"URL": {
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.",
"type": "object",
"title": "A URL represents a parsed URL (technically, a URI reference).",
"title": "URL is a custom URL type that allows validation at configuration load time.",
"properties": {
"ForceQuery": {
"type": "boolean"
Expand Down Expand Up @@ -3028,7 +3056,7 @@
"$ref": "#/definitions/Userinfo"
}
},
"x-go-package": "net/url"
"x-go-package": "github.com/prometheus/common/config"
},
"Userinfo": {
"description": "The Userinfo type is an immutable encapsulation of username and\npassword details for a URL. An existing Userinfo value is guaranteed\nto have a username set (potentially empty, as allowed by RFC 2396),\nand optionally a password.",
Expand Down Expand Up @@ -3204,7 +3232,7 @@
"description": "alerts",
"type": "array",
"items": {
"$ref": "#/definitions/gettableAlert"
"$ref": "#/definitions/GettableAlert"
},
"x-go-name": "Alerts"
},
Expand All @@ -3222,7 +3250,7 @@
"description": "AlertGroups alert groups",
"type": "array",
"items": {
"$ref": "#/definitions/alertGroup"
"$ref": "#/definitions/AlertGroup"
},
"x-go-name": "AlertGroups",
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
Expand Down
22 changes: 22 additions & 0 deletions pkg/services/ngalert/notifier/status.go
@@ -0,0 +1,22 @@
package notifier

import (
"encoding/json"

apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
)

func (am *Alertmanager) GetStatus() apimodels.GettableStatus {
am.reloadConfigMtx.RLock()
defer am.reloadConfigMtx.RUnlock()

var amConfig apimodels.PostableApiAlertingConfig
if am.config != nil {
err := json.Unmarshal(am.config, &amConfig)
if err != nil {
// this should never error here, if the configuration is running it should be valid.
am.logger.Error("unable to marshal alertmanager configuration", "err", err)
}
}
return *apimodels.NewGettableStatus(&amConfig)
}

0 comments on commit d76f08f

Please sign in to comment.