Skip to content
This repository has been archived by the owner on Jul 22, 2020. It is now read-only.

Commit

Permalink
Merge pull request #216 from cloudflare/mapper-refactor
Browse files Browse the repository at this point in the history
Mapper package refactoring
  • Loading branch information
prymitive committed Jan 22, 2018
2 parents 1981898 + c7fb8db commit 90a517b
Show file tree
Hide file tree
Showing 18 changed files with 247 additions and 136 deletions.
5 changes: 4 additions & 1 deletion internal/alertmanager/dedup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ func init() {
log.SetLevel(log.ErrorLevel)
for i, uri := range mock.ListAllMockURIs() {
name := fmt.Sprintf("dedup-mock-%d", i)
am := alertmanager.NewAlertmanager(name, uri, alertmanager.WithRequestTimeout(time.Second))
am, err := alertmanager.NewAlertmanager(name, uri, alertmanager.WithRequestTimeout(time.Second))
if err != nil {
log.Fatal(err)
}
alertmanager.RegisterAlertmanager(am)
}
}
Expand Down
53 changes: 49 additions & 4 deletions internal/alertmanager/models.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package alertmanager

import (
"encoding/json"
"fmt"
"path"
"sort"
Expand Down Expand Up @@ -34,6 +35,8 @@ type Alertmanager struct {
Name string `json:"name"`
// whenever this instance should be proxied
ProxyRequests bool
// transport instances are specific to URI scheme we collect from
transport transport.Transport
// lock protects data access while updating
lock sync.RWMutex
// fields for storing pulled data
Expand All @@ -56,9 +59,19 @@ func (am *Alertmanager) detectVersion() string {
return defaultVersion
}
ver := alertmanagerVersion{}
err = transport.ReadJSON(url, am.RequestTimeout, &ver)

// read raw body from the source
source, err := am.transport.Read(url)
defer source.Close()
if err != nil {
log.Errorf("[%s] %s request failed: %s", am.Name, url, err)
return defaultVersion
}

// decode body as JSON
err = json.NewDecoder(source).Decode(&ver)
if err != nil {
log.Errorf("[%s] %s request failed: %s", am.Name, url, err.Error())
log.Errorf("[%s] %s failed to decode as JSON: %s", am.Name, url, err)
return defaultVersion
}

Expand Down Expand Up @@ -91,8 +104,24 @@ func (am *Alertmanager) pullSilences(version string) error {
return err
}

// generate full URL to collect silences from
url, err := mapper.AbsoluteURL(am.URI)
if err != nil {
log.Errorf("[%s] Failed to generate silences endpoint URL: %s", am.Name, err)
return err
}

start := time.Now()
silences, err := mapper.GetSilences(am.URI, am.RequestTimeout)
// read raw body from the source
source, err := am.transport.Read(url)
defer source.Close()
if err != nil {
log.Errorf("[%s] %s request failed: %s", am.Name, url, err)
return err
}

// decode body text
silences, err := mapper.Decode(source)
if err != nil {
return err
}
Expand Down Expand Up @@ -134,8 +163,24 @@ func (am *Alertmanager) pullAlerts(version string) error {
return err
}

// generate full URL to collect alerts from
url, err := mapper.AbsoluteURL(am.URI)
if err != nil {
log.Errorf("[%s] Failed to generate alerts endpoint URL: %s", am.Name, err)
return err
}

start := time.Now()
groups, err := mapper.GetAlerts(am.URI, am.RequestTimeout)
// read raw body from the source
source, err := am.transport.Read(url)
defer source.Close()
if err != nil {
log.Errorf("[%s] %s request failed: %s", am.Name, url, err)
return err
}

// decode body text
groups, err := mapper.Decode(source)
if err != nil {
return err
}
Expand Down
11 changes: 9 additions & 2 deletions internal/alertmanager/upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/cloudflare/unsee/internal/models"
"github.com/cloudflare/unsee/internal/transport"

log "github.com/sirupsen/logrus"
)
Expand All @@ -18,7 +19,7 @@ var (
)

// NewAlertmanager creates a new Alertmanager instance
func NewAlertmanager(name, uri string, opts ...Option) *Alertmanager {
func NewAlertmanager(name, uri string, opts ...Option) (*Alertmanager, error) {
am := &Alertmanager{
URI: uri,
RequestTimeout: time.Second * 10,
Expand All @@ -40,7 +41,13 @@ func NewAlertmanager(name, uri string, opts ...Option) *Alertmanager {
opt(am)
}

return am
var err error
am.transport, err = transport.NewTransport(am.URI, am.RequestTimeout)
if err != nil {
return am, err
}

return am, nil
}

// RegisterAlertmanager will add an Alertmanager instance to the list of
Expand Down
11 changes: 10 additions & 1 deletion internal/alertmanager/version.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package alertmanager

import (
"encoding/json"
"time"

"github.com/cloudflare/unsee/internal/transport"
Expand Down Expand Up @@ -30,7 +31,15 @@ func GetVersion(uri string, timeout time.Duration) string {
return defaultVersion
}
ver := alertmanagerVersion{}
err = transport.ReadJSON(url, timeout, &ver)

t, err := transport.NewTransport(uri, timeout)
if err != nil {
log.Errorf("Unable to get the version information from %s", url)
return defaultVersion
}

source, err := t.Read(url)
err = json.NewDecoder(source).Decode(&ver)
if err != nil {
log.Errorf("%s request failed: %s", url, err.Error())
return defaultVersion
Expand Down
5 changes: 4 additions & 1 deletion internal/filters/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,10 @@ var tests = []filterTest{
func TestFilters(t *testing.T) {
log.SetLevel(log.ErrorLevel)

am := alertmanager.NewAlertmanager("test", "http://localhost", alertmanager.WithRequestTimeout(time.Second))
am, err := alertmanager.NewAlertmanager("test", "http://localhost", alertmanager.WithRequestTimeout(time.Second))
if err != nil {
t.Error(err)
}
for _, ft := range tests {
alert := models.Alert(ft.Alert)
if &ft.Silence != nil {
Expand Down
29 changes: 16 additions & 13 deletions internal/mapper/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package mapper

import (
"fmt"
"time"
"io"

"github.com/cloudflare/unsee/internal/models"
)
Expand All @@ -12,11 +12,22 @@ var (
silenceMappers = []SilenceMapper{}
)

// AlertMapper implements Alertmanager -> unsee alert data mapping that works
// for a specific range of Alertmanager versions
type AlertMapper interface {
// Mapper converts Alertmanager response body and maps to unsee data structures
type Mapper interface {
IsSupported(version string) bool
GetAlerts(uri string, timeout time.Duration) ([]models.AlertGroup, error)
AbsoluteURL(baseURI string) (string, error)
}

// AlertMapper handles mapping of Alertmanager alert information to unsee AlertGroup models
type AlertMapper interface {
Mapper
Decode(io.ReadCloser) ([]models.AlertGroup, error)
}

// SilenceMapper handles mapping of Alertmanager silence information to unsee Silence models
type SilenceMapper interface {
Mapper
Decode(io.ReadCloser) ([]models.Silence, error)
}

// RegisterAlertMapper allows to register mapper implementing alert data
Expand All @@ -35,14 +46,6 @@ func GetAlertMapper(version string) (AlertMapper, error) {
return nil, fmt.Errorf("Can't find alert mapper for Alertmanager %s", version)
}

// SilenceMapper implements Alertmanager -> unsee silence data mapping that
// works for a specific range of Alertmanager versions
type SilenceMapper interface {
Release() string
IsSupported(version string) bool
GetSilences(uri string, timeout time.Duration) ([]models.Silence, error)
}

// RegisterSilenceMapper allows to register mapper implementing silence data
// handling for specific Alertmanager versions
func RegisterSilenceMapper(m SilenceMapper) {
Expand Down
20 changes: 11 additions & 9 deletions internal/mapper/v04/alerts.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
package v04

import (
"encoding/json"
"errors"
"io"
"sort"
"strconv"
"time"
Expand Down Expand Up @@ -52,25 +54,25 @@ type AlertMapper struct {
mapper.AlertMapper
}

// AbsoluteURL for alerts API endpoint this mapper supports
func (m AlertMapper) AbsoluteURL(baseURI string) (string, error) {
return transport.JoinURL(baseURI, "api/v1/alerts/groups")
}

// IsSupported returns true if given version string is supported
func (m AlertMapper) IsSupported(version string) bool {
versionRange := semver.MustParseRange(">=0.4.0 <0.5.0")
return versionRange(semver.MustParse(version))
}

// GetAlerts will make a request to Alertmanager API and parse the response
// It will only return alerts or error (if any)
func (m AlertMapper) GetAlerts(uri string, timeout time.Duration) ([]models.AlertGroup, error) {
// Decode Alertmanager API response body and return unsee model instances
func (m AlertMapper) Decode(source io.ReadCloser) ([]models.AlertGroup, error) {
groups := []models.AlertGroup{}
receivers := map[string]alertsGroupReceiver{}
resp := alertsGroupsAPISchema{}

url, err := transport.JoinURL(uri, "api/v1/alerts/groups")
if err != nil {
return groups, err
}

err = transport.ReadJSON(url, timeout, &resp)
defer source.Close()
err := json.NewDecoder(source).Decode(&resp)
if err != nil {
return groups, err
}
Expand Down
24 changes: 11 additions & 13 deletions internal/mapper/v04/silences.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
package v04

import (
"encoding/json"
"errors"
"fmt"
"math"
"io"
"strconv"
"time"

Expand Down Expand Up @@ -47,26 +47,24 @@ type SilenceMapper struct {
mapper.SilenceMapper
}

// AbsoluteURL for silences API endpoint this mapper supports
func (m SilenceMapper) AbsoluteURL(baseURI string) (string, error) {
return transport.JoinURL(baseURI, "api/v1/silences")
}

// IsSupported returns true if given version string is supported
func (m SilenceMapper) IsSupported(version string) bool {
versionRange := semver.MustParseRange(">=0.4.0 <0.5.0")
return versionRange(semver.MustParse(version))
}

// GetSilences will make a request to Alertmanager API and parse the response
// It will only return silences or error (if any)
func (m SilenceMapper) GetSilences(uri string, timeout time.Duration) ([]models.Silence, error) {
// Decode Alertmanager API response body and return unsee model instances
func (m SilenceMapper) Decode(source io.ReadCloser) ([]models.Silence, error) {
silences := []models.Silence{}
resp := silenceAPISchema{}

url, err := transport.JoinURL(uri, "api/v1/silences")
if err != nil {
return silences, err
}

// Alertmanager 0.4 uses pagination for silences
url = fmt.Sprintf("%s?limit=%d", url, math.MaxUint32)
err = transport.ReadJSON(url, timeout, &resp)
defer source.Close()
err := json.NewDecoder(source).Decode(&resp)
if err != nil {
return silences, err
}
Expand Down
20 changes: 11 additions & 9 deletions internal/mapper/v05/alerts.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
package v05

import (
"encoding/json"
"errors"
"io"
"sort"
"time"

Expand Down Expand Up @@ -51,25 +53,25 @@ type AlertMapper struct {
mapper.AlertMapper
}

// AbsoluteURL for alerts API endpoint this mapper supports
func (m AlertMapper) AbsoluteURL(baseURI string) (string, error) {
return transport.JoinURL(baseURI, "api/v1/alerts/groups")
}

// IsSupported returns true if given version string is supported
func (m AlertMapper) IsSupported(version string) bool {
versionRange := semver.MustParseRange(">=0.5.0 <=0.6.0")
return versionRange(semver.MustParse(version))
}

// GetAlerts will make a request to Alertmanager API and parse the response
// It will only return alerts or error (if any)
func (m AlertMapper) GetAlerts(uri string, timeout time.Duration) ([]models.AlertGroup, error) {
// Decode Alertmanager API response body and return unsee model instances
func (m AlertMapper) Decode(source io.ReadCloser) ([]models.AlertGroup, error) {
groups := []models.AlertGroup{}
receivers := map[string]alertsGroupReceiver{}
resp := alertsGroupsAPISchema{}

url, err := transport.JoinURL(uri, "api/v1/alerts/groups")
if err != nil {
return groups, err
}

err = transport.ReadJSON(url, timeout, &resp)
defer source.Close()
err := json.NewDecoder(source).Decode(&resp)
if err != nil {
return groups, err
}
Expand Down

0 comments on commit 90a517b

Please sign in to comment.