Files
elmprodvpn/selective-vpn-api/app/transport_client_runtime_alloc_normalize.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)
}