""" NHN Cloud Load Balancer API Module 로드밸런서, 리스너, 풀, 멤버, 헬스 모니터, L7 정책 관리 """ import logging from typing import Optional, List from .base import BaseAPI, NHNCloudEndpoints, Region logger = logging.getLogger(__name__) class ApiLoadBalancer(BaseAPI): """NHN Cloud Load Balancer API 클래스""" def __init__(self, region: str, token: str): """ Args: region: 리전 (kr1: 판교, kr2: 평촌) token: API 인증 토큰 """ super().__init__(region, token) if self.region == Region.KR1: self.lb_url = NHNCloudEndpoints.NETWORK_KR1 else: self.lb_url = NHNCloudEndpoints.NETWORK_KR2 # ==================== Load Balancer ==================== def get_loadbalancer_list( self, id: Optional[str] = None, name: Optional[str] = None, provisioning_status: Optional[str] = None, vip_address: Optional[str] = None, vip_subnet_id: Optional[str] = None, ) -> dict: """ 로드밸런서 목록 조회 Args: id: 로드밸런서 ID로 필터링 name: 로드밸런서 이름으로 필터링 provisioning_status: 프로비저닝 상태로 필터링 (ACTIVE, PENDING_CREATE 등) vip_address: VIP 주소로 필터링 vip_subnet_id: VIP 서브넷 ID로 필터링 """ url = f"{self.lb_url}/v2.0/lbaas/loadbalancers" params = {} if id: params["id"] = id if name: params["name"] = name if provisioning_status: params["provisioning_status"] = provisioning_status if vip_address: params["vip_address"] = vip_address if vip_subnet_id: params["vip_subnet_id"] = vip_subnet_id logger.info(f"[NHN API] 로드밸런서 목록 조회 요청 - URL={url}") result = self._get(url, params=params if params else None) lb_count = len(result.get("loadbalancers", [])) logger.info(f"[NHN API] 로드밸런서 목록 조회 완료 - count={lb_count}") return result def get_loadbalancer_info(self, loadbalancer_id: str) -> dict: """로드밸런서 상세 조회""" url = f"{self.lb_url}/v2.0/lbaas/loadbalancers/{loadbalancer_id}" logger.info(f"[NHN API] 로드밸런서 상세 조회 요청 - loadbalancer_id={loadbalancer_id}") result = self._get(url) logger.info(f"[NHN API] 로드밸런서 상세 조회 완료 - loadbalancer_id={loadbalancer_id}") return result def create_loadbalancer( self, vip_subnet_id: str, name: Optional[str] = None, description: Optional[str] = None, vip_address: Optional[str] = None, admin_state_up: bool = True, loadbalancer_type: str = "shared", ) -> dict: """ 로드밸런서 생성 Args: vip_subnet_id: VIP가 할당될 서브넷 ID (필수) name: 로드밸런서 이름 description: 설명 vip_address: VIP 주소 (지정하지 않으면 자동 할당) admin_state_up: 관리자 상태 loadbalancer_type: 로드밸런서 타입 (shared 또는 dedicated) """ url = f"{self.lb_url}/v2.0/lbaas/loadbalancers" payload = { "loadbalancer": { "vip_subnet_id": vip_subnet_id, "admin_state_up": admin_state_up, "loadbalancer_type": loadbalancer_type, } } if name: payload["loadbalancer"]["name"] = name if description: payload["loadbalancer"]["description"] = description if vip_address: payload["loadbalancer"]["vip_address"] = vip_address logger.info(f"[NHN API] 로드밸런서 생성 요청 - name={name}, vip_subnet_id={vip_subnet_id}") return self._post(url, payload) def update_loadbalancer( self, loadbalancer_id: str, name: Optional[str] = None, description: Optional[str] = None, admin_state_up: Optional[bool] = None, ) -> dict: """ 로드밸런서 수정 Args: loadbalancer_id: 로드밸런서 ID name: 변경할 이름 description: 변경할 설명 admin_state_up: 관리자 상태 """ url = f"{self.lb_url}/v2.0/lbaas/loadbalancers/{loadbalancer_id}" payload = {"loadbalancer": {}} if name is not None: payload["loadbalancer"]["name"] = name if description is not None: payload["loadbalancer"]["description"] = description if admin_state_up is not None: payload["loadbalancer"]["admin_state_up"] = admin_state_up logger.info(f"[NHN API] 로드밸런서 수정 요청 - loadbalancer_id={loadbalancer_id}") return self._put(url, payload) def delete_loadbalancer(self, loadbalancer_id: str) -> dict: """로드밸런서 삭제""" url = f"{self.lb_url}/v2.0/lbaas/loadbalancers/{loadbalancer_id}" logger.info(f"[NHN API] 로드밸런서 삭제 요청 - loadbalancer_id={loadbalancer_id}") return self._delete(url) # ==================== Listener ==================== def get_listener_list( self, loadbalancer_id: Optional[str] = None, protocol: Optional[str] = None, protocol_port: Optional[int] = None, ) -> dict: """ 리스너 목록 조회 Args: loadbalancer_id: 로드밸런서 ID로 필터링 protocol: 프로토콜로 필터링 (TCP, HTTP, HTTPS, TERMINATED_HTTPS) protocol_port: 포트 번호로 필터링 """ url = f"{self.lb_url}/v2.0/lbaas/listeners" params = {} if loadbalancer_id: params["loadbalancer_id"] = loadbalancer_id if protocol: params["protocol"] = protocol if protocol_port: params["protocol_port"] = protocol_port logger.info(f"[NHN API] 리스너 목록 조회 요청") result = self._get(url, params=params if params else None) listener_count = len(result.get("listeners", [])) logger.info(f"[NHN API] 리스너 목록 조회 완료 - count={listener_count}") return result def get_listener_info(self, listener_id: str) -> dict: """리스너 상세 조회""" url = f"{self.lb_url}/v2.0/lbaas/listeners/{listener_id}" logger.info(f"[NHN API] 리스너 상세 조회 요청 - listener_id={listener_id}") return self._get(url) def create_listener( self, loadbalancer_id: str, protocol: str, protocol_port: int, name: Optional[str] = None, description: Optional[str] = None, default_pool_id: Optional[str] = None, connection_limit: int = -1, keepalive_timeout: int = 300, admin_state_up: bool = True, default_tls_container_ref: Optional[str] = None, sni_container_refs: Optional[List[str]] = None, insert_headers: Optional[dict] = None, ) -> dict: """ 리스너 생성 Args: loadbalancer_id: 로드밸런서 ID (필수) protocol: 프로토콜 (TCP, HTTP, HTTPS, TERMINATED_HTTPS) protocol_port: 포트 번호 (1-65535) name: 리스너 이름 description: 설명 default_pool_id: 기본 풀 ID connection_limit: 연결 제한 (-1: 무제한) keepalive_timeout: Keepalive 타임아웃 (초) admin_state_up: 관리자 상태 default_tls_container_ref: TLS 인증서 컨테이너 참조 (TERMINATED_HTTPS 사용 시) sni_container_refs: SNI 인증서 컨테이너 참조 목록 insert_headers: 삽입할 헤더 (X-Forwarded-For, X-Forwarded-Port 등) """ url = f"{self.lb_url}/v2.0/lbaas/listeners" payload = { "listener": { "loadbalancer_id": loadbalancer_id, "protocol": protocol, "protocol_port": protocol_port, "connection_limit": connection_limit, "keepalive_timeout": keepalive_timeout, "admin_state_up": admin_state_up, } } if name: payload["listener"]["name"] = name if description: payload["listener"]["description"] = description if default_pool_id: payload["listener"]["default_pool_id"] = default_pool_id if default_tls_container_ref: payload["listener"]["default_tls_container_ref"] = default_tls_container_ref if sni_container_refs: payload["listener"]["sni_container_refs"] = sni_container_refs if insert_headers: payload["listener"]["insert_headers"] = insert_headers logger.info(f"[NHN API] 리스너 생성 요청 - protocol={protocol}, port={protocol_port}") return self._post(url, payload) def update_listener( self, listener_id: str, name: Optional[str] = None, description: Optional[str] = None, default_pool_id: Optional[str] = None, connection_limit: Optional[int] = None, keepalive_timeout: Optional[int] = None, admin_state_up: Optional[bool] = None, default_tls_container_ref: Optional[str] = None, sni_container_refs: Optional[List[str]] = None, insert_headers: Optional[dict] = None, ) -> dict: """리스너 수정""" url = f"{self.lb_url}/v2.0/lbaas/listeners/{listener_id}" payload = {"listener": {}} if name is not None: payload["listener"]["name"] = name if description is not None: payload["listener"]["description"] = description if default_pool_id is not None: payload["listener"]["default_pool_id"] = default_pool_id if connection_limit is not None: payload["listener"]["connection_limit"] = connection_limit if keepalive_timeout is not None: payload["listener"]["keepalive_timeout"] = keepalive_timeout if admin_state_up is not None: payload["listener"]["admin_state_up"] = admin_state_up if default_tls_container_ref is not None: payload["listener"]["default_tls_container_ref"] = default_tls_container_ref if sni_container_refs is not None: payload["listener"]["sni_container_refs"] = sni_container_refs if insert_headers is not None: payload["listener"]["insert_headers"] = insert_headers logger.info(f"[NHN API] 리스너 수정 요청 - listener_id={listener_id}") return self._put(url, payload) def delete_listener(self, listener_id: str) -> dict: """리스너 삭제""" url = f"{self.lb_url}/v2.0/lbaas/listeners/{listener_id}" logger.info(f"[NHN API] 리스너 삭제 요청 - listener_id={listener_id}") return self._delete(url) # ==================== Pool ==================== def get_pool_list( self, loadbalancer_id: Optional[str] = None, protocol: Optional[str] = None, lb_algorithm: Optional[str] = None, ) -> dict: """ 풀 목록 조회 Args: loadbalancer_id: 로드밸런서 ID로 필터링 protocol: 프로토콜로 필터링 lb_algorithm: 로드밸런싱 알고리즘으로 필터링 """ url = f"{self.lb_url}/v2.0/lbaas/pools" params = {} if loadbalancer_id: params["loadbalancer_id"] = loadbalancer_id if protocol: params["protocol"] = protocol if lb_algorithm: params["lb_algorithm"] = lb_algorithm logger.info(f"[NHN API] 풀 목록 조회 요청") result = self._get(url, params=params if params else None) pool_count = len(result.get("pools", [])) logger.info(f"[NHN API] 풀 목록 조회 완료 - count={pool_count}") return result def get_pool_info(self, pool_id: str) -> dict: """풀 상세 조회""" url = f"{self.lb_url}/v2.0/lbaas/pools/{pool_id}" logger.info(f"[NHN API] 풀 상세 조회 요청 - pool_id={pool_id}") return self._get(url) def create_pool( self, lb_algorithm: str, protocol: str, loadbalancer_id: Optional[str] = None, listener_id: Optional[str] = None, name: Optional[str] = None, description: Optional[str] = None, admin_state_up: bool = True, session_persistence: Optional[dict] = None, ) -> dict: """ 풀 생성 Args: lb_algorithm: 로드밸런싱 알고리즘 (ROUND_ROBIN, LEAST_CONNECTIONS, SOURCE_IP) protocol: 프로토콜 (TCP, HTTP, HTTPS, PROXY) loadbalancer_id: 로드밸런서 ID (listener_id와 둘 중 하나 필수) listener_id: 리스너 ID (loadbalancer_id와 둘 중 하나 필수) name: 풀 이름 description: 설명 admin_state_up: 관리자 상태 session_persistence: 세션 지속성 설정 {type, cookie_name} - type: SOURCE_IP, HTTP_COOKIE, APP_COOKIE - cookie_name: APP_COOKIE 타입 사용 시 쿠키 이름 """ url = f"{self.lb_url}/v2.0/lbaas/pools" payload = { "pool": { "lb_algorithm": lb_algorithm, "protocol": protocol, "admin_state_up": admin_state_up, } } if loadbalancer_id: payload["pool"]["loadbalancer_id"] = loadbalancer_id if listener_id: payload["pool"]["listener_id"] = listener_id if name: payload["pool"]["name"] = name if description: payload["pool"]["description"] = description if session_persistence: payload["pool"]["session_persistence"] = session_persistence logger.info(f"[NHN API] 풀 생성 요청 - algorithm={lb_algorithm}, protocol={protocol}") return self._post(url, payload) def update_pool( self, pool_id: str, name: Optional[str] = None, description: Optional[str] = None, lb_algorithm: Optional[str] = None, admin_state_up: Optional[bool] = None, session_persistence: Optional[dict] = None, ) -> dict: """풀 수정""" url = f"{self.lb_url}/v2.0/lbaas/pools/{pool_id}" payload = {"pool": {}} if name is not None: payload["pool"]["name"] = name if description is not None: payload["pool"]["description"] = description if lb_algorithm is not None: payload["pool"]["lb_algorithm"] = lb_algorithm if admin_state_up is not None: payload["pool"]["admin_state_up"] = admin_state_up if session_persistence is not None: payload["pool"]["session_persistence"] = session_persistence logger.info(f"[NHN API] 풀 수정 요청 - pool_id={pool_id}") return self._put(url, payload) def delete_pool(self, pool_id: str) -> dict: """풀 삭제""" url = f"{self.lb_url}/v2.0/lbaas/pools/{pool_id}" logger.info(f"[NHN API] 풀 삭제 요청 - pool_id={pool_id}") return self._delete(url) # ==================== Member ==================== def get_member_list(self, pool_id: str) -> dict: """풀의 멤버 목록 조회""" url = f"{self.lb_url}/v2.0/lbaas/pools/{pool_id}/members" logger.info(f"[NHN API] 멤버 목록 조회 요청 - pool_id={pool_id}") result = self._get(url) member_count = len(result.get("members", [])) logger.info(f"[NHN API] 멤버 목록 조회 완료 - count={member_count}") return result def get_member_info(self, pool_id: str, member_id: str) -> dict: """멤버 상세 조회""" url = f"{self.lb_url}/v2.0/lbaas/pools/{pool_id}/members/{member_id}" logger.info(f"[NHN API] 멤버 상세 조회 요청 - pool_id={pool_id}, member_id={member_id}") return self._get(url) def create_member( self, pool_id: str, address: str, protocol_port: int, subnet_id: Optional[str] = None, weight: int = 1, admin_state_up: bool = True, ) -> dict: """ 멤버 추가 Args: pool_id: 풀 ID address: 멤버 IP 주소 protocol_port: 멤버 포트 번호 subnet_id: 멤버가 속한 서브넷 ID weight: 가중치 (1-256) admin_state_up: 관리자 상태 """ url = f"{self.lb_url}/v2.0/lbaas/pools/{pool_id}/members" payload = { "member": { "address": address, "protocol_port": protocol_port, "weight": weight, "admin_state_up": admin_state_up, } } if subnet_id: payload["member"]["subnet_id"] = subnet_id logger.info(f"[NHN API] 멤버 추가 요청 - pool_id={pool_id}, address={address}:{protocol_port}") return self._post(url, payload) def update_member( self, pool_id: str, member_id: str, weight: Optional[int] = None, admin_state_up: Optional[bool] = None, ) -> dict: """멤버 수정""" url = f"{self.lb_url}/v2.0/lbaas/pools/{pool_id}/members/{member_id}" payload = {"member": {}} if weight is not None: payload["member"]["weight"] = weight if admin_state_up is not None: payload["member"]["admin_state_up"] = admin_state_up logger.info(f"[NHN API] 멤버 수정 요청 - pool_id={pool_id}, member_id={member_id}") return self._put(url, payload) def delete_member(self, pool_id: str, member_id: str) -> dict: """멤버 삭제""" url = f"{self.lb_url}/v2.0/lbaas/pools/{pool_id}/members/{member_id}" logger.info(f"[NHN API] 멤버 삭제 요청 - pool_id={pool_id}, member_id={member_id}") return self._delete(url) # ==================== Health Monitor ==================== def get_healthmonitor_list(self, pool_id: Optional[str] = None) -> dict: """헬스 모니터 목록 조회""" url = f"{self.lb_url}/v2.0/lbaas/healthmonitors" params = {} if pool_id: params["pool_id"] = pool_id logger.info(f"[NHN API] 헬스 모니터 목록 조회 요청") result = self._get(url, params=params if params else None) hm_count = len(result.get("healthmonitors", [])) logger.info(f"[NHN API] 헬스 모니터 목록 조회 완료 - count={hm_count}") return result def get_healthmonitor_info(self, healthmonitor_id: str) -> dict: """헬스 모니터 상세 조회""" url = f"{self.lb_url}/v2.0/lbaas/healthmonitors/{healthmonitor_id}" logger.info(f"[NHN API] 헬스 모니터 상세 조회 요청 - healthmonitor_id={healthmonitor_id}") return self._get(url) def create_healthmonitor( self, pool_id: str, type: str, delay: int, timeout: int, max_retries: int, max_retries_down: int = 3, http_method: str = "GET", url_path: str = "/", expected_codes: str = "200", admin_state_up: bool = True, host_header: Optional[str] = None, ) -> dict: """ 헬스 모니터 생성 Args: pool_id: 풀 ID type: 헬스 체크 타입 (TCP, HTTP, HTTPS) delay: 헬스 체크 간격 (초) timeout: 응답 대기 시간 (초) max_retries: 최대 재시도 횟수 (정상 판정) max_retries_down: 최대 재시도 횟수 (비정상 판정) http_method: HTTP 메서드 (HTTP/HTTPS 타입 시) url_path: URL 경로 (HTTP/HTTPS 타입 시) expected_codes: 예상 응답 코드 (예: "200", "200,201", "200-204") admin_state_up: 관리자 상태 host_header: Host 헤더 값 """ url = f"{self.lb_url}/v2.0/lbaas/healthmonitors" payload = { "healthmonitor": { "pool_id": pool_id, "type": type, "delay": delay, "timeout": timeout, "max_retries": max_retries, "max_retries_down": max_retries_down, "admin_state_up": admin_state_up, } } # HTTP/HTTPS 전용 옵션 if type in ["HTTP", "HTTPS"]: payload["healthmonitor"]["http_method"] = http_method payload["healthmonitor"]["url_path"] = url_path payload["healthmonitor"]["expected_codes"] = expected_codes if host_header: payload["healthmonitor"]["host_header"] = host_header logger.info(f"[NHN API] 헬스 모니터 생성 요청 - pool_id={pool_id}, type={type}") return self._post(url, payload) def update_healthmonitor( self, healthmonitor_id: str, delay: Optional[int] = None, timeout: Optional[int] = None, max_retries: Optional[int] = None, max_retries_down: Optional[int] = None, http_method: Optional[str] = None, url_path: Optional[str] = None, expected_codes: Optional[str] = None, admin_state_up: Optional[bool] = None, host_header: Optional[str] = None, ) -> dict: """헬스 모니터 수정""" url = f"{self.lb_url}/v2.0/lbaas/healthmonitors/{healthmonitor_id}" payload = {"healthmonitor": {}} if delay is not None: payload["healthmonitor"]["delay"] = delay if timeout is not None: payload["healthmonitor"]["timeout"] = timeout if max_retries is not None: payload["healthmonitor"]["max_retries"] = max_retries if max_retries_down is not None: payload["healthmonitor"]["max_retries_down"] = max_retries_down if http_method is not None: payload["healthmonitor"]["http_method"] = http_method if url_path is not None: payload["healthmonitor"]["url_path"] = url_path if expected_codes is not None: payload["healthmonitor"]["expected_codes"] = expected_codes if admin_state_up is not None: payload["healthmonitor"]["admin_state_up"] = admin_state_up if host_header is not None: payload["healthmonitor"]["host_header"] = host_header logger.info(f"[NHN API] 헬스 모니터 수정 요청 - healthmonitor_id={healthmonitor_id}") return self._put(url, payload) def delete_healthmonitor(self, healthmonitor_id: str) -> dict: """헬스 모니터 삭제""" url = f"{self.lb_url}/v2.0/lbaas/healthmonitors/{healthmonitor_id}" logger.info(f"[NHN API] 헬스 모니터 삭제 요청 - healthmonitor_id={healthmonitor_id}") return self._delete(url) # ==================== L7 Policy ==================== def get_l7policy_list(self, listener_id: Optional[str] = None) -> dict: """L7 정책 목록 조회""" url = f"{self.lb_url}/v2.0/lbaas/l7policies" params = {} if listener_id: params["listener_id"] = listener_id logger.info(f"[NHN API] L7 정책 목록 조회 요청") result = self._get(url, params=params if params else None) policy_count = len(result.get("l7policies", [])) logger.info(f"[NHN API] L7 정책 목록 조회 완료 - count={policy_count}") return result def get_l7policy_info(self, l7policy_id: str) -> dict: """L7 정책 상세 조회""" url = f"{self.lb_url}/v2.0/lbaas/l7policies/{l7policy_id}" logger.info(f"[NHN API] L7 정책 상세 조회 요청 - l7policy_id={l7policy_id}") return self._get(url) def create_l7policy( self, listener_id: str, action: str, position: int = 1, name: Optional[str] = None, description: Optional[str] = None, redirect_pool_id: Optional[str] = None, redirect_url: Optional[str] = None, admin_state_up: bool = True, ) -> dict: """ L7 정책 생성 Args: listener_id: 리스너 ID action: 액션 (REDIRECT_TO_POOL, REDIRECT_TO_URL, REJECT) position: 정책 우선순위 (낮을수록 먼저 적용) name: 정책 이름 description: 설명 redirect_pool_id: 리다이렉트 대상 풀 ID (REDIRECT_TO_POOL 액션 시) redirect_url: 리다이렉트 URL (REDIRECT_TO_URL 액션 시) admin_state_up: 관리자 상태 """ url = f"{self.lb_url}/v2.0/lbaas/l7policies" payload = { "l7policy": { "listener_id": listener_id, "action": action, "position": position, "admin_state_up": admin_state_up, } } if name: payload["l7policy"]["name"] = name if description: payload["l7policy"]["description"] = description if redirect_pool_id: payload["l7policy"]["redirect_pool_id"] = redirect_pool_id if redirect_url: payload["l7policy"]["redirect_url"] = redirect_url logger.info(f"[NHN API] L7 정책 생성 요청 - listener_id={listener_id}, action={action}") return self._post(url, payload) def update_l7policy( self, l7policy_id: str, name: Optional[str] = None, description: Optional[str] = None, action: Optional[str] = None, position: Optional[int] = None, redirect_pool_id: Optional[str] = None, redirect_url: Optional[str] = None, admin_state_up: Optional[bool] = None, ) -> dict: """L7 정책 수정""" url = f"{self.lb_url}/v2.0/lbaas/l7policies/{l7policy_id}" payload = {"l7policy": {}} if name is not None: payload["l7policy"]["name"] = name if description is not None: payload["l7policy"]["description"] = description if action is not None: payload["l7policy"]["action"] = action if position is not None: payload["l7policy"]["position"] = position if redirect_pool_id is not None: payload["l7policy"]["redirect_pool_id"] = redirect_pool_id if redirect_url is not None: payload["l7policy"]["redirect_url"] = redirect_url if admin_state_up is not None: payload["l7policy"]["admin_state_up"] = admin_state_up logger.info(f"[NHN API] L7 정책 수정 요청 - l7policy_id={l7policy_id}") return self._put(url, payload) def delete_l7policy(self, l7policy_id: str) -> dict: """L7 정책 삭제""" url = f"{self.lb_url}/v2.0/lbaas/l7policies/{l7policy_id}" logger.info(f"[NHN API] L7 정책 삭제 요청 - l7policy_id={l7policy_id}") return self._delete(url) # ==================== L7 Rule ==================== def get_l7rule_list(self, l7policy_id: str) -> dict: """L7 룰 목록 조회""" url = f"{self.lb_url}/v2.0/lbaas/l7policies/{l7policy_id}/rules" logger.info(f"[NHN API] L7 룰 목록 조회 요청 - l7policy_id={l7policy_id}") result = self._get(url) rule_count = len(result.get("rules", [])) logger.info(f"[NHN API] L7 룰 목록 조회 완료 - count={rule_count}") return result def get_l7rule_info(self, l7policy_id: str, l7rule_id: str) -> dict: """L7 룰 상세 조회""" url = f"{self.lb_url}/v2.0/lbaas/l7policies/{l7policy_id}/rules/{l7rule_id}" logger.info(f"[NHN API] L7 룰 상세 조회 요청 - l7policy_id={l7policy_id}, l7rule_id={l7rule_id}") return self._get(url) def create_l7rule( self, l7policy_id: str, type: str, compare_type: str, value: str, key: Optional[str] = None, invert: bool = False, admin_state_up: bool = True, ) -> dict: """ L7 룰 생성 Args: l7policy_id: L7 정책 ID type: 룰 타입 (COOKIE, FILE_TYPE, HEADER, HOST_NAME, PATH) compare_type: 비교 방식 (CONTAINS, ENDS_WITH, STARTS_WITH, EQUAL_TO, REGEX) value: 비교할 값 key: 키 값 (COOKIE, HEADER 타입 사용 시) invert: 조건 반전 여부 admin_state_up: 관리자 상태 """ url = f"{self.lb_url}/v2.0/lbaas/l7policies/{l7policy_id}/rules" payload = { "rule": { "type": type, "compare_type": compare_type, "value": value, "invert": invert, "admin_state_up": admin_state_up, } } if key: payload["rule"]["key"] = key logger.info(f"[NHN API] L7 룰 생성 요청 - l7policy_id={l7policy_id}, type={type}") return self._post(url, payload) def update_l7rule( self, l7policy_id: str, l7rule_id: str, type: Optional[str] = None, compare_type: Optional[str] = None, value: Optional[str] = None, key: Optional[str] = None, invert: Optional[bool] = None, admin_state_up: Optional[bool] = None, ) -> dict: """L7 룰 수정""" url = f"{self.lb_url}/v2.0/lbaas/l7policies/{l7policy_id}/rules/{l7rule_id}" payload = {"rule": {}} if type is not None: payload["rule"]["type"] = type if compare_type is not None: payload["rule"]["compare_type"] = compare_type if value is not None: payload["rule"]["value"] = value if key is not None: payload["rule"]["key"] = key if invert is not None: payload["rule"]["invert"] = invert if admin_state_up is not None: payload["rule"]["admin_state_up"] = admin_state_up logger.info(f"[NHN API] L7 룰 수정 요청 - l7policy_id={l7policy_id}, l7rule_id={l7rule_id}") return self._put(url, payload) def delete_l7rule(self, l7policy_id: str, l7rule_id: str) -> dict: """L7 룰 삭제""" url = f"{self.lb_url}/v2.0/lbaas/l7policies/{l7policy_id}/rules/{l7rule_id}" logger.info(f"[NHN API] L7 룰 삭제 요청 - l7policy_id={l7policy_id}, l7rule_id={l7rule_id}") return self._delete(url) # ==================== IP ACL Group ==================== def get_ipacl_group_list(self) -> dict: """IP ACL 그룹 목록 조회""" url = f"{self.lb_url}/v2.0/lbaas/ipacl-groups" logger.info(f"[NHN API] IP ACL 그룹 목록 조회 요청") result = self._get(url) group_count = len(result.get("ipacl_groups", [])) logger.info(f"[NHN API] IP ACL 그룹 목록 조회 완료 - count={group_count}") return result def get_ipacl_group_info(self, ipacl_group_id: str) -> dict: """IP ACL 그룹 상세 조회""" url = f"{self.lb_url}/v2.0/lbaas/ipacl-groups/{ipacl_group_id}" logger.info(f"[NHN API] IP ACL 그룹 상세 조회 요청 - ipacl_group_id={ipacl_group_id}") return self._get(url) def create_ipacl_group( self, action: str, name: Optional[str] = None, description: Optional[str] = None, ipacl_targets: Optional[List[dict]] = None, loadbalancers: Optional[List[str]] = None, ) -> dict: """ IP ACL 그룹 생성 Args: action: 액션 (ALLOW 또는 DENY) name: 그룹 이름 description: 설명 ipacl_targets: IP ACL 타깃 목록 [{ip_address, description}] loadbalancers: 적용할 로드밸런서 ID 목록 """ url = f"{self.lb_url}/v2.0/lbaas/ipacl-groups" payload = { "ipacl_group": { "action": action, } } if name: payload["ipacl_group"]["name"] = name if description: payload["ipacl_group"]["description"] = description if ipacl_targets: payload["ipacl_group"]["ipacl_targets"] = ipacl_targets if loadbalancers: payload["ipacl_group"]["loadbalancers"] = [{"loadbalancer_id": lb_id} for lb_id in loadbalancers] logger.info(f"[NHN API] IP ACL 그룹 생성 요청 - action={action}") return self._post(url, payload) def update_ipacl_group( self, ipacl_group_id: str, name: Optional[str] = None, description: Optional[str] = None, loadbalancers: Optional[List[str]] = None, ) -> dict: """IP ACL 그룹 수정""" url = f"{self.lb_url}/v2.0/lbaas/ipacl-groups/{ipacl_group_id}" payload = {"ipacl_group": {}} if name is not None: payload["ipacl_group"]["name"] = name if description is not None: payload["ipacl_group"]["description"] = description if loadbalancers is not None: payload["ipacl_group"]["loadbalancers"] = [{"loadbalancer_id": lb_id} for lb_id in loadbalancers] logger.info(f"[NHN API] IP ACL 그룹 수정 요청 - ipacl_group_id={ipacl_group_id}") return self._put(url, payload) def delete_ipacl_group(self, ipacl_group_id: str) -> dict: """IP ACL 그룹 삭제""" url = f"{self.lb_url}/v2.0/lbaas/ipacl-groups/{ipacl_group_id}" logger.info(f"[NHN API] IP ACL 그룹 삭제 요청 - ipacl_group_id={ipacl_group_id}") return self._delete(url) # ==================== IP ACL Target ==================== def create_ipacl_target( self, ipacl_group_id: str, ip_address: str, description: Optional[str] = None, ) -> dict: """ IP ACL 타깃 추가 Args: ipacl_group_id: IP ACL 그룹 ID ip_address: IP 주소 또는 CIDR (예: 10.0.0.1, 10.0.0.0/24) description: 설명 """ url = f"{self.lb_url}/v2.0/lbaas/ipacl-targets" payload = { "ipacl_target": { "ipacl_group_id": ipacl_group_id, "ip_address": ip_address, } } if description: payload["ipacl_target"]["description"] = description logger.info(f"[NHN API] IP ACL 타깃 추가 요청 - ipacl_group_id={ipacl_group_id}, ip_address={ip_address}") return self._post(url, payload) def update_ipacl_target( self, ipacl_target_id: str, description: str, ) -> dict: """IP ACL 타깃 수정 (설명만 수정 가능)""" url = f"{self.lb_url}/v2.0/lbaas/ipacl-targets/{ipacl_target_id}" payload = { "ipacl_target": { "description": description, } } logger.info(f"[NHN API] IP ACL 타깃 수정 요청 - ipacl_target_id={ipacl_target_id}") return self._put(url, payload) def delete_ipacl_target(self, ipacl_target_id: str) -> dict: """IP ACL 타깃 삭제""" url = f"{self.lb_url}/v2.0/lbaas/ipacl-targets/{ipacl_target_id}" logger.info(f"[NHN API] IP ACL 타깃 삭제 요청 - ipacl_target_id={ipacl_target_id}") return self._delete(url) # ==================== Quota ==================== def get_quota(self) -> dict: """로드밸런서 쿼타 조회""" url = f"{self.lb_url}/v2.0/lbaas/quotas/default" logger.info(f"[NHN API] 로드밸런서 쿼타 조회 요청") return self._get(url)