Files
msa-django-nhn/nhn/packages/loadbalancer.py
icurfer 7136e76d63
All checks were successful
Build And Test / build-and-push (push) Successful in 53s
v0.0.9 | Add Load Balancer API
- 로드밸런서 CRUD API 추가
- 리스너, 풀, 멤버, 헬스 모니터 API 추가
- L7 정책/룰, IP ACL 그룹/타깃 API 추가
- 쿼타 조회 API 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 11:14:03 +09:00

932 lines
35 KiB
Python

"""
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)