273 lines
7.4 KiB
Go
273 lines
7.4 KiB
Go
|
package hpds_node
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
|
||
|
"git.hpds.cc/Component/network"
|
||
|
"git.hpds.cc/Component/network/log"
|
||
|
"git.hpds.cc/pavement/hpds_node/config"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
mqLogPrefix = "\033[33m[hpds:mq]\033[0m "
|
||
|
)
|
||
|
|
||
|
// MessageQueue is the orchestrator of hpds. There are two types of mq:
|
||
|
// one is Upstream MessageQueue, which is used to connect to multiple downstream mq,
|
||
|
// another one is Downstream MessageQueue (will call it as MessageQueue directly), which is used
|
||
|
// to connected by `Message Queue`, `Access Point` and `Stream Function`
|
||
|
type MessageQueue interface {
|
||
|
// ConfigWorkflow will register workflows from config files to messageQueue.
|
||
|
ConfigWorkflow(conf string) error
|
||
|
|
||
|
// ConfigMesh will register edge-mesh config URL
|
||
|
ConfigMesh(url string) error
|
||
|
|
||
|
// ListenAndServe start mq as server.
|
||
|
ListenAndServe() error
|
||
|
|
||
|
// AddDownstreamMq will add downstream mq.
|
||
|
AddDownstreamMq(downstream MessageQueue) error
|
||
|
|
||
|
// Addr returns the listen address of mq.
|
||
|
Addr() string
|
||
|
|
||
|
// Stats return insight data
|
||
|
Stats() int
|
||
|
|
||
|
// Close will close the mq.
|
||
|
Close() error
|
||
|
|
||
|
// InitOptions initialize options
|
||
|
InitOptions(opts ...Option)
|
||
|
|
||
|
// ReadConfigFile(conf string) error
|
||
|
// AddWorkflow(wf ...core.Workflow) error
|
||
|
// ConfigDownstream(opts ...interface{}) error
|
||
|
// Connect() error
|
||
|
// RemoveDownstreamMq(downstream MessageQueue) error
|
||
|
// ListenAddr() string
|
||
|
}
|
||
|
|
||
|
// messageQueue is the implementation of MessageQueue interface.
|
||
|
type messageQueue struct {
|
||
|
name string
|
||
|
addr string
|
||
|
hasDownStreams bool
|
||
|
server *network.Server
|
||
|
client *network.Client
|
||
|
downstreamMqs []MessageQueue
|
||
|
wfc *config.WorkflowConfig
|
||
|
}
|
||
|
|
||
|
var _ MessageQueue = &messageQueue{}
|
||
|
|
||
|
// NewMqWithOptions create a messageQueue instance.
|
||
|
func NewMqWithOptions(name string, opts ...Option) MessageQueue {
|
||
|
options := NewOptions(opts...)
|
||
|
zipper := createMessageQueueServer(name, options, nil)
|
||
|
zipper.ConfigMesh(options.MeshConfigURL)
|
||
|
|
||
|
return zipper
|
||
|
}
|
||
|
|
||
|
// NewMq create a messageQueue instance from config files.
|
||
|
func NewMq(conf string) (MessageQueue, error) {
|
||
|
config, err := config.ParseWorkflowConfig(conf)
|
||
|
if err != nil {
|
||
|
log.Errorf("%s[ERR] %v", mqLogPrefix, err)
|
||
|
return nil, err
|
||
|
}
|
||
|
// listening address
|
||
|
listenAddr := fmt.Sprintf("%s:%d", config.Host, config.Port)
|
||
|
|
||
|
options := NewOptions()
|
||
|
options.MqAddr = listenAddr
|
||
|
zipper := createMessageQueueServer(config.Name, options, config)
|
||
|
// messageQueue workflow
|
||
|
err = zipper.configWorkflow(config)
|
||
|
|
||
|
return zipper, err
|
||
|
}
|
||
|
|
||
|
// NewDownstreamMq create a messageQueue descriptor for downstream messageQueue.
|
||
|
func NewDownstreamMq(name string, opts ...Option) MessageQueue {
|
||
|
options := NewOptions(opts...)
|
||
|
client := network.NewClient(name, network.ClientTypeMessageQueue, options.ClientOptions...)
|
||
|
|
||
|
return &messageQueue{
|
||
|
name: name,
|
||
|
addr: options.MqAddr,
|
||
|
client: client,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*************** Server ONLY ***************/
|
||
|
// createMessageQueueServer create a messageQueue instance as server.
|
||
|
func createMessageQueueServer(name string, options *Options, cfg *config.WorkflowConfig) *messageQueue {
|
||
|
// create underlying QUIC server
|
||
|
srv := network.NewServer(name, options.ServerOptions...)
|
||
|
z := &messageQueue{
|
||
|
server: srv,
|
||
|
name: name,
|
||
|
addr: options.MqAddr,
|
||
|
wfc: cfg,
|
||
|
}
|
||
|
// initialize
|
||
|
z.init()
|
||
|
return z
|
||
|
}
|
||
|
|
||
|
// ConfigWorkflow will read workflows from config files and register them to messageQueue.
|
||
|
func (z *messageQueue) ConfigWorkflow(conf string) error {
|
||
|
cfg, err := config.ParseWorkflowConfig(conf)
|
||
|
if err != nil {
|
||
|
log.Errorf("%s[ERR] %v", mqLogPrefix, err)
|
||
|
return err
|
||
|
}
|
||
|
log.Debugf("%sConfigWorkflow cfg=%+v", mqLogPrefix, cfg)
|
||
|
return z.configWorkflow(cfg)
|
||
|
}
|
||
|
|
||
|
func (z *messageQueue) configWorkflow(config *config.WorkflowConfig) error {
|
||
|
z.wfc = config
|
||
|
z.server.ConfigMetadataBuilder(newMetadataBuilder())
|
||
|
z.server.ConfigRouter(newRouter(config.Functions))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (z *messageQueue) ConfigMesh(url string) error {
|
||
|
if url == "" {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
log.Printf("%sDownloading mesh config...", mqLogPrefix)
|
||
|
// download mesh conf
|
||
|
res, err := http.Get(url)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer res.Body.Close()
|
||
|
|
||
|
decoder := json.NewDecoder(res.Body)
|
||
|
var configs []config.MeshMessageQueue
|
||
|
err = decoder.Decode(&configs)
|
||
|
if err != nil {
|
||
|
log.Errorf("%s downloaded the Mesh config with err=%v", mqLogPrefix, err)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
log.Printf("%s Successfully downloaded the Mesh config. ", mqLogPrefix)
|
||
|
|
||
|
if len(configs) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
for _, downstream := range configs {
|
||
|
if downstream.Name == z.name {
|
||
|
continue
|
||
|
}
|
||
|
addr := fmt.Sprintf("%s:%d", downstream.Host, downstream.Port)
|
||
|
opts := []Option{WithMqAddr(addr)}
|
||
|
if downstream.Credential != "" {
|
||
|
opts = append(opts, WithCredential(downstream.Credential))
|
||
|
}
|
||
|
_ = z.AddDownstreamMq(NewDownstreamMq(downstream.Name, opts...))
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// ListenAndServe will start messageQueue service.
|
||
|
func (z *messageQueue) ListenAndServe() error {
|
||
|
log.Debugf("%sCreating MessageQueue Server ...", mqLogPrefix)
|
||
|
// check downstream zippers
|
||
|
for _, ds := range z.downstreamMqs {
|
||
|
if dsZipper, ok := ds.(*messageQueue); ok {
|
||
|
go func(dsZipper *messageQueue) {
|
||
|
_ = dsZipper.client.Connect(context.Background(), dsZipper.addr)
|
||
|
z.server.AddDownstreamServer(dsZipper.addr, dsZipper.client)
|
||
|
}(dsZipper)
|
||
|
}
|
||
|
}
|
||
|
return z.server.ListenAndServe(context.Background(), z.addr)
|
||
|
}
|
||
|
|
||
|
// AddDownstreamMq will add downstream messageQueue.
|
||
|
func (z *messageQueue) AddDownstreamMq(downstream MessageQueue) error {
|
||
|
log.Debugf("%sAddDownStreamMq: %v", mqLogPrefix, downstream)
|
||
|
z.downstreamMqs = append(z.downstreamMqs, downstream)
|
||
|
z.hasDownStreams = true
|
||
|
log.Debugf("%scurrent downStreams: %d", mqLogPrefix, len(z.downstreamMqs))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// RemoveDownstreamMq remove downstream messageQueue.
|
||
|
func (z *messageQueue) RemoveDownstreamMq(downstream MessageQueue) error {
|
||
|
index := -1
|
||
|
for i, v := range z.downstreamMqs {
|
||
|
if v.Addr() == downstream.Addr() {
|
||
|
index = i
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// remove from slice
|
||
|
z.downstreamMqs = append(z.downstreamMqs[:index], z.downstreamMqs[index+1:]...)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Addr returns listen address of messageQueue.
|
||
|
func (z *messageQueue) Addr() string {
|
||
|
return z.addr
|
||
|
}
|
||
|
|
||
|
// Close will close a connection. If messageQueue is Server, close the server. If messageQueue is Client, close the client.
|
||
|
func (z *messageQueue) Close() error {
|
||
|
if z.server != nil {
|
||
|
if err := z.server.Close(); err != nil {
|
||
|
log.Errorf("%s Close(): %v", mqLogPrefix, err)
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
if z.client != nil {
|
||
|
if err := z.client.Close(); err != nil {
|
||
|
log.Errorf("%s Close(): %v", mqLogPrefix, err)
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Stats inspects current server.
|
||
|
func (z *messageQueue) Stats() int {
|
||
|
log.Printf("[%s] all connections: %d", z.name, len(z.server.StatsFunctions()))
|
||
|
for connID, name := range z.server.StatsFunctions() {
|
||
|
log.Printf("[%s] -> ConnId=%s, Name=%s", z.name, connID, name)
|
||
|
}
|
||
|
|
||
|
log.Printf("[%s] all downstream mq connected: %d", z.name, len(z.server.DownStreams()))
|
||
|
for k, v := range z.server.DownStreams() {
|
||
|
log.Printf("[%s] |> [%s] %s", z.name, k, v.ServerAddr())
|
||
|
}
|
||
|
|
||
|
log.Printf("[%s] total DataFrames received: %d", z.name, z.server.StatsCounter())
|
||
|
|
||
|
return len(z.server.StatsFunctions())
|
||
|
}
|
||
|
|
||
|
func (z *messageQueue) InitOptions(opts ...Option) {
|
||
|
options := &Options{MqAddr: z.addr}
|
||
|
for _, o := range opts {
|
||
|
o(options)
|
||
|
}
|
||
|
srv := network.NewServer(z.name, options.ServerOptions...)
|
||
|
z.server = srv
|
||
|
if z.wfc != nil {
|
||
|
_ = z.configWorkflow(z.wfc)
|
||
|
}
|
||
|
}
|