Refactor RDP proxy handling and update related tests
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/example/remote-access-platform/agents/rap-node-agent/internal/fabricproto"
|
||||
)
|
||||
|
||||
func TestQUICSyntheticTransportReroutesOnConnectFailure(t *testing.T) {
|
||||
transport := newFakeSyntheticFabricTransport()
|
||||
transport.failConnect["quic://dead.example.test:19443"] = true
|
||||
transport.responses["quic://fast.example.test:19443"] = testSyntheticAckEnvelope("route-1", 1)
|
||||
forward := NewQUICSyntheticTransportFromRouteSets(map[string]FabricRouteSet{
|
||||
"node-b": FabricRouteSetForTransportTargets("cluster-a", "node-a", "node-b", []FabricTransportTarget{
|
||||
{EndpointID: "dead", PeerID: "node-b", Endpoint: "quic://dead.example.test:19443", Transport: "quic"},
|
||||
{EndpointID: "fast", PeerID: "node-b", Endpoint: "quic://fast.example.test:19443", Transport: "quic"},
|
||||
}),
|
||||
}, transport)
|
||||
forward.Timeout = time.Second
|
||||
|
||||
ack, err := forward.SendSynthetic(context.Background(), "node-b", testSyntheticEnvelope("route-1", 1))
|
||||
if err != nil {
|
||||
t.Fatalf("send synthetic: %v", err)
|
||||
}
|
||||
if ack.RouteID != "route-1" || ack.MessageType != SyntheticMessageRouteHealthAck {
|
||||
t.Fatalf("ack = %+v", ack)
|
||||
}
|
||||
if got := transport.connectCount("quic://dead.example.test:19443"); got != 1 {
|
||||
t.Fatalf("dead connect count = %d, want 1", got)
|
||||
}
|
||||
if got := transport.connectCount("quic://fast.example.test:19443"); got != 1 {
|
||||
t.Fatalf("fast connect count = %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQUICFabricServerHandlesSyntheticFrames(t *testing.T) {
|
||||
server, err := StartQUICFabricServer(context.Background(), QUICFabricServerConfig{
|
||||
ListenAddr: "127.0.0.1:0",
|
||||
TLSConfig: testQUICTLSConfig(t),
|
||||
SyntheticForwardHandler: func(_ context.Context, envelope SyntheticEnvelope) (SyntheticEnvelope, error) {
|
||||
return testSyntheticAckEnvelope(envelope.RouteID, envelope.Sequence), nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("start quic fabric server: %v", err)
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
session, err := NewQUICFabricTransport(nil).Connect(ctx, FabricTransportTarget{
|
||||
Endpoint: server.Addr().String(),
|
||||
TLSConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{fabricQUICNextProto},
|
||||
},
|
||||
Timeout: time.Second,
|
||||
InboundBuffer: 4,
|
||||
ErrorBuffer: 4,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("connect: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
payload, err := json.Marshal(testSyntheticEnvelope("route-1", 7))
|
||||
if err != nil {
|
||||
t.Fatalf("marshal envelope: %v", err)
|
||||
}
|
||||
if err := session.Send(ctx, fabricproto.Frame{
|
||||
Type: fabricproto.FrameData,
|
||||
TrafficClass: fabricproto.TrafficClassReliable,
|
||||
StreamID: SyntheticForwardQUICStreamID,
|
||||
Sequence: 42,
|
||||
Payload: payload,
|
||||
}); err != nil {
|
||||
t.Fatalf("send synthetic frame: %v", err)
|
||||
}
|
||||
select {
|
||||
case frame := <-session.Frames():
|
||||
if frame.StreamID != SyntheticForwardQUICStreamID || frame.Sequence != 42 {
|
||||
t.Fatalf("frame = %+v", frame)
|
||||
}
|
||||
ack, err := decodeQUICSyntheticForwardResponse(frame.Payload)
|
||||
if err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if ack.RouteID != "route-1" || ack.MessageType != SyntheticMessageRouteHealthAck || ack.Sequence != 7 {
|
||||
t.Fatalf("ack = %+v", ack)
|
||||
}
|
||||
case err := <-session.Errors():
|
||||
t.Fatalf("session error: %v", err)
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
type fakeSyntheticFabricTransport struct {
|
||||
mu sync.Mutex
|
||||
failConnect map[string]bool
|
||||
responses map[string]SyntheticEnvelope
|
||||
connects map[string]int
|
||||
}
|
||||
|
||||
func newFakeSyntheticFabricTransport() *fakeSyntheticFabricTransport {
|
||||
return &fakeSyntheticFabricTransport{
|
||||
failConnect: map[string]bool{},
|
||||
responses: map[string]SyntheticEnvelope{},
|
||||
connects: map[string]int{},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *fakeSyntheticFabricTransport) Connect(_ context.Context, target FabricTransportTarget) (FabricTransportSession, error) {
|
||||
endpoint := target.Endpoint
|
||||
t.mu.Lock()
|
||||
t.connects[endpoint]++
|
||||
fail := t.failConnect[endpoint]
|
||||
response := t.responses[endpoint]
|
||||
t.mu.Unlock()
|
||||
if fail {
|
||||
return nil, ErrSyntheticPeerUnavailable
|
||||
}
|
||||
return &fakeSyntheticFabricSession{
|
||||
response: response,
|
||||
frames: make(chan fabricproto.Frame, 16),
|
||||
errors: make(chan error, 1),
|
||||
done: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *fakeSyntheticFabricTransport) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *fakeSyntheticFabricTransport) connectCount(endpoint string) int {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
return t.connects[endpoint]
|
||||
}
|
||||
|
||||
type fakeSyntheticFabricSession struct {
|
||||
response SyntheticEnvelope
|
||||
frames chan fabricproto.Frame
|
||||
errors chan error
|
||||
done chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (s *fakeSyntheticFabricSession) Send(_ context.Context, frame fabricproto.Frame) error {
|
||||
if frame.Type != fabricproto.FrameData {
|
||||
return nil
|
||||
}
|
||||
responsePayload, _ := json.Marshal(quicSyntheticForwardResponse{Envelope: s.response})
|
||||
go func() {
|
||||
select {
|
||||
case <-s.done:
|
||||
case s.frames <- fabricproto.Frame{
|
||||
Type: fabricproto.FrameData,
|
||||
TrafficClass: frame.TrafficClass,
|
||||
StreamID: frame.StreamID,
|
||||
Sequence: frame.Sequence,
|
||||
Payload: responsePayload,
|
||||
}:
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *fakeSyntheticFabricSession) Frames() <-chan fabricproto.Frame {
|
||||
return s.frames
|
||||
}
|
||||
|
||||
func (s *fakeSyntheticFabricSession) Errors() <-chan error {
|
||||
return s.errors
|
||||
}
|
||||
|
||||
func (s *fakeSyntheticFabricSession) Close() error {
|
||||
s.once.Do(func() {
|
||||
close(s.done)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *fakeSyntheticFabricSession) Closed() bool {
|
||||
select {
|
||||
case <-s.done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func testSyntheticEnvelope(routeID string, sequence uint64) SyntheticEnvelope {
|
||||
now := time.Now().UTC()
|
||||
return SyntheticEnvelope{
|
||||
ProtocolVersion: ProtocolVersion,
|
||||
RouteID: routeID,
|
||||
ClusterID: "cluster-a",
|
||||
From: PeerIdentity{ClusterID: "cluster-a", NodeID: "node-a"},
|
||||
To: PeerIdentity{ClusterID: "cluster-a", NodeID: "node-b"},
|
||||
Channel: SyntheticChannelFabricControl,
|
||||
MessageType: SyntheticMessageRouteHealth,
|
||||
TTL: 8,
|
||||
HopCount: 1,
|
||||
Visited: []string{"node-a"},
|
||||
Sequence: sequence,
|
||||
SentAt: now,
|
||||
}
|
||||
}
|
||||
|
||||
func testSyntheticAckEnvelope(routeID string, sequence uint64) SyntheticEnvelope {
|
||||
ack := testSyntheticEnvelope(routeID, sequence)
|
||||
ack.From = PeerIdentity{ClusterID: "cluster-a", NodeID: "node-b"}
|
||||
ack.To = PeerIdentity{ClusterID: "cluster-a", NodeID: "node-a"}
|
||||
ack.MessageType = SyntheticMessageRouteHealthAck
|
||||
ack.Visited = []string{"node-a", "node-b"}
|
||||
return ack
|
||||
}
|
||||
Reference in New Issue
Block a user