|
1 | 1 | # The cold path, on a schedule: a node with no identity enrolls with the |
2 | 2 | # control plane, authenticates to a router, finds a service on the mesh and |
3 | | -# speaks MCP to it through the mesh, then exits. The long-lived canaries only |
4 | | -# do this when a pod restarts, so between rollouts nobody re-checks that a new |
5 | | -# user could still join. Each run enrolls a fresh identity; the control |
6 | | -# plane's --node-retention sweep reclaims the rows. |
| 3 | +# speaks MCP to it through the mesh, then reaches the services the SDK |
| 4 | +# canaries serve, and exits. The long-lived canaries only do this when a pod |
| 5 | +# restarts, so between rollouts nobody re-checks that a new user could still |
| 6 | +# join. Each run enrolls a fresh identity; the control plane's |
| 7 | +# --node-retention sweep reclaims the rows. |
7 | 8 | apiVersion: batch/v1 |
8 | 9 | kind: CronJob |
9 | 10 | metadata: |
|
19 | 20 | # One attempt: a retry would hide exactly the flakiness this exists to |
20 | 21 | # measure. A failed Job is the signal. |
21 | 22 | backoffLimit: 0 |
22 | | - # 180s to be ready plus 120s to reach a provider, with headroom. |
23 | | - activeDeadlineSeconds: 360 |
| 23 | + # 180s to be ready, 120s to reach a provider, 120s for the SDK |
| 24 | + # canaries, with headroom. |
| 25 | + activeDeadlineSeconds: 480 |
24 | 26 | template: |
25 | 27 | metadata: |
26 | 28 | labels: |
|
78 | 80 | # The everything canary's MCP server (sam-node-everything-template.yaml): |
79 | 81 | # a real server, so the node advertises it and initialize succeeds. |
80 | 82 | value: everything |
| 83 | + - name: SDK_SERVICES |
| 84 | + # What the SDK canaries publish (sam-sdk-canary-template.yaml), |
| 85 | + # each with a `greet` tool and an A2A endpoint. |
| 86 | + value: "greeter-js greeter-py" |
81 | 87 | resources: |
82 | 88 | requests: |
83 | 89 | cpu: 10m |
@@ -157,8 +163,57 @@ spec: |
157 | 163 | done |
158 | 164 | REACH_S=$(( $(date +%s) - REACH_START )) |
159 | 165 |
|
160 | | - printf '{"probe":"sam-cold-path","ok":true,"ready_s":%d,"router_latency_ms":%s,"connected_peers":%s,"reach_s":%d,"providers_tried":%d,"call_s":%s,"provider":"%s","elapsed_s":%d}\n' \ |
161 | | - "$READY_S" "${ROUTER_MS:-null}" "${PEERS:-null}" "$REACH_S" "$TRIED" "$CALL_S" "$PEER" "$(( $(date +%s) - START ))" |
| 166 | + # 4. A service an SDK member serves (sam-sdk-canary-template.yaml) |
| 167 | + # is reachable from a node the way an agent behind a node |
| 168 | + # reaches it: its MCP tool through the node's own MCP API |
| 169 | + # (call_remote_tool opens /sam/mcp/1.0.0 to the provider), and |
| 170 | + # its A2A endpoint through the egress proxy. The node's MCP |
| 171 | + # endpoint is stateful, so one session is opened first. |
| 172 | + MCP=http://localhost/mcp |
| 173 | + SESSION="" |
| 174 | + mcp_post() { |
| 175 | + curl -s --unix-socket "$SOCK" -o "$2" -D /tmp/mcp.hdr -X POST "$MCP" \ |
| 176 | + -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \ |
| 177 | + ${SESSION:+-H "Mcp-Session-Id: $SESSION"} -d "$1" |
| 178 | + } |
| 179 | + mcp_post '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"sam-probe","version":"0"}}}' /tmp/mcp-init.out \ |
| 180 | + || fail sdk "initialize on the node's MCP API failed" |
| 181 | + SESSION=$(grep -i '^mcp-session-id:' /tmp/mcp.hdr | tr -d '\r' | cut -d' ' -f2) |
| 182 | + [ -n "$SESSION" ] || fail sdk "the node's MCP API returned no session" |
| 183 | + mcp_post '{"jsonrpc":"2.0","method":"notifications/initialized"}' /dev/null |
| 184 | +
|
| 185 | + SDK_START=$(date +%s) |
| 186 | + SDK_PROVIDERS="" |
| 187 | + for svc in ${SDK_SERVICES}; do |
| 188 | + SDK_PEER="" |
| 189 | + LAST_ERR="" |
| 190 | + while [ -z "$SDK_PEER" ]; do |
| 191 | + if curl -sf --unix-socket "$SOCK" -o /tmp/sdk-providers.json \ |
| 192 | + "http://localhost/sam/service/discover?type=mcp&name=${svc}&timeout=20s"; then |
| 193 | + for p in $(grep -oE '"peer_id":"[^"]+"' /tmp/sdk-providers.json | cut -d'"' -f4); do |
| 194 | + mcp_post '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"call_remote_tool","arguments":{"peer_id":"'"$p"'","tool_name":"mcp://'"$svc"'/greet","arguments":{"name":"sam-probe"}}}}' /tmp/sdk-call.out |
| 195 | + if grep -q 'hello sam-probe' /tmp/sdk-call.out; then |
| 196 | + CODE=$(curl -s --unix-socket "$SOCK" -o /tmp/sdk-card.out -w '%{http_code}' "http://localhost/sam/${p}/a2a/${svc}/card") |
| 197 | + if [ "$CODE" = "200" ] && grep -q '"caller"' /tmp/sdk-card.out; then |
| 198 | + SDK_PEER=$p |
| 199 | + break |
| 200 | + fi |
| 201 | + LAST_ERR="${p}: a2a card HTTP ${CODE} $(head -c 120 /tmp/sdk-card.out | tr -d '"\n')" |
| 202 | + else |
| 203 | + LAST_ERR="${p}: $(head -c 160 /tmp/sdk-call.out | tr -d '"\n')" |
| 204 | + fi |
| 205 | + done |
| 206 | + fi |
| 207 | + [ -n "$SDK_PEER" ] && break |
| 208 | + [ $(( $(date +%s) - SDK_START )) -ge 120 ] && fail sdk "no provider of ${svc} answered greet and its card in 120s (last: ${LAST_ERR:-none discovered})" |
| 209 | + sleep 5 |
| 210 | + done |
| 211 | + SDK_PROVIDERS="${SDK_PROVIDERS}${SDK_PROVIDERS:+,}\"${svc}\":\"${SDK_PEER}\"" |
| 212 | + done |
| 213 | + SDK_S=$(( $(date +%s) - SDK_START )) |
| 214 | +
|
| 215 | + printf '{"probe":"sam-cold-path","ok":true,"ready_s":%d,"router_latency_ms":%s,"connected_peers":%s,"reach_s":%d,"providers_tried":%d,"call_s":%s,"provider":"%s","sdk_s":%d,"sdk_providers":{%s},"elapsed_s":%d}\n' \ |
| 216 | + "$READY_S" "${ROUTER_MS:-null}" "${PEERS:-null}" "$REACH_S" "$TRIED" "$CALL_S" "$PEER" "$SDK_S" "$SDK_PROVIDERS" "$(( $(date +%s) - START ))" |
162 | 217 | volumes: |
163 | 218 | - name: config-volume |
164 | 219 | configMap: |
|
0 commit comments