From 85c61a474fa98e51da0daff6c84901084148544c Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 16 May 2026 00:24:21 +0300 Subject: [PATCH] Add fabric session smoke CLI --- .../rap-node-agent/cmd/rap-host-agent/main.go | 66 +++++++++++++++++++ .../DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md | 1 + 2 files changed, 67 insertions(+) diff --git a/agents/rap-node-agent/cmd/rap-host-agent/main.go b/agents/rap-node-agent/cmd/rap-host-agent/main.go index 971f19c..cde5873 100644 --- a/agents/rap-node-agent/cmd/rap-host-agent/main.go +++ b/agents/rap-node-agent/cmd/rap-host-agent/main.go @@ -14,7 +14,9 @@ import ( "time" "github.com/example/remote-access-platform/agents/rap-node-agent/internal/agent" + "github.com/example/remote-access-platform/agents/rap-node-agent/internal/fabricproto" "github.com/example/remote-access-platform/agents/rap-node-agent/internal/hostagent" + "github.com/example/remote-access-platform/agents/rap-node-agent/internal/mesh" ) type installCommandConfig struct { @@ -79,6 +81,10 @@ func main() { if err := runUpdateHostAgentLoop(ctx, os.Args[2:]); err != nil { log.Fatalf("update-host-agent-loop failed: %v", err) } + case "fabric-session-smoke": + if err := runFabricSessionSmoke(ctx, os.Args[2:]); err != nil { + log.Fatalf("fabric-session-smoke failed: %v", err) + } default: usage() os.Exit(2) @@ -110,6 +116,65 @@ func applyStagedSelfUpdate() { _ = os.Remove(backup) } +func runFabricSessionSmoke(ctx context.Context, args []string) error { + fs := flag.NewFlagSet("fabric-session-smoke", flag.ContinueOnError) + var meshURL string + var token string + var timeoutSeconds int + var payload string + fs.StringVar(&meshURL, "mesh-url", getenv("RAP_MESH_SMOKE_URL", ""), "Mesh base URL, for example http://node:19131.") + fs.StringVar(&token, "token", getenv("RAP_FABRIC_SESSION_TOKEN", ""), "Fabric session token starting with rap_fsn_.") + fs.IntVar(&timeoutSeconds, "timeout-seconds", getenvInt("RAP_FABRIC_SESSION_SMOKE_TIMEOUT_SECONDS", 5), "Smoke timeout in seconds.") + fs.StringVar(&payload, "payload", getenv("RAP_FABRIC_SESSION_SMOKE_PAYLOAD", "rap-fabric-session-smoke"), "Ping payload.") + if err := fs.Parse(args); err != nil { + return err + } + if strings.TrimSpace(meshURL) == "" { + return fmt.Errorf("mesh-url is required") + } + if strings.TrimSpace(token) == "" { + return fmt.Errorf("token is required") + } + if timeoutSeconds <= 0 { + timeoutSeconds = 5 + } + smokeCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + defer cancel() + startedAt := time.Now() + response, err := mesh.NewClient(meshURL).SendFabricSessionFrame(smokeCtx, mesh.FabricSessionDialOptions{ + Token: token, + Timeout: time.Duration(timeoutSeconds) * time.Second, + }, fabricproto.Frame{ + Type: fabricproto.FramePing, + Sequence: uint64(startedAt.UnixNano()), + Payload: []byte(payload), + }) + duration := time.Since(startedAt) + result := map[string]any{ + "schema_version": "rap.fabric_session_smoke_result.v1", + "mesh_url": strings.TrimSpace(meshURL), + "ok": err == nil && response.Type == fabricproto.FramePong && string(response.Payload) == payload, + "latency_ms": duration.Milliseconds(), + "response_type": response.Type, + "sequence": response.Sequence, + } + if err != nil { + result["error"] = err.Error() + } + encoded, marshalErr := json.MarshalIndent(result, "", " ") + if marshalErr != nil { + return marshalErr + } + fmt.Println(string(encoded)) + if err != nil { + return err + } + if response.Type != fabricproto.FramePong || string(response.Payload) != payload { + return fmt.Errorf("fabric session smoke returned unexpected response type=%d payload=%q", response.Type, string(response.Payload)) + } + return nil +} + func runInstallLinux(ctx context.Context, args []string) error { fs := flag.NewFlagSet("install-linux", flag.ContinueOnError) cfg := hostagent.LinuxInstallConfig{} @@ -846,6 +911,7 @@ func usage() { rap-host-agent update-host-agent-loop -backend-url URL -cluster-id ID -state-dir DIR rap-host-agent monitor-loop -backend-url URL -cluster-id ID -state-dir DIR --watch-container NAME rap-host-agent monitor-once -backend-url URL -cluster-id ID -state-dir DIR --watch-container NAME + rap-host-agent fabric-session-smoke -mesh-url URL -token rap_fsn_TOKEN rap-host-agent update -backend-url URL -cluster-id ID -node-id ID [-container-name NAME] rap-host-agent update-loop -backend-url URL -cluster-id ID -node-id ID [-container-name NAME] rap-host-agent status [-container-name NAME]`) diff --git a/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md b/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md index 7d9b5c7..eb0f46a 100644 --- a/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md +++ b/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md @@ -259,6 +259,7 @@ Deliverables: Status: started with a transport-neutral `io.Reader`/`io.Writer` frame loop, WebSocket frame adapter in `agents/rap-node-agent/internal/fabricproto`, and a gated/authenticated mesh smoke endpoint/client at `/mesh/v1/fabric/session/ws`. +`rap-host-agent fabric-session-smoke` provides the first operator smoke command. Deliverables: