152 lines
5.1 KiB
Go
152 lines
5.1 KiB
Go
package mesh
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestFabricChannelRouterOpensOnBestRoute(t *testing.T) {
|
|
router := NewFabricChannelRouter(FabricChannelRouterConfig{})
|
|
now := time.Now()
|
|
channel, event, err := router.OpenChannel(testFabricChannelSpec(FabricChannelTargetNode, "node-b"), FabricRouteSet{
|
|
TargetKind: FabricChannelTargetNode,
|
|
TargetID: "node-b",
|
|
Primary: testFabricRoute("route-slow", "node-b", 80, 100, 0, true),
|
|
WarmStandby: []FabricRoute{
|
|
testFabricRoute("route-fast", "node-b", 15, 100, 0, true),
|
|
},
|
|
}, now)
|
|
if err != nil {
|
|
t.Fatalf("open channel: %v", err)
|
|
}
|
|
if channel.RouteID != "route-fast" || channel.State != FabricChannelOpen {
|
|
t.Fatalf("channel = %+v, want route-fast open", channel)
|
|
}
|
|
if event.Type != FabricChannelRouteEventOpened || event.NextRoute.RouteID != "route-fast" {
|
|
t.Fatalf("event = %+v", event)
|
|
}
|
|
}
|
|
|
|
func TestFabricChannelRouterReroutesOnSlowAck(t *testing.T) {
|
|
router := NewFabricChannelRouter(FabricChannelRouterConfig{MaxAckLatencyMs: 30})
|
|
now := time.Now()
|
|
routeSet := FabricRouteSet{
|
|
TargetKind: FabricChannelTargetNode,
|
|
TargetID: "node-b",
|
|
Primary: testFabricRoute("route-primary", "node-b", 10, 100, 0, true),
|
|
WarmStandby: []FabricRoute{
|
|
testFabricRoute("route-standby", "node-b", 20, 100, 0, true),
|
|
},
|
|
}
|
|
channel := FabricChannel{
|
|
Spec: testFabricChannelSpec(FabricChannelTargetNode, "node-b"),
|
|
State: FabricChannelOpen,
|
|
RouteID: "route-primary",
|
|
OpenedAt: now.Add(-time.Minute),
|
|
}
|
|
updated, event, err := router.ObserveChannel(channel, routeSet, FabricChannelObservation{
|
|
ChannelID: channel.Spec.ChannelID,
|
|
RouteID: channel.RouteID,
|
|
AckLatencyMs: 120,
|
|
BytesSent: 4096,
|
|
FramesSent: 4,
|
|
}, now)
|
|
if err != nil {
|
|
t.Fatalf("observe channel: %v", err)
|
|
}
|
|
if event.Type != FabricChannelRouteEventReroute || event.Reason != "ack_latency_threshold" {
|
|
t.Fatalf("event = %+v", event)
|
|
}
|
|
if updated.RouteID != "route-standby" || updated.RerouteCount != 1 || updated.BytesSent != 4096 || updated.FramesSent != 4 {
|
|
t.Fatalf("updated = %+v", updated)
|
|
}
|
|
}
|
|
|
|
func TestFabricChannelRouterReroutesPoolTargetOnFailure(t *testing.T) {
|
|
router := NewFabricChannelRouter(FabricChannelRouterConfig{})
|
|
now := time.Now()
|
|
routeSet := FabricRouteSet{
|
|
TargetKind: FabricChannelTargetPool,
|
|
TargetID: "pool-egress",
|
|
Primary: testFabricPoolRoute("route-node-b", "node-b", 10, true),
|
|
WarmStandby: []FabricRoute{
|
|
testFabricPoolRoute("route-node-c", "node-c", 20, true),
|
|
},
|
|
}
|
|
channel := FabricChannel{
|
|
Spec: testFabricChannelSpec(FabricChannelTargetPool, "pool-egress"),
|
|
State: FabricChannelOpen,
|
|
RouteID: "route-node-b",
|
|
TargetNode: "node-b",
|
|
OpenedAt: now.Add(-time.Minute),
|
|
}
|
|
updated, event, err := router.ObserveChannel(channel, routeSet, FabricChannelObservation{
|
|
ChannelID: channel.Spec.ChannelID,
|
|
RouteID: channel.RouteID,
|
|
Failed: true,
|
|
Reason: "target_failed",
|
|
}, now)
|
|
if err != nil {
|
|
t.Fatalf("observe channel: %v", err)
|
|
}
|
|
if event.Type != FabricChannelRouteEventReroute || event.PreviousRoute.RouteID != "route-node-b" || event.NextRoute.RouteID != "route-node-c" {
|
|
t.Fatalf("event = %+v", event)
|
|
}
|
|
if updated.TargetNode != "node-c" || updated.RouteID != "route-node-c" {
|
|
t.Fatalf("updated = %+v", updated)
|
|
}
|
|
}
|
|
|
|
func TestFabricChannelRouterSuppressesRerouteInsideHysteresis(t *testing.T) {
|
|
router := NewFabricChannelRouter(FabricChannelRouterConfig{MaxAckLatencyMs: 30, MinRerouteInterval: time.Minute})
|
|
now := time.Now()
|
|
channel := FabricChannel{
|
|
Spec: testFabricChannelSpec(FabricChannelTargetNode, "node-b"),
|
|
State: FabricChannelOpen,
|
|
RouteID: "route-primary",
|
|
LastReroute: now.Add(-10 * time.Second),
|
|
}
|
|
updated, event, err := router.ObserveChannel(channel, FabricRouteSet{
|
|
TargetKind: FabricChannelTargetNode,
|
|
TargetID: "node-b",
|
|
Primary: testFabricRoute("route-primary", "node-b", 10, 100, 0, true),
|
|
WarmStandby: []FabricRoute{testFabricRoute("route-standby", "node-b", 20, 100, 0, true)},
|
|
}, FabricChannelObservation{AckLatencyMs: 120}, now)
|
|
if err != nil {
|
|
t.Fatalf("observe channel: %v", err)
|
|
}
|
|
if event.Type != FabricChannelRouteEventNone || updated.RouteID != "route-primary" {
|
|
t.Fatalf("event=%+v updated=%+v", event, updated)
|
|
}
|
|
}
|
|
|
|
func testFabricChannelSpec(kind FabricChannelTargetKind, targetID string) FabricChannelSpec {
|
|
return FabricChannelSpec{
|
|
ChannelID: "channel-1",
|
|
ClusterID: "cluster-1",
|
|
SourceNodeID: "node-a",
|
|
TargetKind: kind,
|
|
TargetID: targetID,
|
|
}
|
|
}
|
|
|
|
func testFabricRoute(routeID string, destination string, latency int, capacity int, active int, healthy bool) FabricRoute {
|
|
return FabricRoute{
|
|
RouteID: routeID,
|
|
ClusterID: "cluster-1",
|
|
SourceNodeID: "node-a",
|
|
DestinationNodeID: destination,
|
|
Hops: []FabricRouteHop{{NodeID: "node-a"}, {NodeID: destination}},
|
|
BaseLatencyMs: latency,
|
|
Capacity: capacity,
|
|
ActiveChannels: active,
|
|
Healthy: healthy,
|
|
}
|
|
}
|
|
|
|
func testFabricPoolRoute(routeID string, destination string, latency int, healthy bool) FabricRoute {
|
|
route := testFabricRoute(routeID, destination, latency, 100, 0, healthy)
|
|
route.PoolID = "pool-egress"
|
|
return route
|
|
}
|