mirror of https://github.com/grafana/grafana
commit
896e6d4662
@ -0,0 +1,79 @@ |
||||
package events |
||||
|
||||
import ( |
||||
"reflect" |
||||
"time" |
||||
) |
||||
|
||||
// Events can be passed to external systems via for example AMPQ
|
||||
// Treat these events as basically DTOs so changes has to be backward compatible
|
||||
|
||||
type Priority string |
||||
|
||||
const ( |
||||
PRIO_DEBUG Priority = "DEBUG" |
||||
PRIO_INFO Priority = "INFO" |
||||
PRIO_ERROR Priority = "ERROR" |
||||
) |
||||
|
||||
type Event struct { |
||||
Timestamp time.Time `json:"timestamp"` |
||||
} |
||||
|
||||
type OnTheWireEvent struct { |
||||
EventType string `json:"event_type"` |
||||
Priority Priority `json:"priority"` |
||||
Timestamp time.Time `json:"timestamp"` |
||||
Payload interface{} `json:"payload"` |
||||
} |
||||
|
||||
type EventBase interface { |
||||
ToOnWriteEvent() *OnTheWireEvent |
||||
} |
||||
|
||||
func ToOnWriteEvent(event interface{}) (*OnTheWireEvent, error) { |
||||
eventType := reflect.TypeOf(event) |
||||
|
||||
wireEvent := OnTheWireEvent{ |
||||
Priority: PRIO_INFO, |
||||
EventType: eventType.Name(), |
||||
Payload: event, |
||||
} |
||||
|
||||
baseField := reflect.ValueOf(event).FieldByName("Timestamp") |
||||
if baseField.IsValid() { |
||||
wireEvent.Timestamp = baseField.Interface().(time.Time) |
||||
} else { |
||||
wireEvent.Timestamp = time.Now() |
||||
} |
||||
|
||||
return &wireEvent, nil |
||||
} |
||||
|
||||
type AccountCreated struct { |
||||
Timestamp time.Time `json:"timestamp"` |
||||
Id int64 `json:"id"` |
||||
Name string `json:"name"` |
||||
} |
||||
|
||||
type AccountUpdated struct { |
||||
Timestamp time.Time `json:"timestamp"` |
||||
Id int64 `json:"id"` |
||||
Name string `json:"name"` |
||||
} |
||||
|
||||
type UserCreated struct { |
||||
Timestamp time.Time `json:"timestamp"` |
||||
Id int64 `json:"id"` |
||||
Name string `json:"name"` |
||||
Login string `json:"login"` |
||||
Email string `json:"email"` |
||||
} |
||||
|
||||
type UserUpdated struct { |
||||
Timestamp time.Time `json:"timestamp"` |
||||
Id int64 `json:"id"` |
||||
Name string `json:"name"` |
||||
Login string `json:"login"` |
||||
Email string `json:"email"` |
||||
} |
@ -0,0 +1,30 @@ |
||||
package events |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"testing" |
||||
"time" |
||||
|
||||
. "github.com/smartystreets/goconvey/convey" |
||||
) |
||||
|
||||
type TestEvent struct { |
||||
Timestamp time.Time |
||||
} |
||||
|
||||
func TestEventCreation(t *testing.T) { |
||||
|
||||
Convey("Event to wire event", t, func() { |
||||
e := TestEvent{ |
||||
Timestamp: time.Unix(1231421123, 223), |
||||
} |
||||
|
||||
wire, _ := ToOnWriteEvent(e) |
||||
So(e.Timestamp.Unix(), ShouldEqual, wire.Timestamp.Unix()) |
||||
So(wire.EventType, ShouldEqual, "TestEvent") |
||||
|
||||
json, _ := json.Marshal(wire) |
||||
So(string(json), ShouldEqual, `{"event_type":"TestEvent","priority":"INFO","timestamp":"2009-01-08T14:25:23.000000223+01:00","payload":{"Timestamp":"2009-01-08T14:25:23.000000223+01:00"}}`) |
||||
}) |
||||
|
||||
} |
@ -0,0 +1,149 @@ |
||||
package eventpublisher |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"log" |
||||
"time" |
||||
|
||||
"github.com/streadway/amqp" |
||||
"github.com/torkelo/grafana-pro/pkg/bus" |
||||
"github.com/torkelo/grafana-pro/pkg/events" |
||||
"github.com/torkelo/grafana-pro/pkg/setting" |
||||
) |
||||
|
||||
var ( |
||||
url string |
||||
exchange string |
||||
conn *amqp.Connection |
||||
channel *amqp.Channel |
||||
) |
||||
|
||||
func getConnection() (*amqp.Connection, error) { |
||||
c, err := amqp.Dial(url) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return c, err |
||||
} |
||||
|
||||
func getChannel() (*amqp.Channel, error) { |
||||
ch, err := conn.Channel() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
err = ch.ExchangeDeclare( |
||||
exchange, // name
|
||||
"topic", // type
|
||||
true, // durable
|
||||
false, // auto-deleted
|
||||
false, // internal
|
||||
false, // no-wait
|
||||
nil, // arguments
|
||||
) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return ch, err |
||||
} |
||||
|
||||
func Init() { |
||||
sec := setting.Cfg.Section("event_publisher") |
||||
|
||||
if !sec.Key("enabled").MustBool(false) { |
||||
return |
||||
} |
||||
|
||||
url = sec.Key("rabbitmq_url").String() |
||||
exchange = sec.Key("exchange").String() |
||||
bus.AddWildcardListener(eventListener) |
||||
|
||||
if err := Setup(); err != nil { |
||||
log.Fatal(4, "Failed to connect to notification queue: %v", err) |
||||
return |
||||
} |
||||
} |
||||
|
||||
// Every connection should declare the topology they expect
|
||||
func Setup() error { |
||||
c, err := getConnection() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
conn = c |
||||
ch, err := getChannel() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
channel = ch |
||||
|
||||
// listen for close events so we can reconnect.
|
||||
errChan := channel.NotifyClose(make(chan *amqp.Error)) |
||||
go func() { |
||||
for e := range errChan { |
||||
fmt.Println("connection to rabbitmq lost.") |
||||
fmt.Println(e) |
||||
fmt.Println("attempting to create new rabbitmq channel.") |
||||
ch, err := getChannel() |
||||
if err == nil { |
||||
channel = ch |
||||
break |
||||
} |
||||
|
||||
//could not create channel, so lets close the connection
|
||||
// and re-create.
|
||||
_ = conn.Close() |
||||
|
||||
for err != nil { |
||||
time.Sleep(2 * time.Second) |
||||
fmt.Println("attempting to reconnect to rabbitmq.") |
||||
err = Setup() |
||||
} |
||||
fmt.Println("Connected to rabbitmq again.") |
||||
} |
||||
}() |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func publish(routingKey string, msgString []byte) { |
||||
err := channel.Publish( |
||||
exchange, //exchange
|
||||
routingKey, // routing key
|
||||
false, // mandatory
|
||||
false, // immediate
|
||||
amqp.Publishing{ |
||||
ContentType: "application/json", |
||||
Body: msgString, |
||||
}, |
||||
) |
||||
if err != nil { |
||||
// failures are most likely because the connection was lost.
|
||||
// the connection will be re-established, so just keep
|
||||
// retrying every 2seconds until we successfully publish.
|
||||
time.Sleep(2 * time.Second) |
||||
fmt.Println("publish failed, retrying.") |
||||
publish(routingKey, msgString) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func eventListener(event interface{}) error { |
||||
wireEvent, err := events.ToOnWriteEvent(event) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
msgString, err := json.Marshal(wireEvent) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
routingKey := fmt.Sprintf("%s.%s", wireEvent.Priority, wireEvent.EventType) |
||||
// this is run in a greenthread and we expect that publish will keep
|
||||
// retrying until the message gets sent.
|
||||
go publish(routingKey, msgString) |
||||
return nil |
||||
} |
@ -0,0 +1,71 @@ |
||||
package sqlstore |
||||
|
||||
import ( |
||||
"github.com/go-xorm/xorm" |
||||
"github.com/torkelo/grafana-pro/pkg/bus" |
||||
"github.com/torkelo/grafana-pro/pkg/log" |
||||
) |
||||
|
||||
type dbTransactionFunc func(sess *xorm.Session) error |
||||
type dbTransactionFunc2 func(sess *session) error |
||||
|
||||
type session struct { |
||||
*xorm.Session |
||||
events []interface{} |
||||
} |
||||
|
||||
func (sess *session) publishAfterCommit(msg interface{}) { |
||||
sess.events = append(sess.events, msg) |
||||
} |
||||
|
||||
func inTransaction(callback dbTransactionFunc) error { |
||||
var err error |
||||
|
||||
sess := x.NewSession() |
||||
defer sess.Close() |
||||
|
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
err = callback(sess) |
||||
|
||||
if err != nil { |
||||
sess.Rollback() |
||||
return err |
||||
} else if err = sess.Commit(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func inTransaction2(callback dbTransactionFunc2) error { |
||||
var err error |
||||
|
||||
sess := session{Session: x.NewSession()} |
||||
|
||||
defer sess.Close() |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
err = callback(&sess) |
||||
|
||||
if err != nil { |
||||
sess.Rollback() |
||||
return err |
||||
} else if err = sess.Commit(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if len(sess.events) > 0 { |
||||
for _, e := range sess.events { |
||||
if err = bus.Publish(e); err != nil { |
||||
log.Error(3, "Failed to publish event after commit", err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
Loading…
Reference in new issue