158 lines
3.5 KiB
Go
158 lines
3.5 KiB
Go
package app
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func normalizeTransportClientsState(st transportClientsState, forceRebalance bool) (transportClientsState, bool) {
|
|
changed := false
|
|
st.Version = transportStateVersion
|
|
if st.Items == nil {
|
|
st.Items = nil
|
|
return st, false
|
|
}
|
|
|
|
out := make([]TransportClient, 0, len(st.Items))
|
|
byID := map[string]int{}
|
|
for _, raw := range st.Items {
|
|
it := raw
|
|
id := sanitizeID(it.ID)
|
|
if id == "" {
|
|
changed = true
|
|
continue
|
|
}
|
|
if it.ID != id {
|
|
it.ID = id
|
|
changed = true
|
|
}
|
|
ifaceID := normalizeTransportIfaceID(it.IfaceID)
|
|
if it.IfaceID != ifaceID {
|
|
it.IfaceID = ifaceID
|
|
changed = true
|
|
}
|
|
|
|
kind := normalizeTransportKind(it.Kind)
|
|
if kind == "" {
|
|
kind = TransportClientSingBox
|
|
changed = true
|
|
}
|
|
it.Kind = kind
|
|
if normCfg, cfgChanged := normalizeTransportClientConfig(it.Kind, it.Config); cfgChanged {
|
|
it.Config = normCfg
|
|
changed = true
|
|
}
|
|
|
|
if strings.TrimSpace(it.Name) == "" {
|
|
it.Name = id
|
|
changed = true
|
|
}
|
|
|
|
status := normalizeTransportStatus(it.Status)
|
|
if status != it.Status {
|
|
it.Status = status
|
|
changed = true
|
|
}
|
|
|
|
if !equalStringSlices(it.Capabilities, defaultTransportCapabilities(it.Kind)) {
|
|
it.Capabilities = defaultTransportCapabilities(it.Kind)
|
|
changed = true
|
|
}
|
|
if normRuntime, runtimeChanged := normalizeTransportRuntimeStored(it.Runtime, it.Kind, it.Config); runtimeChanged {
|
|
it.Runtime = normRuntime
|
|
changed = true
|
|
}
|
|
|
|
if strings.TrimSpace(it.Health.LastCheck) == "" {
|
|
if strings.TrimSpace(it.UpdatedAt) != "" {
|
|
it.Health.LastCheck = it.UpdatedAt
|
|
} else {
|
|
it.Health.LastCheck = time.Now().UTC().Format(time.RFC3339)
|
|
}
|
|
changed = true
|
|
}
|
|
|
|
if idx, ok := byID[it.ID]; ok {
|
|
// Keep deterministic winner for duplicate IDs.
|
|
if preferTransportClient(it, out[idx]) {
|
|
out[idx] = it
|
|
}
|
|
changed = true
|
|
continue
|
|
}
|
|
byID[it.ID] = len(out)
|
|
out = append(out, it)
|
|
}
|
|
|
|
sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
|
|
usedTables := map[string]struct{}{}
|
|
for i := range out {
|
|
if strings.TrimSpace(out[i].RoutingTable) == "" {
|
|
continue
|
|
}
|
|
wantTable := normalizeTransportRoutingTable(out[i].RoutingTable, transportRoutingTableForID(out[i].ID))
|
|
if out[i].RoutingTable != wantTable {
|
|
out[i].RoutingTable = wantTable
|
|
changed = true
|
|
}
|
|
usedTables[wantTable] = struct{}{}
|
|
}
|
|
for i := range out {
|
|
if strings.TrimSpace(out[i].RoutingTable) != "" {
|
|
continue
|
|
}
|
|
wantTable := transportRoutingTableForIDUnique(out[i].ID, usedTables)
|
|
if out[i].RoutingTable != wantTable {
|
|
out[i].RoutingTable = wantTable
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
norm, allocChanged := reconcileTransportAllocations(out, forceRebalance)
|
|
if allocChanged {
|
|
changed = true
|
|
}
|
|
st.Items = norm
|
|
return st, changed
|
|
}
|
|
|
|
func normalizeTransportStatus(st TransportClientStatus) TransportClientStatus {
|
|
switch st {
|
|
case TransportClientStarting, TransportClientUp, TransportClientDegraded, TransportClientDown:
|
|
return st
|
|
default:
|
|
return TransportClientDown
|
|
}
|
|
}
|
|
|
|
func equalStringSlices(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func preferTransportClient(cand, cur TransportClient) bool {
|
|
cu := strings.TrimSpace(cand.UpdatedAt)
|
|
ou := strings.TrimSpace(cur.UpdatedAt)
|
|
if cu != ou {
|
|
if cu == "" {
|
|
return false
|
|
}
|
|
if ou == "" {
|
|
return true
|
|
}
|
|
return cu > ou
|
|
}
|
|
if cand.Enabled != cur.Enabled {
|
|
return cand.Enabled
|
|
}
|
|
return strings.TrimSpace(cand.Name) > strings.TrimSpace(cur.Name)
|
|
}
|