"""배포 영향 분석 — CMDB CI 의존성 그래프 BFS 탐색.""" from __future__ import annotations import logging from collections import deque from typing import List logger = logging.getLogger(__name__) MAX_DEPTH = 3 async def analyze_deploy_impact(ci_id: int, db) -> dict: """ 배포 대상 CI로부터 의존하는 모든 서비스/기관을 BFS로 탐색. Returns: { "ci_id": int, "ci_name": str, "affected_cis": [{"ci_id", "ci_name", "ci_type", "relation_type", "depth"}], "affected_institutions": [str], "risk_level": "LOW"|"MEDIUM"|"HIGH"|"CRITICAL", "risk_reason": str, "recommendation": str } """ from models import ConfigItem, CIRelation, Institution from sqlalchemy import select, or_ root = await db.get(ConfigItem, ci_id) if not root: raise ValueError(f"CI ID {ci_id}를 찾을 수 없습니다.") affected = [] visited = {ci_id} queue = deque([(ci_id, 0)]) affected_inst_ids = set() if root.inst_id: affected_inst_ids.add(root.inst_id) while queue: current_id, depth = queue.popleft() if depth >= MAX_DEPTH: continue # 현재 CI에서 출발하는 관계 조회 (DEPENDS_ON, USES, RUNS_ON 등) relations = (await db.execute( select(CIRelation) .where(or_( CIRelation.from_ci_id == current_id, CIRelation.to_ci_id == current_id, )) )).scalars().all() for rel in relations: next_id = rel.to_ci_id if rel.from_ci_id == current_id else rel.from_ci_id if next_id in visited: continue visited.add(next_id) next_ci = await db.get(ConfigItem, next_id) if not next_ci: continue affected.append({ "ci_id": next_ci.id, "ci_name": next_ci.name, "ci_type": next_ci.ci_type, "relation_type": rel.relation_type if hasattr(rel, "relation_type") else "RELATED", "depth": depth + 1, }) if next_ci.inst_id: affected_inst_ids.add(next_ci.inst_id) queue.append((next_id, depth + 1)) # 영향받는 기관명 수집 inst_names: List[str] = [] for iid in affected_inst_ids: inst = await db.get(Institution, iid) if inst: inst_names.append(inst.inst_name) # 리스크 레벨 산정 count = len(affected) if count == 0: risk_level = "LOW" risk_reason = "영향받는 CI 없음" recommendation = "배포를 안전하게 진행할 수 있습니다." elif count <= 3: risk_level = "MEDIUM" risk_reason = f"영향 CI {count}개" recommendation = "담당자 확인 후 배포를 진행하세요." elif count <= 8: risk_level = "HIGH" risk_reason = f"영향 CI {count}개 — 다수 서비스 영향" recommendation = "CAB 검토 후 유지보수 시간대에 배포하세요." else: risk_level = "CRITICAL" risk_reason = f"영향 CI {count}개 — 광범위한 서비스 영향" recommendation = "P1 수준 변경 관리 절차를 따르세요. 긴급 배포 금지." return { "ci_id": root.id, "ci_name": root.name, "ci_type": root.ci_type, "affected_cis": affected, "affected_institutions": inst_names, "risk_level": risk_level, "risk_reason": risk_reason, "recommendation": recommendation, }