Skip to content
Permalink
Browse files

Merge branch 'master' into ES-TLS

* master:
  cmd/scollector: add systemd physical "predictable" linux net interface names (bosun-monitor#1985)
  cmd/bosun: add notification stats to /api/health (bosun-monitor#2222)
  cmd/bosun: native short links replace google (bosun-monitor#2210)
  docs: update system_configuration docs to reflect RedisDb configuration variable (bosun-monitor#2220)
  cmd/bosun/sched/views.go: added new incident filter `since` (bosun-monitor#2215)
  Bosun complaining about `actionBodyForceClose`, `actionBodyDelayedClose`, `actionBodyCancelClose` intermediately and causing crash with error `couldn't read rules: unknown key actionBodyDelayedClose` after looking at the code cmd/bosun/conf/rule/loaders.go#L442 we found strings.HasSuffix sometime getting matched with 'Close' rather then 'DelayedClose, ForceClose or CancelClose' and later it breaks the switch statement cmd/bosun/conf/rule/loaders.go#L462 due to invalid template type (bosun-monitor#2198)
  Update WMI to support int[] (bosun-monitor#2213)
  Add Scheme option to bosun toml (bosun-monitor#2209)
  Changed load sequence for `macros` to use in the notifications (bosun-monitor#2199)
  Fixed wrong nested ul in unknown notify (bosun-monitor#2208)
  travis: remove slack notification / go 1.9 (bosun-monitor#2203)
  Fix post notification logging (bosun-monitor#2196)
  • Loading branch information...
dschneller committed Feb 26, 2018
2 parents a2566f2 + db70a05 commit ca332bead09f2681307c6d3399a477d5f3737084
@@ -1,11 +1,10 @@
language: go
sudo: false
go:
- 1.8
- 1.9

notifications:
email: false
slack: bosun:2Z0edJEDfXMDnhg94uLNKzNj

install:
- rsync -az ${TRAVIS_BUILD_DIR}/ $HOME/gopath/src/bosun.org/
@@ -1,6 +1,9 @@
# Note: This file is tested as part of Bosun's tests. Editing outside of comments
# may cause tests to fail

# Scheme will be used with Hostname when links are created in templates (i.e. acknowledge links)
Scheme = "https"

# Hostname will be used when links are created in templates (i.e. acknowledge links)
Hostname = "bosun.example.com"

@@ -27,6 +27,12 @@ func init() {
metadata.AddMetricMeta(
"bosun.email.sent_failed", metadata.Counter, metadata.PerSecond,
"The number of email notifications that Bosun failed to send.")
metadata.AddMetricMeta(
"bosun.post.sent", metadata.Counter, metadata.PerSecond,
"The number of post notifications sent by Bosun.")
metadata.AddMetricMeta(
"bosun.post.sent_failed", metadata.Counter, metadata.PerSecond,
"The number of post notifications that Bosun failed to send.")
}

type PreparedNotifications struct {
@@ -99,10 +105,12 @@ func (n *Notification) NotifyAlert(rt *models.RenderedTemplates, c SystemConfPro
}

type PreparedHttp struct {
URL string
Method string
Headers map[string]string `json:",omitempty"`
Body string
URL string
Method string
Headers map[string]string `json:",omitempty"`
Body string
AK string
NotifyName string
}

func (p *PreparedHttp) Send() (int, error) {
@@ -127,16 +135,20 @@ func (p *PreparedHttp) Send() (int, error) {
return 0, err
}
if resp.StatusCode >= 300 {
return resp.StatusCode, fmt.Errorf("bad response on notification %s: %d", p.Method, resp.StatusCode)
collect.Add("post.sent_failed", nil, 1)
return resp.StatusCode, fmt.Errorf("bad response on notification with name %s for alert %s method %s: %d", p.NotifyName, p.AK, p.Method, resp.StatusCode)
}
collect.Add("post.sent", nil, 1)
return resp.StatusCode, nil
}

func (n *Notification) PrepHttp(method string, url string, body string, ak string) *PreparedHttp {
prep := &PreparedHttp{
Method: method,
URL: url,
Headers: map[string]string{},
Method: method,
URL: url,
Headers: map[string]string{},
AK: ak,
NotifyName: n.Name,
}
if method == http.MethodPost {
prep.Body = body
@@ -439,11 +439,14 @@ func (c *Conf) loadNotification(s *parse.SectionNode) {
keyType = strings.TrimPrefix(k, "action")
at := models.ActionNone
// look for and trim suffix if there
// trim the templateKey and explicitly match actiontype
for s, t := range models.ActionShortNames {
if strings.HasSuffix(keyType, s) {
at = t
keyType = keyType[:len(keyType)-len(s)]
break
for _, e := range []string{"Body", "Get", "EmailSubject"} {
if strings.Compare(strings.TrimPrefix(keyType, e), s) == 0 {
at = t
keyType = keyType[:len(keyType)-len(s)]
break
}
}
}
if n.ActionTemplateKeys[at] == nil {
@@ -179,8 +179,8 @@ func NewConf(name string, backends conf.EnabledBackends, sysVars map[string]stri
}

loadSections("template")
loadSections("notification")
loadSections("macro")
loadSections("notification")
loadSections("lookup")
loadSections("alert")

@@ -23,6 +23,7 @@ type SystemConf struct {
TLSKeyFile string

Hostname string
Scheme string // default http
Ping bool
PingDuration Duration // Duration from now to stop pinging hosts based on time since the host tag was touched
TimeAndDate []int // timeanddate.com cities list
@@ -222,6 +223,7 @@ const (
// NewSystemConf retruns a system conf with default values set
func newSystemConf() *SystemConf {
return &SystemConf{
Scheme: "http",
CheckFrequency: Duration{Duration: time.Minute * 5},
DefaultRunEvery: 1,
HTTPListen: defaultHTTPListen,
@@ -539,10 +541,12 @@ func (sc *SystemConf) AnnotateEnabled() bool {
// MakeLink creates a HTML Link based on Bosun's configured Hostname
func (sc *SystemConf) MakeLink(path string, v *url.Values) string {
u := url.URL{
Scheme: "http",
Host: sc.Hostname,
Path: path,
RawQuery: v.Encode(),
Scheme: sc.Scheme,
Host: sc.Hostname,
Path: path,
}
if v != nil {
u.RawQuery = v.Encode()
}
return u.String()
}
@@ -16,6 +16,7 @@ func TestSystemToml(t *testing.T) {
return
}
assert.Equal(t, sc.Hostname, "bosun.example.com", "Hostname not equal")
assert.Equal(t, sc.Scheme, "https", "Scheme does not match")
assert.Equal(t, sc.DefaultRunEvery, 5)
assert.Equal(t, sc.CheckFrequency, Duration{time.Minute})
assert.Equal(t, sc.Ping, true)
@@ -98,7 +98,7 @@ func init() {
{{ range $ak := $alertKeys }}
<li>{{ $ak }}</li>
{{ end }}
<ul>
</ul>
</li>
{{ end }}
</ul>
@@ -3,6 +3,7 @@ package database
import (
"crypto/md5"
"encoding/base64"
"fmt"

"github.com/garyburd/redigo/redis"

@@ -12,6 +13,9 @@ import (
type ConfigDataAccess interface {
SaveTempConfig(text string) (hash string, err error)
GetTempConfig(hash string) (text string, err error)

ShortenLink(fullURL string) (id int, err error)
GetShortLink(id int) (fullURL string, err error)
}

func (d *dataAccess) Configs() ConfigDataAccess {
@@ -46,3 +50,33 @@ func (d *dataAccess) GetTempConfig(hash string) (string, error) {
_, err = conn.Do("EXPIRE", key, configLifetime)
return dat, slog.Wrap(err)
}

const (
shortLinkCounterKey = "shortlinkCount"
shortLinksKey = "shortLinks"
)

func (d *dataAccess) ShortenLink(fullURL string) (id int, err error) {
conn := d.Get()
defer conn.Close()

newID, err := redis.Int(conn.Do("INCR", shortLinkCounterKey))
if err != nil {
return 0, slog.Wrap(err)
}
if _, err := conn.Do("HSET", shortLinksKey, fmt.Sprint(newID), fullURL); err != nil {
return 0, slog.Wrap(err)
}
return newID, nil
}

func (d *dataAccess) GetShortLink(id int) (fullURL string, err error) {
conn := d.Get()
defer conn.Close()

s, err := redis.String(conn.Do("HGET", shortLinksKey, fmt.Sprint(id)))
if err != nil {
return "", slog.Wrap(err)
}
return s, nil
}
@@ -60,6 +60,7 @@ type IncidentSummaryView struct {
Events []EventSummary
WarnNotificationChains [][]string
CritNotificationChains [][]string
LastStatusTime int64
}

func MakeIncidentSummary(c conf.RuleConfProvider, s SilenceTester, is *models.IncidentState) (*IncidentSummaryView, error) {
@@ -107,6 +108,7 @@ func MakeIncidentSummary(c conf.RuleConfProvider, s SilenceTester, is *models.In
Events: eventSummaries,
WarnNotificationChains: conf.GetNotificationChains(warnNotifications),
CritNotificationChains: conf.GetNotificationChains(critNotifications),
LastStatusTime: is.Last().Time.Unix(),
}, nil
}

@@ -237,6 +239,8 @@ func (is IncidentSummaryView) Ask(filter string) (bool, error) {
return is.LastAbnormalStatus.String() == value, nil
case "subject":
return glob.Glob(value, is.Subject), nil
case "since":
return checkTimeArg(is.LastStatusTime, value)
}
return false, nil
}
@@ -8,7 +8,6 @@ import (
"html/template"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"net/url"
@@ -171,6 +170,7 @@ func Listen(httpAddr, httpsAddr, certFile, keyFile string, devMode bool, tsdbHos
handle("/api/rule", JSON(Rule), canRunTests).Name("rule_test").Methods(POST)
handle("/api/rule/notification/test", JSON(TestHTTPNotification), canRunTests).Name("rule__notification_test").Methods(POST)
handle("/api/shorten", JSON(Shorten), canViewDash).Name("shorten")
handle("/s/{id}", JSON(GetShortLink), canViewDash).Name("shortlink")
handle("/api/silence/clear", JSON(SilenceClear), canSilence).Name("silence_clear")
handle("/api/silence/get", JSON(SilenceGet), canViewDash).Name("silence_get").Methods(GET)
handle("/api/silence/set", JSON(SilenceSet), canSilence).Name("silence_set")
@@ -406,41 +406,29 @@ func JSON(h func(miniprofiler.Timer, http.ResponseWriter, *http.Request) (interf
}

func Shorten(_ miniprofiler.Timer, w http.ResponseWriter, r *http.Request) (interface{}, error) {
u := url.URL{
Scheme: "https",
Host: "www.googleapis.com",
Path: "/urlshortener/v1/url",
}
if schedule.SystemConf.GetShortURLKey() != "" {
u.RawQuery = "key=" + schedule.SystemConf.GetShortURLKey()
}
j, err := json.Marshal(struct {
LongURL string `json:"longUrl"`
}{
r.Referer(),
})
id, err := schedule.DataAccess.Configs().ShortenLink(r.Referer())
if err != nil {
return nil, err
}
return struct {
ID string `json:"id"`
}{schedule.SystemConf.MakeLink(fmt.Sprintf("/s/%d", id), nil)}, nil
}

transport := &http.Transport{
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
}
if InternetProxy != nil {
transport.Proxy = http.ProxyURL(InternetProxy)
func GetShortLink(t miniprofiler.Timer, w http.ResponseWriter, r *http.Request) (interface{}, error) {
// on any error or bad param, just redirect to index. Otherwise 302 to stored url
vars := mux.Vars(r)
idv := vars["id"]
id, err := strconv.Atoi(idv)
targetURL := ""
if err != nil {
return Index(t, w, r)
}
c := http.Client{Transport: transport}

req, err := c.Post(u.String(), "application/json", bytes.NewBuffer(j))
targetURL, err = schedule.DataAccess.Configs().GetShortLink(id)
if err != nil {
return nil, err
return Index(t, w, r)
}
io.Copy(w, req.Body)
req.Body.Close()
http.Redirect(w, r, targetURL, 302)
return nil, nil
}

@@ -450,6 +438,15 @@ type Health struct {
Quiet bool
UptimeSeconds int64
StartEpoch int64
Notifications NotificationStats
}

type NotificationStats struct {
// Post and email notifiaction stats
PostNotificationsSuccess int64
PostNotificationsFailed int64
EmailNotificationsSuccess int64
EmailNotificationsFailed int64
}

func Reload(t miniprofiler.Timer, w http.ResponseWriter, r *http.Request) (interface{}, error) {
@@ -477,10 +474,19 @@ func Quiet(t miniprofiler.Timer, w http.ResponseWriter, r *http.Request) (interf

func HealthCheck(t miniprofiler.Timer, w http.ResponseWriter, r *http.Request) (interface{}, error) {
var h Health
var n NotificationStats
h.RuleCheck = schedule.LastCheck.After(time.Now().Add(-schedule.SystemConf.GetCheckFrequency()))
h.Quiet = schedule.GetQuiet()
h.UptimeSeconds = int64(time.Since(startTime).Seconds())
h.StartEpoch = startTime.Unix()

//notifications stats
n.PostNotificationsSuccess = collect.Get("post.sent", nil)
n.PostNotificationsFailed = collect.Get("post.sent_failed", nil)
n.EmailNotificationsSuccess = collect.Get("email.sent", nil)
n.EmailNotificationsFailed = collect.Get("email.sent_failed", nil)

h.Notifications = n
return h, nil
}

@@ -51,8 +51,11 @@ var netFields = []struct {
{"compressed", metadata.Counter, metadata.Count},
}

var ifstatRE = regexp.MustCompile(`\s+(eth\d+|em\d+_\d+/\d+|em\d+_\d+|em\d+|` +
`bond\d+|team\d+|` + `p\d+p\d+_\d+/\d+|p\d+p\d+_\d+|p\d+p\d+):(.*)`)
var ifstatRE = regexp.MustCompile(`\s*(eth\d+|em\d+_\d+/\d+|em\d+_\d+|em\d+|` +
`bond\d+|team\d+|` +
`p\d+p\d+_\d+/\d+|p\d+p\d+_\d+|p\d+p\d+|` +
`en[[:alnum:]]+|wl[[:alnum:]]+|ww[[:alnum:]]+` + // Systemd predictable interface names
`):(.*)`)

func c_ipcount_linux() (opentsdb.MultiDataPoint, error) {
var md opentsdb.MultiDataPoint
@@ -369,6 +369,22 @@ func Add(metric string, ts opentsdb.TagSet, inc int64) error {
return nil
}

func Get(metric string, ts opentsdb.TagSet) int64 {
var counter_value int64
if err := check(metric, &ts); err != nil {
return 0
}
tss := metric + ts.String()
mlock.Lock()
if counters[tss] != nil {
counter_value = counters[tss].value
} else {
counter_value = 0
}
mlock.Unlock()
return counter_value
}

type putMetric struct {
metric string
ts opentsdb.TagSet
@@ -168,6 +168,8 @@ Returns a list of alert summaries matching the given filter (defaults to all).
Returns an object of internal health checks. True values are good, falses are
bad.

`Note: all health checks stats are kept in memory and reset upon bosun restart`

### /api/run

Runs a rule check. Returns an error if one is already running (either from the
@@ -242,6 +242,9 @@ The default is to use ledis. If Both Redis and ledis are defined, Redis will tak
#### RedisHost
The Redis hostname and port.

#### RedisDb
Optional integer database to store bosun data. Defaults to 0.

#### RedisPassword
Optional password to use when connecting to Redis.

Oops, something went wrong.

0 comments on commit ca332be

Please sign in to comment.
You can’t perform that action at this time.