diff --git a/infrastructure/kubernetes/apps/qdrant/auth-config.yaml b/infrastructure/kubernetes/apps/qdrant/auth-config.yaml new file mode 100644 index 00000000..224bec4b --- /dev/null +++ b/infrastructure/kubernetes/apps/qdrant/auth-config.yaml @@ -0,0 +1,31 @@ +--- +# Qdrant Auth Configuration (API keys) +apiVersion: v1 +kind: Secret +metadata: + name: qdrant-api-keys + namespace: qdrant +type: Opaque +stringData: + # TODO: Замінити на реальні API ключі (згенеровані через generate-keys.sh) + memory-service-key: "change-me-memory-service" + worker-daemon-key: "change-me-worker-daemon" + matrix-gateway-key: "change-me-matrix-gateway" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: qdrant-auth-config + namespace: qdrant +data: + # Qdrant config з API key auth + qdrant-config.yaml: | + service: + api_key: ${QDRANT_API_KEY} # З Secret + enable_cors: true + + cluster: + enabled: false # Single node для початку + + storage: + storage_path: /qdrant/storage diff --git a/infrastructure/kubernetes/nats/auth-config.yaml b/infrastructure/kubernetes/nats/auth-config.yaml new file mode 100644 index 00000000..f26ead62 --- /dev/null +++ b/infrastructure/kubernetes/nats/auth-config.yaml @@ -0,0 +1,39 @@ +--- +# NATS Auth Configuration (nkeys) +# Використання: після генерації operator JWT через generate-operator.sh + +apiVersion: v1 +kind: Secret +metadata: + name: nats-operator-jwt + namespace: nats +type: Opaque +stringData: + # TODO: Замінити на реальний operator JWT + operator.jwt: | + eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ... + system-account.jwt: | + eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ... +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nats-auth-config + namespace: nats +data: + # Приклад конфігурації з auth (після генерації JWT) + nats-auth.conf: | + port: 4222 + http_port: 8222 + server_name: POD_NAME_PLACEHOLDER + + # Operator JWT + operator: /etc/nats/jwt/operator.jwt + system_account: SYSTEM + resolver: MEMORY + + jetstream { + store_dir: /data/jetstream + max_mem_store: 2G + max_file_store: 50G + } diff --git a/infrastructure/test-full-flow.sh b/infrastructure/test-full-flow.sh new file mode 100755 index 00000000..d589ff0b --- /dev/null +++ b/infrastructure/test-full-flow.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# Тестування повного потоку: Matrix → Gateway → NATS → Worker → Memory Service + +set -e + +echo "🧪 Тестування повного потоку..." +echo "" + +# Кольори для виводу +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +# Перевірка компонентів +echo "=== Перевірка компонентів ===" + +# 1. NATS +echo -n "NATS JetStream: " +if kubectl get pods -n nats -l app=nats --field-selector=status.phase=Running 2>/dev/null | grep -q nats; then + echo -e "${GREEN}✅ Running${NC}" +else + echo -e "${RED}❌ Not running${NC}" + exit 1 +fi + +# 2. Memory Service +echo -n "Memory Service: " +if kubectl get pods -n daarion -l app=memory-service --field-selector=status.phase=Running 2>/dev/null | grep -q memory-service; then + echo -e "${GREEN}✅ Running${NC}" +else + echo -e "${YELLOW}⚠️ Not running (може бути нормально)${NC}" +fi + +# 3. Qdrant +echo -n "Qdrant: " +if kubectl get pods -n qdrant -l app=qdrant --field-selector=status.phase=Running 2>/dev/null | grep -q qdrant; then + echo -e "${GREEN}✅ Running${NC}" +else + echo -e "${YELLOW}⚠️ Not running (може бути нормально)${NC}" +fi + +echo "" +echo "=== Перевірка NATS streams ===" +NATS_POD=$(kubectl get pods -n nats -l app=nats -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) +if [ -n "$NATS_POD" ]; then + kubectl exec -n nats "$NATS_POD" -- wget -qO- http://localhost:8222/jsz 2>/dev/null | python3 -m json.tool 2>/dev/null | grep -E '"streams"|"name"' | head -10 || echo "Streams не знайдено" +else + echo "NATS pod не знайдено" +fi + +echo "" +echo "=== Тест створення job ===" +echo "Створюю тестовий job через Python..." + +cat << 'PYEOF' | python3 +import asyncio +import json +from nats.js import api +from nats.aio.client import Client as NATS + +async def test_job(): + nc = NATS() + try: + await nc.connect("nats://nats-client.nats:4222") + js = nc.jetstream() + + # Тестовий job + job = { + "job_id": "test-001", + "idempotency_key": "sha256:test", + "type": "embed", + "priority": "online", + "input": { + "text": ["Тестовий текст для embedding"], + "model": "cohere/embed-multilingual-v3.0", + "dims": 1024 + } + } + + # Публікація + ack = await js.publish("mm.embed.online", json.dumps(job).encode()) + print(f"✅ Job опубліковано: seq={ack.seq}") + + await nc.close() + except Exception as e: + print(f"❌ Помилка: {e}") + +asyncio.run(test_job()) +PYEOF + +echo "" +echo "✅ Тестування завершено!" diff --git a/services/memory-service/app/auth.py b/services/memory-service/app/auth.py index 7d7b3789..f1558c75 100644 --- a/services/memory-service/app/auth.py +++ b/services/memory-service/app/auth.py @@ -5,7 +5,7 @@ JWT Authentication для Memory Service import os import jwt import time -from typing import Optional +from typing import Optional, Union from fastapi import HTTPException, Security from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from app.config import get_settings @@ -42,8 +42,21 @@ def verify_jwt_token(token: str) -> dict: raise HTTPException(status_code=401, detail="Invalid token") +async def get_current_service_optional( + credentials: Optional[HTTPAuthorizationCredentials] = Security(security, auto_error=False) +) -> Optional[dict]: + """Dependency для отримання поточного сервісу з JWT (опціонально)""" + if not credentials: + return None + token = credentials.credentials + try: + payload = verify_jwt_token(token) + return payload + except HTTPException: + return None + async def get_current_service(credentials: HTTPAuthorizationCredentials = Security(security)) -> dict: - """Dependency для отримання поточного сервісу з JWT""" + """Dependency для отримання поточного сервісу з JWT (обов'язково)""" token = credentials.credentials payload = verify_jwt_token(token) return payload diff --git a/services/memory-service/app/main.py b/services/memory-service/app/main.py index bf50fec6..918ed0b5 100644 --- a/services/memory-service/app/main.py +++ b/services/memory-service/app/main.py @@ -8,6 +8,7 @@ DAARION Memory Service - FastAPI Application """ from contextlib import asynccontextmanager from typing import List, Optional +from fastapi import Depends from uuid import UUID import structlog from fastapi import FastAPI, HTTPException, Query @@ -23,6 +24,7 @@ from .models import ( ) from .vector_store import vector_store from .database import db +from .auth import get_current_service, get_current_service_optional logger = structlog.get_logger() settings = get_settings() @@ -76,8 +78,12 @@ async def health(): # ============================================================================ @app.post("/threads", response_model=ThreadResponse) -async def create_thread(request: CreateThreadRequest): +async def create_thread( + request: CreateThreadRequest, + service: Optional[dict] = Depends(get_current_service_optional) +): """Create new conversation thread""" + # Auth опціональний: якщо JWT надано, перевіряємо; якщо ні - дозволяємо (dev режим) thread = await db.create_thread( org_id=request.org_id, user_id=request.user_id, @@ -123,7 +129,10 @@ async def list_threads( # ============================================================================ @app.post("/events", response_model=EventResponse) -async def add_event(request: AddEventRequest): +async def add_event( + request: AddEventRequest, + service: Optional[dict] = Depends(get_current_service_optional) +): """Add event to conversation (message, tool call, etc.)""" event = await db.add_event( thread_id=request.thread_id, @@ -158,7 +167,10 @@ async def get_events( # ============================================================================ @app.post("/memories", response_model=MemoryResponse) -async def create_memory(request: CreateMemoryRequest): +async def create_memory( + request: CreateMemoryRequest, + service: Optional[dict] = Depends(get_current_service_optional) +): """Create long-term memory item""" # Create in PostgreSQL memory = await db.create_memory(