2018-01-29 19:44:18 +00:00
|
|
|
// Copyright (c) 2016 Arista Networks, Inc.
|
|
|
|
// Use of this source code is governed by the Apache License 2.0
|
|
|
|
// that can be found in the COPYING file.
|
|
|
|
|
|
|
|
package producer
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/aristanetworks/goarista/kafka"
|
|
|
|
"github.com/aristanetworks/goarista/kafka/openconfig"
|
|
|
|
|
|
|
|
"github.com/Shopify/sarama"
|
|
|
|
"github.com/aristanetworks/glog"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Producer forwards messages recvd on a channel to kafka.
|
|
|
|
type Producer interface {
|
|
|
|
Start()
|
|
|
|
Write(proto.Message)
|
|
|
|
Stop()
|
|
|
|
}
|
|
|
|
|
|
|
|
type producer struct {
|
|
|
|
notifsChan chan proto.Message
|
|
|
|
kafkaProducer sarama.AsyncProducer
|
|
|
|
encoder kafka.MessageEncoder
|
|
|
|
done chan struct{}
|
|
|
|
wg sync.WaitGroup
|
|
|
|
}
|
|
|
|
|
|
|
|
// New creates new Kafka producer
|
|
|
|
func New(encoder kafka.MessageEncoder,
|
|
|
|
kafkaAddresses []string, kafkaConfig *sarama.Config) (Producer, error) {
|
|
|
|
|
|
|
|
if kafkaConfig == nil {
|
|
|
|
kafkaConfig := sarama.NewConfig()
|
|
|
|
hostname, err := os.Hostname()
|
|
|
|
if err != nil {
|
|
|
|
hostname = ""
|
|
|
|
}
|
|
|
|
kafkaConfig.ClientID = hostname
|
|
|
|
kafkaConfig.Producer.Compression = sarama.CompressionSnappy
|
|
|
|
kafkaConfig.Producer.Return.Successes = true
|
|
|
|
kafkaConfig.Producer.RequiredAcks = sarama.WaitForAll
|
|
|
|
}
|
|
|
|
|
|
|
|
kafkaProducer, err := sarama.NewAsyncProducer(kafkaAddresses, kafkaConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
p := &producer{
|
|
|
|
notifsChan: make(chan proto.Message),
|
|
|
|
kafkaProducer: kafkaProducer,
|
|
|
|
encoder: encoder,
|
|
|
|
done: make(chan struct{}),
|
|
|
|
wg: sync.WaitGroup{},
|
|
|
|
}
|
|
|
|
return p, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start makes producer to start processing writes.
|
|
|
|
// This method is non-blocking.
|
|
|
|
func (p *producer) Start() {
|
|
|
|
p.wg.Add(3)
|
|
|
|
go p.handleSuccesses()
|
|
|
|
go p.handleErrors()
|
|
|
|
go p.run()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *producer) run() {
|
|
|
|
defer p.wg.Done()
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case batch, open := <-p.notifsChan:
|
|
|
|
if !open {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err := p.produceNotifications(batch)
|
|
|
|
if err != nil {
|
|
|
|
if _, ok := err.(openconfig.UnhandledSubscribeResponseError); !ok {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case <-p.done:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *producer) Write(msg proto.Message) {
|
|
|
|
select {
|
|
|
|
case p.notifsChan <- msg:
|
|
|
|
case <-p.done:
|
|
|
|
// TODO: This should probably return an EOF error, but that
|
|
|
|
// would change the API
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *producer) Stop() {
|
|
|
|
close(p.done)
|
|
|
|
p.wg.Wait()
|
|
|
|
p.kafkaProducer.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *producer) produceNotifications(protoMessage proto.Message) error {
|
|
|
|
messages, err := p.encoder.Encode(protoMessage)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, m := range messages {
|
|
|
|
select {
|
|
|
|
case <-p.done:
|
|
|
|
return nil
|
|
|
|
case p.kafkaProducer.Input() <- m:
|
2018-09-05 15:36:14 +00:00
|
|
|
glog.V(9).Infof("Message produced to Kafka: %v", m)
|
2018-01-29 19:44:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleSuccesses reads from the producer's successes channel and collects some
|
|
|
|
// information for monitoring
|
|
|
|
func (p *producer) handleSuccesses() {
|
|
|
|
defer p.wg.Done()
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case msg, open := <-p.kafkaProducer.Successes():
|
|
|
|
if !open {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p.encoder.HandleSuccess(msg)
|
|
|
|
case <-p.done:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleErrors reads from the producer's errors channel and collects some information
|
|
|
|
// for monitoring
|
|
|
|
func (p *producer) handleErrors() {
|
|
|
|
defer p.wg.Done()
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case msg, open := <-p.kafkaProducer.Errors():
|
|
|
|
if !open {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p.encoder.HandleError(msg)
|
|
|
|
case <-p.done:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|