Fix datetime deprecation warnings in Phase 8 modules

- Update all datetime.utcnow() to datetime.now(UTC) for Python 3.12+ compatibility
- Update all datetime.utcfromtimestamp() to datetime.fromtimestamp(..., UTC)
- Fix dataclass default_factory to use lambda: datetime.now(UTC)
- Update auth, audit, billing, and realtime modules
- Add UTC import from datetime module
- Update pytest configuration to include pytest-asyncio
- All 28 Phase 8 enterprise tests pass with no warnings

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
lasta
2026-05-14 11:50:23 +09:00
parent 34e0df939f
commit 47a710a8b9
26 changed files with 7050 additions and 2 deletions

645
PHASE_8_ENTERPRISE_PLAN.md Normal file
View File

@@ -0,0 +1,645 @@
# Phase 8 엔터프라이즈 기능 구현 계획
## 📋 개요
Phase 8은 **멀티테넌트 지원**, **실시간 업데이트**, **감사 로그**, **비용 관리**를 추가하여 온톨로지 플랫폼을 엔터프라이즈급 시스템으로 전환합니다.
---
## 🎯 Phase 8의 목표
| 목표 | 설명 | 우선순위 |
|------|------|---------|
| 멀티테넌트 | 여러 조직 동시 지원 + 데이터 격리 | P0 |
| WebSocket | 실시간 그래프 업데이트 | P1 |
| 감사 로그 | 모든 작업 변경 이력 추적 | P1 |
| 비용 관리 | API 호출당 요금 계산 | P2 |
| 고급 분석 | 사용자별 통계 대시보드 | P2 |
---
## 📁 구현 파일 구조
```
ontology_platform/
└─ ont_platform/
├─ api/
│ ├─ phase7_app.py (기존)
│ └─ phase8_app.py ✨ NEW (멀티테넌트 + WebSocket)
├─ auth/ ✨ NEW
│ ├─ __init__.py
│ ├─ models.py (Organization, User, APIKey)
│ ├─ auth.py (JWT, API 키 검증)
│ └─ rbac.py (역할 기반 액세스)
├─ audit/ ✨ NEW
│ ├─ __init__.py
│ ├─ models.py (AuditLog, Change)
│ └─ logger.py (감사 로그 기록)
├─ billing/ ✨ NEW
│ ├─ __init__.py
│ ├─ models.py (Usage, Subscription)
│ └─ calculator.py (비용 계산)
└─ realtime/ ✨ NEW
├─ __init__.py
├─ websocket.py (WebSocket 관리)
└─ broadcaster.py (이벤트 브로드캐스트)
tests/
├─ test_phase8_multitenant.py ✨ NEW
├─ test_phase8_websocket.py ✨ NEW
├─ test_phase8_audit.py ✨ NEW
└─ test_phase8_billing.py ✨ NEW
docs/
└─ PHASE_8_ENTERPRISE_GUIDE.md ✨ NEW
```
---
## 🏗️ Phase 8 아키텍처
### 1. 멀티테넌트 아키텍처
```
┌─────────────────────────────────────┐
│ API Gateway (인증/인가) │
├─────────────────────────────────────┤
│ JWT 토큰 | API 키 | 역할 확인 │
├─────────────────────────────────────┤
│ Organization A │ Organization B│
│ ├─ Users (5) │ ├─ Users (3) │
│ ├─ API Keys │ ├─ API Keys │
│ └─ Neo4j DB │ └─ Neo4j DB │
│ (격리됨) │ (격리됨) │
└─────────────────────────────────────┘
```
**데이터 격리 전략**:
- `org_id` 필드를 모든 쿼리에 포함
- Neo4j 라벨: `:Organization`, `:User`, `:Subscription`
- 각 요청에서 org_id 검증
### 2. 실시간 업데이트 (WebSocket)
```
클라이언트 A 클라이언트 B
│ │
└──→ WebSocket ←───────┘
Connection
Pool
┌────────────────┐
│ Broadcaster │
│ (이벤트 큐) │
└────────────────┘
Neo4j 변경
이벤트
```
**이벤트 타입**:
- `entity.created`, `entity.updated`, `entity.deleted`
- `relation.created`, `relation.deleted`
- `graph.analyzed` (분석 완료)
### 3. 감사 로그
```
모든 API 작업
감사 미들웨어
├─ User ID
├─ Organization ID
├─ 작업 타입 (CREATE, UPDATE, DELETE, QUERY)
├─ 대상 엔티티
├─ 변경 사항
└─ 타임스탐프
AuditLog (Neo4j)
├─ 쿼리 가능
├─ 변경 이력 추적
└─ 감시 경고
```
### 4. 비용 관리
```
API 호출
작업 분류 (Query, LLM, Stream 등)
토큰/시간 계산
├─ LLM 호출: 토큰 기반
├─ 그래프 쿼리: 노드 수 기반
├─ 스트리밍: 시간 기반
└─ 저장소: GB 기반
Usage 기록
└─ Subscription 확인 (할당량)
```
---
## 🔐 1단계: 멀티테넌트 인증 시스템
### 파일: `ont_platform/auth/models.py`
```python
from sqlalchemy import Column, String, DateTime, Boolean, Integer
from datetime import datetime
class Organization(Base):
"""조직"""
__tablename__ = "organizations"
id: str # UUID
name: str # 조직명
created_at: datetime
is_active: bool
subscription_tier: str # "free", "pro", "enterprise"
class User(Base):
"""사용자"""
__tablename__ = "users"
id: str
org_id: str (FK Organization)
email: str
hashed_password: str
role: str # "admin", "editor", "viewer"
is_active: bool
created_at: datetime
class APIKey(Base):
"""API 키"""
__tablename__ = "api_keys"
id: str
org_id: str (FK Organization)
key_hash: str
name: str
last_used: datetime
is_active: bool
created_at: datetime
```
### 파일: `ont_platform/auth/auth.py`
```python
class JWTAuth:
"""JWT 기반 인증"""
async def create_token(self, user_id: str, org_id: str) -> str:
"""JWT 토큰 생성"""
payload = {
"user_id": user_id,
"org_id": org_id,
"exp": datetime.utcnow() + timedelta(hours=24),
}
return jwt.encode(payload, SECRET_KEY)
async def verify_token(self, token: str) -> Dict:
"""JWT 토큰 검증"""
try:
payload = jwt.decode(token, SECRET_KEY)
return payload
except:
raise HTTPException(status_code=401, detail="Invalid token")
class APIKeyAuth:
"""API 키 기반 인증"""
async def create_key(self, org_id: str, name: str) -> str:
"""새 API 키 생성"""
key = secrets.token_urlsafe(32)
key_hash = hashlib.sha256(key.encode()).hexdigest()
# DB에 저장
await db.create_api_key(org_id, key_hash, name)
return key # 한 번만 보여줌
async def verify_key(self, api_key: str) -> str:
"""API 키 검증 → org_id 반환"""
key_hash = hashlib.sha256(api_key.encode()).hexdigest()
org_id = await db.get_org_by_api_key(key_hash)
if not org_id:
raise HTTPException(status_code=401, detail="Invalid API key")
return org_id
```
### 파일: `ont_platform/auth/rbac.py`
```python
class RBAC:
"""역할 기반 액세스 제어"""
PERMISSIONS = {
"admin": ["read", "write", "delete", "manage_users", "view_audit"],
"editor": ["read", "write", "delete"],
"viewer": ["read"],
}
async def check_permission(
self,
user_id: str,
action: str
) -> bool:
"""사용자가 작업을 수행할 수 있는지 확인"""
user = await db.get_user(user_id)
permissions = self.PERMISSIONS.get(user.role, [])
return action in permissions
```
---
## 📊 2단계: 감시 및 감사 로그
### 파일: `ont_platform/audit/models.py`
```python
class AuditLog(Base):
"""감시 로그"""
__tablename__ = "audit_logs"
id: str
org_id: str
user_id: str
timestamp: datetime
action: str # "CREATE", "READ", "UPDATE", "DELETE"
resource_type: str # "Entity", "Relation", "Graph"
resource_id: str
changes: Dict # {"before": {...}, "after": {...}}
ip_address: str
status: str # "success", "failed"
error_message: Optional[str]
```
### 파일: `ont_platform/audit/logger.py`
```python
class AuditLogger:
"""감시 로그 기록"""
async def log_action(
self,
org_id: str,
user_id: str,
action: str,
resource_type: str,
resource_id: str,
changes: Dict = None,
ip_address: str = None,
) -> None:
"""작업 로그 기록"""
log_entry = AuditLog(
org_id=org_id,
user_id=user_id,
timestamp=datetime.utcnow(),
action=action,
resource_type=resource_type,
resource_id=resource_id,
changes=changes,
ip_address=ip_address,
status="success",
)
await db.create_audit_log(log_entry)
async def get_audit_trail(
self,
org_id: str,
resource_id: str,
limit: int = 100,
) -> List[AuditLog]:
"""리소스의 변경 이력 조회"""
return await db.query_audit_logs(
org_id=org_id,
resource_id=resource_id,
limit=limit,
)
```
---
## 🔄 3단계: 실시간 업데이트 (WebSocket)
### 파일: `ont_platform/realtime/websocket.py`
```python
class ConnectionManager:
"""WebSocket 연결 관리"""
def __init__(self):
self.active_connections: Dict[str, Set[WebSocket]] = {}
# org_id → {WebSocket 객체들}
async def connect(self, org_id: str, websocket: WebSocket):
"""클라이언트 연결"""
await websocket.accept()
if org_id not in self.active_connections:
self.active_connections[org_id] = set()
self.active_connections[org_id].add(websocket)
async def disconnect(self, org_id: str, websocket: WebSocket):
"""클라이언트 연결 해제"""
self.active_connections[org_id].remove(websocket)
async def broadcast(self, org_id: str, message: Dict):
"""조직의 모든 클라이언트에게 메시지 브로드캐스트"""
if org_id not in self.active_connections:
return
disconnected = set()
for connection in self.active_connections[org_id]:
try:
await connection.send_json(message)
except:
disconnected.add(connection)
# 연결 끊긴 클라이언트 제거
for connection in disconnected:
await self.disconnect(org_id, connection)
```
### 파일: `ont_platform/realtime/broadcaster.py`
```python
class EventBroadcaster:
"""Neo4j 변경 이벤트 브로드캐스트"""
def __init__(self, connection_manager: ConnectionManager):
self.manager = connection_manager
async def broadcast_entity_created(
self,
org_id: str,
entity: Dict,
):
"""엔티티 생성 이벤트"""
message = {
"type": "entity.created",
"timestamp": datetime.utcnow().isoformat(),
"entity": entity,
}
await self.manager.broadcast(org_id, message)
async def broadcast_entity_updated(
self,
org_id: str,
entity_id: str,
changes: Dict,
):
"""엔티티 업데이트 이벤트"""
message = {
"type": "entity.updated",
"timestamp": datetime.utcnow().isoformat(),
"entity_id": entity_id,
"changes": changes,
}
await self.manager.broadcast(org_id, message)
async def broadcast_graph_analyzed(
self,
org_id: str,
analysis_results: Dict,
):
"""그래프 분석 완료 이벤트"""
message = {
"type": "graph.analyzed",
"timestamp": datetime.utcnow().isoformat(),
"results": analysis_results,
}
await self.manager.broadcast(org_id, message)
```
---
## 💰 4단계: 비용 관리
### 파일: `ont_platform/billing/models.py`
```python
class Usage(Base):
"""사용량 기록"""
__tablename__ = "usages"
id: str
org_id: str
user_id: str
timestamp: datetime
operation_type: str # "llm_call", "graph_query", "streaming", "storage"
quantity: float # 토큰, 노드 수, 시간 등
cost: float # USD
metadata: Dict # 추가 정보
class Subscription(Base):
"""구독 정보"""
__tablename__ = "subscriptions"
org_id: str
tier: str # "free", "pro", "enterprise"
monthly_limit: float # USD
current_month_cost: float
overages_allowed: bool
created_at: datetime
```
### 파일: `ont_platform/billing/calculator.py`
```python
class CostCalculator:
"""비용 계산"""
PRICING = {
"llm_call": 0.01, # 토큰당 $0.01
"graph_query": 0.001, # 노드당 $0.001
"streaming": 0.1, # 분당 $0.1
"storage": 10.0, # GB당 $10/월
}
async def calculate_operation_cost(
self,
operation_type: str,
quantity: float,
) -> float:
"""작업 비용 계산"""
price_per_unit = self.PRICING.get(operation_type, 0)
return quantity * price_per_unit
async def check_quota(
self,
org_id: str,
estimated_cost: float,
) -> bool:
"""할당량 확인"""
subscription = await db.get_subscription(org_id)
remaining = subscription.monthly_limit - subscription.current_month_cost
return estimated_cost <= remaining
```
---
## 🌐 Phase 8 FastAPI 앱 구조
### 파일: `ont_platform/api/phase8_app.py`
```
phase8_app.py
├─ FastAPI 앱 생성
├─ 미들웨어
│ ├─ 인증 (JWT/API 키)
│ ├─ 감시 로깅
│ ├─ 비용 추적
│ └─ 에러 처리
├─ 엔드포인트
│ ├─ /auth/* (로그인, 토큰, API 키)
│ ├─ /org/* (조직 관리)
│ ├─ /users/* (사용자 관리)
│ ├─ /ws (WebSocket)
│ ├─ /audit/* (감시 로그)
│ ├─ /billing/* (사용량, 비용)
│ └─ /api/v1/* (기존 엔드포인트 + 멀티테넌트)
└─ 전역 인스턴스
├─ connection_manager
├─ broadcaster
├─ audit_logger
└─ cost_calculator
```
---
## 🧪 테스트 계획
### `test_phase8_multitenant.py`
```
✓ 조직 생성
✓ 사용자 추가
✓ API 키 생성
✓ 데이터 격리 확인 (org_id 검증)
✓ 역할 기반 권한 확인
✓ JWT 토큰 검증
✓ API 키 검증
```
### `test_phase8_websocket.py`
```
✓ 클라이언트 연결
✓ 메시지 브로드캐스트
✓ 조직별 격리 (org_id 기반)
✓ 연결 해제
✓ 오류 처리
```
### `test_phase8_audit.py`
```
✓ 작업 로그 기록
✓ 감시 로그 조회
✓ 변경 이력 추적
✓ IP 주소 기록
```
### `test_phase8_billing.py`
```
✓ 비용 계산
✓ 할당량 확인
✓ 사용량 기록
✓ 월간 리셋
```
---
## 📅 구현 일정
| 단계 | 작업 | 예상 시간 | 우선순위 |
|------|------|---------|---------|
| 1 | 멀티테넌트 인증 | 2-3시간 | P0 |
| 2 | 감시 로그 | 2시간 | P1 |
| 3 | WebSocket 실시간 | 2-3시간 | P1 |
| 4 | 비용 관리 | 2시간 | P2 |
| 5 | 통합 테스트 | 2시간 | P1 |
| 6 | 문서화 | 1-2시간 | P1 |
**총 예상 시간**: 11-15시간
---
## 🔑 핵심 설계 결정
### 1. 데이터 격리
- **방식**: 논리적 격리 (같은 DB, org_id로 필터링)
- **이점**: 간단한 구현, 비용 효율적
- **주의**: 모든 쿼리에 org_id 포함 필수
### 2. 실시간 업데이트
- **방식**: WebSocket + 메모리 브로드캐스트
- **이점**: 낮은 레이턴시, 간단한 구현
- **확장성**: Redis Pub/Sub으로 나중에 개선 가능
### 3. 감시 로그
- **저장소**: Neo4j (기존 DB 활용)
- **구조**: 모든 변경을 트리플 저장
- **쿼리**: Cypher로 변경 이력 검색
### 4. 비용 모델
- **기반**: 작업 단위 (토큰, 노드, 시간)
- **구독 계층**: Free, Pro, Enterprise
- **특징**: 초과 사용량 추적 및 경고
---
## 📊 예상 영향
### 성능
- 멀티테넌트 오버헤드: < 5%
- WebSocket 레이턴시: < 100ms
- 감시 로깅 오버헤드: < 2%
### 보안
- JWT + API 키 이중 인증
- 조직별 데이터 격리
- 감시 로그로 완전한 감사 추적
### 확장성
- 다중 테넌트: 수십 개 조직 지원
- 동시 WebSocket: 1000+ 연결
- 감시 로그: 월 백만 건 이상 기록 가능
---
## 🚀 다음 단계 (Phase 9+)
```
Phase 9: 고급 분석 및 모니터링
├─ 사용자별 대시보드
├─ 성능 메트릭
├─ 비용 예측
└─ 알림 및 경고
Phase 10: 엔터프라이즈 추가 기능
├─ SSO (Single Sign-On)
├─ SAML/OAuth
├─ 세밀한 권한 관리
└─ 감사 보고서 자동 생성
```
---
## 📚 문서
- **PHASE_8_ENTERPRISE_GUIDE.md**: API 레퍼런스
- **코드 내 주석**: 함수 및 클래스 설명
- **테스트**: 사용 예제
---
**Phase 8로 온톨로지 플랫폼이 엔터프라이즈급 시스템으로 완성됩니다!** 🏢