Add silence API and memory storage

This commit is contained in:
Fabian Reinartz 2015-07-01 13:17:08 +02:00
parent e730242749
commit d4c90e9e28
5 changed files with 282 additions and 32 deletions

178
api.go Normal file
View File

@ -0,0 +1,178 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
"github.com/prometheus/common/route"
"golang.org/x/net/context"
)
type API struct {
state State
// context is an indirection for testing.
context func(r *http.Request) context.Context
}
func NewAPI(r *route.Router, s State) *API {
api := &API{
state: s,
context: route.Context,
}
// r.Get("/alerts", s.getAlerts)
// r.Post("/alerts", s.addAlerts)
r.Get("/silences", api.listSilences)
r.Post("/silences", api.addSilence)
r.Get("/silence/:sid", api.getSilence)
r.Put("/silence/:sid", api.setSilence)
r.Del("/silence/:sid", api.delSilence)
return api
}
type errorType string
const (
errorNone errorType = ""
errorTimeout = "timeout"
errorCanceled = "canceled"
errorBadData = "bad_data"
)
type apiError struct {
typ errorType
err error
}
func (e *apiError) Error() string {
return fmt.Sprintf("%s: %s", e.typ, e.err)
}
func (api *API) addSilence(w http.ResponseWriter, r *http.Request) {
var sil Silence
if err := receive(r, &sil); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// TODO(fabxc): validate input.
if err := api.state.Silence().Set(&sil); err != nil {
respondError(w, apiError{
typ: errorBadData,
err: err,
}, nil)
return
}
respond(w, nil)
}
func (api *API) getSilence(w http.ResponseWriter, r *http.Request) {
sid := route.Param(api.context(r), "sid")
sil, err := api.state.Silence().Get(sid)
if err != nil {
http.Error(w, fmt.Sprint("Error getting silence: ", err), http.StatusNotFound)
return
}
respond(w, &sil)
}
func (api *API) setSilence(w http.ResponseWriter, r *http.Request) {
var sil Silence
if err := receive(r, &sil); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// TODO(fabxc): validate input.
sil.ID = route.Param(api.context(r), "sid")
if err := api.state.Silence().Set(&sil); err != nil {
respondError(w, apiError{
typ: errorBadData,
err: err,
}, &sil)
return
}
respond(w, nil)
}
func (api *API) delSilence(w http.ResponseWriter, r *http.Request) {
sid := route.Param(api.context(r), "sid")
if err := api.state.Silence().Del(sid); err != nil {
respondError(w, apiError{
typ: errorBadData,
err: err,
}, nil)
return
}
respond(w, nil)
}
func (api *API) listSilences(w http.ResponseWriter, r *http.Request) {
sils, err := api.state.Silence().GetAll()
if err != nil {
respondError(w, apiError{
typ: errorBadData,
err: err,
}, nil)
return
}
respond(w, sils)
}
type status string
const (
statusSuccess status = "success"
statusError = "error"
)
type response struct {
Status status `json:"status"`
Data interface{} `json:"data,omitempty"`
ErrorType errorType `json:"errorType,omitempty"`
Error string `json:"error,omitempty"`
}
func respond(w http.ResponseWriter, data interface{}) {
w.WriteHeader(200)
w.Header().Set("Content-Type", "application/json")
b, err := json.Marshal(&response{
Status: statusSuccess,
Data: data,
})
if err != nil {
return
}
w.Write(b)
}
func respondError(w http.ResponseWriter, apiErr apiError, data interface{}) {
w.WriteHeader(422)
w.Header().Set("Content-Type", "application/json")
b, err := json.Marshal(&response{
Status: statusError,
ErrorType: apiErr.typ,
Error: apiErr.err.Error(),
Data: data,
})
if err != nil {
return
}
w.Write(b)
}
func receive(r *http.Request, v interface{}) error {
dec := json.NewDecoder(r.Body)
defer r.Body.Close()
return dec.Decode(v)
}

12
main.go
View File

@ -13,10 +13,18 @@
package main
import (
"net/http"
"github.com/prometheus/common/route"
)
func main() {
state := NewMemState()
am := New(state)
router := route.New()
go am.Run()
NewAPI(router.WithPrefix("/api"), state)
http.ListenAndServe(":9091", router)
}

View File

