214 lines
6.8 KiB
Go
214 lines
6.8 KiB
Go
package mesh
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestSyntheticRelaySchedulerDequeuesByQoSPriority(t *testing.T) {
|
|
scheduler := testRelayScheduler()
|
|
telemetry := testRelayEnvelope(SyntheticChannelTelemetry, SyntheticMessageTelemetry, 1)
|
|
routeControl := testRelayEnvelope(SyntheticChannelRouteControl, SyntheticMessageRouteHealth, 2)
|
|
fabricControl := testRelayEnvelope(SyntheticChannelFabricControl, SyntheticMessageProbe, 3)
|
|
|
|
if _, err := scheduler.Enqueue(telemetry); err != nil {
|
|
t.Fatalf("enqueue telemetry: %v", err)
|
|
}
|
|
if _, err := scheduler.Enqueue(routeControl); err != nil {
|
|
t.Fatalf("enqueue route control: %v", err)
|
|
}
|
|
if _, err := scheduler.Enqueue(fabricControl); err != nil {
|
|
t.Fatalf("enqueue fabric control: %v", err)
|
|
}
|
|
|
|
first, err := scheduler.Dequeue()
|
|
if err != nil {
|
|
t.Fatalf("dequeue first: %v", err)
|
|
}
|
|
second, err := scheduler.Dequeue()
|
|
if err != nil {
|
|
t.Fatalf("dequeue second: %v", err)
|
|
}
|
|
third, err := scheduler.Dequeue()
|
|
if err != nil {
|
|
t.Fatalf("dequeue third: %v", err)
|
|
}
|
|
if first.Channel != SyntheticChannelFabricControl {
|
|
t.Fatalf("first channel = %q, want fabric_control", first.Channel)
|
|
}
|
|
if second.Channel != SyntheticChannelRouteControl {
|
|
t.Fatalf("second channel = %q, want route_control", second.Channel)
|
|
}
|
|
if third.Channel != SyntheticChannelTelemetry {
|
|
t.Fatalf("third channel = %q, want telemetry", third.Channel)
|
|
}
|
|
}
|
|
|
|
func TestSyntheticRelaySchedulerDropsOldestTelemetryOnly(t *testing.T) {
|
|
scheduler := testRelayScheduler()
|
|
first := testRelayEnvelope(SyntheticChannelTelemetry, SyntheticMessageTelemetry, 1)
|
|
second := testRelayEnvelope(SyntheticChannelTelemetry, SyntheticMessageTelemetry, 2)
|
|
|
|
if result, err := scheduler.Enqueue(first); err != nil || result.Dropped {
|
|
t.Fatalf("enqueue first result=%+v err=%v", result, err)
|
|
}
|
|
result, err := scheduler.Enqueue(second)
|
|
if err != nil {
|
|
t.Fatalf("enqueue second: %v", err)
|
|
}
|
|
if !result.Dropped || result.DroppedSequence != 1 {
|
|
t.Fatalf("result = %+v, want dropped sequence 1", result)
|
|
}
|
|
dequeued, err := scheduler.Dequeue()
|
|
if err != nil {
|
|
t.Fatalf("dequeue: %v", err)
|
|
}
|
|
if dequeued.Sequence != 2 {
|
|
t.Fatalf("dequeued sequence = %d, want 2", dequeued.Sequence)
|
|
}
|
|
metrics := scheduler.SnapshotQueueMetrics()
|
|
if metrics.Dropped != 1 || metrics.Enqueued != 2 {
|
|
t.Fatalf("metrics = %+v, want one drop and two enqueues", metrics)
|
|
}
|
|
}
|
|
|
|
func TestSyntheticRelaySchedulerRejectsFullReliableQueue(t *testing.T) {
|
|
scheduler := testRelayScheduler()
|
|
first := testRelayEnvelope(SyntheticChannelFabricControl, SyntheticMessageProbe, 1)
|
|
second := testRelayEnvelope(SyntheticChannelFabricControl, SyntheticMessageProbe, 2)
|
|
|
|
if _, err := scheduler.Enqueue(first); err != nil {
|
|
t.Fatalf("enqueue first: %v", err)
|
|
}
|
|
_, err := scheduler.Enqueue(second)
|
|
if !errors.Is(err, ErrSyntheticRelayQueueFull) {
|
|
t.Fatalf("err = %v, want ErrSyntheticRelayQueueFull", err)
|
|
}
|
|
dequeued, err := scheduler.Dequeue()
|
|
if err != nil {
|
|
t.Fatalf("dequeue: %v", err)
|
|
}
|
|
if dequeued.Sequence != 1 {
|
|
t.Fatalf("dequeued sequence = %d, want 1", dequeued.Sequence)
|
|
}
|
|
metrics := scheduler.SnapshotQueueMetrics()
|
|
if metrics.Dropped != 0 || metrics.Rejected != 1 {
|
|
t.Fatalf("metrics = %+v, want no drop and one rejection", metrics)
|
|
}
|
|
}
|
|
|
|
func TestSyntheticRelaySchedulerRejectsInvalidEnvelopes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mutate func(*SyntheticEnvelope)
|
|
want error
|
|
}{
|
|
{
|
|
name: "wrong cluster",
|
|
mutate: func(envelope *SyntheticEnvelope) {
|
|
envelope.ClusterID = "cluster-2"
|
|
},
|
|
want: ErrClusterMismatch,
|
|
},
|
|
{
|
|
name: "wrong node",
|
|
mutate: func(envelope *SyntheticEnvelope) {
|
|
envelope.To.NodeID = "node-x"
|
|
},
|
|
want: ErrNodeMismatch,
|
|
},
|
|
{
|
|
name: "unauthorized channel",
|
|
mutate: func(envelope *SyntheticEnvelope) {
|
|
envelope.Channel = "rdp_render"
|
|
},
|
|
want: ErrUnauthorizedChannel,
|
|
},
|
|
{
|
|
name: "unsupported message",
|
|
mutate: func(envelope *SyntheticEnvelope) {
|
|
envelope.MessageType = "rdp.input"
|
|
},
|
|
want: ErrUnsupportedSyntheticMessage,
|
|
},
|
|
{
|
|
name: "ttl exhausted",
|
|
mutate: func(envelope *SyntheticEnvelope) {
|
|
envelope.TTL = 0
|
|
},
|
|
want: ErrTTLExhausted,
|
|
},
|
|
{
|
|
name: "loop detected",
|
|
mutate: func(envelope *SyntheticEnvelope) {
|
|
envelope.Visited = append(envelope.Visited, "node-r")
|
|
},
|
|
want: ErrLoopDetected,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
scheduler := testRelayScheduler()
|
|
envelope := testRelayEnvelope(SyntheticChannelFabricControl, SyntheticMessageProbe, 1)
|
|
tt.mutate(&envelope)
|
|
_, err := scheduler.Enqueue(envelope)
|
|
if !errors.Is(err, tt.want) {
|
|
t.Fatalf("err = %v, want %v", err, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSyntheticRelaySchedulerDisabledRejects(t *testing.T) {
|
|
scheduler := NewSyntheticRelayScheduler(SyntheticRelaySchedulerConfig{
|
|
Enabled: false,
|
|
Local: PeerIdentity{ClusterID: "cluster-1", NodeID: "node-r"},
|
|
})
|
|
_, err := scheduler.Enqueue(testRelayEnvelope(SyntheticChannelFabricControl, SyntheticMessageProbe, 1))
|
|
if !errors.Is(err, ErrMeshRuntimeDisabled) {
|
|
t.Fatalf("err = %v, want ErrMeshRuntimeDisabled", err)
|
|
}
|
|
if _, err := scheduler.Dequeue(); !errors.Is(err, ErrMeshRuntimeDisabled) {
|
|
t.Fatalf("dequeue err = %v, want ErrMeshRuntimeDisabled", err)
|
|
}
|
|
}
|
|
|
|
func TestSyntheticRelaySchedulerQueueDepthSnapshot(t *testing.T) {
|
|
scheduler := testRelayScheduler()
|
|
if _, err := scheduler.Enqueue(testRelayEnvelope(SyntheticChannelFabricControl, SyntheticMessageProbe, 1)); err != nil {
|
|
t.Fatalf("enqueue fabric control: %v", err)
|
|
}
|
|
if _, err := scheduler.Enqueue(testRelayEnvelope(SyntheticChannelRouteControl, SyntheticMessageRouteHealth, 2)); err != nil {
|
|
t.Fatalf("enqueue route control: %v", err)
|
|
}
|
|
metrics := scheduler.SnapshotQueueMetrics()
|
|
if metrics.QueueDepths[SyntheticChannelFabricControl] != 1 {
|
|
t.Fatalf("fabric_control depth = %d, want 1", metrics.QueueDepths[SyntheticChannelFabricControl])
|
|
}
|
|
if metrics.QueueDepths[SyntheticChannelRouteControl] != 1 {
|
|
t.Fatalf("route_control depth = %d, want 1", metrics.QueueDepths[SyntheticChannelRouteControl])
|
|
}
|
|
}
|
|
|
|
func testRelayScheduler() *SyntheticRelayScheduler {
|
|
return NewSyntheticRelayScheduler(SyntheticRelaySchedulerConfig{
|
|
Enabled: true,
|
|
Local: PeerIdentity{ClusterID: "cluster-1", NodeID: "node-r"},
|
|
QueuePolicies: []SyntheticRelayQueuePolicy{
|
|
{Channel: SyntheticChannelFabricControl, Capacity: 1, Droppable: false},
|
|
{Channel: SyntheticChannelRouteControl, Capacity: 1, Droppable: false},
|
|
{Channel: SyntheticChannelTelemetry, Capacity: 1, Droppable: true},
|
|
},
|
|
})
|
|
}
|
|
|
|
func testRelayEnvelope(channel string, messageType string, sequence uint64) SyntheticEnvelope {
|
|
route := testRoute("route-relay-scheduler", []string{"node-a", "node-r", "node-b"})
|
|
envelope := testEnvelope(route, "node-a", "node-r")
|
|
envelope.Channel = channel
|
|
envelope.MessageType = messageType
|
|
envelope.Sequence = sequence
|
|
return envelope
|
|
}
|