Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

*: remove v1/alerts/groups API endpoint #1525

Merged
merged 3 commits into from
Aug 23, 2018
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Next release

* [CHANGE] Revert Alertmanager working directory changes in Docker image back to `/alertmanager` (#1435)
* [CHANGE] Remove `api/v1/alerts/groups` GET endpoint (#1508)
* [FEATURE] [amtool] Added `config routes` tools for vizualization and testing routes (#1511)

## 0.15.2 / 2018-08-14
Expand Down
40 changes: 12 additions & 28 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ var corsHeaders = map[string]string{
"Cache-Control": "no-cache, no-store, must-revalidate",
}

// Alert is the API representation of an alert, which is a regular alert
// annotated with silencing and inhibition info.
type Alert struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Thanks for moving APIAlert into api/api.go.

*model.Alert
Status types.AlertStatus `json:"status"`
Receivers []string `json:"receivers"`
Fingerprint string `json:"fingerprint"`
}

// Enables cross-site script calls.
func setCORS(w http.ResponseWriter) {
for h, v := range corsHeaders {
Expand All @@ -89,20 +98,17 @@ type API struct {
peer *cluster.Peer
logger log.Logger

groups groupsFn
getAlertStatus getAlertStatusFn

mtx sync.RWMutex
}

type groupsFn func([]*labels.Matcher) dispatch.AlertOverview
type getAlertStatusFn func(model.Fingerprint) types.AlertStatus

// New returns a new API.
func New(
alerts provider.Alerts,
silences *silence.Silences,
gf groupsFn,
sf getAlertStatusFn,
peer *cluster.Peer,
l log.Logger,
Expand All @@ -114,7 +120,6 @@ func New(
return &API{
alerts: alerts,
silences: silences,
groups: gf,
getAlertStatus: sf,
uptime: time.Now(),
peer: peer,
Expand All @@ -137,7 +142,6 @@ func (api *API) Register(r *route.Router) {
r.Get("/status", wrap(api.status))
r.Get("/receivers", wrap(api.receivers))

r.Get("/alerts/groups", wrap(api.alertGroups))
r.Get("/alerts", wrap(api.listAlerts))
r.Post("/alerts", wrap(api.addAlerts))

Expand Down Expand Up @@ -242,33 +246,13 @@ func getClusterStatus(p *cluster.Peer) *clusterStatus {
return s
}

func (api *API) alertGroups(w http.ResponseWriter, r *http.Request) {
var err error
matchers := []*labels.Matcher{}

if filter := r.FormValue("filter"); filter != "" {
matchers, err = parse.Matchers(filter)
if err != nil {
api.respondError(w, apiError{
typ: errorBadData,
err: err,
}, nil)
return
}
}

groups := api.groups(matchers)

api.respond(w, groups)
}

func (api *API) listAlerts(w http.ResponseWriter, r *http.Request) {
var (
err error
receiverFilter *regexp.Regexp
// Initialize result slice to prevent api returning `null` when there
// are no alerts present
res = []*dispatch.APIAlert{}
res = []*Alert{}
matchers = []*labels.Matcher{}

showActive, showInhibited bool
Expand Down Expand Up @@ -386,14 +370,14 @@ func (api *API) listAlerts(w http.ResponseWriter, r *http.Request) {
continue
}

apiAlert := &dispatch.APIAlert{
alert := &Alert{
Alert: &a.Alert,
Status: status,
Receivers: receivers,
Fingerprint: a.Fingerprint().String(),
}

res = append(res, apiAlert)
res = append(res, alert)
}
api.mtx.RUnlock()

Expand Down
7 changes: 3 additions & 4 deletions api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ func (f *fakeAlerts) GetPending() provider.AlertIterator {
return provider.NewAlertIterator(ch, done, f.err)
}

func groupAlerts([]*labels.Matcher) dispatch.AlertOverview { return dispatch.AlertOverview{} }
func newGetAlertStatus(f *fakeAlerts) func(model.Fingerprint) types.AlertStatus {
return func(fp model.Fingerprint) types.AlertStatus {
status := types.AlertStatus{SilencedBy: []string{}, InhibitedBy: []string{}}
Expand Down Expand Up @@ -133,7 +132,7 @@ func TestAddAlerts(t *testing.T) {
}

alertsProvider := newFakeAlerts([]*types.Alert{}, tc.err)
api := New(alertsProvider, nil, groupAlerts, newGetAlertStatus(alertsProvider), nil, nil)
api := New(alertsProvider, nil, newGetAlertStatus(alertsProvider), nil, nil)

r, err := http.NewRequest("POST", "/api/v1/alerts", bytes.NewReader(b))
w := httptest.NewRecorder()
Expand Down Expand Up @@ -260,7 +259,7 @@ func TestListAlerts(t *testing.T) {
},
} {
alertsProvider := newFakeAlerts(alerts, tc.err)
api := New(alertsProvider, nil, groupAlerts, newGetAlertStatus(alertsProvider), nil, nil)
api := New(alertsProvider, nil, newGetAlertStatus(alertsProvider), nil, nil)
api.route = dispatch.NewRoute(&config.Route{Receiver: "def-receiver"}, nil)

r, err := http.NewRequest("GET", "/api/v1/alerts", nil)
Expand Down Expand Up @@ -293,7 +292,7 @@ func TestListAlerts(t *testing.T) {
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
retAlerts := []*dispatch.APIAlert{}
retAlerts := []*Alert{}
err = json.Unmarshal(b, &retAlerts)
if err != nil {
t.Fatalf("Unexpected error %v", err)
Expand Down
4 changes: 0 additions & 4 deletions cmd/alertmanager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import (
"github.com/prometheus/common/promlog"
"github.com/prometheus/common/route"
"github.com/prometheus/common/version"
"github.com/prometheus/prometheus/pkg/labels"
"gopkg.in/alecthomas/kingpin.v2"
)

Expand Down Expand Up @@ -292,9 +291,6 @@ func main() {
apiv := api.New(
alerts,
silences,
func(matchers []*labels.Matcher) dispatch.AlertOverview {
return disp.Groups(matchers)
},
marker.Status,
peer,
logger,
Expand Down
109 changes: 0 additions & 109 deletions dispatch/dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/pkg/labels"
"golang.org/x/net/context"

"github.com/prometheus/alertmanager/notify"
Expand Down Expand Up @@ -84,103 +83,6 @@ func (d *Dispatcher) Run() {
close(d.done)
}

// AlertBlock contains a list of alerts associated with a set of
// routing options.
type AlertBlock struct {
RouteOpts *RouteOpts `json:"routeOpts"`
Alerts []*APIAlert `json:"alerts"`
}

// APIAlert is the API representation of an alert, which is a regular alert
// annotated with silencing and inhibition info.
type APIAlert struct {
*model.Alert
Status types.AlertStatus `json:"status"`
Receivers []string `json:"receivers"`
Fingerprint string `json:"fingerprint"`
}

// AlertGroup is a list of alert blocks grouped by the same label set.
type AlertGroup struct {
Labels model.LabelSet `json:"labels"`
GroupKey string `json:"groupKey"`
Blocks []*AlertBlock `json:"blocks"`
}

// AlertOverview is a representation of all active alerts in the system.
type AlertOverview []*AlertGroup

func (ao AlertOverview) Swap(i, j int) { ao[i], ao[j] = ao[j], ao[i] }
func (ao AlertOverview) Less(i, j int) bool { return ao[i].Labels.Before(ao[j].Labels) }
func (ao AlertOverview) Len() int { return len(ao) }

func matchesFilterLabels(a *APIAlert, matchers []*labels.Matcher) bool {
for _, m := range matchers {
if v, prs := a.Labels[model.LabelName(m.Name)]; !prs || !m.Matches(string(v)) {
return false
}
}

return true
}

// Groups populates an AlertOverview from the dispatcher's internal state.
func (d *Dispatcher) Groups(matchers []*labels.Matcher) AlertOverview {
overview := AlertOverview{}

d.mtx.RLock()
defer d.mtx.RUnlock()

seen := map[model.Fingerprint]*AlertGroup{}

for route, ags := range d.aggrGroups {
for _, ag := range ags {
alertGroup, ok := seen[ag.fingerprint()]
if !ok {
alertGroup = &AlertGroup{Labels: ag.labels}
alertGroup.GroupKey = ag.GroupKey()

seen[ag.fingerprint()] = alertGroup
}

now := time.Now()

var apiAlerts []*APIAlert
for _, a := range types.Alerts(ag.alertSlice()...) {
if !a.EndsAt.IsZero() && a.EndsAt.Before(now) {
continue
}
status := d.marker.Status(a.Fingerprint())
aa := &APIAlert{
Alert: a,
Status: status,
Fingerprint: a.Fingerprint().String(),
}

if !matchesFilterLabels(aa, matchers) {
continue
}

apiAlerts = append(apiAlerts, aa)
}
if len(apiAlerts) == 0 {
continue
}

alertGroup.Blocks = append(alertGroup.Blocks, &AlertBlock{
RouteOpts: &route.RouteOpts,
Alerts: apiAlerts,
})

overview = append(overview, alertGroup)
}
}

sort.Sort(overview)

return overview
}

func (d *Dispatcher) run(it provider.AlertIterator) {
cleanup := time.NewTicker(30 * time.Second)
defer cleanup.Stop()
Expand Down Expand Up @@ -341,17 +243,6 @@ func (ag *aggrGroup) String() string {
return ag.GroupKey()
}

func (ag *aggrGroup) alertSlice() []*types.Alert {
ag.mtx.RLock()
defer ag.mtx.RUnlock()

var alerts []*types.Alert
for _, a := range ag.alerts {
alerts = append(alerts, a)
}
return alerts
}

func (ag *aggrGroup) run(nf notifyFunc) {
ag.done = make(chan struct{})

Expand Down
75 changes: 0 additions & 75 deletions dispatch/dispatch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,87 +22,12 @@ import (

"github.com/go-kit/kit/log"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/pkg/labels"
"golang.org/x/net/context"

"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types"
)

func newAPIAlert(labels model.LabelSet) APIAlert {
return APIAlert{
Alert: &model.Alert{
Labels: labels,
StartsAt: time.Now().Add(1 * time.Minute),
EndsAt: time.Now().Add(1 * time.Hour),
},
}
}

func TestFilterLabels(t *testing.T) {

var (
a1 = newAPIAlert(model.LabelSet{
"a": "v1",
"b": "v2",
"c": "v3",
})
a2 = newAPIAlert(model.LabelSet{
"a": "v1",
"b": "v2",
"c": "v4",
})
a3 = newAPIAlert(model.LabelSet{
"a": "v1",
"b": "v2",
"c": "v5",
})
a4 = newAPIAlert(model.LabelSet{
"foo": "bar",
"baz": "qux",
})
alertsSlices = []struct {
in, want []APIAlert
}{
{
in: []APIAlert{a1, a2, a3},
want: []APIAlert{a1, a2, a3},
},
{
in: []APIAlert{a1, a4},
want: []APIAlert{a1},
},
{
in: []APIAlert{a4},
want: []APIAlert{},
},
}
)

matcher, err := labels.NewMatcher(labels.MatchRegexp, "c", "v.*")
if err != nil {
t.Fatalf("error making matcher: %v", err)
}
matcher2, err := labels.NewMatcher(labels.MatchEqual, "a", "v1")
if err != nil {
t.Fatalf("error making matcher: %v", err)
}

matchers := []*labels.Matcher{matcher, matcher2}

for _, alerts := range alertsSlices {
got := []APIAlert{}
for _, a := range alerts.in {
if matchesFilterLabels(&a, matchers) {
got = append(got, a)
}
}
if !reflect.DeepEqual(got, alerts.want) {
t.Fatalf("error: returned alerts do not match:\ngot %v\nwant %v", got, alerts.want)
}
}
}

func TestAggrGroup(t *testing.T) {
lset := model.LabelSet{
"a": "v1",
Expand Down