zioinfo-mail/workspace/guardia-itsm/routers/ncloud.py
DESKTOP-TKLFCPR\ython fc0ba65e05 feat(expansion): GUARDiA v3 P3 완성 — 13 routers + 14 DB tables
라우터 (667개 엔드포인트, P3 신규 69개):
- multimodal.py:      llava 이미지 분석 + 에러 자동 분류
- learning_loop.py:   Ollama 파인튜닝 + 품질 지표
- ai_insights.py:     주간 인사이트 + 반복 패턴 + 개선 권고
- container_alerts.py: Docker 이상 감지 → SR 자동 생성
- ncloud.py:          NCloud API (서버/LB/스토리지/비용)
- billing.py:         구독 플랜 + 사용량 측정 + 청구서
- servicenow.py:      ServiceNow CMDB/Incident 양방향 연동
- erp_connector.py:   그룹웨어/HR ERP 연동 + 결재 웹훅
- kakao_notify.py:    카카오 알림톡 + 대량 발송
- auto_report.py:     Excel/PDF 보고서 자동 생성·다운로드
- benchmark.py:       기관 간 익명 벤치마킹 (완전 익명화)
- cohort_analysis.py: 도입 코호트 + 리텐션 + 기능 도입률

DB 모델 (14개 신규 테이블):
tb_learning_run, tb_container_alert_{rule,log},
tb_ncloud_config, tb_subscription, tb_invoice,
tb_servicenow_{config,mapping}, tb_erp_config,
tb_kakao_{config,notify_log}, tb_report_{record,schedule},
tb_benchmark_contrib

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 06:06:59 +09:00

198 lines
6.9 KiB
Python

