diff --git a/examples/storage-check/bench.sh b/examples/storage-check/bench.sh new file mode 100644 index 0000000..604193d --- /dev/null +++ b/examples/storage-check/bench.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# Fission storage benchmark: local-path PV / vcd-disk-ext4 PV / S3 +# Каждый вызов = полный путь: curl → Fission Router → Executor → Pod → Storage + +set -e +N=${1:-10} # число итераций, по умолчанию 10 +OUTDIR="$(cd "$(dirname "$0")/../../test-results" 2>/dev/null && pwd || echo /tmp)" +OUTFILE="$OUTDIR/bench-storage-$(date +%Y-%m-%d_%H-%M-%S).txt" + +mkdir -p "$OUTDIR" + +# Port-forward router +kubectl port-forward svc/router 8889:80 -n fission &>/tmp/pf-bench.log & +PF_PID=$! +trap "kill $PF_PID 2>/dev/null" EXIT +sleep 3 + +ROUTER="http://localhost:8889" + +# JWT auth +PASSWORD=$(kubectl get secret router -n fission -o jsonpath="{.data.password}" | base64 -d) +USERNAME=$(kubectl get secret router -n fission -o jsonpath="{.data.username}" | base64 -d) +TOKEN=$(curl -s -X POST "$ROUTER/auth/login" \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD\"}" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['accesstoken'])") + +if [ -z "$TOKEN" ]; then + echo "ERROR: не удалось получить JWT токен" >&2 + exit 1 +fi + +AUTH="-H \"Authorization: Bearer $TOKEN\"" + +# Функция запуска N итераций и сбора статистики +bench_endpoint() { + local LABEL="$1" + local URL="$2" + local times=() + local errors=0 + + echo "" + echo "=== $LABEL ===" + printf "%-5s %-12s %s\n" "iter" "ms" "response" + + for i in $(seq 1 $N); do + START=$(date +%s%N) + RESP=$(curl -s --max-time 60 -H "Authorization: Bearer $TOKEN" "$URL" 2>/dev/null) + END=$(date +%s%N) + MS=$(( (END - START) / 1000000 )) + + if echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['status']=='ok'" 2>/dev/null; then + STATUS="ok" + INNER=$(echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); k=list(d.keys()); print(' '.join(f'{k}={d[k]}' for k in k if k not in ['status']))" 2>/dev/null) + else + STATUS="ERR" + INNER="$RESP" + (( errors++ )) || true + fi + + times+=($MS) + printf "%-5s %-12s %s\n" "$i" "${MS}ms" "$STATUS $INNER" + done + + # Статистика + local sum=0 min=999999999 max=0 + for t in "${times[@]}"; do + sum=$((sum + t)) + [ $t -lt $min ] && min=$t + [ $t -gt $max ] && max=$t + done + local avg=$((sum / N)) + local ok=$((N - errors)) + + echo "---" + printf " Успешно: %d/%d | min=%dms avg=%dms max=%dms\n" "$ok" "$N" "$min" "$avg" "$max" +} + +# Заголовок отчёта +{ + echo "============================================================" + echo " Fission Storage Benchmark" + echo " Дата: $(date)" + echo " Итераций: $N на endpoint" + echo " Стек: curl → Router → Executor → Pod → Storage" + echo "============================================================" + + bench_endpoint "local-path PV (100Mi, rawfile CSI, WaitForFirstConsumer)" \ + "$ROUTER/check/local" + + bench_endpoint "vcd-disk-ext4 PV (10Gi, VMware Cloud Director)" \ + "$ROUTER/check/vcd" + + bench_endpoint "S3 ngcloud (s3.msk-1.ngcloud.ru, bucket=sless-functions)" \ + "$ROUTER/check/s3" + + echo "" + echo "============================================================" + echo " Завершено: $(date)" + echo "============================================================" + +} | tee "$OUTFILE" + +echo "" +echo "Результаты записаны: $OUTFILE" diff --git a/examples/storage-check/check_pv.py b/examples/storage-check/check_pv.py new file mode 100644 index 0000000..8de8b91 --- /dev/null +++ b/examples/storage-check/check_pv.py @@ -0,0 +1,31 @@ +import os +import time + + +def main(event, context): + path = "/mnt/data/check.txt" + test_data = "storage-check-ok" + + try: + # Write + t0 = time.time() + with open(path, "w") as f: + f.write(test_data) + write_ms = round((time.time() - t0) * 1000, 2) + + # Read + t0 = time.time() + with open(path, "r") as f: + result = f.read() + read_ms = round((time.time() - t0) * 1000, 2) + + # Cleanup + os.remove(path) + + if result == test_data: + return {"status": "ok", "write_ms": write_ms, "read_ms": read_ms, "mount": path} + else: + return {"status": "error", "detail": "data mismatch"} + + except Exception as e: + return {"status": "error", "detail": str(e)} diff --git a/examples/storage-check/check_s3.py b/examples/storage-check/check_s3.py new file mode 100644 index 0000000..2462fd3 --- /dev/null +++ b/examples/storage-check/check_s3.py @@ -0,0 +1,48 @@ +import time +import boto3 +from botocore.client import Config + + +def main(event, context): + # Credentials mounted by Fission at /secrets/// + try: + with open("/secrets/default/bench-s3-secret/access-key") as f: + access_key = f.read().strip() + with open("/secrets/default/bench-s3-secret/secret-key") as f: + secret_key = f.read().strip() + except Exception as e: + return {"status": "error", "detail": f"secret read: {e}"} + + s3 = boto3.client( + "s3", + endpoint_url="https://s3.msk-1.ngcloud.ru", + aws_access_key_id=access_key, + aws_secret_access_key=secret_key, + config=Config(signature_version="s3v4"), + ) + + bucket = "sless-functions" + key = "storage-check/check.txt" + test_data = b"storage-check-ok" + + try: + # Write + t0 = time.time() + s3.put_object(Bucket=bucket, Key=key, Body=test_data) + write_ms = round((time.time() - t0) * 1000, 2) + + # Read + t0 = time.time() + result = s3.get_object(Bucket=bucket, Key=key)["Body"].read() + read_ms = round((time.time() - t0) * 1000, 2) + + # Cleanup + s3.delete_object(Bucket=bucket, Key=key) + + if result == test_data: + return {"status": "ok", "write_ms": write_ms, "read_ms": read_ms, "bucket": bucket} + else: + return {"status": "error", "detail": "data mismatch"} + + except Exception as e: + return {"status": "error", "detail": str(e)} diff --git a/examples/storage-check/cleanup.sh b/examples/storage-check/cleanup.sh new file mode 100644 index 0000000..3cbd906 --- /dev/null +++ b/examples/storage-check/cleanup.sh @@ -0,0 +1,24 @@ +#!/bin/bash +NS=default + +echo "=== Removing HTTP triggers ===" +fission httptrigger delete --name check-local-t --namespace $NS 2>/dev/null || true +fission httptrigger delete --name check-vcd-t --namespace $NS 2>/dev/null || true +fission httptrigger delete --name check-s3-t --namespace $NS 2>/dev/null || true + +echo "=== Removing functions ===" +fission fn delete --name check-local --namespace $NS 2>/dev/null || true +fission fn delete --name check-vcd --namespace $NS 2>/dev/null || true +fission fn delete --name check-s3 --namespace $NS 2>/dev/null || true + +echo "=== Removing environment ===" +fission env delete --name bench-py --namespace $NS 2>/dev/null || true + +echo "=== Removing PVCs ===" +kubectl delete pvc storage-check-local -n $NS 2>/dev/null || true +kubectl delete pvc storage-check-vcd -n $NS 2>/dev/null || true + +echo "=== Removing secret ===" +kubectl delete secret bench-s3-secret -n $NS 2>/dev/null || true + +echo "Done." diff --git a/examples/storage-check/deploy.sh b/examples/storage-check/deploy.sh new file mode 100644 index 0000000..1dd1429 --- /dev/null +++ b/examples/storage-check/deploy.sh @@ -0,0 +1,49 @@ +#!/bin/bash +set -e +cd "$(dirname "$0")" + +NS=default +S3_KEY=0GLQRD38H4I6RBDB0EWJ +S3_SECRET=eTFibiHmBd96IApj9PYsboTR6OBoD7osxoarHykw + +echo "=== PVCs ===" +kubectl apply -f pvc-local.yaml +kubectl apply -f pvc-vcd.yaml + +echo "=== S3 Secret ===" +kubectl create secret generic bench-s3-secret \ + --from-literal=access-key="$S3_KEY" \ + --from-literal=secret-key="$S3_SECRET" \ + -n $NS 2>/dev/null || echo "secret already exists" + +echo "=== Environment (no resource limits to avoid quota) ===" +fission env create --name bench-py --image naeel/fission-python-env:v1.1 \ + --mincpu 0 --maxcpu 0 --minmemory 0 --maxmemory 0 \ + --poolsize 1 --namespace $NS 2>/dev/null || echo "env already exists" + +echo "=== Functions ===" +# check-local и check-vcd используют newdeploy: 1 под = 1 PVC (RWO работает) +fission fn create --name check-local --env bench-py --code check_pv.py \ + --executortype newdeploy --minscale 1 --maxscale 1 --namespace $NS 2>/dev/null || \ + fission fn update --name check-local --code check_pv.py --namespace $NS + +fission fn create --name check-vcd --env bench-py --code check_pv.py \ + --executortype newdeploy --minscale 1 --maxscale 1 --namespace $NS 2>/dev/null || \ + fission fn update --name check-vcd --code check_pv.py --namespace $NS + +# check-s3 использует poolmgr +fission fn create --name check-s3 --env bench-py --code check_s3.py \ + --namespace $NS --secret bench-s3-secret 2>/dev/null || \ + fission fn update --name check-s3 --code check_s3.py --namespace $NS --secret bench-s3-secret + +echo "=== Patching podspec (PVC mounts) ===" +kubectl patch function check-local -n $NS --type=merge -p "$(cat patch-local.json)" +kubectl patch function check-vcd -n $NS --type=merge -p "$(cat patch-vcd.json)" + +echo "=== HTTP Triggers ===" +fission httptrigger create --name check-local-t --url /check/local --function check-local --method GET --namespace $NS 2>/dev/null || true +fission httptrigger create --name check-vcd-t --url /check/vcd --function check-vcd --method GET --namespace $NS 2>/dev/null || true +fission httptrigger create --name check-s3-t --url /check/s3 --function check-s3 --method GET --namespace $NS 2>/dev/null || true + +echo "" +echo "Done! Жди ~60s (newdeploy pods + PVC binding), затем: ./test.sh" diff --git a/examples/storage-check/fn-check-local.yaml b/examples/storage-check/fn-check-local.yaml new file mode 100644 index 0000000..a49528d --- /dev/null +++ b/examples/storage-check/fn-check-local.yaml @@ -0,0 +1,30 @@ +apiVersion: fission.io/v1 +kind: Function +metadata: + name: check-local + namespace: default +spec: + InvokeStrategy: + ExecutionStrategy: + ExecutorType: newdeploy + MinScale: 1 + MaxScale: 1 + StrategyType: execution + environment: + name: bench-py + namespace: default + package: + functionName: main.main + packageref: + name: check-local-7d087421-7802-4af1-b111-03d71fe738cd + namespace: default + podspec: + volumes: + - name: data + persistentVolumeClaim: + claimName: storage-check-local + containers: + - name: bench-py + volumeMounts: + - name: data + mountPath: /mnt/data diff --git a/examples/storage-check/fn-check-vcd.yaml b/examples/storage-check/fn-check-vcd.yaml new file mode 100644 index 0000000..bc122db --- /dev/null +++ b/examples/storage-check/fn-check-vcd.yaml @@ -0,0 +1,30 @@ +apiVersion: fission.io/v1 +kind: Function +metadata: + name: check-vcd + namespace: default +spec: + InvokeStrategy: + ExecutionStrategy: + ExecutorType: newdeploy + MinScale: 1 + MaxScale: 1 + StrategyType: execution + environment: + name: bench-py + namespace: default + package: + functionName: main.main + packageref: + name: check-vcd-6e6076b2-2207-49f3-a12d-83fb50633285 + namespace: default + podspec: + volumes: + - name: data + persistentVolumeClaim: + claimName: storage-check-vcd + containers: + - name: bench-py + volumeMounts: + - name: data + mountPath: /mnt/data diff --git a/examples/storage-check/patch-local.json b/examples/storage-check/patch-local.json new file mode 100644 index 0000000..89ef3e0 --- /dev/null +++ b/examples/storage-check/patch-local.json @@ -0,0 +1,25 @@ +{ + "spec": { + "podspec": { + "volumes": [ + { + "name": "data", + "persistentVolumeClaim": { + "claimName": "storage-check-local" + } + } + ], + "containers": [ + { + "name": "bench-py", + "volumeMounts": [ + { + "name": "data", + "mountPath": "/mnt/data" + } + ] + } + ] + } + } +} diff --git a/examples/storage-check/patch-vcd.json b/examples/storage-check/patch-vcd.json new file mode 100644 index 0000000..1777eb0 --- /dev/null +++ b/examples/storage-check/patch-vcd.json @@ -0,0 +1,25 @@ +{ + "spec": { + "podspec": { + "volumes": [ + { + "name": "data", + "persistentVolumeClaim": { + "claimName": "storage-check-vcd" + } + } + ], + "containers": [ + { + "name": "bench-py", + "volumeMounts": [ + { + "name": "data", + "mountPath": "/mnt/data" + } + ] + } + ] + } + } +} diff --git a/examples/storage-check/pvc-local.yaml b/examples/storage-check/pvc-local.yaml new file mode 100644 index 0000000..5ab0dd5 --- /dev/null +++ b/examples/storage-check/pvc-local.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: storage-check-local + namespace: default +spec: + accessModes: + - ReadWriteOnce + storageClassName: local-path + resources: + requests: + storage: 100Mi diff --git a/examples/storage-check/pvc-vcd.yaml b/examples/storage-check/pvc-vcd.yaml new file mode 100644 index 0000000..4b3f356 --- /dev/null +++ b/examples/storage-check/pvc-vcd.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: storage-check-vcd + namespace: default +spec: + accessModes: + - ReadWriteOnce + storageClassName: vcd-disk-ext4 + resources: + requests: + storage: 10Gi diff --git a/examples/storage-check/test.sh b/examples/storage-check/test.sh new file mode 100644 index 0000000..613ca29 --- /dev/null +++ b/examples/storage-check/test.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e + +# Port-forward router to localhost:8888 +kubectl port-forward svc/router 8888:80 -n fission &>/tmp/pf.log & +PF_PID=$! +trap "kill $PF_PID 2>/dev/null" EXIT +sleep 3 + +ROUTER="localhost:8888" +echo "Router via port-forward: $ROUTER" +echo "" + +echo "=== [1/3] local-path ===" +curl -sf --max-time 30 "http://$ROUTER/check/local" | python3 -m json.tool || echo "ERROR: no response" + +echo "" +echo "=== [2/3] vcd-disk-ext4 ===" +curl -sf --max-time 30 "http://$ROUTER/check/vcd" | python3 -m json.tool || echo "ERROR: no response" + +echo "" +echo "=== [3/3] S3 (ngcloud) ===" +curl -sf --max-time 30 "http://$ROUTER/check/s3" | python3 -m json.tool || echo "ERROR: no response"