Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,19 @@ func main() {
log.Println("Done.")

webService := &web.WebService{
// REST API Service.
AlertManagerService: &api.AlertManagerService{
Aggregator: aggregator,
Suppressor: suppressor,
},

// Template-based page handlers.
AlertsHandler: &web.AlertsHandler{
Aggregator: aggregator,
},
SilencesHandler: &web.SilencesHandler{
Suppressor: suppressor,
},
}
go webService.ServeForever()

Expand Down
14 changes: 12 additions & 2 deletions manager/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,25 @@ import (
"sort"
)

const eventNameLabel = "name"

type EventFingerprint uint64

type EventLabels map[string]string
type EventPayload map[string]string

// Event models an action triggered by Prometheus.
type Event struct {
// Label value pairs for purpose of aggregation, matching, and disposition
// dispatching. This must minimally include a "name" label.
Labels map[string]string
Labels EventLabels
// Extra key/value information which is not used for aggregation.
Payload map[string]string
Payload EventPayload
}

func (e Event) Name() string {
// BUG: ensure in a proper place that all events have a name?
return e.Labels[eventNameLabel]
}

func (e Event) Fingerprint() EventFingerprint {
Expand Down
50 changes: 49 additions & 1 deletion manager/suppressor.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
package manager

import (
"encoding/json"
"fmt"
"log"
"sync"
"time"
)

type SuppressionId uint
type Suppressions []*Suppression

type Suppression struct {
// The numeric ID of the suppression.
Expand All @@ -40,7 +42,53 @@ type Suppression struct {
expiryTimer *time.Timer
}

type Suppressions []*Suppression
type ApiSilence struct {
CreatedBy string
CreatedAtSeconds int64
EndsAtSeconds int64
Comment string
Filters map[string]string
}

func (s *Suppression) MarshalJSON() ([]byte, error) {
filters := map[string]string{}
for _, f := range s.Filters {
name := f.Name.String()[1 : len(f.Name.String())-1]
value := f.Value.String()[1 : len(f.Value.String())-1]
filters[name] = value
}

return json.Marshal(&ApiSilence{
CreatedBy: s.CreatedBy,
CreatedAtSeconds: s.CreatedAt.Unix(),
EndsAtSeconds: s.EndsAt.Unix(),
Comment: s.Comment,
Filters: filters,
})
}

func (s *Suppression) UnmarshalJSON(data []byte) error {
sc := &ApiSilence{}
json.Unmarshal(data, sc)

filters := make(Filters, 0, len(sc.Filters))
for label, value := range sc.Filters {
filters = append(filters, NewFilter(label, value))
}

if sc.EndsAtSeconds == 0 {
sc.EndsAtSeconds = time.Now().Add(time.Hour).Unix()
}

*s = Suppression{
CreatedBy: sc.CreatedBy,
CreatedAt: time.Now().UTC(),
EndsAt: time.Unix(sc.EndsAtSeconds, 0).UTC(),
Comment: sc.Comment,
Filters: filters,
}
return nil
}

type Suppressor struct {
// Suppressions managed by this Suppressor.
Expand Down
3 changes: 2 additions & 1 deletion web/alerts.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
package web

import (
"github.com/prometheus/alert_manager/manager"
"net/http"

"github.com/prometheus/alert_manager/manager"
)

type AlertStatus struct {
Expand Down
8 changes: 7 additions & 1 deletion web/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ import (
type AlertManagerService struct {
gorest.RestService `root:"/api/" consumes:"application/json" produces:"application/json"`

addEvents gorest.EndPoint `method:"POST" path:"/event" postdata:"Events"`
addEvents gorest.EndPoint `method:"POST" path:"/events" postdata:"Events"`
addSilence gorest.EndPoint `method:"POST" path:"/silences" postdata:"Suppression"`
getSilence gorest.EndPoint `method:"GET" path:"/silences/{id:int}" output:"string"`
updateSilence gorest.EndPoint `method:"POST" path:"/silences/{id:int}" postdata:"Suppression"`
delSilence gorest.EndPoint `method:"DELETE" path:"/silences/{id:int}"`
silenceSummary gorest.EndPoint `method:"GET" path:"/silences" output:"string"`

Aggregator *manager.Aggregator
Suppressor *manager.Suppressor
}
93 changes: 93 additions & 0 deletions web/api/silence.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2013 Prometheus Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package api

import (
"encoding/json"
"fmt"
"log"
"net/http"

"code.google.com/p/gorest"

"github.com/prometheus/alert_manager/manager"
)

type Silence struct {
CreatedBy string
CreatedAtSeconds int64
EndsAtSeconds int64
Comment string
Filters map[string]string
}

func (s AlertManagerService) AddSilence(sc manager.Suppression) {
// BUG: add server-side form validation.
id := s.Suppressor.AddSuppression(&sc)

rb := s.ResponseBuilder()
rb.SetResponseCode(http.StatusCreated)
rb.Location(fmt.Sprintf("/api/silences/%d", id))
}

func (s AlertManagerService) GetSilence(id int) string {
rb := s.ResponseBuilder()
rb.SetContentType(gorest.Application_Json)
silence, err := s.Suppressor.GetSuppression(manager.SuppressionId(id))
if err != nil {
log.Printf("Error getting silence: %s", err)
rb.SetResponseCode(http.StatusNotFound)
return err.Error()
}

resultBytes, err := json.Marshal(&silence)
if err != nil {
log.Printf("Error marshalling silence: %s", err)
rb.SetResponseCode(http.StatusInternalServerError)
return err.Error()
}
return string(resultBytes)
}

func (s AlertManagerService) UpdateSilence(sc manager.Suppression, id int) {
// BUG: add server-side form validation.
sc.Id = manager.SuppressionId(id)
if err := s.Suppressor.UpdateSuppression(&sc); err != nil {
log.Printf("Error updating silence: %s", err)
rb := s.ResponseBuilder()
rb.SetResponseCode(http.StatusNotFound)
}
}

func (s AlertManagerService) DelSilence(id int) {
if err := s.Suppressor.DelSuppression(manager.SuppressionId(id)); err != nil {
log.Printf("Error deleting silence: %s", err)
rb := s.ResponseBuilder()
rb.SetResponseCode(http.StatusNotFound)
}
}

func (s AlertManagerService) SilenceSummary() string {
rb := s.ResponseBuilder()
rb.SetContentType(gorest.Application_Json)
silenceSummary := s.Suppressor.SuppressionSummary()

resultBytes, err := json.Marshal(silenceSummary)
if err != nil {
log.Printf("Error marshalling silences: %s", err)
rb.SetResponseCode(http.StatusInternalServerError)
return err.Error()
}
return string(resultBytes)
}
15 changes: 13 additions & 2 deletions web/silences.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,21 @@ package web

import (
"net/http"

"github.com/prometheus/alert_manager/manager"
)

type SilencesHandler struct{}
type SilenceStatus struct {
Silences manager.Suppressions
}

type SilencesHandler struct {
Suppressor *manager.Suppressor
}

func (h *SilencesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
executeTemplate(w, "silences", nil)
silenceStatus := &SilenceStatus{
Silences: h.Suppressor.SuppressionSummary(),
}
executeTemplate(w, "silences", silenceStatus)
}
6 changes: 1 addition & 5 deletions web/static/css/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@ body {
padding-top: 60px;
}

#create_silence_modal th {
#edit_silence_modal th {
text-align: left;
}

.add_silence_form {
display: inline;
}

.del_label_button {
margin-bottom: 10px;
}
Loading