2021-02-12 10:21:51 +00:00
|
|
|
// Info: The idea about the ring buffer is that we have a FIFO
|
|
|
|
// buffer where we store all incomming messages requested by
|
|
|
|
// operators. Each message processed will also be stored in a DB.
|
|
|
|
//
|
|
|
|
// Idea: All incomming messages should be handled from the in-memory
|
|
|
|
// buffered channel, but when they are put on the buffer they should
|
|
|
|
// also be written to the DB with a handled flag set to false.
|
|
|
|
// When a message have left the buffer the handled flag should be
|
|
|
|
// set to true.
|
|
|
|
package steward
|
|
|
|
|
2021-02-15 10:28:27 +00:00
|
|
|
import (
|
2021-02-16 03:57:54 +00:00
|
|
|
"encoding/json"
|
2021-02-15 10:28:27 +00:00
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"strconv"
|
2021-02-16 13:29:32 +00:00
|
|
|
"sync"
|
2021-02-15 10:28:27 +00:00
|
|
|
|
|
|
|
bolt "go.etcd.io/bbolt"
|
|
|
|
)
|
|
|
|
|
|
|
|
// samValue represents one message with a subject. This
|
|
|
|
// struct type is used when storing and retreiving from
|
|
|
|
// db.
|
|
|
|
type samDBValue struct {
|
|
|
|
ID int
|
|
|
|
Data subjectAndMessage
|
|
|
|
}
|
2021-02-12 10:21:51 +00:00
|
|
|
|
|
|
|
// ringBuffer holds the data of the buffer,
|
|
|
|
type ringBuffer struct {
|
2021-02-16 13:29:32 +00:00
|
|
|
bufData chan samDBValue
|
2021-02-16 03:57:54 +00:00
|
|
|
db *bolt.DB
|
|
|
|
totalMessagesIndex int
|
2021-02-16 13:29:32 +00:00
|
|
|
mu sync.Mutex
|
2021-02-12 10:21:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// newringBuffer is a push/pop storage for values.
|
|
|
|
func newringBuffer(size int) *ringBuffer {
|
2021-02-15 10:28:27 +00:00
|
|
|
db, err := bolt.Open("./incommmingBuffer.db", 0600, nil)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error: failed to open db: %v\n", err)
|
|
|
|
}
|
2021-02-12 10:21:51 +00:00
|
|
|
return &ringBuffer{
|
2021-02-16 13:29:32 +00:00
|
|
|
bufData: make(chan samDBValue, size),
|
2021-02-15 10:28:27 +00:00
|
|
|
db: db,
|
2021-02-12 10:21:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// start will process incomming messages through the inCh,
|
2021-02-15 10:28:27 +00:00
|
|
|
// put the messages on a buffered channel
|
2021-02-12 10:21:51 +00:00
|
|
|
// and deliver messages out when requested on the outCh.
|
2021-02-16 13:29:32 +00:00
|
|
|
func (r *ringBuffer) start(inCh chan subjectAndMessage, outCh chan samDBValue) {
|
2021-02-12 10:21:51 +00:00
|
|
|
// Starting both writing and reading in separate go routines so we
|
|
|
|
// can write and read concurrently.
|
|
|
|
|
2021-02-16 03:57:54 +00:00
|
|
|
const samValueBucket string = "samValueBucket"
|
|
|
|
const indexValueBucket string = "indexValueBucket"
|
2021-02-15 10:28:27 +00:00
|
|
|
|
2021-02-16 03:57:54 +00:00
|
|
|
r.totalMessagesIndex = r.getIndexValue(indexValueBucket)
|
2021-02-15 10:28:27 +00:00
|
|
|
|
2021-02-12 10:21:51 +00:00
|
|
|
// Fill the buffer when new data arrives
|
2021-02-16 11:59:37 +00:00
|
|
|
go r.fillBuffer(inCh, samValueBucket, indexValueBucket)
|
2021-02-15 10:28:27 +00:00
|
|
|
|
2021-02-16 11:59:37 +00:00
|
|
|
go r.processBufferMessages(samValueBucket, outCh)
|
|
|
|
}
|
2021-02-15 10:28:27 +00:00
|
|
|
|
2021-02-16 11:59:37 +00:00
|
|
|
// fillBuffer will fill the buffer in the ringbuffer reading from the inchannel.
|
|
|
|
// It will also store the messages in a K/V DB while being processed.
|
|
|
|
func (r *ringBuffer) fillBuffer(inCh chan subjectAndMessage, samValueBucket string, indexValueBucket string) {
|
|
|
|
// Check for incomming messages. These are typically comming from
|
|
|
|
// the go routine who reads inmsg.txt.
|
|
|
|
for v := range inCh {
|
|
|
|
// --- Store the incomming message in the k/v store ---
|
|
|
|
|
2021-02-16 13:29:32 +00:00
|
|
|
// Get a unique number for the message to use when storing
|
|
|
|
// it in the databases, and also use when further processing.
|
|
|
|
r.mu.Lock()
|
|
|
|
dbID := r.totalMessagesIndex
|
|
|
|
r.mu.Unlock()
|
|
|
|
|
2021-02-16 11:59:37 +00:00
|
|
|
// Create a structure for JSON marshaling.
|
|
|
|
samV := samDBValue{
|
2021-02-16 13:29:32 +00:00
|
|
|
ID: dbID,
|
2021-02-16 11:59:37 +00:00
|
|
|
Data: v,
|
|
|
|
}
|
2021-02-15 10:28:27 +00:00
|
|
|
|
2021-02-16 11:59:37 +00:00
|
|
|
js, err := json.Marshal(samV)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error: gob encoding samValue: %v\n", err)
|
|
|
|
}
|
2021-02-15 10:28:27 +00:00
|
|
|
|
2021-02-16 11:59:37 +00:00
|
|
|
// Store the incomming message in key/value store
|
2021-02-16 13:29:32 +00:00
|
|
|
err = r.dbUpdate(r.db, samValueBucket, strconv.Itoa(dbID), js)
|
2021-02-16 11:59:37 +00:00
|
|
|
if err != nil {
|
|
|
|
// TODO: Handle error
|
|
|
|
log.Printf("error: dbUpdate samValue failed: %v\n", err)
|
2021-02-12 10:21:51 +00:00
|
|
|
}
|
2021-02-15 10:28:27 +00:00
|
|
|
|
2021-02-16 11:59:37 +00:00
|
|
|
// Send the message to some process to consume it.
|
2021-02-16 13:29:32 +00:00
|
|
|
r.bufData <- samV
|
2021-02-16 11:59:37 +00:00
|
|
|
|
|
|
|
// Increment index, and store the new value to the database.
|
2021-02-16 13:29:32 +00:00
|
|
|
r.mu.Lock()
|
2021-02-16 11:59:37 +00:00
|
|
|
r.totalMessagesIndex++
|
|
|
|
fmt.Printf("*** NEXT INDEX NUMBER INCREMENTED: %v\n", r.totalMessagesIndex)
|
2021-02-16 13:29:32 +00:00
|
|
|
r.mu.Unlock()
|
|
|
|
|
2021-02-16 11:59:37 +00:00
|
|
|
fmt.Println("---------------------------------------------------------")
|
2021-02-16 13:29:32 +00:00
|
|
|
r.dbUpdate(r.db, indexValueBucket, "index", []byte(strconv.Itoa(dbID)))
|
2021-02-16 11:59:37 +00:00
|
|
|
}
|
2021-02-12 10:21:51 +00:00
|
|
|
|
2021-02-16 11:59:37 +00:00
|
|
|
// When done close the buffer channel
|
|
|
|
close(r.bufData)
|
|
|
|
}
|
2021-02-16 11:29:15 +00:00
|
|
|
|
2021-02-16 11:59:37 +00:00
|
|
|
// processBufferMessages will pick messages from the buffer, and process them
|
|
|
|
// one by one. The messages will be delivered on the outCh, and it will wait
|
|
|
|
// until a signal is received on the done channel before it continues with the
|
|
|
|
// next message.
|
2021-02-16 13:29:32 +00:00
|
|
|
func (r *ringBuffer) processBufferMessages(samValueBucket string, outCh chan samDBValue) {
|
2021-02-16 11:59:37 +00:00
|
|
|
// Range over the buffer of messages to pass on to processes.
|
|
|
|
for v := range r.bufData {
|
|
|
|
// Create a done channel per message. A process started by the
|
|
|
|
// spawnProcess function will handle incomming messages sequentaly.
|
|
|
|
// So in the spawnProcess function we put a struct{} value when a
|
|
|
|
// message is processed on the "done" channel and an ack is received
|
|
|
|
// for a message, and we wait here for the "done" to be received.
|
|
|
|
|
|
|
|
// We start the actual processing of an individual message here within
|
|
|
|
// it's own go routine. Reason is that we don't want to block other
|
|
|
|
// messages being blocked while waiting for the done signal, or if an
|
|
|
|
// error with an individual message occurs.
|
2021-02-16 13:29:32 +00:00
|
|
|
go func(v samDBValue) {
|
|
|
|
v.Data.Message.done = make(chan struct{})
|
2021-02-12 10:21:51 +00:00
|
|
|
outCh <- v
|
2021-02-16 03:57:54 +00:00
|
|
|
|
2021-02-16 11:29:15 +00:00
|
|
|
// ----------TESTING
|
2021-02-16 13:29:32 +00:00
|
|
|
<-v.Data.done
|
2021-02-16 11:29:15 +00:00
|
|
|
fmt.Println("-----------------------------------------------------------")
|
|
|
|
fmt.Printf("### DONE WITH THE MESSAGE\n")
|
|
|
|
fmt.Println("-----------------------------------------------------------")
|
|
|
|
|
2021-02-16 13:29:32 +00:00
|
|
|
r.deleteKeyFromBucket(samValueBucket, strconv.Itoa(v.ID))
|
|
|
|
fmt.Printf("******* DELETED KEY %v FROM BUCKET*******", v.ID)
|
|
|
|
|
2021-02-16 03:57:54 +00:00
|
|
|
// TODO: Delete the messages here. The SAM handled here, do
|
|
|
|
// not contain the totalMessageID, so we might need to change
|
|
|
|
// the struct we pass around.
|
|
|
|
// IDEA: Add a go routine for each message handled here, and include
|
|
|
|
// a done channel in the structure, so a go routine handling the
|
|
|
|
// message will be able to signal back here that the message have
|
|
|
|
// been processed, and that we then can delete it out of the K/V Store.
|
2021-02-16 05:43:09 +00:00
|
|
|
|
|
|
|
// Dump the whole KV store
|
2021-02-16 11:29:15 +00:00
|
|
|
err := r.dumpBucket(samValueBucket)
|
2021-02-16 05:43:09 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("* Error: dump of db failed: %v\n", err)
|
|
|
|
}
|
2021-02-16 11:59:37 +00:00
|
|
|
}(v)
|
2021-02-16 05:43:09 +00:00
|
|
|
|
2021-02-16 11:59:37 +00:00
|
|
|
}
|
2021-02-12 10:21:51 +00:00
|
|
|
|
2021-02-16 11:59:37 +00:00
|
|
|
close(outCh)
|
2021-02-12 10:21:51 +00:00
|
|
|
}
|
2021-02-15 10:28:27 +00:00
|
|
|
|
2021-02-16 11:29:15 +00:00
|
|
|
func (r *ringBuffer) dumpBucket(bucket string) error {
|
2021-02-16 05:43:09 +00:00
|
|
|
err := r.db.View(func(tx *bolt.Tx) error {
|
|
|
|
bu := tx.Bucket([]byte(bucket))
|
|
|
|
|
|
|
|
fmt.Println("--------------------------DUMP---------------------------")
|
|
|
|
bu.ForEach(func(k, v []byte) error {
|
|
|
|
var vv samDBValue
|
|
|
|
err := json.Unmarshal(v, &vv)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error: dumpBucket json.Umarshal failed: %v\n", err)
|
|
|
|
}
|
|
|
|
fmt.Printf("k: %s, v: %v\n", k, vv)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2021-02-16 13:29:32 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *ringBuffer) deleteKeyFromBucket(bucket string, key string) error {
|
|
|
|
err := r.db.Update(func(tx *bolt.Tx) error {
|
|
|
|
bu := tx.Bucket([]byte(bucket))
|
|
|
|
|
|
|
|
err := bu.Delete([]byte(key))
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error: delete key in bucket %v failed: %v\n", bucket, err)
|
|
|
|
}
|
2021-02-16 05:43:09 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-02-16 03:57:54 +00:00
|
|
|
func (r *ringBuffer) getIndexValue(indexBucket string) int {
|
|
|
|
const indexKey string = "index"
|
|
|
|
indexB, err := r.dbView(r.db, indexBucket, indexKey)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error: getIndexValue: dbView: %v\n", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
index, err := strconv.Atoi(string(indexB))
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error: getIndexValue: strconv.Atoi : %v\n", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf("**** RETURNING INDEX, WITH VALUE = %v\n", index)
|
|
|
|
|
|
|
|
return index
|
|
|
|
}
|
|
|
|
|
2021-02-15 10:28:27 +00:00
|
|
|
func (r *ringBuffer) dbView(db *bolt.DB, bucket string, key string) ([]byte, error) {
|
|
|
|
var value []byte
|
|
|
|
//View is a help function to get values out of the database.
|
|
|
|
err := db.View(func(tx *bolt.Tx) error {
|
|
|
|
//Open a bucket to get key's and values from.
|
|
|
|
bu := tx.Bucket([]byte(bucket))
|
2021-02-16 03:57:54 +00:00
|
|
|
if bu == nil {
|
|
|
|
log.Printf("info: no such bucket exist: %v\n", bucket)
|
|
|
|
return nil
|
|
|
|
}
|
2021-02-15 10:28:27 +00:00
|
|
|
|
|
|
|
v := bu.Get([]byte(key))
|
|
|
|
if len(v) == 0 {
|
2021-02-16 03:57:54 +00:00
|
|
|
log.Printf("info: view: key not found\n")
|
|
|
|
return nil
|
2021-02-15 10:28:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
value = v
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return value, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//dbUpdate will update the specified bucket with a key and value.
|
|
|
|
func (r *ringBuffer) dbUpdate(db *bolt.DB, bucket string, key string, value []byte) error {
|
|
|
|
err := db.Update(func(tx *bolt.Tx) error {
|
|
|
|
//Create a bucket
|
|
|
|
bu, err := tx.CreateBucketIfNotExists([]byte(bucket))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error: CreateBuckerIfNotExists failed: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
//Put a value into the bucket.
|
|
|
|
if err := bu.Put([]byte(key), []byte(value)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
//If all was ok, we should return a nil for a commit to happen. Any error
|
|
|
|
// returned will do a rollback.
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|