Skip to content

Commit

Permalink
Add snmptrap service
Browse files Browse the repository at this point in the history
  • Loading branch information
titilambert committed Sep 21, 2016
1 parent 333fd6b commit 0461fad
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [#909](https://github.com/influxdata/kapacitor/pull/909): Allow duration / duration in TICKscript.
- [#777](https://github.com/influxdata/kapacitor/issues/777): Add support for string manipulation functions.
- [#886](https://github.com/influxdata/kapacitor/issues/886): Add ability to set specific HTTP port and hostname per configured InfluxDB cluster.
- [#923](https://github.com/influxdata/kapacitor/pull/929): Add SNMP trap service for alerting

### Bugfixes

Expand Down
2 changes: 1 addition & 1 deletion LICENSE_OF_DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Dependencies
* github.com/dustin/go-humanize [MIT](https://github.com/dustin/go-humanize/blob/master/LICENSE)
* github.com/golang/protobuf [BSD](https://github.com/golang/protobuf/blob/master/LICENSE)
* github.com/gorhill/cronexpr [APLv2](https://github.com/gorhill/cronexpr/blob/master/APLv2)
* github.com/k-sone/snmpgo [MIT](https://github.com/k-sone/snmpgo/blob/master/LICENSE)
* github.com/kimor79/gollectd [BSD](https://github.com/kimor79/gollectd/blob/master/LICENSE)
* github.com/mattn/go-runewidth [MIT](https://github.com/mattn/go-runewidth/blob/master/README.mkd)
* github.com/pkg/errors [BSD](https://github.com/pkg/errors/blob/master/LICENSE)
Expand All @@ -19,4 +20,3 @@ Dependencies
* github.com/stretchr/testify [MIT](https://github.com/stretchr/testify/blob/master/LICENSE)
* github.com/twinj/uuid [MIT](https://github.com/twinj/uuid/blob/master/LICENSE)
* gopkg.in/gomail.v2 [MIT](https://github.com/go-gomail/gomail/blob/v2/LICENSE)

55 changes: 55 additions & 0 deletions alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,26 @@ func newAlertNode(et *ExecutingTask, n *pipeline.AlertNode, l *log.Logger) (an *
n.IsStateChangesOnly = true
}

for _, snmp := range n.SnmpHandlers {
// Validate snmp templates
var tmp_trap [][]interface{}
for _, trap := range snmp.TrapList {
var row []interface{}
for _, attr := range trap {
tpattrtmpl, err := text.New("trap").Parse(attr.(string))
if err != nil {
return nil, err
}
row = append(row, tpattrtmpl)
}
tmp_trap = append(tmp_trap, row)
}

snmp := snmp
snmp.TrapList = tmp_trap
an.handlers = append(an.handlers, func(ad *AlertData) { an.handleSnmp(snmp, ad) })
}

for _, telegram := range n.TelegramHandlers {
telegram := telegram
an.handlers = append(an.handlers, func(ad *AlertData) { an.handleTelegram(telegram, ad) })
Expand Down Expand Up @@ -1077,6 +1097,41 @@ func (a *AlertNode) handleSlack(slack *pipeline.SlackHandler, ad *AlertData) {
}
}

func (a *AlertNode) handleSnmp(snmp *pipeline.SnmpHandler, ad *AlertData) {
if a.et.tm.SnmpService == nil {
a.logger.Println("E! failed to send SNMP traps. SNMP is not enabled")
return
}

// Template
var buf bytes.Buffer
var tmp_trap [][]interface{}
for _, trap := range snmp.TrapList {
var row []interface{}
for _, attr := range trap {
err := attr.(*text.Template).Execute(&buf, ad.info)
a.logger.Printf("L! \n\nATTTTTT %s\n\n", attr)
if err != nil {
a.logger.Printf("E! failed to evaluate SNMP Trap attribute template %s", attr)
return
}
row = append(row, buf.String())
a.logger.Printf("L! \n\nROW %s\n\n", row)
buf.Reset()
}
tmp_trap = append(tmp_trap, row)
}

err := a.et.tm.SnmpService.Alert(
tmp_trap,
ad.Level,
)
if err != nil {
a.logger.Println("E! failed to send alert data by SNMP traps:", err)
return
}
}

func (a *AlertNode) handleTelegram(telegram *pipeline.TelegramHandler, ad *AlertData) {
if a.et.tm.TelegramService == nil {
a.logger.Println("E! failed to send Telegram message. Telegram is not enabled")
Expand Down
77 changes: 77 additions & 0 deletions pipeline/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,10 @@ type AlertNode struct {
// Send alert to Talk.
// tick:ignore
TalkHandlers []*TalkHandler `tick:"Talk"`

// Send alert using SNMPtraps.
// tick:ignore
SnmpHandlers []*SnmpHandler `tick:"Snmp"`
}

func newAlertNode(wants EdgeType) *AlertNode {
Expand Down Expand Up @@ -1254,3 +1258,76 @@ func (a *AlertNode) Talk() *TalkHandler {
type TalkHandler struct {
*AlertNode
}

// Send the alert using SNMP traps.
// To allow Kapacitor to post SNMP traps,
//
// Example:
// [snmp]
// enabled = true
// target-ip = "127.0.0.1"
// target-port = 9162
// community = "public"
// version = "2c"
// state-changes-only = false
//
// In order to not post a message every alert interval
// use AlertNode.StateChangesOnly so that only events
// where the alert changed state are posted to the channel.
//
// Example:
// stream
// |alert()
// .snmp()
// .trap('1.3.6.1.2.1.1.7', 'i', {{ Index field value }})
//
// Send alerts to `target-ip:target-port` on OID '1.3.6.1.2.1.1.7'
//
// tick:property
func (a *AlertNode) Snmp(trap ...[]interface{}) *SnmpHandler {
snmp := &SnmpHandler{
AlertNode: a,
TrapList: trap,
}
a.SnmpHandlers = append(a.SnmpHandlers, snmp)
return snmp
}

// Email AlertHandler
// tick:embedded:AlertNode.Email
type SnmpHandler struct {
*AlertNode

// List of email recipients.
// tick:ignore
TrapList [][]interface{} `tick:"Trap"`
}

// Define the To addresses for the email alert.
// Multiple calls append to the existing list of addresses.
// If empty uses the addresses from the configuration.
//
// Example:
// |alert()
// .id('{{ .Name }}')
// // Email subject
// .meassage('{{ .ID }}:{{ .Level }}')
// //Email body as HTML
// .details('''
//<h1>{{ .ID }}</h1>
//<b>{{ .Message }}</b>
//Value: {{ index .Fields "value" }}
//''')
// .email('[email protected]')
// .to('[email protected]')
// .to('[email protected]')
//
// All three email addresses will receive the alert message.
//
// Passing addresses to the `email` property directly or using the `email.to` property is the same.
// tick:property
func (h *SnmpHandler) Trap(trap ...interface{}) *SnmpHandler {
// TODO check element validity
h.TrapList = append(h.TrapList, trap)
return h
}
3 changes: 3 additions & 0 deletions server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/influxdata/kapacitor/services/sensu"
"github.com/influxdata/kapacitor/services/slack"
"github.com/influxdata/kapacitor/services/smtp"
"github.com/influxdata/kapacitor/services/snmp"
"github.com/influxdata/kapacitor/services/stats"
"github.com/influxdata/kapacitor/services/storage"
"github.com/influxdata/kapacitor/services/talk"
Expand Down Expand Up @@ -57,6 +58,7 @@ type Config struct {
PagerDuty pagerduty.Config `toml:"pagerduty"`
Sensu sensu.Config `toml:"sensu"`
Slack slack.Config `toml:"slack"`
Snmp snmp.Config `toml:"snmp"`
Telegram telegram.Config `toml:"telegram"`
HipChat hipchat.Config `toml:"hipchat"`
Alerta alerta.Config `toml:"alerta"`
Expand Down Expand Up @@ -93,6 +95,7 @@ func NewConfig() *Config {
c.PagerDuty = pagerduty.NewConfig()
c.Sensu = sensu.NewConfig()
c.Slack = slack.NewConfig()
c.Snmp = snmp.NewConfig()
c.Telegram = telegram.NewConfig()
c.HipChat = hipchat.NewConfig()
c.Alerta = alerta.NewConfig()
Expand Down
13 changes: 13 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/influxdata/kapacitor/services/sensu"
"github.com/influxdata/kapacitor/services/slack"
"github.com/influxdata/kapacitor/services/smtp"
"github.com/influxdata/kapacitor/services/snmp"
"github.com/influxdata/kapacitor/services/stats"
"github.com/influxdata/kapacitor/services/storage"
"github.com/influxdata/kapacitor/services/talk"
Expand Down Expand Up @@ -159,6 +160,7 @@ func New(c *Config, buildInfo BuildInfo, logService logging.Interface) (*Server,
s.appendHipChatService()
s.appendAlertaService()
s.appendSlackService()
s.appendSnmpService()
s.appendSensuService()
s.appendTalkService()

Expand Down Expand Up @@ -352,6 +354,17 @@ func (s *Server) appendSlackService() {
}
}

func (s *Server) appendSnmpService() {
c := s.config.Snmp
if c.Enabled {
l := s.LogService.NewLogger("[snmp] ", log.LstdFlags)
srv := snmp.NewService(c, l)
s.TaskMaster.SnmpService = srv

s.AppendService("snmp", srv)
}
}

func (s *Server) appendTelegramService() {
c := s.config.Telegram
if c.Enabled {
Expand Down
21 changes: 21 additions & 0 deletions services/snmp/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package snmp

type Config struct {
// Whether Snmptrap is enabled.
Enabled bool `toml:"enabled"`
// NMS IP (Network Management Station).
TargetIp string `toml:"target-ip"`
// NMS port
TargetPort int `toml:"target-port"`
// SNMP Community
Community string `toml:"community"`
// SNMP Version
Version string `toml:"version"`
// Whether all alerts should automatically use stateChangesOnly mode.
// Only applies if global is also set.
StateChangesOnly bool `toml:"state-changes-only"`
}

func NewConfig() Config {
return Config{}
}
114 changes: 114 additions & 0 deletions services/snmp/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package snmp

import (
"errors"
"fmt"
"log"
"strconv"

"github.com/influxdata/kapacitor"
"github.com/k-sone/snmpgo"
)

type Service struct {
targetIp string
targetPort int
community string
version string
stateChangesOnly bool
logger *log.Logger
}

func NewService(c Config, l *log.Logger) *Service {
return &Service{
targetIp: c.TargetIp,
targetPort: c.TargetPort,
community: c.Community,
version: c.Version,
stateChangesOnly: c.StateChangesOnly,
logger: l,
}
}

func (s *Service) Open() error {
return nil
}

func (s *Service) Close() error {
return nil
}

func (s *Service) StateChangesOnly() bool {
return s.stateChangesOnly
}

func (s *Service) Alert(traps [][]interface{}, level kapacitor.AlertLevel) error {
address := s.targetIp + strconv.Itoa(s.targetPort)
snmp, err := snmpgo.NewSNMP(snmpgo.SNMPArguments{
Version: snmpgo.V2c,
Address: address,
Retries: 1,
Community: s.community,
})

var varBinds snmpgo.VarBinds
for _, trap := range traps {
fmt.Printf("\n\n\nREERRRR %s", trap)
oid_str := trap[0].(string)
oid_type_raw := trap[1].(string)
oid, _ := snmpgo.NewOid(oid_str)
// http://docstore.mik.ua/orelly/networking_2ndEd/snmp/ch10_03.htm
switch oid_type_raw {
case "a":
return errors.New("Snmptrap Datatype 'IP address' not supported")
case "c":
oid_value, err := strconv.ParseInt(trap[2].(string), 10, 64)
if err != nil {
return err
}
varBinds = append(varBinds, snmpgo.NewVarBind(oid, snmpgo.NewCounter64(uint64(oid_value))))
case "d":
return errors.New("Snmptrap Datatype 'Decimal string' not supported")
case "i":
oid_value, err := strconv.ParseInt(trap[2].(string), 10, 64)
if err != nil {
return err
}
varBinds = append(varBinds, snmpgo.NewVarBind(oid, snmpgo.NewInteger(int32(oid_value))))
case "n":
varBinds = append(varBinds, snmpgo.NewVarBind(oid, snmpgo.NewNull()))
case "o":
return errors.New("Snmptrap Datatype 'Object ID' not supported")
case "s":
oid_value := []byte(trap[2].(string))
varBinds = append(varBinds, snmpgo.NewVarBind(oid, snmpgo.NewOctetString(oid_value)))
case "t":
oid_value, err := strconv.ParseInt(trap[2].(string), 10, 64)
if err != nil {
return err
}
varBinds = append(varBinds, snmpgo.NewVarBind(oid, snmpgo.NewTimeTicks(uint32(oid_value))))
case "u":
return errors.New("Snmptrap Datatype 'Unsigned integer' not supported")
case "x":
return errors.New("Snmptrap Datatype 'Hexadecimal string' not supported")
default:
return errors.New("Snmptrap Datatype not supported: " + oid_type_raw)
}
}

if err = snmp.Open(); err != nil {
// Failed to open connection
fmt.Println(err)
return err
}
defer snmp.Close()

if err = snmp.V2Trap(varBinds); err != nil {
// Failed to request
fmt.Println(err)
return err
}

return nil
}
5 changes: 5 additions & 0 deletions task_master.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ type TaskMaster struct {
StateChangesOnly() bool
Alert(channel, message string, level AlertLevel) error
}
SnmpService interface {
StateChangesOnly() bool
Alert(trap [][]interface{}, level AlertLevel) error
}
TelegramService interface {
Global() bool
StateChangesOnly() bool
Expand Down Expand Up @@ -181,6 +185,7 @@ func (tm *TaskMaster) New(id string) *TaskMaster {
n.VictorOpsService = tm.VictorOpsService
n.PagerDutyService = tm.PagerDutyService
n.SlackService = tm.SlackService
n.SnmpService = tm.SnmpService
n.HipChatService = tm.HipChatService
n.AlertaService = tm.AlertaService
n.SensuService = tm.SensuService
Expand Down
1 change: 1 addition & 0 deletions vendor.list
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ github.com/golang/protobuf
github.com/gorhill/cronexpr
github.com/influxdata/influxdb 1.0
github.com/influxdata/usage-client
github.com/k-sone/snmpgo
github.com/kimor79/gollectd
github.com/mattn/go-runewidth
github.com/pkg/errors
Expand Down

0 comments on commit 0461fad

Please sign in to comment.