discovery service

This commit is contained in:
Stephan D
2026-01-02 02:44:01 +01:00
parent 97ba7500dc
commit ea1c69f14a
47 changed files with 2799 additions and 701 deletions

View File

@@ -13,6 +13,7 @@ const (
type RegistryEntry struct {
ID string `json:"id"`
InstanceID string `bson:"instanceId" json:"instanceId"`
Service string `json:"service"`
Rail string `json:"rail,omitempty"`
Network string `json:"network,omitempty"`
@@ -29,8 +30,10 @@ type RegistryEntry struct {
}
type Registry struct {
mu sync.RWMutex
entries map[string]*RegistryEntry
mu sync.RWMutex
entries map[string]*RegistryEntry
byID map[string]map[string]struct{}
byInstance map[string]map[string]struct{}
}
type UpdateResult struct {
@@ -42,23 +45,31 @@ type UpdateResult struct {
func NewRegistry() *Registry {
return &Registry{
entries: map[string]*RegistryEntry{},
entries: map[string]*RegistryEntry{},
byID: map[string]map[string]struct{}{},
byInstance: map[string]map[string]struct{}{},
}
}
func (r *Registry) UpsertFromAnnouncement(announce Announcement, now time.Time) UpdateResult {
entry := registryEntryFromAnnouncement(normalizeAnnouncement(announce), now)
key := registryEntryKey(entry)
if key == "" {
return UpdateResult{Entry: entry}
}
r.mu.Lock()
defer r.mu.Unlock()
existing, ok := r.entries[entry.ID]
existing, ok := r.entries[key]
wasHealthy := false
if ok && existing != nil {
wasHealthy = existing.isHealthyAt(now)
r.unindexEntry(key, existing)
}
entry.Healthy = entry.isHealthyAt(now)
r.entries[entry.ID] = &entry
r.entries[key] = &entry
r.indexEntry(key, &entry)
return UpdateResult{
Entry: entry,
@@ -68,10 +79,45 @@ func (r *Registry) UpsertFromAnnouncement(announce Announcement, now time.Time)
}
}
func (r *Registry) UpdateHeartbeat(id string, status string, ts time.Time, now time.Time) (UpdateResult, bool) {
func (r *Registry) UpsertEntry(entry RegistryEntry, now time.Time) UpdateResult {
entry = normalizeEntry(entry)
key := registryEntryKey(entry)
if key == "" {
return UpdateResult{Entry: entry}
}
if entry.LastHeartbeat.IsZero() {
entry.LastHeartbeat = now
}
if strings.TrimSpace(entry.Status) == "" {
entry.Status = "ok"
}
r.mu.Lock()
defer r.mu.Unlock()
existing, ok := r.entries[key]
wasHealthy := false
if ok && existing != nil {
wasHealthy = existing.isHealthyAt(now)
r.unindexEntry(key, existing)
}
entry.Healthy = entry.isHealthyAt(now)
r.entries[key] = &entry
r.indexEntry(key, &entry)
return UpdateResult{
Entry: entry,
IsNew: !ok,
WasHealthy: wasHealthy,
BecameHealthy: !wasHealthy && entry.Healthy,
}
}
func (r *Registry) UpdateHeartbeat(id string, instanceID string, status string, ts time.Time, now time.Time) []UpdateResult {
id = strings.TrimSpace(id)
if id == "" {
return UpdateResult{}, false
instanceID = strings.TrimSpace(instanceID)
if id == "" && instanceID == "" {
return nil
}
if status == "" {
status = "ok"
@@ -83,21 +129,54 @@ func (r *Registry) UpdateHeartbeat(id string, status string, ts time.Time, now t
r.mu.Lock()
defer r.mu.Unlock()
entry, ok := r.entries[id]
if !ok || entry == nil {
return UpdateResult{}, false
keys := keysFromIndex(r.byInstance[instanceID])
if len(keys) == 0 && id != "" {
keys = keysFromIndex(r.byID[id])
}
wasHealthy := entry.isHealthyAt(now)
entry.Status = status
entry.LastHeartbeat = ts
entry.Healthy = entry.isHealthyAt(now)
if len(keys) == 0 {
return nil
}
results := make([]UpdateResult, 0, len(keys))
for _, key := range keys {
entry := r.entries[key]
if entry == nil {
continue
}
if id != "" && entry.ID != id {
continue
}
if instanceID != "" && entry.InstanceID != instanceID {
continue
}
wasHealthy := entry.isHealthyAt(now)
entry.Status = status
entry.LastHeartbeat = ts
entry.Healthy = entry.isHealthyAt(now)
return UpdateResult{
Entry: *entry,
IsNew: false,
WasHealthy: wasHealthy,
BecameHealthy: !wasHealthy && entry.Healthy,
}, true
results = append(results, UpdateResult{
Entry: *entry,
IsNew: false,
WasHealthy: wasHealthy,
BecameHealthy: !wasHealthy && entry.Healthy,
})
}
return results
}
func (r *Registry) Delete(key string) bool {
key = strings.TrimSpace(key)
if key == "" {
return false
}
r.mu.Lock()
defer r.mu.Unlock()
entry, ok := r.entries[key]
if !ok {
return false
}
delete(r.entries, key)
r.unindexEntry(key, entry)
return true
}
func (r *Registry) List(now time.Time, onlyHealthy bool) []RegistryEntry {
@@ -123,6 +202,7 @@ func registryEntryFromAnnouncement(announce Announcement, now time.Time) Registr
status := "ok"
return RegistryEntry{
ID: strings.TrimSpace(announce.ID),
InstanceID: strings.TrimSpace(announce.InstanceID),
Service: strings.TrimSpace(announce.Service),
Rail: strings.ToUpper(strings.TrimSpace(announce.Rail)),
Network: strings.ToUpper(strings.TrimSpace(announce.Network)),
@@ -138,8 +218,33 @@ func registryEntryFromAnnouncement(announce Announcement, now time.Time) Registr
}
}
func normalizeEntry(entry RegistryEntry) RegistryEntry {
entry.ID = strings.TrimSpace(entry.ID)
entry.InstanceID = strings.TrimSpace(entry.InstanceID)
if entry.InstanceID == "" {
entry.InstanceID = entry.ID
}
entry.Service = strings.TrimSpace(entry.Service)
entry.Rail = strings.ToUpper(strings.TrimSpace(entry.Rail))
entry.Network = strings.ToUpper(strings.TrimSpace(entry.Network))
entry.Operations = normalizeStrings(entry.Operations, false)
entry.Currencies = normalizeStrings(entry.Currencies, true)
entry.InvokeURI = strings.TrimSpace(entry.InvokeURI)
entry.Version = strings.TrimSpace(entry.Version)
entry.Status = strings.TrimSpace(entry.Status)
entry.Health = normalizeHealth(entry.Health)
if entry.Limits != nil {
entry.Limits = normalizeLimits(*entry.Limits)
}
return entry
}
func normalizeAnnouncement(announce Announcement) Announcement {
announce.ID = strings.TrimSpace(announce.ID)
announce.InstanceID = strings.TrimSpace(announce.InstanceID)
if announce.InstanceID == "" {
announce.InstanceID = announce.ID
}
announce.Service = strings.TrimSpace(announce.Service)
announce.Rail = strings.ToUpper(strings.TrimSpace(announce.Rail))
announce.Network = strings.ToUpper(strings.TrimSpace(announce.Network))
@@ -239,6 +344,67 @@ func cloneStrings(values []string) []string {
return out
}
func (r *Registry) indexEntry(key string, entry *RegistryEntry) {
if r == nil || entry == nil || key == "" {
return
}
if entry.ID != "" {
addIndex(r.byID, entry.ID, key)
}
if entry.InstanceID != "" {
addIndex(r.byInstance, entry.InstanceID, key)
}
}
func (r *Registry) unindexEntry(key string, entry *RegistryEntry) {
if r == nil || entry == nil || key == "" {
return
}
if entry.ID != "" {
removeIndex(r.byID, entry.ID, key)
}
if entry.InstanceID != "" {
removeIndex(r.byInstance, entry.InstanceID, key)
}
}
func addIndex(index map[string]map[string]struct{}, id string, key string) {
if id == "" || key == "" {
return
}
set := index[id]
if set == nil {
set = map[string]struct{}{}
index[id] = set
}
set[key] = struct{}{}
}
func removeIndex(index map[string]map[string]struct{}, id string, key string) {
if id == "" || key == "" {
return
}
set := index[id]
if set == nil {
return
}
delete(set, key)
if len(set) == 0 {
delete(index, id)
}
}
func keysFromIndex(index map[string]struct{}) []string {
if len(index) == 0 {
return nil
}
keys := make([]string, 0, len(index))
for key := range index {
keys = append(keys, key)
}
return keys
}
func (e *RegistryEntry) isHealthyAt(now time.Time) bool {
if e == nil {
return false