Skip to content

Commit 41f5132

Browse files
authored
Merge pull request #258 from aojea/harden
Improve Inference: Expose OpenAI endpoint and Improve discovery
2 parents 65163ff + 9b5018e commit 41f5132

31 files changed

Lines changed: 3863 additions & 117 deletions

‎.github/workflows/deploy.yaml‎

Lines changed: 189 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -520,12 +520,10 @@ jobs:
520520
--external-addr=/ip4/${EXTERNAL_IP}/udp/4501/quic-v1'
521521
522522
523-
- name: Deploy Canaries
523+
- name: Deploy COP Canary
524524
env:
525525
VAR_ENV_NAME: ${{ vars.ENV_NAME }}
526526
VAR_IMAGE_TAG: ${{ env.IMAGE_TAG }}
527-
VAR_GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
528-
VAR_HF_TOKEN: ${{ secrets.HF_TOKEN }}
529527
run: |
530528
print_rollout_diagnostics() {
531529
local namespace="$1"
@@ -554,44 +552,89 @@ jobs:
554552
export CANARY_NAMESPACE="sam-canary-${ENV_NAME}"
555553
export NAMESPACE="sam-${ENV_NAME}"
556554
export IMAGE_TAG="${VAR_IMAGE_TAG}"
557-
export GEMINI_API_KEY="${VAR_GEMINI_API_KEY}"
558-
export HF_TOKEN="${VAR_HF_TOKEN}"
559-
560-
# Read and indent banana bot playground script for the ConfigMap template
561-
export BANANA_BOT_SCRIPT=$(cat site/content/docs/snippets/banana_bot_playground.py | sed 's/^/ /')
562-
# Provision the openclaw-secret, preserving the gateway-token if it exists
563-
GATEWAY_TOKEN=$(kubectl get secret openclaw-secret-${ENV_NAME} -n ${CANARY_NAMESPACE} -o jsonpath="{.data.gateway-token}" 2>/dev/null | base64 -d || openssl rand -hex 16)
564-
kubectl create secret generic openclaw-secret-${ENV_NAME} \
565-
--namespace=${CANARY_NAMESPACE} \
566-
--from-literal=gateway-token="${GATEWAY_TOKEN}" \
567-
--from-literal=gemini-api-key="${GEMINI_API_KEY}" \
568-
--dry-run=client -o yaml | kubectl apply -f -
569-
570-
kubectl create secret generic vllm-secret-${ENV_NAME} \
571-
--namespace=${CANARY_NAMESPACE} \
572-
--from-literal=hf-token="${HF_TOKEN}" \
573-
--dry-run=client -o yaml | kubectl apply -f -
574555
575-
envsubst < .github/k8s/sam-node-template.yaml | kubectl apply -f -
576-
envsubst < .github/k8s/sam-node-openclaw-template.yaml | kubectl apply -f -
577-
envsubst < .github/k8s/sam-node-vllm-template.yaml | kubectl apply -f -
578-
envsubst < .github/k8s/sam-node-everything-template.yaml | kubectl apply -f -
579556
envsubst < .github/k8s/sam-node-cop-template.yaml | kubectl apply -f -
580-
envsubst < .github/k8s/sam-box-canary-template.yaml | kubectl apply -f -
581-
582557
kubectl rollout status deployment/cop-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=120s || {
583558
echo "Cop Canary Deployment failed!"
584559
print_rollout_diagnostics "${CANARY_NAMESPACE}" "cop-canary-${ENV_NAME}" "app=cop-canary-${ENV_NAME}"
585560
exit 1
586561
}
587562
563+
- name: Deploy SAM Box Canary
564+
env:
565+
VAR_ENV_NAME: ${{ vars.ENV_NAME }}
566+
VAR_IMAGE_TAG: ${{ env.IMAGE_TAG }}
567+
run: |
568+
print_rollout_diagnostics() {
569+
local namespace="$1"
570+
local deployment="$2"
571+
local selector="$3"
572+
573+
echo "Collecting diagnostics for deployment/${deployment} in namespace ${namespace}..."
574+
575+
kubectl describe deployment/${deployment} -n ${namespace} || true
576+
kubectl get pods -n ${namespace} -l "${selector}" -o wide || true
577+
kubectl describe pods -n ${namespace} -l "${selector}" || true
578+
kubectl get events -n ${namespace} --sort-by=.lastTimestamp || true
579+
580+
for pod in $(kubectl get pods -n ${namespace} -l "${selector}" -o name 2>/dev/null); do
581+
echo "==== Describe ${pod} ===="
582+
kubectl describe -n ${namespace} "${pod}" || true
583+
584+
for container in $(kubectl get -n ${namespace} "${pod}" -o jsonpath='{.spec.containers[*].name}' 2>/dev/null); do
585+
echo "==== Logs for ${pod} container ${container} ===="
586+
kubectl logs -n ${namespace} "${pod#pod/}" -c "${container}" --tail=-1 || true
587+
done
588+
done
589+
}
590+
591+
export ENV_NAME="${VAR_ENV_NAME}"
592+
export CANARY_NAMESPACE="sam-canary-${ENV_NAME}"
593+
export NAMESPACE="sam-${ENV_NAME}"
594+
export IMAGE_TAG="${VAR_IMAGE_TAG}"
595+
596+
envsubst < .github/k8s/sam-box-canary-template.yaml | kubectl apply -f -
588597
kubectl rollout status deployment/box-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=120s || {
589598
echo "sam-box Canary Deployment failed!"
590599
print_rollout_diagnostics "${CANARY_NAMESPACE}" "box-canary-${ENV_NAME}" "app=box-canary-${ENV_NAME}"
591600
exit 1
592601
}
593602
594-
603+
- name: Deploy SAM Node Canary
604+
env:
605+
VAR_ENV_NAME: ${{ vars.ENV_NAME }}
606+
VAR_IMAGE_TAG: ${{ env.IMAGE_TAG }}
607+
run: |
608+
print_rollout_diagnostics() {
609+
local namespace="$1"
610+
local deployment="$2"
611+
local selector="$3"
612+
613+
echo "Collecting diagnostics for deployment/${deployment} in namespace ${namespace}..."
614+
615+
kubectl describe deployment/${deployment} -n ${namespace} || true
616+
kubectl get pods -n ${namespace} -l "${selector}" -o wide || true
617+
kubectl describe pods -n ${namespace} -l "${selector}" || true
618+
kubectl get events -n ${namespace} --sort-by=.lastTimestamp || true
619+
620+
for pod in $(kubectl get pods -n ${namespace} -l "${selector}" -o name 2>/dev/null); do
621+
echo "==== Describe ${pod} ===="
622+
kubectl describe -n ${namespace} "${pod}" || true
623+
624+
for container in $(kubectl get -n ${namespace} "${pod}" -o jsonpath='{.spec.containers[*].name}' 2>/dev/null); do
625+
echo "==== Logs for ${pod} container ${container} ===="
626+
kubectl logs -n ${namespace} "${pod#pod/}" -c "${container}" --tail=-1 || true
627+
done
628+
done
629+
}
630+
631+
export ENV_NAME="${VAR_ENV_NAME}"
632+
export CANARY_NAMESPACE="sam-canary-${ENV_NAME}"
633+
export NAMESPACE="sam-${ENV_NAME}"
634+
export IMAGE_TAG="${VAR_IMAGE_TAG}"
635+
export BANANA_BOT_SCRIPT=$(cat site/content/docs/snippets/banana_bot_playground.py | sed 's/^/ /')
636+
637+
envsubst < .github/k8s/sam-node-template.yaml | kubectl apply -f -
595638
kubectl rollout status deployment/sam-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=120s || {
596639
echo "Canary Deployment failed!"
597640
print_rollout_diagnostics "${CANARY_NAMESPACE}" "sam-canary-${ENV_NAME}" "app=sam-canary-${ENV_NAME}"
@@ -602,6 +645,49 @@ jobs:
602645
exit 1
603646
}
604647
648+
- name: Deploy OpenClaw Canary
649+
env:
650+
VAR_ENV_NAME: ${{ vars.ENV_NAME }}
651+
VAR_IMAGE_TAG: ${{ env.IMAGE_TAG }}
652+
VAR_GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
653+
run: |
654+
print_rollout_diagnostics() {
655+
local namespace="$1"
656+
local deployment="$2"
657+
local selector="$3"
658+
659+
echo "Collecting diagnostics for deployment/${deployment} in namespace ${namespace}..."
660+
661+
kubectl describe deployment/${deployment} -n ${namespace} || true
662+
kubectl get pods -n ${namespace} -l "${selector}" -o wide || true
663+
kubectl describe pods -n ${namespace} -l "${selector}" || true
664+
kubectl get events -n ${namespace} --sort-by=.lastTimestamp || true
665+
666+
for pod in $(kubectl get pods -n ${namespace} -l "${selector}" -o name 2>/dev/null); do
667+
echo "==== Describe ${pod} ===="
668+
kubectl describe -n ${namespace} "${pod}" || true
669+
670+
for container in $(kubectl get -n ${namespace} "${pod}" -o jsonpath='{.spec.containers[*].name}' 2>/dev/null); do
671+
echo "==== Logs for ${pod} container ${container} ===="
672+
kubectl logs -n ${namespace} "${pod#pod/}" -c "${container}" --tail=-1 || true
673+
done
674+
done
675+
}
676+
677+
export ENV_NAME="${VAR_ENV_NAME}"
678+
export CANARY_NAMESPACE="sam-canary-${ENV_NAME}"
679+
export NAMESPACE="sam-${ENV_NAME}"
680+
export IMAGE_TAG="${VAR_IMAGE_TAG}"
681+
export GEMINI_API_KEY="${VAR_GEMINI_API_KEY}"
682+
683+
GATEWAY_TOKEN=$(kubectl get secret openclaw-secret-${ENV_NAME} -n ${CANARY_NAMESPACE} -o jsonpath="{.data.gateway-token}" 2>/dev/null | base64 -d || openssl rand -hex 16)
684+
kubectl create secret generic openclaw-secret-${ENV_NAME} \
685+
--namespace=${CANARY_NAMESPACE} \
686+
--from-literal=gateway-token="${GATEWAY_TOKEN}" \
687+
--from-literal=gemini-api-key="${GEMINI_API_KEY}" \
688+
--dry-run=client -o yaml | kubectl apply -f -
689+
690+
envsubst < .github/k8s/sam-node-openclaw-template.yaml | kubectl apply -f -
605691
kubectl rollout status deployment/openclaw-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=120s || {
606692
echo "OpenClaw Canary Deployment failed!"
607693
print_rollout_diagnostics "${CANARY_NAMESPACE}" "openclaw-canary-${ENV_NAME}" "app=openclaw-canary-${ENV_NAME}"
@@ -612,6 +698,48 @@ jobs:
612698
exit 1
613699
}
614700
701+
- name: Deploy vLLM Canary
702+
if: vars.ENV_NAME != 'hub'
703+
env:
704+
VAR_ENV_NAME: ${{ vars.ENV_NAME }}
705+
VAR_IMAGE_TAG: ${{ env.IMAGE_TAG }}
706+
VAR_HF_TOKEN: ${{ secrets.HF_TOKEN }}
707+
run: |
708+
print_rollout_diagnostics() {
709+
local namespace="$1"
710+
local deployment="$2"
711+
local selector="$3"
712+
713+
echo "Collecting diagnostics for deployment/${deployment} in namespace ${namespace}..."
714+
715+
kubectl describe deployment/${deployment} -n ${namespace} || true
716+
kubectl get pods -n ${namespace} -l "${selector}" -o wide || true
717+
kubectl describe pods -n ${namespace} -l "${selector}" || true
718+
kubectl get events -n ${namespace} --sort-by=.lastTimestamp || true
719+
720+
for pod in $(kubectl get pods -n ${namespace} -l "${selector}" -o name 2>/dev/null); do
721+
echo "==== Describe ${pod} ===="
722+
kubectl describe -n ${namespace} "${pod}" || true
723+
724+
for container in $(kubectl get -n ${namespace} "${pod}" -o jsonpath='{.spec.containers[*].name}' 2>/dev/null); do
725+
echo "==== Logs for ${pod} container ${container} ===="
726+
kubectl logs -n ${namespace} "${pod#pod/}" -c "${container}" --tail=-1 || true
727+
done
728+
done
729+
}
730+
731+
export ENV_NAME="${VAR_ENV_NAME}"
732+
export CANARY_NAMESPACE="sam-canary-${ENV_NAME}"
733+
export NAMESPACE="sam-${ENV_NAME}"
734+
export IMAGE_TAG="${VAR_IMAGE_TAG}"
735+
export HF_TOKEN="${VAR_HF_TOKEN}"
736+
737+
kubectl create secret generic vllm-secret-${ENV_NAME} \
738+
--namespace=${CANARY_NAMESPACE} \
739+
--from-literal=hf-token="${HF_TOKEN}" \
740+
--dry-run=client -o yaml | kubectl apply -f -
741+
742+
envsubst < .github/k8s/sam-node-vllm-template.yaml | kubectl apply -f -
615743
kubectl rollout status deployment/vllm-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=300s || {
616744
echo "vLLM Canary Deployment failed!"
617745
print_rollout_diagnostics "${CANARY_NAMESPACE}" "vllm-canary-${ENV_NAME}" "app=vllm-canary-${ENV_NAME}"
@@ -622,6 +750,40 @@ jobs:
622750
exit 1
623751
}
624752
753+
- name: Deploy Everything Canary
754+
env:
755+
VAR_ENV_NAME: ${{ vars.ENV_NAME }}
756+
VAR_IMAGE_TAG: ${{ env.IMAGE_TAG }}
757+
run: |
758+
print_rollout_diagnostics() {
759+
local namespace="$1"
760+
local deployment="$2"
761+
local selector="$3"
762+
763+
echo "Collecting diagnostics for deployment/${deployment} in namespace ${namespace}..."
764+
765+
kubectl describe deployment/${deployment} -n ${namespace} || true
766+
kubectl get pods -n ${namespace} -l "${selector}" -o wide || true
767+
kubectl describe pods -n ${namespace} -l "${selector}" || true
768+
kubectl get events -n ${namespace} --sort-by=.lastTimestamp || true
769+
770+
for pod in $(kubectl get pods -n ${namespace} -l "${selector}" -o name 2>/dev/null); do
771+
echo "==== Describe ${pod} ===="
772+
kubectl describe -n ${namespace} "${pod}" || true
773+
774+
for container in $(kubectl get -n ${namespace} "${pod}" -o jsonpath='{.spec.containers[*].name}' 2>/dev/null); do
775+
echo "==== Logs for ${pod} container ${container} ===="
776+
kubectl logs -n ${namespace} "${pod#pod/}" -c "${container}" --tail=-1 || true
777+
done
778+
done
779+
}
780+
781+
export ENV_NAME="${VAR_ENV_NAME}"
782+
export CANARY_NAMESPACE="sam-canary-${ENV_NAME}"
783+
export NAMESPACE="sam-${ENV_NAME}"
784+
export IMAGE_TAG="${VAR_IMAGE_TAG}"
785+
786+
envsubst < .github/k8s/sam-node-everything-template.yaml | kubectl apply -f -
625787
kubectl rollout status deployment/everything-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=120s || {
626788
echo "Everything Canary Deployment failed!"
627789
print_rollout_diagnostics "${CANARY_NAMESPACE}" "everything-canary-${ENV_NAME}" "app=everything-canary-${ENV_NAME}"

‎api/network.go‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
package api
1616

1717
import (
18+
"crypto/sha256"
19+
"encoding/hex"
1820
"fmt"
1921
"regexp"
2022
"strings"
@@ -43,6 +45,11 @@ const (
4345
// GossipControlPlaneSync is the GossipSub topic used by the control plane to sync cluster state.
4446
GossipControlPlaneSync = "/sam/control-plane/sync/v1"
4547

48+
// DiscoveryTopicPrefix is the GossipSub topic namespace for interest-scoped
49+
// service announcements (ServiceAnnounce messages). Full topics are built
50+
// with DiscoveryTopic; the version segment allows wire evolution.
51+
DiscoveryTopicPrefix = "/sam/discovery/v1"
52+
4653
// DefaultAudience is the default audience string used in OIDC token validation.
4754
DefaultAudience = "sam-mesh-audience"
4855
)
@@ -100,6 +107,37 @@ const (
100107
// This helps backward-compatibility with services that strictly distinguish
101108
// between a root path "/" and an empty path "".
102109
HeaderSamNoTrailingSlash = "X-Sam-No-Trailing-Slash"
110+
111+
// HeaderSamRequiredRegion constrains an inference request on the sidecar's
112+
// OpenAI-compatible endpoints (/v1/*) to providers whose declared region is
113+
// in the comma-separated list of continent codes (see ValidateRegion);
114+
// invalid codes are rejected with HTTP 400. It can only narrow what mesh
115+
// policy allows, never widen it. Absent means any region permitted by
116+
// policy.
117+
//
118+
// Reserved as part of the sidecar contract; enforced by the provider
119+
// scorer. Region declarations are routing hints until attested via the
120+
// node's Biscuit (see LabelRegion).
121+
HeaderSamRequiredRegion = "X-Sam-Required-Region"
122+
)
123+
124+
// ============================================================================
125+
// Well-Known Node Labels
126+
// ============================================================================
127+
//
128+
// Placement rule: anything two nodes must agree on to interoperate (topics,
129+
// headers, labels, wire caps, canonical value forms) is defined here in api/.
130+
// Behavioral tuning a node decides alone (intervals, TTLs, table bounds)
131+
// lives in the package that owns the behavior.
132+
133+
const (
134+
// LabelRegion is the operator-declared jurisdiction of a node, as a
135+
// continent code validated by ValidateRegion (e.g. "EU"). It is carried in
136+
// ServiceAnnounce messages as a routing hint. Absent means the node makes
137+
// no region claim, and consumers with a region requirement will never
138+
// select it. Operator-declared labels always take precedence over
139+
// runtime-derived values.
140+
LabelRegion = "region"
103141
)
104142

105143
// ============================================================================
@@ -159,6 +197,23 @@ func ServiceTypeToString(t ServiceType) (string, error) {
159197
}
160198
}
161199

200+
// DiscoveryTopic returns the GossipSub topic for announcements about one
201+
// routing key (a model ID for inference, a tool name for MCP). Keys are
202+
// hashed so topic names stay bounded; consumers match exact keys from the
203+
// ServiceAnnounce payload, so hash collisions only merge announcement
204+
// streams, never routing decisions.
205+
func DiscoveryTopic(t ServiceType, key string) (string, error) {
206+
typeStr, err := ServiceTypeToString(t)
207+
if err != nil {
208+
return "", err
209+
}
210+
if key == "" {
211+
return "", fmt.Errorf("discovery topic key cannot be empty")
212+
}
213+
sum := sha256.Sum256([]byte(key))
214+
return fmt.Sprintf("%s/%s/%s", DiscoveryTopicPrefix, typeStr, hex.EncodeToString(sum[:8])), nil
215+
}
216+
162217
// ============================================================================
163218
// Parsing & Routing Utilities
164219
// ============================================================================

0 commit comments

Comments
 (0)