v0.0.9 | Add Load Balancer API
All checks were successful
Build And Test / build-and-push (push) Successful in 53s

- 로드밸런서 CRUD API 추가
- 리스너, 풀, 멤버, 헬스 모니터 API 추가
- L7 정책/룰, IP ACL 그룹/타깃 API 추가
- 쿼타 조회 API 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-16 11:14:03 +09:00
parent 57526a0f13
commit 7136e76d63
9 changed files with 2658 additions and 1 deletions

View File

@ -4,6 +4,7 @@ from .vpc import ApiVpc
from .nks import ApiNks
from .storage import ApiStorageObject
from .dnsplus import ApiDnsPlus
from .loadbalancer import ApiLoadBalancer
__all__ = [
"NHNCloudToken",
@ -12,4 +13,5 @@ __all__ = [
"ApiNks",
"ApiStorageObject",
"ApiDnsPlus",
"ApiLoadBalancer",
]

View File

@ -0,0 +1,931 @@
"""
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)