Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8790de6038 |
@@ -0,0 +1,80 @@
|
|||||||
|
# ComfyUI — GPU-accelerated image generation on worker-1.
|
||||||
|
# Uses 1x V100 32GB (sm70). Freed by scaling ornith 2→1.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: comfyui
|
||||||
|
namespace: comfyui
|
||||||
|
labels:
|
||||||
|
app: comfyui
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: comfyui
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: comfyui
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: worker-1
|
||||||
|
runtimeClassName: nvidia
|
||||||
|
containers:
|
||||||
|
- name: comfyui
|
||||||
|
image: ghcr.io/ai-dock/comfyui:v2-cuda-12.1.1-base-22.04
|
||||||
|
ports:
|
||||||
|
- containerPort: 8188
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: NVIDIA_VISIBLE_DEVICES
|
||||||
|
value: "all"
|
||||||
|
- name: COMFYUI_FLAGS
|
||||||
|
value: "--listen 0.0.0.0 --port 8188"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "4"
|
||||||
|
memory: 8Gi
|
||||||
|
nvidia.com/gpu: "1"
|
||||||
|
limits:
|
||||||
|
cpu: "8"
|
||||||
|
memory: 16Gi
|
||||||
|
nvidia.com/gpu: "1"
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /workspace/ComfyUI/models
|
||||||
|
name: models
|
||||||
|
- mountPath: /workspace/ComfyUI/output
|
||||||
|
name: output
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 8188
|
||||||
|
periodSeconds: 10
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 8188
|
||||||
|
failureThreshold: 60
|
||||||
|
periodSeconds: 10
|
||||||
|
volumes:
|
||||||
|
- name: models
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: comfyui-models
|
||||||
|
- name: output
|
||||||
|
emptyDir: {}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: comfyui-models
|
||||||
|
namespace: comfyui
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: longhorn
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 50Gi
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: comfyui
|
||||||
|
namespace: comfyui
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
||||||
|
# WebSocket support for ComfyUI's live preview
|
||||||
|
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-set-headers: "Upgrade"
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: comfy.riotpiao.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: comfyui
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: comfyui
|
||||||
|
namespace: comfyui
|
||||||
|
labels:
|
||||||
|
app: comfyui
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: comfyui
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8188
|
||||||
|
protocol: TCP
|
||||||
@@ -14,6 +14,7 @@ resources:
|
|||||||
- ornith.yaml
|
- ornith.yaml
|
||||||
- reasoning.yaml
|
- reasoning.yaml
|
||||||
- reranker.yaml
|
- reranker.yaml
|
||||||
|
- qwen-cpu.yaml
|
||||||
- networkpolicy.yaml
|
- networkpolicy.yaml
|
||||||
# No namespace transformer: every file sets its own, and the transformer would
|
# No namespace transformer: every file sets its own, and the transformer would
|
||||||
# rewrite metadata.namespace on anything cross-namespace added later.
|
# rewrite metadata.namespace on anything cross-namespace added later.
|
||||||
|
|||||||
@@ -33,12 +33,8 @@ spec:
|
|||||||
|
|
||||||
ollama pull ornith:35b
|
ollama pull ornith:35b
|
||||||
|
|
||||||
ollama pull qwen2.5:3b-instruct
|
|
||||||
|
|
||||||
ollama run ornith:35b "ok" >/dev/null 2>&1 || true
|
ollama run ornith:35b "ok" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
ollama run qwen2.5:3b-instruct "ok" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
wait $SERVE_PID
|
wait $SERVE_PID
|
||||||
|
|
||||||
'
|
'
|
||||||
@@ -54,7 +50,7 @@ spec:
|
|||||||
- name: OLLAMA_NUM_PARALLEL
|
- name: OLLAMA_NUM_PARALLEL
|
||||||
value: '1'
|
value: '1'
|
||||||
- name: OLLAMA_MAX_LOADED_MODELS
|
- name: OLLAMA_MAX_LOADED_MODELS
|
||||||
value: '2'
|
value: '1'
|
||||||
image: ollama/ollama:0.32.9@sha256:1685741456770df6e3cceb2a945a5f75e020f658d1701509668d6f4688f1dd3f
|
image: ollama/ollama:0.32.9@sha256:1685741456770df6e3cceb2a945a5f75e020f658d1701509668d6f4688f1dd3f
|
||||||
name: kserve-container
|
name: kserve-container
|
||||||
ports:
|
ports:
|
||||||
@@ -65,8 +61,7 @@ spec:
|
|||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
- ollama ps 2>/dev/null | grep -q ornith && ollama ps 2>/dev/null |
|
- ollama ps 2>/dev/null | grep -q ornith
|
||||||
grep -q qwen2.5
|
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
@@ -82,8 +77,7 @@ spec:
|
|||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
- ollama ps 2>/dev/null | grep -q ornith && ollama ps 2>/dev/null |
|
- ollama ps 2>/dev/null | grep -q ornith
|
||||||
grep -q qwen2.5
|
|
||||||
failureThreshold: 120
|
failureThreshold: 120
|
||||||
periodSeconds: 15
|
periodSeconds: 15
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
@@ -91,13 +85,10 @@ spec:
|
|||||||
name: models
|
name: models
|
||||||
deploymentStrategy:
|
deploymentStrategy:
|
||||||
type: Recreate
|
type: Recreate
|
||||||
# 2 replicas -- each its own GPU, each loading both ornith:35b and
|
# 1 replica -- ornith:35b only. qwen2.5:3b moved to CPU on cp-2.
|
||||||
# qwen2.5:3b-instruct -- so 2 concurrent implementer-style calls each
|
# Frees 1 GPU for ComfyUI.
|
||||||
# get an independent instance instead of contending on one, at the
|
maxReplicas: 1
|
||||||
# cost of judge/qwen traffic still sharing whichever replica an
|
minReplicas: 1
|
||||||
# implementer call also lands on.
|
|
||||||
maxReplicas: 2
|
|
||||||
minReplicas: 2
|
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
kubernetes.io/hostname: worker-1
|
kubernetes.io/hostname: worker-1
|
||||||
runtimeClassName: nvidia
|
runtimeClassName: nvidia
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# qwen2.5:3b-instruct on CPU (talos-cp-2, 144GB RAM, 24 cores).
|
||||||
|
# Moved off GPU to free a V100 for ComfyUI. Latency ~10x slower
|
||||||
|
# than GPU but sufficient for lightweight tasks (summarization,
|
||||||
|
# classification, quick answers).
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: qwen-cpu
|
||||||
|
namespace: llm-serving
|
||||||
|
labels:
|
||||||
|
app: qwen-cpu
|
||||||
|
app.kubernetes.io/name: qwen-cpu
|
||||||
|
app.kubernetes.io/part-of: llm-serving
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: qwen-cpu
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: qwen-cpu
|
||||||
|
app.kubernetes.io/name: qwen-cpu
|
||||||
|
app.kubernetes.io/part-of: llm-serving
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: talos-cp-2
|
||||||
|
tolerations:
|
||||||
|
- key: node-role.kubernetes.io/control-plane
|
||||||
|
operator: Exists
|
||||||
|
effect: NoSchedule
|
||||||
|
containers:
|
||||||
|
- name: ollama
|
||||||
|
image: ollama/ollama:0.32.9@sha256:1685741456770df6e3cceb2a945a5f75e020f658d1701509668d6f4688f1dd3f
|
||||||
|
command: ["/bin/sh", "-c"]
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
ollama serve &
|
||||||
|
SERVE_PID=$!
|
||||||
|
until ollama list >/dev/null 2>&1; do sleep 2; done
|
||||||
|
ollama pull qwen2.5:3b-instruct
|
||||||
|
ollama run qwen2.5:3b-instruct "ok" >/dev/null 2>&1 || true
|
||||||
|
wait $SERVE_PID
|
||||||
|
env:
|
||||||
|
- name: OLLAMA_HOST
|
||||||
|
value: "0.0.0.0:8080"
|
||||||
|
- name: OLLAMA_MODELS
|
||||||
|
value: /root/.ollama/models
|
||||||
|
- name: OLLAMA_CONTEXT_LENGTH
|
||||||
|
value: "32768"
|
||||||
|
- name: OLLAMA_KEEP_ALIVE
|
||||||
|
value: "-1"
|
||||||
|
- name: OLLAMA_MAX_LOADED_MODELS
|
||||||
|
value: "1"
|
||||||
|
- name: OLLAMA_NUM_PARALLEL
|
||||||
|
value: "2"
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["/bin/sh", "-c", "ollama ps 2>/dev/null | grep -q qwen2.5"]
|
||||||
|
periodSeconds: 10
|
||||||
|
startupProbe:
|
||||||
|
exec:
|
||||||
|
command: ["/bin/sh", "-c", "ollama ps 2>/dev/null | grep -q qwen2.5"]
|
||||||
|
failureThreshold: 60
|
||||||
|
periodSeconds: 10
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "4"
|
||||||
|
memory: 4Gi
|
||||||
|
limits:
|
||||||
|
cpu: "8"
|
||||||
|
memory: 8Gi
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /root/.ollama
|
||||||
|
name: ollama-data
|
||||||
|
volumes:
|
||||||
|
- name: ollama-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: qwen-cpu-data
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: qwen-cpu
|
||||||
|
namespace: llm-serving
|
||||||
|
labels:
|
||||||
|
app: qwen-cpu
|
||||||
|
app.kubernetes.io/part-of: llm-serving
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: qwen-cpu
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
# Small PVC for qwen2.5:3b model weights (~1.9GB).
|
||||||
|
# Separate from llm-models PVC which is pinned to worker-1.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: qwen-cpu-data
|
||||||
|
namespace: llm-serving
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: longhorn
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: comfyui
|
||||||
|
namespace: argocd
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: comfyui
|
||||||
|
app.kubernetes.io/component: image-generation
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "8"
|
||||||
|
spec:
|
||||||
|
project: homelab
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
source:
|
||||||
|
repoURL: https://forgejo.riotpiao.com/rock/homelab.git
|
||||||
|
targetRevision: main
|
||||||
|
path: k8s/apps/comfyui
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: comfyui
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
retry:
|
||||||
|
limit: 5
|
||||||
|
backoff:
|
||||||
|
duration: 5s
|
||||||
|
factor: 2
|
||||||
|
maxDuration: 3m
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# End-to-end auth flow tests for api-gateway.
|
|
||||||
# Validates: JWT auth, identity headers, token exchange, error handling.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# ./scripts/test-auth-e2e.sh
|
|
||||||
#
|
|
||||||
# Requires: kubectl access to cluster, curl, jq
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
API="https://api.riotpiao.com"
|
|
||||||
TOKEN_URL="https://authentik.riotpiao.com/application/o/token/"
|
|
||||||
PASS=0
|
|
||||||
FAIL=0
|
|
||||||
SKIP=0
|
|
||||||
|
|
||||||
# Colors
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[0;33m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
check() {
|
|
||||||
local name="$1" expected="$2" actual="$3"
|
|
||||||
if [ "$expected" = "$actual" ]; then
|
|
||||||
echo -e " ${GREEN}✓${NC} $name"
|
|
||||||
PASS=$((PASS + 1))
|
|
||||||
else
|
|
||||||
echo -e " ${RED}✗${NC} $name (expected $expected, got $actual)"
|
|
||||||
FAIL=$((FAIL + 1))
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
skip() {
|
|
||||||
echo -e " ${YELLOW}○${NC} $1 (skipped: $2)"
|
|
||||||
SKIP=$((SKIP + 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Load credentials ---
|
|
||||||
echo "Loading credentials from cluster..."
|
|
||||||
P_SECRET=$(kubectl get secret -n portfolio portfolio-agent-oidc -o jsonpath='{.data.CLIENT_SECRET}' | base64 -d 2>/dev/null) || true
|
|
||||||
M_SECRET=$(kubectl get secret -n poimen memory-agent-oidc -o jsonpath='{.data.CLIENT_SECRET}' | base64 -d 2>/dev/null) || true
|
|
||||||
|
|
||||||
if [ -z "$P_SECRET" ] || [ -z "$M_SECRET" ]; then
|
|
||||||
echo "ERROR: Cannot read service account secrets. Check kubectl context."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- Get tokens ---
|
|
||||||
echo ""
|
|
||||||
echo "=== Acquiring tokens ==="
|
|
||||||
|
|
||||||
P_TOKEN=$(curl -s -X POST "$TOKEN_URL" \
|
|
||||||
-d "grant_type=client_credentials&client_id=portfolio-agent&client_secret=$P_SECRET&scope=openid roles" \
|
|
||||||
| jq -r '.access_token')
|
|
||||||
|
|
||||||
M_TOKEN=$(curl -s -X POST "$TOKEN_URL" \
|
|
||||||
-d "grant_type=client_credentials&client_id=memory-agent&client_secret=$M_SECRET&scope=openid roles" \
|
|
||||||
| jq -r '.access_token')
|
|
||||||
|
|
||||||
if [ "$P_TOKEN" = "null" ] || [ -z "$P_TOKEN" ]; then
|
|
||||||
echo "ERROR: Failed to get portfolio-agent token"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo " portfolio-agent: token acquired"
|
|
||||||
echo " memory-agent: token acquired"
|
|
||||||
|
|
||||||
# --- Scenario 1: LLM inference ---
|
|
||||||
echo ""
|
|
||||||
echo "=== Scenario 1: LLM inference (llm:inference) ==="
|
|
||||||
|
|
||||||
CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/v1/chat/completions" \
|
|
||||||
-H "Authorization: Bearer $P_TOKEN" -H "Content-Type: application/json" \
|
|
||||||
-d '{"model":"qwen2.5:3b-instruct","messages":[{"role":"user","content":"ok"}],"max_tokens":2}')
|
|
||||||
check "portfolio-agent -> qwen chat" "200" "$CODE"
|
|
||||||
|
|
||||||
CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/v1/chat/completions" \
|
|
||||||
-H "Authorization: Bearer $M_TOKEN" -H "Content-Type: application/json" \
|
|
||||||
-d '{"model":"reasoning","messages":[{"role":"user","content":"ok"}],"max_tokens":2}')
|
|
||||||
check "memory-agent -> reasoning chat" "200" "$CODE"
|
|
||||||
|
|
||||||
CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/v1/embeddings" \
|
|
||||||
-H "Authorization: Bearer $P_TOKEN" -H "Content-Type: application/json" \
|
|
||||||
-d '{"model":"nomic-ai/nomic-embed-text-v2-moe","input":"test"}')
|
|
||||||
check "portfolio-agent -> embeddings" "200" "$CODE"
|
|
||||||
|
|
||||||
CODE=$(curl -s -o /dev/null -w "%{http_code}" "$API/v1/models")
|
|
||||||
check "GET /v1/models (no auth)" "200" "$CODE"
|
|
||||||
|
|
||||||
# --- Scenario 2: Auth enforcement ---
|
|
||||||
echo ""
|
|
||||||
echo "=== Scenario 2: Auth enforcement ==="
|
|
||||||
|
|
||||||
CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/v1/chat/completions" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"model":"qwen2.5:3b-instruct","messages":[{"role":"user","content":"ok"}]}')
|
|
||||||
check "no token -> 401" "401" "$CODE"
|
|
||||||
|
|
||||||
CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/v1/chat/completions" \
|
|
||||||
-H "Authorization: Bearer garbage-token" -H "Content-Type: application/json" \
|
|
||||||
-d '{"model":"qwen2.5:3b-instruct","messages":[{"role":"user","content":"ok"}]}')
|
|
||||||
check "bad token -> 403" "403" "$CODE"
|
|
||||||
|
|
||||||
# --- Scenario 3: Token claims ---
|
|
||||||
echo ""
|
|
||||||
echo "=== Scenario 3: Token claims ==="
|
|
||||||
|
|
||||||
decode_jwt() { python3 -c "import base64,json,sys; p=sys.argv[1].split('.')[1]; p+='='*(4-len(p)%4); print(json.dumps(json.loads(base64.urlsafe_b64decode(p))))" "$1"; }
|
|
||||||
|
|
||||||
P_CLAIMS=$(decode_jwt "$P_TOKEN")
|
|
||||||
P_ROLES=$(echo "$P_CLAIMS" | jq -r '.roles | join(",")')
|
|
||||||
check "portfolio-agent has llm:inference" "true" "$(echo "$P_ROLES" | grep -q 'llm:inference' && echo true || echo false)"
|
|
||||||
check "portfolio-agent has s3:read" "true" "$(echo "$P_ROLES" | grep -q 's3:read' && echo true || echo false)"
|
|
||||||
check "portfolio-agent has sqs:read" "true" "$(echo "$P_ROLES" | grep -q 'sqs:read' && echo true || echo false)"
|
|
||||||
|
|
||||||
M_CLAIMS=$(decode_jwt "$M_TOKEN")
|
|
||||||
M_ROLES=$(echo "$M_CLAIMS" | jq -r '.roles | join(",")')
|
|
||||||
check "memory-agent has memory:write" "true" "$(echo "$M_ROLES" | grep -q 'memory:write' && echo true || echo false)"
|
|
||||||
check "memory-agent has s3:write" "true" "$(echo "$M_ROLES" | grep -q 's3:write' && echo true || echo false)"
|
|
||||||
|
|
||||||
# --- Scenario 4: X-Service dispatch ---
|
|
||||||
echo ""
|
|
||||||
echo "=== Scenario 4: X-Service dispatch ==="
|
|
||||||
|
|
||||||
CODE=$(curl -s -o /dev/null -w "%{http_code}" "$API/" \
|
|
||||||
-H "X-Service: s3" -H "X-Resource: list-objects" \
|
|
||||||
-H "Authorization: Bearer $P_TOKEN")
|
|
||||||
check "portfolio-agent -> S3 list (reaches MinIO)" "403" "$CODE"
|
|
||||||
# 403 = MinIO reached but rejects JWT (needs SigV4). Not a gateway auth issue.
|
|
||||||
|
|
||||||
# Memory service might be down
|
|
||||||
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$API/" \
|
|
||||||
-H "X-Service: memory" -H "X-Resource: skills" \
|
|
||||||
-H "Authorization: Bearer $P_TOKEN" 2>/dev/null)
|
|
||||||
if [ "$CODE" = "200" ]; then
|
|
||||||
check "portfolio-agent -> memory skills" "200" "$CODE"
|
|
||||||
elif [ "$CODE" = "502" ]; then
|
|
||||||
skip "portfolio-agent -> memory skills" "poimen-memory pods down"
|
|
||||||
else
|
|
||||||
check "portfolio-agent -> memory skills" "200" "$CODE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- Scenario 5: Auth endpoints ---
|
|
||||||
echo ""
|
|
||||||
echo "=== Scenario 5: Auth endpoints ==="
|
|
||||||
|
|
||||||
# /auth/token — may return 400 if password grant not working, but endpoint should respond
|
|
||||||
CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/auth/token" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"username":"rock","password":"wrong"}')
|
|
||||||
# Should get 400 (bad creds forwarded from Authentik) or 502 (upstream issue), not 404
|
|
||||||
if [ "$CODE" != "404" ] && [ "$CODE" != "405" ]; then
|
|
||||||
check "/auth/token endpoint exists" "true" "true"
|
|
||||||
else
|
|
||||||
check "/auth/token endpoint exists" "not-404" "$CODE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
CODE=$(curl -s -o /dev/null -w "%{http_code}" -X GET "$API/auth/token")
|
|
||||||
check "GET /auth/token -> 405" "405" "$CODE"
|
|
||||||
|
|
||||||
CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/auth/refresh" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"refresh_token":"invalid"}')
|
|
||||||
if [ "$CODE" != "404" ] && [ "$CODE" != "405" ]; then
|
|
||||||
check "/auth/refresh endpoint exists" "true" "true"
|
|
||||||
else
|
|
||||||
check "/auth/refresh endpoint exists" "not-404" "$CODE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- Summary ---
|
|
||||||
echo ""
|
|
||||||
echo "========================================="
|
|
||||||
TOTAL=$((PASS + FAIL + SKIP))
|
|
||||||
echo -e "Results: ${GREEN}$PASS passed${NC}, ${RED}$FAIL failed${NC}, ${YELLOW}$SKIP skipped${NC} / $TOTAL total"
|
|
||||||
echo "========================================="
|
|
||||||
|
|
||||||
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|
|
||||||
Reference in New Issue
Block a user