173 lines
4.1 KiB
Go
173 lines
4.1 KiB
Go
package discovery
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
msg "github.com/tech/sendico/pkg/messaging"
|
|
"github.com/tech/sendico/pkg/mlogger"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type Announcer struct {
|
|
logger mlogger.Logger
|
|
producer msg.Producer
|
|
sender string
|
|
announce Announcement
|
|
|
|
startOnce sync.Once
|
|
stopOnce sync.Once
|
|
stopCh chan struct{}
|
|
doneCh chan struct{}
|
|
}
|
|
|
|
func NewAnnouncer(logger mlogger.Logger, producer msg.Producer, sender string, announce Announcement) *Announcer {
|
|
if logger != nil {
|
|
logger = logger.Named("discovery")
|
|
}
|
|
announce = normalizeAnnouncement(announce)
|
|
if announce.Service == "" {
|
|
announce.Service = strings.TrimSpace(sender)
|
|
}
|
|
if announce.InstanceID == "" {
|
|
announce.InstanceID = InstanceID()
|
|
}
|
|
if announce.ID == "" {
|
|
announce.ID = DefaultEntryID(announce.Service)
|
|
}
|
|
if announce.InvokeURI == "" && announce.Service != "" {
|
|
announce.InvokeURI = DefaultInvokeURI(announce.Service)
|
|
}
|
|
return &Announcer{
|
|
logger: logger,
|
|
producer: producer,
|
|
sender: strings.TrimSpace(sender),
|
|
announce: announce,
|
|
stopCh: make(chan struct{}),
|
|
doneCh: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func (a *Announcer) Start() {
|
|
if a == nil {
|
|
return
|
|
}
|
|
a.startOnce.Do(func() {
|
|
if a.producer == nil {
|
|
a.logWarn("Discovery announce skipped: producer not configured", announcementFields(a.announce)...)
|
|
close(a.doneCh)
|
|
return
|
|
}
|
|
if strings.TrimSpace(a.announce.ID) == "" {
|
|
a.logWarn("Discovery announce skipped: missing instance id", announcementFields(a.announce)...)
|
|
close(a.doneCh)
|
|
return
|
|
}
|
|
a.logInfo("Discovery announcer starting", announcementFields(a.announce)...)
|
|
a.sendAnnouncement()
|
|
a.sendHeartbeat()
|
|
go a.heartbeatLoop()
|
|
})
|
|
}
|
|
|
|
func (a *Announcer) Stop() {
|
|
if a == nil {
|
|
return
|
|
}
|
|
a.stopOnce.Do(func() {
|
|
close(a.stopCh)
|
|
<-a.doneCh
|
|
a.logInfo("Discovery announcer stopped", announcementFields(a.announce)...)
|
|
})
|
|
}
|
|
|
|
func (a *Announcer) heartbeatLoop() {
|
|
defer close(a.doneCh)
|
|
interval := time.Duration(a.announce.Health.IntervalSec) * time.Second
|
|
if interval <= 0 {
|
|
interval = time.Duration(DefaultHealthIntervalSec) * time.Second
|
|
}
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-a.stopCh:
|
|
return
|
|
case <-ticker.C:
|
|
a.sendHeartbeat()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *Announcer) sendAnnouncement() {
|
|
env := NewServiceAnnounceEnvelope(a.sender, a.announce)
|
|
event := ServiceAnnounceEvent()
|
|
if a.announce.Rail != "" {
|
|
env = NewGatewayAnnounceEnvelope(a.sender, a.announce)
|
|
event = GatewayAnnounceEvent()
|
|
}
|
|
if err := a.producer.SendMessage(env); err != nil {
|
|
fields := append(announcementFields(a.announce), zap.String("event", event.ToString()), zap.Error(err))
|
|
a.logWarn("Failed to publish discovery announce", fields...)
|
|
return
|
|
}
|
|
a.logInfo("Discovery announce published", append(announcementFields(a.announce), zap.String("event", event.ToString()))...)
|
|
}
|
|
|
|
func (a *Announcer) sendHeartbeat() {
|
|
hb := Heartbeat{
|
|
ID: a.announce.ID,
|
|
InstanceID: a.announce.InstanceID,
|
|
Status: "ok",
|
|
TS: time.Now().Unix(),
|
|
}
|
|
if err := a.producer.SendMessage(NewHeartbeatEnvelope(a.sender, hb)); err != nil {
|
|
fields := append(announcementFields(a.announce), zap.String("event", HeartbeatEvent().ToString()), zap.Error(err))
|
|
a.logWarn("Failed to publish discovery heartbeat", fields...)
|
|
}
|
|
}
|
|
|
|
func (a *Announcer) logInfo(message string, fields ...zap.Field) {
|
|
if a.logger == nil {
|
|
return
|
|
}
|
|
a.logger.Info(message, fields...)
|
|
}
|
|
|
|
func (a *Announcer) logWarn(message string, fields ...zap.Field) {
|
|
if a.logger == nil {
|
|
return
|
|
}
|
|
a.logger.Warn(message, fields...)
|
|
}
|
|
|
|
func DefaultEntryID(service string) string {
|
|
clean := strings.ToLower(strings.TrimSpace(service))
|
|
if clean == "" {
|
|
clean = "service"
|
|
}
|
|
host, _ := os.Hostname()
|
|
host = strings.ToLower(strings.TrimSpace(host))
|
|
uid := uuid.NewString()
|
|
if host == "" {
|
|
return clean + "_" + uid
|
|
}
|
|
return clean + "_" + host + "_" + uid
|
|
}
|
|
|
|
func DefaultInstanceID(service string) string {
|
|
return DefaultEntryID(service)
|
|
}
|
|
|
|
func DefaultInvokeURI(service string) string {
|
|
clean := strings.ToLower(strings.TrimSpace(service))
|
|
if clean == "" {
|
|
return ""
|
|
}
|
|
return "grpc://" + clean
|
|
}
|