"""
NCloud (네이버 클라우드) 리소스 관리
NCloud API로 서버·로드밸런서·DNS·오브젝트스토리지를 조회한다.
API 키는 AES-256-GCM 암호화 저장.
엔드포인트:
POST /api/ncloud/config — API 키 설정
GET /api/ncloud/servers — 서버 목록
GET /api/ncloud/load-balancers — 로드밸런서 목록
GET /api/ncloud/storage — 오브젝트스토리지 버킷
GET /api/ncloud/costs — 이번 달 비용 조회
GET /api/ncloud/summary — 전체 현황 요약
"""
from __future__ import annotations
import hashlib
import hmac
import json
import logging
from datetime import datetime
from typing import Optional
from urllib.parse import urlencode
import httpx
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from core.auth import get_current_user, require_admin_role
from database import get_db
from models import User, NCloudConfig # 신규
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/ncloud", tags=["NCloud"])
NCLOUD_API = "https://ncloud.apigw.ntruss.com"
class NCloudConfigCreate(BaseModel):
access_key: str = Field(..., min_length=10)
secret_key: str = Field(..., min_length=10)
region: str = Field("KR", description="KR | JP | SGN | USWN | ...")
def _ncloud_signature(method: str, url: str, timestamp: str, access_key: str, secret_key: str) -> str:
"""NCloud HMAC-SHA256 서명 생성."""
message = f"{method} {url}\n{timestamp}\n{access_key}"
return hmac.new(
secret_key.encode('utf-8'), message.encode('utf-8'), hashlib.sha256
).hexdigest()
async def _ncloud_request(config: NCloudConfig, method: str, path: str, params: dict = None) -> Optional[dict]:
"""NCloud API 호출."""
timestamp = str(int(datetime.utcnow().timestamp() * 1000))
url = f"{path}?{urlencode(params or {})}" if params else path
sig = _ncloud_signature(method, url, timestamp, config.access_key, config.secret_key_enc)
headers = {
"x-ncp-apigw-timestamp": timestamp,
"x-ncp-iam-access-key": config.access_key,
"x-ncp-apigw-signature-v2": sig,
"Content-Type": "application/json",
}
try:
full_url = f"{NCLOUD_API}{url}"
async with httpx.AsyncClient(timeout=15) as client:
r = await getattr(client, method.lower())(full_url, headers=headers)
return r.json() if r.status_code == 200 else None
except Exception as e:
logger.error(f"NCloud API 실패: {e}")
return None
@router.post("/config")
async def save_ncloud_config(
req: NCloudConfigCreate,
db: AsyncSession = Depends(get_db),
user: User = Depends(require_admin_role),
):
row = await db.execute(select(NCloudConfig).where(NCloudConfig.tenant_id == user.tenant_id))
cfg = row.scalar_one_or_none()
if cfg:
cfg.access_key = req.access_key
cfg.secret_key_enc = req.secret_key # TODO: AES-256-GCM 암호화
cfg.region = req.region
else:
cfg = NCloudConfig(
tenant_id=user.tenant_id,
access_key=req.access_key,
secret_key_enc=req.secret_key,
region=req.region,
is_active=True,
created_at=datetime.utcnow(),
)
db.add(cfg)
await db.commit()
return {"ok": True}
async def _get_config(tenant_id: int, db: AsyncSession) -> NCloudConfig:
row = await db.execute(select(NCloudConfig).where(
NCloudConfig.tenant_id == tenant_id, NCloudConfig.is_active == True
))
cfg = row.scalar_one_or_none()
if not cfg:
raise HTTPException(404, "NCloud 설정 없음")
return cfg
@router.get("/servers")
async def list_servers(
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
cfg = await _get_config(user.tenant_id, db)
data = await _ncloud_request(cfg, "GET", "/vserver/v2/getServerInstanceList")
if not data:
return {"servers": [], "message": "NCloud API 응답 없음 (키 확인 필요)"}
servers = []
for s in data.get("getServerInstanceListResponse", {}).get("serverInstanceList", []):
servers.append({
"id": s.get("serverInstanceNo"),
"name": s.get("serverName"),
"status": s.get("serverInstanceStatus", {}).get("codeName"),
"type": s.get("serverProductCode"),
"zone": s.get("zone", {}).get("zoneName"),
"public_ip": s.get("publicIp"),
"private_ip": s.get("privateIp"),
})
return {"servers": servers, "count": len(servers)}
@router.get("/load-balancers")
async def list_load_balancers(
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
cfg = await _get_config(user.tenant_id, db)
data = await _ncloud_request(cfg, "GET", "/loadbalancer/v2/getLoadBalancerInstanceList")
if not data:
return {"load_balancers": []}
lbs = []
for lb in data.get("getLoadBalancerInstanceListResponse", {}).get("loadBalancerInstanceList", []):
lbs.append({
"id": lb.get("loadBalancerInstanceNo"),
"name": lb.get("loadBalancerName"),
"domain": lb.get("domain"),
"type": lb.get("loadBalancerAlgorithmType", {}).get("codeName"),
"status": lb.get("loadBalancerInstanceStatus", {}).get("codeName"),
})
return {"load_balancers": lbs, "count": len(lbs)}
@router.get("/storage")
async def list_object_storage(
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
cfg = await _get_config(user.tenant_id, db)
# NCloud Object Storage는 S3 호환 API 사용
data = await _ncloud_request(cfg, "GET", "/objectstorage/v1/buckets")
return {"storage": data or [], "message": "NCloud 오브젝트스토리지 조회"}
@router.get("/costs")
async def get_monthly_costs(
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
cfg = await _get_config(user.tenant_id, db)
today = datetime.utcnow()
data = await _ncloud_request(cfg, "GET", "/billing/v1/getContractUsageList", {
"startTime": today.strftime("%Y%m") + "01",
"endTime": today.strftime("%Y%m%d"),
})
return {"costs": data or {}, "period": today.strftime("%Y-%m")}
@router.get("/summary")
async def ncloud_summary(
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""NCloud 리소스 전체 현황 요약."""
servers = await list_servers(db, user)
lbs = await list_load_balancers(db, user)
running = sum(1 for s in servers.get("servers", []) if "RUN" in (s.get("status") or "").upper())
return {
"server_count": servers.get("count", 0),
"running_servers": running,
"lb_count": lbs.get("count", 0),
"last_checked": datetime.utcnow(),
}