feat: wake reconciled sessions for queued dispatch

This commit is contained in:
2026-09-06 10:03:11 +00:00
parent 3f6fb89a41
commit ef43e9592c
7 changed files with 210 additions and 24 deletions
+6
View File
@@ -254,6 +254,12 @@ layer = "unit"
status = "implemented"
tests = ["internal/server/store/command_test.go:TestRecordCommandAcceptanceFencesGeneration_HP_DISPATCH_05"]
[[requirements]]
id = "HP-DISPATCH-06"
layer = "integration"
status = "implemented"
tests = ["test/integration/clientagent/clientagent_integration_test.go:TestControlQueueWakesReconciledSession_HP_DISPATCH_06"]
[[requirements]]
id = "HP-EVENT-01"
layer = "unit"
@@ -13,8 +13,11 @@ import (
"github.com/rvbox/rvbox/internal/agentproto"
"github.com/rvbox/rvbox/internal/client/agent"
"github.com/rvbox/rvbox/internal/domain"
"github.com/rvbox/rvbox/internal/server/control"
"github.com/rvbox/rvbox/internal/server/session"
"github.com/rvbox/rvbox/internal/server/store"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
)
@@ -137,3 +140,61 @@ func TestWebSocketDispatchAfterReconciliation_HP_DISPATCH_03(t *testing.T) {
t.Fatalf("command lifecycle = %d, %v", lifecycle, err)
}
}
func TestControlQueueWakesReconciledSession_HP_DISPATCH_06(t *testing.T) {
ctx := context.Background()
persistence, err := store.Open(ctx, store.Options{DataDir: filepath.Join(t.TempDir(), "state"), BusyTimeout: time.Second})
if err != nil {
t.Fatal(err)
}
defer persistence.Close()
registry := session.NewRegistry()
server := httptest.NewServer(&session.AgentServer{Store: persistence, Registry: registry, Path: "/v1/agent", Limits: agentproto.DefaultLimits()})
defer server.Close()
controlService, err := control.NewService(control.Options{Store: persistence, WakeClient: registry.Wake, CursorKey: []byte("0123456789abcdef0123456789abcdef")})
if err != nil {
t.Fatal(err)
}
address := "ws" + strings.TrimPrefix(server.URL, "http") + "/v1/agent"
transport, err := agent.DialWebSocket(ctx, address, nil)
if err != nil {
t.Fatal(err)
}
defer transport.Close()
hello := &rvboxv1.ClientHello{ClientId: "win-wake-client", SupportedProtocol: &rvboxv1.ProtocolRange{Major: 1, MinMinor: 0, MaxMinor: 0}, DaemonVersion: "test", Platform: rvboxv1.Platform_PLATFORM_WINDOWS, Architecture: "amd64", DaemonCwd: `C:\`, SupportedShells: []rvboxv1.ShellType{rvboxv1.ShellType_SHELL_POWERSHELL}, ClientInstanceId: "019c46f1-1d02-7000-8000-000000000076", MaxRunningCommands: 1, MaxQueuedCommands: 1, SentAt: timestamppb.Now()}
accepted, err := agent.Handshake(ctx, transport, hello, agentproto.DefaultLimits())
if err != nil {
t.Fatal(err)
}
if _, err := agent.Reconcile(ctx, transport, accepted, &rvboxv1.ReconcileSnapshot{}, agentproto.DefaultLimits()); err != nil {
t.Fatal(err)
}
issue, err := domain.ParseUUIDv7("019c46f1-1d02-7000-8000-000000000077")
if err != nil {
t.Fatal(err)
}
response, err := controlService.RunCommand(ctx, &rvboxv1.RunCommandRequest{TargetClientId: hello.GetClientId(), RequestId: issue.String(), Spec: &rvboxv1.ExecutionSpec{ShellType: rvboxv1.ShellType_SHELL_POWERSHELL, Source: &rvboxv1.ExecutionSpec_CommandText{CommandText: "Write-Output wake"}}})
if err != nil || response.GetIssueUuid() != issue.String() {
t.Fatalf("control queue response = %#v, %v", response, err)
}
readContext, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
encoded, err := transport.Read(readContext)
if err != nil {
t.Fatal(err)
}
envelope, err := agentproto.DecodeEnvelope(encoded, agentproto.DefaultLimits(), rvboxv1.Platform_PLATFORM_WINDOWS)
if err != nil || envelope.GetCommandDispatch() == nil || envelope.GetCommandDispatch().GetIssueUuid() != issue.String() {
t.Fatalf("woken dispatch = %#v, %v", envelope, err)
}
_, missingErr := controlService.RunCommand(ctx, &rvboxv1.RunCommandRequest{TargetClientId: "missing", RequestId: fixedIssueID(0x78), Spec: &rvboxv1.ExecutionSpec{ShellType: rvboxv1.ShellType_SHELL_POWERSHELL, Source: &rvboxv1.ExecutionSpec_CommandText{CommandText: "x"}}})
if status.Code(missingErr) != codes.NotFound {
t.Fatal("missing target did not return not found")
}
}
func fixedIssueID(last byte) string {
issue, _ := domain.ParseUUIDv7("019c46f1-1d02-7000-8000-000000000001")
issue[15] = last
return issue.String()
}