@ -28,6 +28,27 @@ type Matcher struct {
regex *regexp.Regexp
}
// IsRegex returns true of the matcher compares against a regular expression.
func (m *Matcher) IsRegex() bool {
return m.isRegex
}
// Match checks whether the label of the matcher has the specified
// matching value.
func (m *Matcher) Match(lset model.LabelSet) bool {
// Unset labels are treated as unset labels globally. Thus, if a
// label is not set we retrieve the empty label which is correct
// for the comparison below.
v := lset[m.Name]
if m.isRegex {
return m.regex.MatchString(string(v))
}
return string(v) == m.Value
}
// NewMatcher returns a new matcher that compares against equality of
// the given value.
func NewMatcher(name model.LabelName, value string) *Matcher {
return &Matcher{
Name: name,
@ -35,6 +56,8 @@ func NewMatcher(name model.LabelName, value string) *Matcher {
}
}
// NewRegexMatcher returns a new matcher that treats value as a regular
// expression which is used for matching.
func NewRegexMatcher(name model.LabelName, value string) (*Matcher, error) {
re, err := regexp.Compile(value)
if err != nil {
@ -50,3 +73,13 @@ func NewRegexMatcher(name model.LabelName, value string) (*Matcher, error) {
}
type Matchers []*Matcher
// MatchAll checks whether all matchers are fulfilled against the given label set.
func (ms Matchers) MatchAll(lset model.LabelSet) bool {
for _, m := range ms {
if !m.Match(lset) {
return false
}
}
return true
}

View File

@ -19,7 +19,7 @@ import (
type Silence struct {
// The numeric ID of the silence.
ID uint64
ID string
// Name/email of the silence creator.
CreatedBy string

View File

@ -1,31 +1,18 @@
package main
import (
"fmt"
"sync"
"github.com/prometheus/common/model"
)
// Manager handles active alerts
type Manager struct {
state State
}
func New(s State) *Manager {
return &Manager{
state: s,
}
}
// Run starts the processing of the manager and blocks.
func (m *Manager) Run() {
}
// A State serves the Alertmanager's internal state about active silences.
type State interface {
AlertState
ConfigState
NotifyState
SilenceState
Silence() SilenceState
// Config() ConfigState
// Notify() NotifyState
// Alert() AlertState
}
type AlertState interface{}
@ -36,33 +23,77 @@ type NotifyState interface{}
type SilenceState interface {
// Silences returns a list of all silences.
Silences() ([]*Silence, error)
GetAll() ([]*Silence, error)
// SetSilence sets the given silence.
SetSilence(*Silence) error
Set(*Silence) error
Del(sid string) error
Get(sid string) (*Silence, error)
}
// memState implements the State interface based on in-memory storage.
type memState struct {
silences map[uint64]*Silence
silences *memSilences
mtx sync.RWMutex
}
func NewMemState() State {
return &memState{
silences: map[uint64]*Silence{},
silences: &memSilences{
m: map[string]*Silence{},
nextID: 1,
},
}
}
func (s *memState) Silences() ([]*Silence, error) {
sils := make([]*Silence, 0, len(s.silences))
for _, sil := range s.silences {
func (s *memState) Silence() SilenceState {
return s.silences
}
type memSilences struct {
m map[string]*Silence
mtx sync.RWMutex
nextID uint64
}
func (s *memSilences) genID() string {
sid := fmt.Sprintf("%x", s.nextID)
s.nextID++
return sid
}
func (s *memSilences) Get(sid string) (*Silence, error) {
return nil, nil
}
func (s *memSilences) Del(sid string) error {
if _, ok := s.m[sid]; !ok {
return fmt.Errorf("silence with ID %s does not exist", sid)
}
delete(s.m, sid)
return nil
}
func (s *memSilences) GetAll() ([]*Silence, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
sils := make([]*Silence, 0, len(s.m))
for _, sil := range s.m {
sils = append(sils, sil)
}
return sils, nil
}
func (s *memState) SetSilence(sil *Silence) error {
s.silences[sil.ID] = sil
func (s *memSilences) Set(sil *Silence) error {
s.mtx.RLock()
defer s.mtx.RUnlock()
if sil.ID == "" {
sil.ID = s.genID()
}
s.m[sil.ID] = sil
return nil
}