125 lines
6.3 KiB
Python
125 lines
6.3 KiB
Python
"""Manager Gen6 — FinOps v2: 비용 최적화·예산 예측·자원 효율화·태깅"""
|
|
import uuid
|
|
from datetime import datetime, timedelta
|
|
from typing import Any, Dict, List, Optional
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from pydantic import BaseModel
|
|
|
|
router = APIRouter(prefix="/api/finops2", tags=["FinOps v2"])
|
|
|
|
_budgets: Dict[str, Dict] = {}
|
|
_cost_policies: Dict[str, Dict] = {}
|
|
_allocations: Dict[str, Dict] = {}
|
|
|
|
class BudgetCreate(BaseModel):
|
|
name: str; amount: float; period: str = "monthly" # monthly|quarterly|annual
|
|
category: str = "infra"; owner: str = ""
|
|
alert_threshold: float = 0.8 # 80% 도달 시 알림
|
|
|
|
class CostPolicy(BaseModel):
|
|
name: str; rule: str; action: str = "alert" # alert|block|scale_down
|
|
threshold: float; resource_type: str = "compute"
|
|
|
|
# ── 비용 현황 ────────────────────────────────────────────────────────────────
|
|
@router.get("/costs/summary")
|
|
async def cost_summary(period: str = "monthly"):
|
|
return {"period": period, "total": 3420000, "currency": "KRW",
|
|
"breakdown": [
|
|
{"category": "compute", "amount": 1820000, "ratio": 53.2},
|
|
{"category": "storage", "amount": 820000, "ratio": 24.0},
|
|
{"category": "network", "amount": 480000, "ratio": 14.0},
|
|
{"category": "license", "amount": 300000, "ratio": 8.8},
|
|
], "vs_last_period": -5.2, "ts": datetime.utcnow().isoformat()}
|
|
|
|
@router.get("/costs/trend")
|
|
async def cost_trend(months: int = 6):
|
|
import random
|
|
return {"months": months, "trend": [
|
|
{"month": f"2026-{6-i:02d}", "amount": round(random.uniform(3000000, 3800000))}
|
|
for i in range(months)
|
|
]}
|
|
|
|
@router.get("/costs/by-resource")
|
|
async def cost_by_resource(resource_type: str = "compute"):
|
|
return {"resource_type": resource_type, "resources": [
|
|
{"id": "app-01", "name": "Application Server 01", "cost": 680000, "usage_pct": 71.2, "efficiency": "good"},
|
|
{"id": "app-02", "name": "Application Server 02", "cost": 520000, "usage_pct": 43.1, "efficiency": "poor"},
|
|
{"id": "db-01", "name": "Database Server 01", "cost": 620000, "usage_pct": 82.4, "efficiency": "good"},
|
|
]}
|
|
|
|
# ── 예산 관리 ────────────────────────────────────────────────────────────────
|
|
@router.post("/budgets")
|
|
async def create_budget(budget: BudgetCreate):
|
|
bid = f"BUD-{uuid.uuid4().hex[:8].upper()}"
|
|
_budgets[bid] = {**budget.model_dump(), "id": bid, "spent": 0.0,
|
|
"created_at": datetime.utcnow().isoformat()}
|
|
return _budgets[bid]
|
|
|
|
@router.get("/budgets")
|
|
async def list_budgets():
|
|
default = [
|
|
{"id": "BUD-2026-01", "name": "2026 인프라 예산", "amount": 50000000, "spent": 20520000,
|
|
"ratio": 41.0, "period": "annual", "status": "on_track"},
|
|
]
|
|
custom = list(_budgets.values())
|
|
return {"budgets": default + custom, "total": len(default) + len(custom)}
|
|
|
|
@router.get("/budgets/{bid}/forecast")
|
|
async def budget_forecast(bid: str):
|
|
b = _budgets.get(bid)
|
|
return {"budget_id": bid, "forecast_eop": 42000000,
|
|
"projected_ratio": 84.0, "risk": "low",
|
|
"months_remaining": 6, "avg_monthly_spend": 3420000}
|
|
|
|
# ── 비용 최적화 권고 ─────────────────────────────────────────────────────────
|
|
@router.get("/optimizations")
|
|
async def optimization_recommendations():
|
|
return {"recommendations": [
|
|
{"id": "OPT-001", "resource": "app-02", "type": "right_sizing",
|
|
"current_spec": "4vCPU/8GB", "recommended": "2vCPU/4GB",
|
|
"monthly_saving": 240000, "risk": "low", "confidence": 0.91},
|
|
{"id": "OPT-002", "resource": "app-03", "type": "shutdown_schedule",
|
|
"detail": "야간·주말 비사용 — 스케줄 종료 권장",
|
|
"monthly_saving": 180000, "risk": "low", "confidence": 0.95},
|
|
{"id": "OPT-003", "resource": "storage-old", "type": "archive",
|
|
"detail": "180일 이상 미접근 데이터 아카이브 권장",
|
|
"monthly_saving": 60000, "risk": "low", "confidence": 0.88},
|
|
], "total_potential_saving": 480000, "ts": datetime.utcnow().isoformat()}
|
|
|
|
@router.post("/optimizations/{oid}/apply")
|
|
async def apply_optimization(oid: str, auto: bool = False):
|
|
return {"optimization_id": oid, "applied": True, "auto": auto,
|
|
"ts": datetime.utcnow().isoformat(), "estimated_saving": 240000}
|
|
|
|
# ── 비용 정책 ────────────────────────────────────────────────────────────────
|
|
@router.post("/cost-policies")
|
|
async def create_cost_policy(policy: CostPolicy):
|
|
pid = f"CPOL-{uuid.uuid4().hex[:8].upper()}"
|
|
_cost_policies[pid] = {**policy.model_dump(), "id": pid, "active": True,
|
|
"triggered_count": 0, "created_at": datetime.utcnow().isoformat()}
|
|
return _cost_policies[pid]
|
|
|
|
@router.get("/cost-policies")
|
|
async def list_cost_policies():
|
|
return {"policies": list(_cost_policies.values()), "total": len(_cost_policies)}
|
|
|
|
# ── 태깅 거버넌스 ────────────────────────────────────────────────────────────
|
|
@router.get("/tags/compliance")
|
|
async def tag_compliance():
|
|
return {"total_resources": 48, "tagged": 41, "untagged": 7,
|
|
"compliance_rate": 85.4,
|
|
"required_tags": ["environment", "owner", "cost_center", "project"],
|
|
"untagged_resources": ["network-fw-01", "backup-storage-02"]}
|
|
|
|
@router.get("/tags/allocation")
|
|
async def cost_allocation_by_tag(tag_key: str = "project"):
|
|
return {"tag_key": tag_key, "allocations": [
|
|
{"tag_value": "guardia", "cost": 2100000, "ratio": 61.4},
|
|
{"tag_value": "zioinfo-web", "cost": 820000, "ratio": 24.0},
|
|
{"tag_value": "shared", "cost": 500000, "ratio": 14.6},
|
|
]}
|
|
|
|
@router.get("/health")
|
|
async def health():
|
|
return {"status": "ok", "budgets": len(_budgets), "policies": len(_cost_policies)}
|