software steht so weit.
jetzt build + docker build und testen
This commit is contained in:
1 parent
42f0c0accb
commit
ca6cdcca63
90 files changed
+10298
-480
No files matched your search
@@ -16,6 +16,8 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"sultan/pkg/license"
|
||||
@@ -54,11 +56,22 @@ type NodeState struct {
|
||||
|
||||
// NodeContext ist, was die Policy über einen Knoten wissen muss.
|
||||
type NodeContext struct {
|
||||
Tenant policy.Tenant
|
||||
License license.License
|
||||
Drained bool
|
||||
Tenant policy.Tenant
|
||||
License license.License
|
||||
Drained bool
|
||||
Maintenance *policy.Maintenance // Wartung des Knotens (nil = keine)
|
||||
// Disconnected: Knoten getrennt, Routen werden (noch) gehalten – sie
|
||||
// zeigen die Wartungsseite.
|
||||
Disconnected bool
|
||||
}
|
||||
|
||||
// Labels, mit denen ein Workload (Container, sultan.d-Dienst) seine Services
|
||||
// in Wartung schickt.
|
||||
const (
|
||||
LabelMaintenance = "sultan.maintenance"
|
||||
LabelMaintenancePublicReason = "sultan.maintenance.public_reason"
|
||||
)
|
||||
|
||||
// Directory liefert den NodeContext; für gelöschte/unbekannte Knoten
|
||||
// ErrUnknownNode (ggf. verpackt).
|
||||
type Directory interface {
|
||||
@@ -207,6 +220,7 @@ func (r *Registry) evaluate(ctx context.Context, nodeID string, state NodeState)
|
||||
workloads := slices.Clone(state.Workloads)
|
||||
slices.SortStableFunc(workloads, func(a, b Workload) int { return cmp.Compare(a.Name, b.Name) })
|
||||
merged := &traefik.Configuration{}
|
||||
serviceMaint := map[string]policy.Maintenance{}
|
||||
seen := map[string]bool{}
|
||||
for _, w := range workloads {
|
||||
if seen[w.Name] {
|
||||
@@ -225,6 +239,11 @@ func (r *Registry) evaluate(ctx context.Context, nodeID string, state NodeState)
|
||||
continue
|
||||
}
|
||||
merged = m
|
||||
if maint, ok := workloadMaintenance(w.Labels); ok && c.HTTP != nil {
|
||||
for svc := range c.HTTP.Services {
|
||||
serviceMaint[svc] = maint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
down := map[string]bool{}
|
||||
@@ -232,12 +251,15 @@ func (r *Registry) evaluate(ctx context.Context, nodeID string, state NodeState)
|
||||
down[s] = true
|
||||
}
|
||||
res, err := r.Policy.Apply(policy.Input{
|
||||
Tenant: nc.Tenant,
|
||||
License: nc.License,
|
||||
NodeID: nodeID,
|
||||
NodeDrained: nc.Drained,
|
||||
Config: merged,
|
||||
DownServices: down,
|
||||
Tenant: nc.Tenant,
|
||||
License: nc.License,
|
||||
NodeID: nodeID,
|
||||
NodeDrained: nc.Drained,
|
||||
Config: merged,
|
||||
DownServices: down,
|
||||
Maintenance: nc.Maintenance,
|
||||
ServiceMaintenance: serviceMaint,
|
||||
Disconnected: nc.Disconnected,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -249,6 +271,22 @@ func (r *Registry) evaluate(ctx context.Context, nodeID string, state NodeState)
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// workloadMaintenance liest sultan.maintenance(=true) und den optionalen
|
||||
// öffentlichen Grund; Schlüssel ohne Beachtung der Groß-/Kleinschreibung.
|
||||
func workloadMaintenance(labels map[string]string) (policy.Maintenance, bool) {
|
||||
var on bool
|
||||
var m policy.Maintenance
|
||||
for k, v := range labels {
|
||||
switch strings.ToLower(k) {
|
||||
case LabelMaintenance:
|
||||
on, _ = strconv.ParseBool(strings.TrimSpace(v))
|
||||
case LabelMaintenancePublicReason:
|
||||
m.PublicReason = strings.TrimSpace(v)
|
||||
}
|
||||
}
|
||||
return m, on
|
||||
}
|
||||
|
||||
// store übernimmt entry und meldet nur Verstöße, die vorher nicht bestanden.
|
||||
func (r *Registry) store(nodeID string, entry *nodeEntry) {
|
||||
if r.nodes == nil {
|
||||
|
||||
@@ -398,3 +398,46 @@ func TestRegistry_Concurrent(t *testing.T) {
|
||||
t.Errorf("%d Router, erwartet 20", n)
|
||||
}
|
||||
}
|
||||
|
||||
// Container/Dienst in Wartung per Label: nur dessen Router gehen zum Lander.
|
||||
func TestRegistry_ServiceMaintenanceLabel(t *testing.T) {
|
||||
r, _, _ := newRegistry()
|
||||
shop := wp("shop", "100.64.0.1", "shop.kunde.de")
|
||||
shop.Labels["traefik.http.routers.wp.rule"] = "Host(`shop.kunde.de`)"
|
||||
shop.Labels = map[string]string{
|
||||
"traefik.enable": "true",
|
||||
"traefik.http.routers.shop.rule": "Host(`shop.kunde.de`)",
|
||||
"traefik.http.services.shop.loadbalancer.server.port": "8080",
|
||||
"sultan.maintenance": "true",
|
||||
"sultan.maintenance.public_reason": "Inventur",
|
||||
}
|
||||
r.Update(ctx, "n1", NodeState{Workloads: []Workload{wp("wordpress", "100.64.0.1", "kunde.de"), shop}})
|
||||
|
||||
cfg := r.Config().HTTP
|
||||
if cfg.Routers["kunde__n1__shop"].Service != policy.InterceptorService {
|
||||
t.Error("Dienst mit sultan.maintenance=true nicht umgeleitet")
|
||||
}
|
||||
if cfg.Routers["kunde__n1__wp"].Service == policy.InterceptorService {
|
||||
t.Error("anderer Dienst wurde umgeleitet")
|
||||
}
|
||||
hdr := cfg.Middlewares["sultan__kunde__n1__shop"]["headers"].(map[string]any)["customRequestHeaders"].(map[string]any)
|
||||
if hdr[policy.HeaderPublicReason] != "Inventur" {
|
||||
t.Errorf("öffentlicher Grund: %v", hdr)
|
||||
}
|
||||
|
||||
shop.Labels["sultan.maintenance"] = "false"
|
||||
r.Update(ctx, "n1", NodeState{Workloads: []Workload{shop}})
|
||||
if r.Config().HTTP.Routers["kunde__n1__shop"].Service == policy.InterceptorService {
|
||||
t.Error("sultan.maintenance=false leitet um")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_NodeMaintenanceFromDirectory(t *testing.T) {
|
||||
r, dir, _ := newRegistry()
|
||||
r.Update(ctx, "n1", NodeState{Workloads: []Workload{wp("wordpress", "100.64.0.1", "kunde.de")}})
|
||||
dir.set("n1", func(nc *NodeContext) { nc.Maintenance = &policy.Maintenance{Until: time.Now().Add(time.Hour)} })
|
||||
r.Refresh(ctx)
|
||||
if r.Config().HTTP.Routers["kunde__n1__wp"].Service != policy.InterceptorService {
|
||||
t.Error("Knotenwartung aus dem Directory nicht angewendet")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user