1
0
Fork 0
mirror of https://github.com/postmannen/ctrl.git synced 2024-12-14 12:37:31 +00:00

refactored, and implemented error messages to central

This commit is contained in:
postmannen 2021-02-24 15:43:31 +01:00
parent f812c4307e
commit 61e2343f18
9 changed files with 174 additions and 85 deletions

View file

@ -16,6 +16,7 @@ func main() {
brokerAddress := flag.String("brokerAddress", "0", "the address of the message broker") brokerAddress := flag.String("brokerAddress", "0", "the address of the message broker")
profilingPort := flag.String("profilingPort", "", "The number of the profiling port") profilingPort := flag.String("profilingPort", "", "The number of the profiling port")
promHostAndPort := flag.String("promHostAndPort", ":2112", "host and port for prometheus listener, e.g. localhost:2112") promHostAndPort := flag.String("promHostAndPort", ":2112", "host and port for prometheus listener, e.g. localhost:2112")
centralErrorLogger := flag.Bool("centralErrorLogger", false, "seet to true if this is the node that should receive the error log's from other nodes")
//isCentral := flag.Bool("isCentral", false, "used to indicate that this is the central master that will subscribe to error message subjects") //isCentral := flag.Bool("isCentral", false, "used to indicate that this is the central master that will subscribe to error message subjects")
flag.Parse() flag.Parse()
@ -28,7 +29,7 @@ func main() {
} }
s, err := steward.NewServer(*brokerAddress, *nodeName, *promHostAndPort) s, err := steward.NewServer(*brokerAddress, *nodeName, *promHostAndPort, *centralErrorLogger)
if err != nil { if err != nil {
log.Printf("error: failed to connect to broker: %v\n", err) log.Printf("error: failed to connect to broker: %v\n", err)
os.Exit(1) os.Exit(1)

View file

@ -40,7 +40,7 @@ func newErrorKernel() *errorKernel {
// process if it should continue or not based not based on how severe // process if it should continue or not based not based on how severe
// the error where. This should be right after sending the error // the error where. This should be right after sending the error
// sending in the process. // sending in the process.
func (e *errorKernel) startErrorKernel() { func (e *errorKernel) startErrorKernel(newMessagesCh chan<- []subjectAndMessage) {
// TODO: For now it will just print the error messages to the // TODO: For now it will just print the error messages to the
// console. // console.
go func() { go func() {
@ -63,6 +63,24 @@ func (e *errorKernel) startErrorKernel() {
// log.Printf("*** error_kernel: %#v, type=%T\n", er, er) // log.Printf("*** error_kernel: %#v, type=%T\n", er, er)
log.Printf("TESTING, we received and error from the process, but we're telling the process back to continue\n") log.Printf("TESTING, we received and error from the process, but we're telling the process back to continue\n")
// // TESTING: Creating an error message to send to errorCentral
// fmt.Printf(" --- Sending error message to central !!!!!!!!!!!!!!!!!!!!!!!!!!!!\n")
// sam := subjectAndMessage{
// Subject: Subject{
// ToNode: "errorCentral",
// CommandOrEvent: EventNACK,
// Method: ErrorLog,
// },
// Message: Message{
// ToNode: "errorCentral",
// Data: []string{"some tull here .............."},
// CommandOrEvent: EventNACK,
// Method: ErrorLog,
// },
// }
// newMessagesCh <- []subjectAndMessage{sam}
er.errorActionCh <- errActionContinue er.errorActionCh <- errActionContinue
}() }()
} }

Binary file not shown.

78
message-and-subject.go Normal file
View file

@ -0,0 +1,78 @@
package steward
import (
"bytes"
"encoding/gob"
"fmt"
)
// --- Message
type Message struct {
ToNode node `json:"toNode" yaml:"toNode"`
// The Unique ID of the message
ID int `json:"id" yaml:"id"`
// The actual data in the message
// TODO: Change this to a slice instead...or maybe use an
// interface type here to handle several data types ?
Data []string `json:"data" yaml:"data"`
// The type of the message being sent
CommandOrEvent CommandOrEvent `json:"commandOrEvent" yaml:"commandOrEvent"`
// method, what is this message doing, etc. shellCommand, syslog, etc.
Method Method `json:"method" yaml:"method"`
FromNode node
// done is used to signal when a message is fully processed.
// This is used when choosing when to move the message from
// the ringbuffer into the time series log.
done chan struct{}
}
// gobEncodePayload will encode the message structure along with its
// valued in gob binary format.
// TODO: Check if it adds value to compress with gzip.
func gobEncodeMessage(m Message) ([]byte, error) {
var buf bytes.Buffer
gobEnc := gob.NewEncoder(&buf)
err := gobEnc.Encode(m)
if err != nil {
return nil, fmt.Errorf("error: gob.Encode failed: %v", err)
}
return buf.Bytes(), nil
}
// --- Subject
type node string
// subject contains the representation of a subject to be used with one
// specific process
type Subject struct {
// node, the name of the node
ToNode string `json:"node" yaml:"toNode"`
// messageType, command/event
CommandOrEvent CommandOrEvent `json:"commandOrEvent" yaml:"commandOrEvent"`
// method, what is this message doing, etc. shellCommand, syslog, etc.
Method Method `json:"method" yaml:"method"`
// messageCh is the channel for receiving new content to be sent
messageCh chan Message
}
// newSubject will return a new variable of the type subject, and insert
// all the values given as arguments. It will also create the channel
// to receive new messages on the specific subject.
func newSubject(method Method, commandOrEvent CommandOrEvent, node string) Subject {
return Subject{
ToNode: node,
CommandOrEvent: commandOrEvent,
Method: method,
messageCh: make(chan Message),
}
}
// subjectName is the complete representation of a subject
type subjectName string
func (s Subject) name() subjectName {
return subjectName(fmt.Sprintf("%s.%s.%s", s.Method, s.CommandOrEvent, s.ToNode))
}

View file

@ -36,7 +36,7 @@ func (s *server) processNewMessages(dbFileName string, newSAM chan []subjectAndM
}() }()
// Process the messages that are in the ring buffer. Check and // Process the messages that are in the ring buffer. Check and
// send if there are a specific subject for it, and no subject // send if there are a specific subject for it, and if no subject
// exist throw an error. // exist throw an error.
go func() { go func() {
for samTmp := range ringBufferOutCh { for samTmp := range ringBufferOutCh {
@ -46,9 +46,11 @@ func (s *server) processNewMessages(dbFileName string, newSAM chan []subjectAndM
// it was unable to process the message with the reason // it was unable to process the message with the reason
// why ? // why ?
if _, ok := s.methodsAvailable.CheckIfExists(sam.Message.Method); !ok { if _, ok := s.methodsAvailable.CheckIfExists(sam.Message.Method); !ok {
log.Printf("error: the method do not exist: %v\n", sam.Message.Method)
continue continue
} }
if !s.commandOrEventAvailable.CheckIfExists(sam.Message.CommandOrEvent) { if !s.commandOrEventAvailable.CheckIfExists(sam.Message.CommandOrEvent) {
log.Printf("error: the command or evnt do not exist: %v\n", sam.Message.CommandOrEvent)
continue continue
} }
@ -72,7 +74,7 @@ func (s *server) processNewMessages(dbFileName string, newSAM chan []subjectAndM
// If no process to handle the specific subject exist, // If no process to handle the specific subject exist,
// the we create and spawn one. // the we create and spawn one.
} else { } else {
// If a publisher do not exist for the given subject, create it, and // If a publisher process do not exist for the given subject, create it, and
// by using the goto at the end redo the process for this specific message. // by using the goto at the end redo the process for this specific message.
log.Printf("info: did not find that specific subject, starting new process for subject: %v\n", subjName) log.Printf("info: did not find that specific subject, starting new process for subject: %v\n", subjName)
@ -96,7 +98,7 @@ func (s *server) publishMessages(proc process) {
// Wait and read the next message on the message channel // Wait and read the next message on the message channel
m := <-proc.subject.messageCh m := <-proc.subject.messageCh
m.ID = s.processes[proc.subject.name()].messageID m.ID = s.processes[proc.subject.name()].messageID
messageDeliver(proc, m, s.natsConn) s.messageDeliverNats(proc, m)
m.done <- struct{}{} m.done <- struct{}{}
// Increment the counter for the next message to be sent. // Increment the counter for the next message to be sent.

View file

@ -185,8 +185,10 @@ func (r *ringBuffer) processBufferMessages(samValueBucket string, outCh chan sam
// Listen on the done channel here , so a go routine handling the // Listen on the done channel here , so a go routine handling the
// message will be able to signal back here that the message have // 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. // been processed, and that we then can delete it out of the K/V Store.
fmt.Printf("#.#.#.#.#.#.#.# Before DONE: %v\n", v)
<-v.Data.done <-v.Data.done
log.Printf("info: done with message %v\n", v.ID) log.Printf("info: done with message %v\n", v.ID)
fmt.Printf("#.#.#.#.#.#.#.# Got DONE: %v\n", v)
// Since we are now done with the specific message we can delete // Since we are now done with the specific message we can delete
// it out of the K/V Store. // it out of the K/V Store.

123
server.go
View file

@ -14,25 +14,6 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )
type Message struct {
ToNode node `json:"toNode" yaml:"toNode"`
// The Unique ID of the message
ID int `json:"id" yaml:"id"`
// The actual data in the message
// TODO: Change this to a slice instead...or maybe use an
// interface type here to handle several data types ?
Data []string `json:"data" yaml:"data"`
// The type of the message being sent
CommandOrEvent CommandOrEvent `json:"commandOrEvent" yaml:"commandOrEvent"`
// method, what is this message doing, etc. shellCommand, syslog, etc.
Method Method `json:"method" yaml:"method"`
FromNode node
// done is used to signal when a message is fully processed.
// This is used when choosing when to move the message from
// the ringbuffer into the time series log.
done chan struct{}
}
// server is the structure that will hold the state about spawned // server is the structure that will hold the state about spawned
// processes on a local instance. // processes on a local instance.
type server struct { type server struct {
@ -45,9 +26,11 @@ type server struct {
nodeName string nodeName string
// Mutex for locking when writing to the process map // Mutex for locking when writing to the process map
mu sync.Mutex mu sync.Mutex
// The channel where we receive new messages read from file. // The channel where we put new messages read from file,
// or some other process who wants to send something via the
// system
// We can than range this channel for new messages to process. // We can than range this channel for new messages to process.
inputFromFileCh chan []subjectAndMessage newMessagesCh chan []subjectAndMessage
// errorKernel is doing all the error handling like what to do if // errorKernel is doing all the error handling like what to do if
// an error occurs. // an error occurs.
// TODO: Will also send error messages to cental error subscriber. // TODO: Will also send error messages to cental error subscriber.
@ -65,10 +48,12 @@ type server struct {
// how to forward the data for a received message of type log to a // how to forward the data for a received message of type log to a
// central logger. // central logger.
subscriberServices *subscriberServices subscriberServices *subscriberServices
// Is this the central error logger ?
centralErrorLogger bool
} }
// newServer will prepare and return a server type // newServer will prepare and return a server type
func NewServer(brokerAddress string, nodeName string, promHostAndPort string) (*server, error) { func NewServer(brokerAddress string, nodeName string, promHostAndPort string, centralErrorLogger bool) (*server, error) {
conn, err := nats.Connect(brokerAddress, nil) conn, err := nats.Connect(brokerAddress, nil)
if err != nil { if err != nil {
log.Printf("error: nats.Connect failed: %v\n", err) log.Printf("error: nats.Connect failed: %v\n", err)
@ -81,11 +66,12 @@ func NewServer(brokerAddress string, nodeName string, promHostAndPort string) (*
nodeName: nodeName, nodeName: nodeName,
natsConn: conn, natsConn: conn,
processes: make(map[subjectName]process), processes: make(map[subjectName]process),
inputFromFileCh: make(chan []subjectAndMessage), newMessagesCh: make(chan []subjectAndMessage),
methodsAvailable: m.GetMethodsAvailable(), methodsAvailable: m.GetMethodsAvailable(),
commandOrEventAvailable: coe.GetCommandOrEventAvailable(), commandOrEventAvailable: coe.GetCommandOrEventAvailable(),
metrics: newMetrics(promHostAndPort), metrics: newMetrics(promHostAndPort),
subscriberServices: newSubscriberServices(), subscriberServices: newSubscriberServices(),
centralErrorLogger: centralErrorLogger,
} }
return s, nil return s, nil
@ -100,13 +86,13 @@ func (s *server) Start() {
// Start the error kernel that will do all the error handling // Start the error kernel that will do all the error handling
// not done within a process. // not done within a process.
s.errorKernel = newErrorKernel() s.errorKernel = newErrorKernel()
s.errorKernel.startErrorKernel() s.errorKernel.startErrorKernel(s.newMessagesCh)
// Start collecting the metrics // Start collecting the metrics
go s.startMetrics() go s.startMetrics()
// Start the checking the input file for new messages from operator. // Start the checking the input file for new messages from operator.
go s.getMessagesFromFile("./", "inmsg.txt", s.inputFromFileCh) go s.getMessagesFromFile("./", "inmsg.txt", s.newMessagesCh)
// Start the textLogging service that will run on the subscribers // Start the textLogging service that will run on the subscribers
// TODO: This should only be started if the flag value provided when // TODO: This should only be started if the flag value provided when
@ -122,7 +108,7 @@ func (s *server) Start() {
s.printProcessesMap() s.printProcessesMap()
// Start the processing of new messaging from an input channel. // Start the processing of new messaging from an input channel.
s.processNewMessages("./incommmingBuffer.db", s.inputFromFileCh) s.processNewMessages("./incommmingBuffer.db", s.newMessagesCh)
select {} select {}
@ -146,40 +132,6 @@ func (s *server) printProcessesMap() {
fmt.Println("--------------------------------------------------------------------------------------------") fmt.Println("--------------------------------------------------------------------------------------------")
} }
type node string
// subject contains the representation of a subject to be used with one
// specific process
type Subject struct {
// node, the name of the node
ToNode string `json:"node" yaml:"toNode"`
// messageType, command/event
CommandOrEvent CommandOrEvent `json:"commandOrEvent" yaml:"commandOrEvent"`
// method, what is this message doing, etc. shellCommand, syslog, etc.
Method Method `json:"method" yaml:"method"`
// messageCh is the channel for receiving new content to be sent
messageCh chan Message
}
// newSubject will return a new variable of the type subject, and insert
// all the values given as arguments. It will also create the channel
// to receive new messages on the specific subject.
func newSubject(method Method, commandOrEvent CommandOrEvent, node string) Subject {
return Subject{
ToNode: node,
CommandOrEvent: commandOrEvent,
Method: method,
messageCh: make(chan Message),
}
}
// subjectName is the complete representation of a subject
type subjectName string
func (s Subject) name() subjectName {
return subjectName(fmt.Sprintf("%s.%s.%s", s.Method, s.CommandOrEvent, s.ToNode))
}
// processKind are either kindSubscriber or kindPublisher, and are // processKind are either kindSubscriber or kindPublisher, and are
// used to distinguish the kind of process to spawn and to know // used to distinguish the kind of process to spawn and to know
// the process kind put in the process map. // the process kind put in the process map.
@ -255,7 +207,7 @@ func (s *server) spawnWorkerProcess(proc process) {
} }
} }
func messageDeliver(proc process, message Message, natsConn *nats.Conn) { func (s *server) messageDeliverNats(proc process, message Message) {
for { for {
dataPayload, err := gobEncodeMessage(message) dataPayload, err := gobEncodeMessage(message)
if err != nil { if err != nil {
@ -276,7 +228,7 @@ func messageDeliver(proc process, message Message, natsConn *nats.Conn) {
// that sends out a message every second. // that sends out a message every second.
// //
// Create a subscriber for the reply message. // Create a subscriber for the reply message.
subReply, err := natsConn.SubscribeSync(msg.Reply) subReply, err := s.natsConn.SubscribeSync(msg.Reply)
if err != nil { if err != nil {
log.Printf("error: nc.SubscribeSync failed: %v\n", err) log.Printf("error: nc.SubscribeSync failed: %v\n", err)
os.Exit(1) os.Exit(1)
@ -284,7 +236,7 @@ func messageDeliver(proc process, message Message, natsConn *nats.Conn) {
} }
// Publish message // Publish message
err = natsConn.PublishMsg(msg) err = s.natsConn.PublishMsg(msg)
if err != nil { if err != nil {
log.Printf("error: publish failed: %v\n", err) log.Printf("error: publish failed: %v\n", err)
continue continue
@ -292,6 +244,7 @@ func messageDeliver(proc process, message Message, natsConn *nats.Conn) {
// If the message is an ACK type of message we must check that a // If the message is an ACK type of message we must check that a
// reply, and if it is not we don't wait here at all. // reply, and if it is not we don't wait here at all.
fmt.Printf("---- MESSAGE : %v\n", message)
if message.CommandOrEvent == CommandACK || message.CommandOrEvent == EventACK { if message.CommandOrEvent == CommandACK || message.CommandOrEvent == EventACK {
// Wait up until 10 seconds for a reply, // Wait up until 10 seconds for a reply,
// continue and resend if to reply received. // continue and resend if to reply received.
@ -307,20 +260,6 @@ func messageDeliver(proc process, message Message, natsConn *nats.Conn) {
} }
} }
// gobEncodePayload will encode the message structure along with its
// valued in gob binary format.
// TODO: Check if it adds value to compress with gzip.
func gobEncodeMessage(m Message) ([]byte, error) {
var buf bytes.Buffer
gobEnc := gob.NewEncoder(&buf)
err := gobEnc.Encode(m)
if err != nil {
return nil, fmt.Errorf("error: gob.Encode failed: %v", err)
}
return buf.Bytes(), nil
}
// handler will deserialize the message when a new message is received, // handler will deserialize the message when a new message is received,
// check the MessageType field in the message to decide what kind of // check the MessageType field in the message to decide what kind of
// message it is and then it will check how to handle that message type, // message it is and then it will check how to handle that message type,
@ -330,7 +269,7 @@ func gobEncodeMessage(m Message) ([]byte, error) {
// the state of the message being processed, and then reply back to the // the state of the message being processed, and then reply back to the
// correct sending process's reply, meaning so we ACK back to the correct // correct sending process's reply, meaning so we ACK back to the correct
// publisher. // publisher.
func (s *server) subscriberHandler(natsConn *nats.Conn, node string, msg *nats.Msg) { func (s *server) subscriberHandler(natsConn *nats.Conn, thisNode string, msg *nats.Msg) {
message := Message{} message := Message{}
@ -357,7 +296,7 @@ func (s *server) subscriberHandler(natsConn *nats.Conn, node string, msg *nats.M
log.Printf("error: subscriberHandler: method type not available: %v\n", message.CommandOrEvent) log.Printf("error: subscriberHandler: method type not available: %v\n", message.CommandOrEvent)
} }
fmt.Printf("*** DEBUG: BEFORE CALLING HANDLER: ACK\n") fmt.Printf("*** DEBUG: BEFORE CALLING HANDLER: ACK\n")
out, err := mf.handler(s, message, node) out, err := mf.handler(s, message, thisNode)
if err != nil { if err != nil {
// TODO: Send to error kernel ? // TODO: Send to error kernel ?
@ -366,6 +305,8 @@ func (s *server) subscriberHandler(natsConn *nats.Conn, node string, msg *nats.M
// Send a confirmation message back to the publisher // Send a confirmation message back to the publisher
natsConn.Publish(msg.Reply, out) natsConn.Publish(msg.Reply, out)
sendErrorMessage(s.newMessagesCh, node(thisNode))
case message.CommandOrEvent == CommandNACK || message.CommandOrEvent == EventNACK: case message.CommandOrEvent == CommandNACK || message.CommandOrEvent == EventNACK:
log.Printf("info: subscriberHandler: message.CommandOrEvent received was = %v, preparing to call handler\n", message.CommandOrEvent) log.Printf("info: subscriberHandler: message.CommandOrEvent received was = %v, preparing to call handler\n", message.CommandOrEvent)
mf, ok := s.methodsAvailable.CheckIfExists(message.Method) mf, ok := s.methodsAvailable.CheckIfExists(message.Method)
@ -376,7 +317,7 @@ func (s *server) subscriberHandler(natsConn *nats.Conn, node string, msg *nats.M
// since we don't send a reply for a NACK message, we don't care about the // since we don't send a reply for a NACK message, we don't care about the
// out return when calling mf.handler // out return when calling mf.handler
fmt.Printf("*** DEBUG: BEFORE CALLING HANDLER: NACK\n") fmt.Printf("*** DEBUG: BEFORE CALLING HANDLER: NACK\n")
_, err := mf.handler(s, message, node) _, err := mf.handler(s, message, thisNode)
if err != nil { if err != nil {
// TODO: Send to error kernel ? // TODO: Send to error kernel ?
@ -386,3 +327,25 @@ func (s *server) subscriberHandler(natsConn *nats.Conn, node string, msg *nats.M
log.Printf("info: did not find that specific type of command: %#v\n", message.CommandOrEvent) log.Printf("info: did not find that specific type of command: %#v\n", message.CommandOrEvent)
} }
} }
func sendErrorMessage(newMessagesCh chan<- []subjectAndMessage, FromNode node) {
// --- Testing
// TESTING: Creating an error message to send to errorCentral
fmt.Printf(" --- Sending error message to central !!!!!!!!!!!!!!!!!!!!!!!!!!!!\n")
sam := subjectAndMessage{
Subject: Subject{
ToNode: "errorCentral",
CommandOrEvent: EventNACK,
Method: ErrorLog,
},
Message: Message{
ToNode: "errorCentral",
FromNode: FromNode,
Data: []string{"some tull here .............."},
CommandOrEvent: EventNACK,
Method: ErrorLog,
},
}
newMessagesCh <- []subjectAndMessage{sam}
}

View file

@ -26,6 +26,7 @@ func (m Method) GetMethodsAvailable() MethodsAvailable {
ShellCommand: methodCommandShellCommand{}, ShellCommand: methodCommandShellCommand{},
TextLogging: methodEventTextLogging{}, TextLogging: methodEventTextLogging{},
SayHello: methodEventSayHello{}, SayHello: methodEventSayHello{},
ErrorLog: methodEventErrorLog{},
}, },
} }
@ -39,6 +40,8 @@ const (
TextLogging Method = "TextLogging" TextLogging Method = "TextLogging"
// Send Hello I'm here message // Send Hello I'm here message
SayHello Method = "SayHello" SayHello Method = "SayHello"
// Error log methods to centralError
ErrorLog Method = "ErrorLog"
) )
type MethodsAvailable struct { type MethodsAvailable struct {
@ -120,3 +123,14 @@ func (m methodEventSayHello) handler(s *server, message Message, node string) ([
outMsg := []byte("confirmed from: " + node + ": " + fmt.Sprint(message.ID)) outMsg := []byte("confirmed from: " + node + ": " + fmt.Sprint(message.ID))
return outMsg, nil return outMsg, nil
} }
// ---
type methodEventErrorLog struct{}
func (m methodEventErrorLog) handler(s *server, message Message, node string) ([]byte, error) {
log.Printf("----------------------------------------------------------------------------..\n")
log.Printf("Received error from: %v, containing: %v", message.FromNode, message.Data)
log.Printf("----------------------------------------------------------------------------..\n")
return nil, nil
}

View file

@ -50,4 +50,15 @@ func (s *server) subscribersStart() {
// fmt.Printf("*** %#v\n", proc) // fmt.Printf("*** %#v\n", proc)
go s.spawnWorkerProcess(proc) go s.spawnWorkerProcess(proc)
} }
if s.centralErrorLogger {
// Start a subscriber for ErrorLog messages
{
fmt.Printf("Starting ErrorLog subscriber: %#v\n", s.nodeName)
sub := newSubject(ErrorLog, EventNACK, "errorCentral")
proc := s.processPrepareNew(sub, s.errorKernel.errorCh, processKindSubscriber)
// fmt.Printf("*** %#v\n", proc)
go s.spawnWorkerProcess(proc)
}
}
} }