99 lines
4.3 KiB
Go
99 lines
4.3 KiB
Go
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
agentpb "git.ipao.vip/rogee/go-sip/gen/agent"
|
|
"git.ipao.vip/rogee/go-sip/internal/agent"
|
|
"git.ipao.vip/rogee/go-sip/internal/session"
|
|
"git.ipao.vip/rogee/go-sip/internal/store"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
"google.golang.org/grpc/test/bufconn"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
func TestRecordingFactsCrossRealMutualTLSUnaryTransportWithoutExternalOSS(t *testing.T) {
|
|
server, database, _, request, snapshot := recordingRPCFixture(t)
|
|
caPEM, caCert, caKey := testCertificate(t, nil, nil, true, nil, nil)
|
|
serverPEM, _, _ := testCertificate(t, caCert, caKey, false, []string{"dispatcher.local"}, nil)
|
|
clientPEM, clientCert, _ := testCertificate(t, caCert, caKey, false, []string{"agent.local"}, nil)
|
|
serverTLS, err := NewServerTLSConfig(caPEM.certPEM, serverPEM.certPEM, serverPEM.keyPEM)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
clientTLS, err := NewClientTLSConfig(caPEM.certPEM, clientPEM.certPEM, clientPEM.keyPEM, "dispatcher.local")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
server.TrustedFingerprints = map[string]struct{}{CertificateFingerprint(clientCert): {}}
|
|
var agentGeneration, activeGeneration atomic.Uint64
|
|
var fencedReports atomic.Int32
|
|
agentGeneration.Store(request.Meta.SessionGeneration)
|
|
activeGeneration.Store(request.Meta.SessionGeneration + 1)
|
|
server.AuthorizeSession = func(meta *agentpb.RequestMeta) error {
|
|
active := activeGeneration.Load()
|
|
if meta.SessionGeneration == active {
|
|
return nil
|
|
}
|
|
if meta.SessionGeneration > 0 && meta.SessionGeneration < active && active-meta.SessionGeneration == 1 {
|
|
fencedReports.Add(1)
|
|
agentGeneration.Store(active)
|
|
return session.ErrGenerationTransition
|
|
}
|
|
return store.ErrCommandConflict
|
|
}
|
|
listener := bufconn.Listen(1 << 20)
|
|
grpcServer := grpc.NewServer(grpc.Creds(credentials.NewTLS(serverTLS)))
|
|
agentpb.RegisterAgentControlServiceServer(grpcServer, server)
|
|
go func() { _ = grpcServer.Serve(listener) }()
|
|
t.Cleanup(func() { grpcServer.Stop(); _ = listener.Close() })
|
|
conn, err := grpc.NewClient("bufnet",
|
|
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }),
|
|
grpc.WithTransportCredentials(credentials.NewTLS(clientTLS)),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = conn.Close() })
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
client := agent.RecordingClient{
|
|
Client: agentpb.NewAgentControlServiceClient(conn), DispatcherID: request.DispatcherId, TenantID: request.TenantId, SourceEventID: request.SourceEventId,
|
|
Session: func(context.Context) (*agentpb.RequestMeta, error) {
|
|
meta := proto.Clone(request.Meta).(*agentpb.RequestMeta)
|
|
meta.SessionGeneration = agentGeneration.Load()
|
|
return meta, nil
|
|
},
|
|
}
|
|
grant, err := client.RequestUpload(ctx, request.Asset, request.UploadId)
|
|
if err != nil || grant.GetBucket() != "mock-bucket" {
|
|
t.Fatalf("mTLS Agent request did not bind Dispatcher-owned bucket: bucket=%q err=%v", grant.GetBucket(), err)
|
|
}
|
|
activeGeneration.Add(1)
|
|
if err := client.ReportEnded(ctx); err != nil {
|
|
t.Fatalf("mTLS Agent end did not durably acknowledge original execution: %v", err)
|
|
}
|
|
observation := &agentpb.UploadObservation{UploadId: request.UploadId, RecordingId: request.Asset.AssetId, PutStatusCode: 200, SizeBytes: request.Asset.SizeBytes, ChecksumSha256: request.Asset.ChecksumSha256}
|
|
activeGeneration.Add(1)
|
|
resultID, err := client.ReportFinal(ctx, recordingResultPayload(t, snapshot, grant), observation)
|
|
if err != nil || resultID == "" {
|
|
t.Fatalf("mTLS Agent final result not persisted: fact=%q err=%v", resultID, err)
|
|
}
|
|
if fencedReports.Load() != 3 || agentGeneration.Load() != activeGeneration.Load() {
|
|
t.Fatalf("three mTLS recording facts did not cross the verified Agent session cutovers: fenced=%d current=%d", fencedReports.Load(), agentGeneration.Load())
|
|
}
|
|
if request.Meta.OperationId != "request-1" || request.Meta.IdempotencyKey != "request-1" {
|
|
t.Fatal("concurrent Agent session metadata was mutated by the recording client")
|
|
}
|
|
outbox, err := database.ListPendingOutbox(request.DispatcherId)
|
|
if err != nil || len(outbox) != 2 || outbox[0].EventType != "call.execute" || outbox[1].EventType != "call.execute.result" {
|
|
t.Fatalf("gRPC facts did not produce one ACK and one result: outbox=%+v err=%v", outbox, err)
|
|
}
|
|
}
|