2020-09-28 14:13:10 +00:00
|
|
|
package stream
|
|
|
|
|
|
|
|
import (
|
2021-02-11 15:40:59 +00:00
|
|
|
"bytes"
|
2020-09-28 14:13:10 +00:00
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
2020-10-04 19:12:35 +00:00
|
|
|
|
2021-02-11 15:40:59 +00:00
|
|
|
"github.com/hashicorp/go-msgpack/codec"
|
|
|
|
|
2020-10-04 19:12:35 +00:00
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
2020-09-28 14:13:10 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2020-10-08 18:27:52 +00:00
|
|
|
// JsonHeartbeat is an empty JSON object to send as a heartbeat
|
2020-09-28 14:13:10 +00:00
|
|
|
// Avoids creating many heartbeat instances
|
2020-10-08 18:27:52 +00:00
|
|
|
JsonHeartbeat = &structs.EventJson{Data: []byte("{}")}
|
2020-09-28 14:13:10 +00:00
|
|
|
)
|
|
|
|
|
2020-10-08 18:27:52 +00:00
|
|
|
// JsonStream is used to send new line delimited JSON and heartbeats
|
2020-09-28 14:13:10 +00:00
|
|
|
// to a destination (out channel)
|
2020-10-08 18:27:52 +00:00
|
|
|
type JsonStream struct {
|
|
|
|
// ctx is a passed in context used to notify the json stream
|
|
|
|
// when it should terminate
|
|
|
|
ctx context.Context
|
|
|
|
|
|
|
|
outCh chan *structs.EventJson
|
2020-09-28 14:13:10 +00:00
|
|
|
|
|
|
|
// heartbeat is the interval to send heartbeat messages to keep a connection
|
|
|
|
// open.
|
2020-10-08 18:27:52 +00:00
|
|
|
heartbeatTick *time.Ticker
|
2020-09-28 14:13:10 +00:00
|
|
|
}
|
|
|
|
|
2020-10-08 18:27:52 +00:00
|
|
|
// NewJsonStream creates a new json stream that will output Json structs
|
|
|
|
// to the passed output channel. The constructor starts a goroutine
|
2021-05-21 17:17:07 +00:00
|
|
|
// to begin heartbeating on its set interval and also sends an initial heartbeat
|
|
|
|
// to notify the client about the successful connection initialization.
|
2020-10-08 18:27:52 +00:00
|
|
|
func NewJsonStream(ctx context.Context, heartbeat time.Duration) *JsonStream {
|
|
|
|
s := &JsonStream{
|
|
|
|
ctx: ctx,
|
|
|
|
outCh: make(chan *structs.EventJson, 10),
|
|
|
|
heartbeatTick: time.NewTicker(heartbeat),
|
2020-09-28 14:13:10 +00:00
|
|
|
}
|
|
|
|
|
2021-05-21 17:17:07 +00:00
|
|
|
s.outCh <- JsonHeartbeat
|
2020-10-08 18:27:52 +00:00
|
|
|
go s.heartbeat()
|
2020-09-28 14:13:10 +00:00
|
|
|
|
2020-10-08 18:27:52 +00:00
|
|
|
return s
|
2020-09-28 14:13:10 +00:00
|
|
|
}
|
|
|
|
|
2020-10-08 18:27:52 +00:00
|
|
|
func (n *JsonStream) OutCh() chan *structs.EventJson {
|
|
|
|
return n.outCh
|
|
|
|
}
|
2020-09-28 14:13:10 +00:00
|
|
|
|
2020-10-08 18:27:52 +00:00
|
|
|
func (n *JsonStream) heartbeat() {
|
2020-09-28 14:13:10 +00:00
|
|
|
for {
|
|
|
|
select {
|
2020-10-08 18:27:52 +00:00
|
|
|
case <-n.ctx.Done():
|
2020-09-28 14:13:10 +00:00
|
|
|
return
|
2020-10-08 18:27:52 +00:00
|
|
|
case <-n.heartbeatTick.C:
|
2020-09-28 14:13:10 +00:00
|
|
|
// Send a heartbeat frame
|
|
|
|
select {
|
2020-10-08 18:27:52 +00:00
|
|
|
case n.outCh <- JsonHeartbeat:
|
|
|
|
case <-n.ctx.Done():
|
2020-09-28 14:13:10 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send encodes an object into Newline delimited json. An error is returned
|
|
|
|
// if json encoding fails or if the stream is no longer running.
|
2020-10-08 18:27:52 +00:00
|
|
|
func (n *JsonStream) Send(v interface{}) error {
|
|
|
|
if n.ctx.Err() != nil {
|
|
|
|
return n.ctx.Err()
|
|
|
|
}
|
2020-09-28 14:13:10 +00:00
|
|
|
|
2021-02-11 15:40:59 +00:00
|
|
|
var buf bytes.Buffer
|
2021-04-02 13:31:10 +00:00
|
|
|
enc := codec.NewEncoder(&buf, structs.JsonHandleWithExtensions)
|
2021-02-11 15:40:59 +00:00
|
|
|
err := enc.Encode(v)
|
2020-10-08 18:27:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error marshaling json for stream: %w", err)
|
2020-09-28 14:13:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
2020-10-08 18:27:52 +00:00
|
|
|
case <-n.ctx.Done():
|
|
|
|
return fmt.Errorf("error stream is no longer running: %w", err)
|
2021-02-11 15:40:59 +00:00
|
|
|
case n.outCh <- &structs.EventJson{Data: buf.Bytes()}:
|
2020-09-28 14:13:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|