""" NHN Cloud API Views REST API 엔드포인트 정의 """ import logging from rest_framework import status from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import AllowAny, IsAuthenticated from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from .serializers import ( TokenRequestSerializer, TokenResponseSerializer, ComputeInstanceSerializer, VpcSerializer, SubnetSerializer, NksClusterSerializer, NksNodeGroupSerializer, NksNodeActionSerializer, NksClusterResizeSerializer, NksClusterUpgradeSerializer, NksAutoscaleConfigSerializer, NksNodeGroupUpgradeSerializer, NksNodeGroupUpdateSerializer, NksUserScriptSerializer, NksApiEndpointIpAclSerializer, StorageContainerSerializer, ErrorResponseSerializer, # DNS Plus DnsZoneSerializer, DnsZoneUpdateSerializer, DnsRecordSetSerializer, DnsRecordSetUpdateSerializer, DnsPoolSerializer, DnsPoolUpdateSerializer, DnsGslbSerializer, DnsGslbUpdateSerializer, DnsPoolConnectSerializer, DnsHealthCheckSerializer, DnsHealthCheckUpdateSerializer, DnsIdListSerializer, # Load Balancer LoadBalancerSerializer, LoadBalancerUpdateSerializer, LBListenerSerializer, LBListenerUpdateSerializer, LBPoolSerializer, LBPoolUpdateSerializer, LBMemberSerializer, LBMemberUpdateSerializer, LBHealthMonitorSerializer, LBHealthMonitorUpdateSerializer, LBL7PolicySerializer, LBL7PolicyUpdateSerializer, LBL7RuleSerializer, LBL7RuleUpdateSerializer, LBIpAclGroupSerializer, LBIpAclGroupUpdateSerializer, LBIpAclTargetCreateSerializer, ) from .packages import NHNCloudToken, ApiCompute, ApiVpc, ApiNks, ApiStorageObject, ApiDnsPlus from .packages.loadbalancer import ApiLoadBalancer from .packages.base import NHNCloudAPIError logger = logging.getLogger(__name__) # ==================== Common Headers ==================== region_header = openapi.Parameter( "X-NHN-Region", openapi.IN_HEADER, description="NHN Cloud 리전 (kr1: 판교, kr2: 평촌)", type=openapi.TYPE_STRING, default="kr2", ) token_header = openapi.Parameter( "X-NHN-Token", openapi.IN_HEADER, description="NHN Cloud API 토큰", type=openapi.TYPE_STRING, required=True, ) tenant_header = openapi.Parameter( "X-NHN-Tenant-ID", openapi.IN_HEADER, description="NHN Cloud 테넌트 ID", type=openapi.TYPE_STRING, required=True, ) def get_nhn_headers(request): """요청 헤더에서 NHN Cloud 정보 추출""" headers = { "region": request.headers.get("X-NHN-Region", "kr2"), "token": request.headers.get("X-NHN-Token"), "tenant_id": request.headers.get("X-NHN-Tenant-ID"), "storage_account": request.headers.get("X-NHN-Storage-Account"), } # 토큰은 앞 8자리만 로깅 (보안) token_preview = headers["token"][:8] + "..." if headers["token"] else "None" logger.info(f"[Request Headers] region={headers['region']}, token={token_preview}, tenant_id={headers['tenant_id']}") return headers # ==================== Token API ==================== class TokenCreateView(APIView): """토큰 생성 API""" authentication_classes = [] # 인증 완전히 건너뜀 permission_classes = [AllowAny] @swagger_auto_schema( operation_summary="NHN Cloud API 토큰 생성", operation_description="NHN Cloud API 인증 토큰을 생성합니다.", request_body=TokenRequestSerializer, responses={ 200: TokenResponseSerializer, 400: ErrorResponseSerializer, 401: ErrorResponseSerializer, }, ) def post(self, request): logger.info(f"[Token] 토큰 생성 요청 수신 - client_ip={request.META.get('REMOTE_ADDR')}") serializer = TokenRequestSerializer(data=request.data) if not serializer.is_valid(): logger.warning(f"[Token] 요청 데이터 유효성 검사 실패: {serializer.errors}") return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) tenant_id = serializer.validated_data["tenant_id"] username = serializer.validated_data["username"] logger.info(f"[Token] 토큰 발급 시도 - tenant_id={tenant_id}, username={username}") try: token_manager = NHNCloudToken( tenant_id=tenant_id, username=username, password=serializer.validated_data["password"], ) result = token_manager.create_token() token_preview = result.token[:8] + "..." if result.token else "None" logger.info(f"[Token] 토큰 발급 성공 - tenant_id={tenant_id}, token={token_preview}, expires_at={result.expires_at}") return Response( { "token": result.token, "tenant_id": result.tenant_id, "expires_at": result.expires_at, }, status=status.HTTP_200_OK, ) except NHNCloudAPIError as e: logger.error(f"[Token] 토큰 발급 실패 - tenant_id={tenant_id}, username={username}, error={e.message}, code={e.code}") return Response( {"error": e.message, "code": e.code}, status=status.HTTP_401_UNAUTHORIZED, ) # ==================== Compute API ==================== class ComputeFlavorListView(APIView): """Flavor 목록 조회 API""" @swagger_auto_schema( operation_summary="Flavor 목록 조회", manual_parameters=[region_header, token_header, tenant_header], responses={200: "Flavor 목록"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiCompute(headers["region"], headers["tenant_id"], headers["token"]) return Response(api.get_flavor_list()) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class ComputeKeypairListView(APIView): """Keypair 목록 조회 API""" @swagger_auto_schema( operation_summary="Keypair 목록 조회", manual_parameters=[region_header, token_header, tenant_header], responses={200: "Keypair 목록"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiCompute(headers["region"], headers["tenant_id"], headers["token"]) return Response(api.get_keypair_list()) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class ComputeImageListView(APIView): """이미지 목록 조회 API""" @swagger_auto_schema( operation_summary="이미지 목록 조회", manual_parameters=[region_header, token_header, tenant_header], responses={200: "이미지 목록"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiCompute(headers["region"], headers["tenant_id"], headers["token"]) return Response(api.get_image_list()) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class ComputeInstanceListView(APIView): """인스턴스 목록 조회 API""" @swagger_auto_schema( operation_summary="인스턴스 목록 조회", manual_parameters=[region_header, token_header, tenant_header], responses={200: "인스턴스 목록"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiCompute(headers["region"], headers["tenant_id"], headers["token"]) # detail=true 파라미터로 상세 조회 여부 결정 if request.query_params.get("detail", "true").lower() == "true": return Response(api.get_instance_list_detail()) return Response(api.get_instance_list()) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class ComputeInstanceDetailView(APIView): """인스턴스 상세/삭제 API""" @swagger_auto_schema( operation_summary="인스턴스 상세 조회", manual_parameters=[region_header, token_header, tenant_header], responses={200: "인스턴스 상세 정보"}, ) def get(self, request, server_id): headers = get_nhn_headers(request) try: api = ApiCompute(headers["region"], headers["tenant_id"], headers["token"]) return Response(api.get_instance_info(server_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="인스턴스 삭제 (비동기)", manual_parameters=[region_header, token_header, tenant_header], responses={202: "작업 ID 반환"}, ) def delete(self, request, server_id): headers = get_nhn_headers(request) try: from .tasks import delete_instance_async task = delete_instance_async( region=headers["region"], tenant_id=headers["tenant_id"], token=headers["token"], server_id=server_id, server_name=request.query_params.get("name", ""), ) return Response( {"task_id": str(task.id), "status": task.status, "message": "인스턴스 삭제 작업이 시작되었습니다."}, status=status.HTTP_202_ACCEPTED, ) except Exception as e: logger.exception("인스턴스 삭제 작업 시작 실패") return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) class ComputeInstanceCreateView(APIView): """인스턴스 생성 API (비동기)""" @swagger_auto_schema( operation_summary="인스턴스 생성 (비동기)", manual_parameters=[region_header, token_header, tenant_header], request_body=ComputeInstanceSerializer, responses={202: "작업 ID 반환"}, ) def post(self, request): headers = get_nhn_headers(request) serializer = ComputeInstanceSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: from .tasks import create_instance_async # 비동기 작업 시작 task = create_instance_async( region=headers["region"], tenant_id=headers["tenant_id"], token=headers["token"], instance_data=serializer.validated_data, ) return Response( { "task_id": str(task.id), "status": task.status, "message": "인스턴스 생성 작업이 시작되었습니다.", }, status=status.HTTP_202_ACCEPTED, ) except Exception as e: logger.exception("인스턴스 생성 작업 시작 실패") return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) class ComputeInstanceActionView(APIView): """인스턴스 액션 API (시작/정지/재부팅) - 비동기""" @swagger_auto_schema( operation_summary="인스턴스 액션 (start/stop/reboot) - 비동기", manual_parameters=[region_header, token_header, tenant_header], request_body=openapi.Schema( type=openapi.TYPE_OBJECT, properties={ "action": openapi.Schema( type=openapi.TYPE_STRING, enum=["start", "stop", "reboot"], description="액션 타입", ), }, required=["action"], ), responses={202: "작업 ID 반환"}, ) def post(self, request, server_id): headers = get_nhn_headers(request) action = request.data.get("action") if action not in ["start", "stop", "reboot"]: return Response( {"error": "Invalid action. Use: start, stop, reboot"}, status=status.HTTP_400_BAD_REQUEST, ) try: from .tasks import instance_action_async task = instance_action_async( region=headers["region"], tenant_id=headers["tenant_id"], token=headers["token"], server_id=server_id, action=action, server_name=request.data.get("name", ""), hard=request.data.get("hard", False), ) return Response( {"task_id": str(task.id), "status": task.status, "action": action, "message": f"인스턴스 {action} 작업이 시작되었습니다."}, status=status.HTTP_202_ACCEPTED, ) except Exception as e: logger.exception(f"인스턴스 {action} 작업 시작 실패") return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) # ==================== VPC API ==================== class VpcListView(APIView): """VPC 목록 조회 API""" @swagger_auto_schema( operation_summary="VPC 목록 조회", manual_parameters=[region_header, token_header], responses={200: "VPC 목록"}, ) def get(self, request): logger.info(f"[VPC] VPC 목록 조회 요청") headers = get_nhn_headers(request) try: api = ApiVpc(headers["region"], headers["token"]) result = api.get_vpc_list() vpc_count = len(result.get("vpcs", [])) logger.info(f"[VPC] VPC 목록 조회 성공 - count={vpc_count}") return Response(result) except NHNCloudAPIError as e: logger.error(f"[VPC] VPC 목록 조회 실패 - error={e.message}") return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class VpcCreateView(APIView): """VPC 생성 API (비동기)""" @swagger_auto_schema( operation_summary="VPC 생성 (비동기)", manual_parameters=[region_header, token_header], request_body=VpcSerializer, responses={202: "작업 ID 반환"}, ) def post(self, request): headers = get_nhn_headers(request) serializer = VpcSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: from .tasks import create_vpc_async task = create_vpc_async( region=headers["region"], token=headers["token"], name=serializer.validated_data["name"], cidr=serializer.validated_data["cidr"], ) return Response( {"task_id": str(task.id), "status": task.status, "message": "VPC 생성 작업이 시작되었습니다."}, status=status.HTTP_202_ACCEPTED, ) except Exception as e: logger.exception("VPC 생성 작업 시작 실패") return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) class VpcDetailView(APIView): """VPC 상세/삭제 API""" @swagger_auto_schema( operation_summary="VPC 상세 조회", manual_parameters=[region_header, token_header], responses={200: "VPC 상세 정보"}, ) def get(self, request, vpc_id): logger.info(f"[VPC] VPC 상세 조회 요청 - vpc_id={vpc_id}") headers = get_nhn_headers(request) try: api = ApiVpc(headers["region"], headers["token"]) result = api.get_vpc_info(vpc_id) vpc_name = result.get("vpc", {}).get("name", "unknown") logger.info(f"[VPC] VPC 상세 조회 성공 - vpc_id={vpc_id}, name={vpc_name}") return Response(result) except NHNCloudAPIError as e: logger.error(f"[VPC] VPC 상세 조회 실패 - vpc_id={vpc_id}, error={e.message}") return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="VPC 삭제 (비동기)", manual_parameters=[region_header, token_header], responses={202: "작업 ID 반환"}, ) def delete(self, request, vpc_id): logger.info(f"[VPC] VPC 삭제 요청 - vpc_id={vpc_id}") headers = get_nhn_headers(request) try: from .tasks import delete_vpc_async task = delete_vpc_async( region=headers["region"], token=headers["token"], vpc_id=vpc_id, vpc_name=request.query_params.get("name", ""), ) logger.info(f"[VPC] VPC 삭제 작업 시작 - vpc_id={vpc_id}, task_id={task.id}") return Response( {"task_id": str(task.id), "status": task.status, "message": "VPC 삭제 작업이 시작되었습니다."}, status=status.HTTP_202_ACCEPTED, ) except Exception as e: logger.exception(f"[VPC] VPC 삭제 작업 시작 실패 - vpc_id={vpc_id}") return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) class SubnetListView(APIView): """서브넷 목록 조회 API""" @swagger_auto_schema( operation_summary="서브넷 목록 조회", manual_parameters=[region_header, token_header], responses={200: "서브넷 목록"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiVpc(headers["region"], headers["token"]) return Response(api.get_subnet_list()) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class SubnetCreateView(APIView): """서브넷 생성 API (비동기)""" @swagger_auto_schema( operation_summary="서브넷 생성 (비동기)", manual_parameters=[region_header, token_header], request_body=SubnetSerializer, responses={202: "작업 ID 반환"}, ) def post(self, request): headers = get_nhn_headers(request) serializer = SubnetSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: from .tasks import create_subnet_async task = create_subnet_async( region=headers["region"], token=headers["token"], vpc_id=serializer.validated_data["vpc_id"], cidr=serializer.validated_data["cidr"], name=serializer.validated_data["name"], ) return Response( {"task_id": str(task.id), "status": task.status, "message": "서브넷 생성 작업이 시작되었습니다."}, status=status.HTTP_202_ACCEPTED, ) except Exception as e: logger.exception("서브넷 생성 작업 시작 실패") return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) class SubnetDetailView(APIView): """서브넷 상세/삭제 API""" @swagger_auto_schema( operation_summary="서브넷 상세 조회", manual_parameters=[region_header, token_header], responses={200: "서브넷 상세 정보"}, ) def get(self, request, subnet_id): headers = get_nhn_headers(request) try: api = ApiVpc(headers["region"], headers["token"]) return Response(api.get_subnet_info(subnet_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="서브넷 삭제 (비동기)", manual_parameters=[region_header, token_header], responses={202: "작업 ID 반환"}, ) def delete(self, request, subnet_id): headers = get_nhn_headers(request) try: from .tasks import delete_subnet_async task = delete_subnet_async( region=headers["region"], token=headers["token"], subnet_id=subnet_id, subnet_name=request.query_params.get("name", ""), ) return Response( {"task_id": str(task.id), "status": task.status, "message": "서브넷 삭제 작업이 시작되었습니다."}, status=status.HTTP_202_ACCEPTED, ) except Exception as e: logger.exception("서브넷 삭제 작업 시작 실패") return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) class FloatingIpListView(APIView): """Floating IP 목록 조회 API""" @swagger_auto_schema( operation_summary="Floating IP 목록 조회", manual_parameters=[region_header, token_header], responses={200: "Floating IP 목록"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiVpc(headers["region"], headers["token"]) return Response(api.get_floating_ip_list()) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== Security Group API ==================== class SecurityGroupListView(APIView): """보안 그룹 목록 조회 API""" @swagger_auto_schema( operation_summary="보안 그룹 목록 조회", manual_parameters=[region_header, token_header], responses={200: "보안 그룹 목록"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiVpc(headers["region"], headers["token"]) return Response(api.get_security_group_list()) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== Async Task API ==================== class AsyncTaskListView(APIView): """비동기 작업 목록 조회 API""" @swagger_auto_schema( operation_summary="비동기 작업 목록 조회", responses={200: "작업 목록"}, ) def get(self, request): from .models import AsyncTask # 최근 100개만 조회 tasks = AsyncTask.objects.all()[:100] data = [ { "id": str(task.id), "task_type": task.task_type, "status": task.status, "resource_name": task.resource_name, "resource_id": task.resource_id, "error_message": task.error_message, "created_at": task.created_at.isoformat(), "completed_at": task.completed_at.isoformat() if task.completed_at else None, } for task in tasks ] return Response({"tasks": data}) class AsyncTaskDetailView(APIView): """비동기 작업 상세 조회 API""" @swagger_auto_schema( operation_summary="비동기 작업 상태 조회", responses={200: "작업 상태"}, ) def get(self, request, task_id): from .models import AsyncTask try: task = AsyncTask.objects.get(id=task_id) return Response( { "id": str(task.id), "task_type": task.task_type, "status": task.status, "resource_name": task.resource_name, "resource_id": task.resource_id, "request_data": task.request_data, "result_data": task.result_data, "error_message": task.error_message, "created_at": task.created_at.isoformat(), "updated_at": task.updated_at.isoformat(), "completed_at": task.completed_at.isoformat() if task.completed_at else None, } ) except AsyncTask.DoesNotExist: return Response({"error": "작업을 찾을 수 없습니다."}, status=status.HTTP_404_NOT_FOUND) # ==================== NKS API ==================== class NksSupportsView(APIView): """NKS 지원 버전 및 작업 종류 조회 API""" @swagger_auto_schema( operation_summary="지원되는 Kubernetes 버전 조회", manual_parameters=[region_header, token_header], responses={200: "지원 버전 및 작업 종류"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiNks(headers["region"], headers["token"]) return Response(api.get_supports()) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksClusterListView(APIView): """NKS 클러스터 목록 조회 API""" @swagger_auto_schema( operation_summary="NKS 클러스터 목록 조회", manual_parameters=[region_header, token_header], responses={200: "클러스터 목록"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiNks(headers["region"], headers["token"]) return Response(api.get_cluster_list()) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksClusterDetailView(APIView): """NKS 클러스터 상세/삭제 API""" @swagger_auto_schema( operation_summary="NKS 클러스터 상세 조회", manual_parameters=[region_header, token_header], responses={200: "클러스터 상세 정보"}, ) def get(self, request, cluster_name): headers = get_nhn_headers(request) try: api = ApiNks(headers["region"], headers["token"]) return Response(api.get_cluster_info(cluster_name)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="NKS 클러스터 삭제 (비동기)", manual_parameters=[region_header, token_header], responses={202: "작업 ID 반환"}, ) def delete(self, request, cluster_name): headers = get_nhn_headers(request) try: from .tasks import delete_nks_cluster_async task = delete_nks_cluster_async( region=headers["region"], token=headers["token"], cluster_name=cluster_name, ) return Response( {"task_id": str(task.id), "status": task.status, "message": "NKS 클러스터 삭제 작업이 시작되었습니다."}, status=status.HTTP_202_ACCEPTED, ) except Exception as e: logger.exception("NKS 클러스터 삭제 작업 시작 실패") return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) class NksClusterConfigView(APIView): """NKS 클러스터 kubeconfig 조회 API""" @swagger_auto_schema( operation_summary="NKS 클러스터 kubeconfig 조회", manual_parameters=[region_header, token_header], responses={200: "kubeconfig"}, ) def get(self, request, cluster_name): headers = get_nhn_headers(request) try: api = ApiNks(headers["region"], headers["token"]) config = api.get_cluster_config(cluster_name) return Response({"config": config}) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksClusterCreateView(APIView): """NKS 클러스터 생성 API (비동기)""" @swagger_auto_schema( operation_summary="NKS 클러스터 생성 (비동기)", manual_parameters=[region_header, token_header], request_body=NksClusterSerializer, responses={202: "작업 ID 반환"}, ) def post(self, request): headers = get_nhn_headers(request) serializer = NksClusterSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: cluster_data = serializer.validated_data.copy() # Public 클러스터인 경우 External Network/Subnet 자동 조회 if cluster_data.get("is_public", True): vpc_api = ApiVpc(headers["region"], headers["token"]) external_networks = vpc_api.get_external_network_id() networks = external_networks.get("networks", []) if not networks: return Response( {"error": "External Network를 찾을 수 없습니다."}, status=status.HTTP_400_BAD_REQUEST, ) # 첫 번째 External Network 사용 external_network = networks[0] cluster_data["external_network_id"] = external_network.get("id") # External Network의 서브넷 가져오기 subnets = external_network.get("subnets", []) if subnets: cluster_data["external_subnet_id"] = subnets[0] else: return Response( {"error": "External Subnet을 찾을 수 없습니다."}, status=status.HTTP_400_BAD_REQUEST, ) logger.info( f"[NKS] External Network 자동 설정 - network_id={cluster_data['external_network_id']}, " f"subnet_id={cluster_data['external_subnet_id']}" ) from .tasks import create_nks_cluster_async task = create_nks_cluster_async( region=headers["region"], token=headers["token"], cluster_data=cluster_data, ) return Response( {"task_id": str(task.id), "status": task.status, "message": "NKS 클러스터 생성 작업이 시작되었습니다."}, status=status.HTTP_202_ACCEPTED, ) except NHNCloudAPIError as e: logger.exception("External Network 조회 실패") return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) except Exception as e: logger.exception("NKS 클러스터 생성 작업 시작 실패") return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) class NksClusterResizeView(APIView): """NKS 클러스터 리사이즈 API""" @swagger_auto_schema( operation_summary="NKS 클러스터 노드 수 조정", manual_parameters=[region_header, token_header], request_body=NksClusterResizeSerializer, responses={200: "리사이즈 결과"}, ) def post(self, request, cluster_name): headers = get_nhn_headers(request) serializer = NksClusterResizeSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiNks(headers["region"], headers["token"]) result = api.resize_cluster(cluster_name, serializer.validated_data["node_count"]) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksClusterUpgradeView(APIView): """NKS 클러스터 업그레이드 API""" @swagger_auto_schema( operation_summary="NKS 클러스터 Kubernetes 버전 업그레이드", manual_parameters=[region_header, token_header], request_body=NksClusterUpgradeSerializer, responses={200: "업그레이드 결과"}, ) def post(self, request, cluster_name): headers = get_nhn_headers(request) serializer = NksClusterUpgradeSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiNks(headers["region"], headers["token"]) result = api.upgrade_cluster(cluster_name, serializer.validated_data["kubernetes_version"]) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksClusterEventsView(APIView): """NKS 클러스터 작업 이력 API""" @swagger_auto_schema( operation_summary="NKS 클러스터 작업 이력 조회", manual_parameters=[region_header, token_header], responses={200: "작업 이력 목록"}, ) def get(self, request, cluster_uuid): headers = get_nhn_headers(request) try: api = ApiNks(headers["region"], headers["token"]) return Response(api.get_cluster_events(cluster_uuid)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksClusterEventDetailView(APIView): """NKS 클러스터 작업 이력 상세 API""" @swagger_auto_schema( operation_summary="NKS 클러스터 작업 이력 상세 조회", manual_parameters=[region_header, token_header], responses={200: "작업 이력 상세"}, ) def get(self, request, cluster_uuid, event_uuid): headers = get_nhn_headers(request) try: api = ApiNks(headers["region"], headers["token"]) return Response(api.get_cluster_event(cluster_uuid, event_uuid)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksClusterCertificatesView(APIView): """NKS 클러스터 인증서 갱신 API""" @swagger_auto_schema( operation_summary="NKS 클러스터 인증서 갱신", manual_parameters=[region_header, token_header], responses={200: "갱신 결과"}, ) def post(self, request, cluster_name): headers = get_nhn_headers(request) try: api = ApiNks(headers["region"], headers["token"]) result = api.renew_certificates(cluster_name) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksClusterIpAclView(APIView): """NKS 클러스터 API 엔드포인트 IP 접근 제어 API""" @swagger_auto_schema( operation_summary="API 엔드포인트 IP 접근 제어 조회", manual_parameters=[region_header, token_header], responses={200: "IP 접근 제어 설정"}, ) def get(self, request, cluster_name): headers = get_nhn_headers(request) try: api = ApiNks(headers["region"], headers["token"]) return Response(api.get_api_endpoint_ipacl(cluster_name)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="API 엔드포인트 IP 접근 제어 설정", manual_parameters=[region_header, token_header], request_body=NksApiEndpointIpAclSerializer, responses={200: "설정 결과"}, ) def post(self, request, cluster_name): headers = get_nhn_headers(request) serializer = NksApiEndpointIpAclSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiNks(headers["region"], headers["token"]) result = api.set_api_endpoint_ipacl( cluster_name, serializer.validated_data["enable"], serializer.validated_data.get("allowed_cidrs"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== NKS Node Group API ==================== class NksNodeGroupListView(APIView): """NKS 노드 그룹 목록 조회 API""" @swagger_auto_schema( operation_summary="노드 그룹 목록 조회", manual_parameters=[region_header, token_header], responses={200: "노드 그룹 목록"}, ) def get(self, request, cluster_name): headers = get_nhn_headers(request) try: api = ApiNks(headers["region"], headers["token"]) return Response(api.get_nodegroup_list(cluster_name)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksNodeGroupCreateView(APIView): """NKS 노드 그룹 생성 API""" @swagger_auto_schema( operation_summary="노드 그룹 생성", manual_parameters=[region_header, token_header], request_body=NksNodeGroupSerializer, responses={200: "생성된 노드 그룹 정보"}, ) def post(self, request, cluster_name): headers = get_nhn_headers(request) serializer = NksNodeGroupSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiNks(headers["region"], headers["token"]) result = api.create_nodegroup( cluster_name=cluster_name, nodegroup_name=serializer.validated_data["nodegroup_name"], instance_type=serializer.validated_data["instance_type"], node_count=serializer.validated_data.get("node_count", 1), availability_zone=serializer.validated_data.get("availability_zone"), boot_volume_size=serializer.validated_data.get("boot_volume_size", 50), boot_volume_type=serializer.validated_data.get("boot_volume_type", "General SSD"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksNodeGroupDetailView(APIView): """NKS 노드 그룹 상세/삭제/수정 API""" @swagger_auto_schema( operation_summary="노드 그룹 상세 조회", manual_parameters=[region_header, token_header], responses={200: "노드 그룹 상세 정보"}, ) def get(self, request, cluster_name, nodegroup_name): headers = get_nhn_headers(request) try: api = ApiNks(headers["region"], headers["token"]) return Response(api.get_nodegroup_info(cluster_name, nodegroup_name)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="노드 그룹 삭제", manual_parameters=[region_header, token_header], responses={200: "삭제 결과"}, ) def delete(self, request, cluster_name, nodegroup_name): headers = get_nhn_headers(request) try: api = ApiNks(headers["region"], headers["token"]) result = api.delete_nodegroup(cluster_name, nodegroup_name) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="노드 그룹 설정 변경", manual_parameters=[region_header, token_header], request_body=NksNodeGroupUpdateSerializer, responses={200: "변경된 노드 그룹 정보"}, ) def patch(self, request, cluster_name, nodegroup_name): headers = get_nhn_headers(request) serializer = NksNodeGroupUpdateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiNks(headers["region"], headers["token"]) result = api.update_nodegroup( cluster_name=cluster_name, nodegroup_name=nodegroup_name, instance_type=serializer.validated_data.get("instance_type"), node_count=serializer.validated_data.get("node_count"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksNodeGroupUpgradeView(APIView): """NKS 노드 그룹 업그레이드 API""" @swagger_auto_schema( operation_summary="노드 그룹 Kubernetes 버전 업그레이드", manual_parameters=[region_header, token_header], request_body=NksNodeGroupUpgradeSerializer, responses={200: "업그레이드 결과"}, ) def post(self, request, cluster_name, nodegroup_name): headers = get_nhn_headers(request) serializer = NksNodeGroupUpgradeSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiNks(headers["region"], headers["token"]) result = api.upgrade_nodegroup( cluster_name=cluster_name, nodegroup_name=nodegroup_name, kubernetes_version=serializer.validated_data["kubernetes_version"], max_unavailable_worker=serializer.validated_data.get("max_unavailable_worker", 1), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksNodeGroupUserScriptView(APIView): """NKS 노드 그룹 사용자 스크립트 API""" @swagger_auto_schema( operation_summary="노드 그룹 사용자 스크립트 설정", manual_parameters=[region_header, token_header], request_body=NksUserScriptSerializer, responses={200: "설정 결과"}, ) def post(self, request, cluster_name, nodegroup_name): headers = get_nhn_headers(request) serializer = NksUserScriptSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiNks(headers["region"], headers["token"]) result = api.set_nodegroup_userscript( cluster_name=cluster_name, nodegroup_name=nodegroup_name, userscript=serializer.validated_data["userscript"], ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksNodeStartView(APIView): """NKS 워커 노드 시작 API""" @swagger_auto_schema( operation_summary="워커 노드 시작", manual_parameters=[region_header, token_header], request_body=NksNodeActionSerializer, responses={200: "시작 결과"}, ) def post(self, request, cluster_name, nodegroup_name): headers = get_nhn_headers(request) serializer = NksNodeActionSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiNks(headers["region"], headers["token"]) result = api.start_node( cluster_name=cluster_name, nodegroup_name=nodegroup_name, node_id=serializer.validated_data["node_id"], ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksNodeStopView(APIView): """NKS 워커 노드 중지 API""" @swagger_auto_schema( operation_summary="워커 노드 중지", manual_parameters=[region_header, token_header], request_body=NksNodeActionSerializer, responses={200: "중지 결과"}, ) def post(self, request, cluster_name, nodegroup_name): headers = get_nhn_headers(request) serializer = NksNodeActionSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiNks(headers["region"], headers["token"]) result = api.stop_node( cluster_name=cluster_name, nodegroup_name=nodegroup_name, node_id=serializer.validated_data["node_id"], ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class NksAutoscaleConfigView(APIView): """NKS 오토스케일러 설정 API""" @swagger_auto_schema( operation_summary="오토스케일러 설정 조회", manual_parameters=[region_header, token_header], responses={200: "오토스케일러 설정"}, ) def get(self, request, cluster_name, nodegroup_name): headers = get_nhn_headers(request) try: api = ApiNks(headers["region"], headers["token"]) return Response(api.get_autoscale_config(cluster_name, nodegroup_name)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="오토스케일러 설정 변경", manual_parameters=[region_header, token_header], request_body=NksAutoscaleConfigSerializer, responses={200: "변경된 오토스케일러 설정"}, ) def post(self, request, cluster_name, nodegroup_name): headers = get_nhn_headers(request) serializer = NksAutoscaleConfigSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiNks(headers["region"], headers["token"]) result = api.set_autoscale_config( cluster_name=cluster_name, nodegroup_name=nodegroup_name, ca_enable=serializer.validated_data["ca_enable"], ca_max_node_count=serializer.validated_data.get("ca_max_node_count"), ca_min_node_count=serializer.validated_data.get("ca_min_node_count"), ca_scale_down_enable=serializer.validated_data.get("ca_scale_down_enable"), ca_scale_down_delay_after_add=serializer.validated_data.get("ca_scale_down_delay_after_add"), ca_scale_down_unneeded_time=serializer.validated_data.get("ca_scale_down_unneeded_time"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== Storage API ==================== storage_account_header = openapi.Parameter( "X-NHN-Storage-Account", openapi.IN_HEADER, description="NHN Cloud Object Storage 계정 (AUTH_...)", type=openapi.TYPE_STRING, required=True, ) class StorageContainerListView(APIView): """스토리지 컨테이너 목록 조회 API""" @swagger_auto_schema( operation_summary="컨테이너 목록 조회", manual_parameters=[region_header, token_header, storage_account_header], responses={200: "컨테이너 목록"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiStorageObject( headers["region"], headers["token"], headers["storage_account"] ) containers = api.get_container_list() return Response({"containers": containers}) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class StorageContainerCreateView(APIView): """스토리지 컨테이너 생성 API (비동기)""" @swagger_auto_schema( operation_summary="컨테이너 생성 (비동기)", manual_parameters=[region_header, token_header, storage_account_header], request_body=StorageContainerSerializer, responses={202: "작업 ID 반환"}, ) def post(self, request): headers = get_nhn_headers(request) serializer = StorageContainerSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: from .tasks import create_storage_container_async task = create_storage_container_async( region=headers["region"], token=headers["token"], storage_account=headers["storage_account"], container_name=serializer.validated_data["name"], ) return Response( {"task_id": str(task.id), "status": task.status, "message": "컨테이너 생성 작업이 시작되었습니다."}, status=status.HTTP_202_ACCEPTED, ) except Exception as e: logger.exception("컨테이너 생성 작업 시작 실패") return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) class StorageContainerDetailView(APIView): """스토리지 컨테이너 상세/삭제 API""" @swagger_auto_schema( operation_summary="컨테이너 오브젝트 목록 조회", manual_parameters=[region_header, token_header, storage_account_header], responses={200: "오브젝트 목록"}, ) def get(self, request, container_name): headers = get_nhn_headers(request) try: api = ApiStorageObject( headers["region"], headers["token"], headers["storage_account"] ) objects = api.get_object_list(container_name) return Response({"objects": objects}) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="컨테이너 삭제 (비동기)", manual_parameters=[region_header, token_header, storage_account_header], responses={202: "작업 ID 반환"}, ) def delete(self, request, container_name): headers = get_nhn_headers(request) try: from .tasks import delete_storage_container_async task = delete_storage_container_async( region=headers["region"], token=headers["token"], storage_account=headers["storage_account"], container_name=container_name, ) return Response( {"task_id": str(task.id), "status": task.status, "message": "컨테이너 삭제 작업이 시작되었습니다."}, status=status.HTTP_202_ACCEPTED, ) except Exception as e: logger.exception("컨테이너 삭제 작업 시작 실패") return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) # ==================== DNS Plus API ==================== appkey_header = openapi.Parameter( "X-NHN-Appkey", openapi.IN_HEADER, description="NHN Cloud 앱키", type=openapi.TYPE_STRING, required=True, ) def get_appkey(request): """요청 헤더에서 앱키 추출""" appkey = request.headers.get("X-NHN-Appkey") if not appkey: raise NHNCloudAPIError("X-NHN-Appkey 헤더가 필요합니다.", code=400) return appkey # ==================== DNS Zone API ==================== class DnsZoneListView(APIView): """DNS Zone 목록 조회 API""" @swagger_auto_schema( operation_summary="DNS Zone 목록 조회", manual_parameters=[appkey_header], responses={200: "Zone 목록"}, ) def get(self, request): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) page = int(request.query_params.get("page", 1)) limit = int(request.query_params.get("limit", 50)) search_name = request.query_params.get("search") return Response(api.get_zone_list( search_zone_name=search_name, page=page, limit=limit, )) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class DnsZoneCreateView(APIView): """DNS Zone 생성 API""" @swagger_auto_schema( operation_summary="DNS Zone 생성", manual_parameters=[appkey_header], request_body=DnsZoneSerializer, responses={200: "생성된 Zone 정보"}, ) def post(self, request): try: appkey = get_appkey(request) serializer = DnsZoneSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) api = ApiDnsPlus(appkey) result = api.create_zone( zone_name=serializer.validated_data["zone_name"], description=serializer.validated_data.get("description"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class DnsZoneDetailView(APIView): """DNS Zone 상세/수정/삭제 API""" @swagger_auto_schema( operation_summary="DNS Zone 상세 조회", manual_parameters=[appkey_header], responses={200: "Zone 상세 정보"}, ) def get(self, request, zone_id): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) return Response(api.get_zone(zone_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="DNS Zone 수정", manual_parameters=[appkey_header], request_body=DnsZoneUpdateSerializer, responses={200: "수정된 Zone 정보"}, ) def put(self, request, zone_id): try: appkey = get_appkey(request) serializer = DnsZoneUpdateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) api = ApiDnsPlus(appkey) result = api.update_zone(zone_id, serializer.validated_data["description"]) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="DNS Zone 삭제", manual_parameters=[appkey_header], responses={200: "삭제 결과"}, ) def delete(self, request, zone_id): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) result = api.delete_zones([zone_id]) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== DNS Record Set API ==================== class DnsRecordSetListView(APIView): """DNS 레코드 세트 목록 조회 API""" @swagger_auto_schema( operation_summary="레코드 세트 목록 조회", manual_parameters=[appkey_header], responses={200: "레코드 세트 목록"}, ) def get(self, request, zone_id): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) page = int(request.query_params.get("page", 1)) limit = int(request.query_params.get("limit", 50)) search_name = request.query_params.get("search") recordset_type = request.query_params.get("type") return Response(api.get_recordset_list( zone_id=zone_id, recordset_type_list=[recordset_type] if recordset_type else None, search_recordset_name=search_name, page=page, limit=limit, )) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class DnsRecordSetCreateView(APIView): """DNS 레코드 세트 생성 API""" @swagger_auto_schema( operation_summary="레코드 세트 생성", manual_parameters=[appkey_header], request_body=DnsRecordSetSerializer, responses={200: "생성된 레코드 세트 정보"}, ) def post(self, request, zone_id): try: appkey = get_appkey(request) serializer = DnsRecordSetSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) api = ApiDnsPlus(appkey) result = api.create_recordset( zone_id=zone_id, recordset_name=serializer.validated_data["recordset_name"], recordset_type=serializer.validated_data["recordset_type"], recordset_ttl=serializer.validated_data["recordset_ttl"], record_list=serializer.validated_data["record_list"], ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class DnsRecordSetDetailView(APIView): """DNS 레코드 세트 상세/수정/삭제 API""" @swagger_auto_schema( operation_summary="레코드 세트 상세 조회", manual_parameters=[appkey_header], responses={200: "레코드 세트 상세 정보"}, ) def get(self, request, zone_id, recordset_id): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) return Response(api.get_recordset(zone_id, recordset_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="레코드 세트 수정", manual_parameters=[appkey_header], request_body=DnsRecordSetUpdateSerializer, responses={200: "수정된 레코드 세트 정보"}, ) def put(self, request, zone_id, recordset_id): try: appkey = get_appkey(request) serializer = DnsRecordSetUpdateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) api = ApiDnsPlus(appkey) result = api.update_recordset( zone_id=zone_id, recordset_id=recordset_id, recordset_type=serializer.validated_data["recordset_type"], recordset_ttl=serializer.validated_data["recordset_ttl"], record_list=serializer.validated_data["record_list"], ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="레코드 세트 삭제", manual_parameters=[appkey_header], responses={200: "삭제 결과"}, ) def delete(self, request, zone_id, recordset_id): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) result = api.delete_recordsets(zone_id, [recordset_id]) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== DNS Pool API ==================== class DnsPoolListView(APIView): """DNS Pool 목록 조회 API""" @swagger_auto_schema( operation_summary="Pool 목록 조회", manual_parameters=[appkey_header], responses={200: "Pool 목록"}, ) def get(self, request): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) page = int(request.query_params.get("page", 1)) limit = int(request.query_params.get("limit", 50)) search_name = request.query_params.get("search") return Response(api.get_pool_list( search_pool_name=search_name, page=page, limit=limit, )) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class DnsPoolCreateView(APIView): """DNS Pool 생성 API""" @swagger_auto_schema( operation_summary="Pool 생성", manual_parameters=[appkey_header], request_body=DnsPoolSerializer, responses={200: "생성된 Pool 정보"}, ) def post(self, request): try: appkey = get_appkey(request) serializer = DnsPoolSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) api = ApiDnsPlus(appkey) result = api.create_pool( pool_name=serializer.validated_data["pool_name"], endpoint_list=serializer.validated_data["endpoint_list"], pool_disabled=serializer.validated_data.get("pool_disabled", False), health_check_id=serializer.validated_data.get("health_check_id"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class DnsPoolDetailView(APIView): """DNS Pool 상세/수정/삭제 API""" @swagger_auto_schema( operation_summary="Pool 상세 조회", manual_parameters=[appkey_header], responses={200: "Pool 상세 정보"}, ) def get(self, request, pool_id): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) return Response(api.get_pool(pool_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="Pool 수정", manual_parameters=[appkey_header], request_body=DnsPoolUpdateSerializer, responses={200: "수정된 Pool 정보"}, ) def put(self, request, pool_id): try: appkey = get_appkey(request) serializer = DnsPoolUpdateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) api = ApiDnsPlus(appkey) result = api.update_pool( pool_id=pool_id, pool_name=serializer.validated_data.get("pool_name"), endpoint_list=serializer.validated_data.get("endpoint_list"), pool_disabled=serializer.validated_data.get("pool_disabled"), health_check_id=serializer.validated_data.get("health_check_id"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="Pool 삭제", manual_parameters=[appkey_header], responses={200: "삭제 결과"}, ) def delete(self, request, pool_id): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) result = api.delete_pools([pool_id]) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== DNS GSLB API ==================== class DnsGslbListView(APIView): """DNS GSLB 목록 조회 API""" @swagger_auto_schema( operation_summary="GSLB 목록 조회", manual_parameters=[appkey_header], responses={200: "GSLB 목록"}, ) def get(self, request): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) page = int(request.query_params.get("page", 1)) limit = int(request.query_params.get("limit", 50)) search_name = request.query_params.get("search") return Response(api.get_gslb_list( search_gslb_name=search_name, page=page, limit=limit, )) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class DnsGslbCreateView(APIView): """DNS GSLB 생성 API""" @swagger_auto_schema( operation_summary="GSLB 생성", manual_parameters=[appkey_header], request_body=DnsGslbSerializer, responses={200: "생성된 GSLB 정보"}, ) def post(self, request): try: appkey = get_appkey(request) serializer = DnsGslbSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) api = ApiDnsPlus(appkey) result = api.create_gslb( gslb_name=serializer.validated_data["gslb_name"], gslb_ttl=serializer.validated_data["gslb_ttl"], gslb_routing_rule=serializer.validated_data["gslb_routing_rule"], gslb_disabled=serializer.validated_data.get("gslb_disabled", False), connected_pool_list=serializer.validated_data.get("connected_pool_list"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class DnsGslbDetailView(APIView): """DNS GSLB 상세/수정/삭제 API""" @swagger_auto_schema( operation_summary="GSLB 상세 조회", manual_parameters=[appkey_header], responses={200: "GSLB 상세 정보"}, ) def get(self, request, gslb_id): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) return Response(api.get_gslb(gslb_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="GSLB 수정", manual_parameters=[appkey_header], request_body=DnsGslbUpdateSerializer, responses={200: "수정된 GSLB 정보"}, ) def put(self, request, gslb_id): try: appkey = get_appkey(request) serializer = DnsGslbUpdateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) api = ApiDnsPlus(appkey) result = api.update_gslb( gslb_id=gslb_id, gslb_name=serializer.validated_data.get("gslb_name"), gslb_ttl=serializer.validated_data.get("gslb_ttl"), gslb_routing_rule=serializer.validated_data.get("gslb_routing_rule"), gslb_disabled=serializer.validated_data.get("gslb_disabled"), connected_pool_list=serializer.validated_data.get("connected_pool_list"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="GSLB 삭제", manual_parameters=[appkey_header], responses={200: "삭제 결과"}, ) def delete(self, request, gslb_id): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) result = api.delete_gslbs([gslb_id]) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class DnsGslbPoolConnectView(APIView): """GSLB에 Pool 연결/수정/해제 API""" @swagger_auto_schema( operation_summary="GSLB에 Pool 연결", manual_parameters=[appkey_header], request_body=DnsPoolConnectSerializer, responses={200: "연결 결과"}, ) def post(self, request, gslb_id, pool_id): try: appkey = get_appkey(request) serializer = DnsPoolConnectSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) api = ApiDnsPlus(appkey) result = api.connect_pool( gslb_id=gslb_id, pool_id=pool_id, connected_pool_order=serializer.validated_data["connected_pool_order"], connected_pool_region_content=serializer.validated_data.get("connected_pool_region_content"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="연결된 Pool 수정", manual_parameters=[appkey_header], request_body=DnsPoolConnectSerializer, responses={200: "수정 결과"}, ) def put(self, request, gslb_id, pool_id): try: appkey = get_appkey(request) serializer = DnsPoolConnectSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) api = ApiDnsPlus(appkey) result = api.update_connected_pool( gslb_id=gslb_id, pool_id=pool_id, connected_pool_order=serializer.validated_data["connected_pool_order"], connected_pool_region_content=serializer.validated_data.get("connected_pool_region_content"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="Pool 연결 해제", manual_parameters=[appkey_header], responses={200: "연결 해제 결과"}, ) def delete(self, request, gslb_id, pool_id): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) result = api.disconnect_pools(gslb_id, [pool_id]) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== DNS Health Check API ==================== class DnsHealthCheckListView(APIView): """DNS Health Check 목록 조회 API""" @swagger_auto_schema( operation_summary="Health Check 목록 조회", manual_parameters=[appkey_header], responses={200: "Health Check 목록"}, ) def get(self, request): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) page = int(request.query_params.get("page", 1)) limit = int(request.query_params.get("limit", 50)) search_name = request.query_params.get("search") return Response(api.get_health_check_list( search_health_check_name=search_name, page=page, limit=limit, )) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class DnsHealthCheckCreateView(APIView): """DNS Health Check 생성 API""" @swagger_auto_schema( operation_summary="Health Check 생성", manual_parameters=[appkey_header], request_body=DnsHealthCheckSerializer, responses={200: "생성된 Health Check 정보"}, ) def post(self, request): try: appkey = get_appkey(request) serializer = DnsHealthCheckSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) api = ApiDnsPlus(appkey) result = api.create_health_check( health_check_name=serializer.validated_data["health_check_name"], health_check_protocol=serializer.validated_data["health_check_protocol"], health_check_port=serializer.validated_data["health_check_port"], health_check_interval=serializer.validated_data.get("health_check_interval", 30), health_check_timeout=serializer.validated_data.get("health_check_timeout", 5), health_check_retries=serializer.validated_data.get("health_check_retries", 2), health_check_path=serializer.validated_data.get("health_check_path"), health_check_expected_codes=serializer.validated_data.get("health_check_expected_codes"), health_check_expected_body=serializer.validated_data.get("health_check_expected_body"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class DnsHealthCheckDetailView(APIView): """DNS Health Check 상세/수정/삭제 API""" @swagger_auto_schema( operation_summary="Health Check 상세 조회", manual_parameters=[appkey_header], responses={200: "Health Check 상세 정보"}, ) def get(self, request, health_check_id): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) return Response(api.get_health_check(health_check_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="Health Check 수정", manual_parameters=[appkey_header], request_body=DnsHealthCheckUpdateSerializer, responses={200: "수정된 Health Check 정보"}, ) def put(self, request, health_check_id): try: appkey = get_appkey(request) serializer = DnsHealthCheckUpdateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) api = ApiDnsPlus(appkey) result = api.update_health_check( health_check_id=health_check_id, health_check_name=serializer.validated_data.get("health_check_name"), health_check_protocol=serializer.validated_data.get("health_check_protocol"), health_check_port=serializer.validated_data.get("health_check_port"), health_check_interval=serializer.validated_data.get("health_check_interval"), health_check_timeout=serializer.validated_data.get("health_check_timeout"), health_check_retries=serializer.validated_data.get("health_check_retries"), health_check_path=serializer.validated_data.get("health_check_path"), health_check_expected_codes=serializer.validated_data.get("health_check_expected_codes"), health_check_expected_body=serializer.validated_data.get("health_check_expected_body"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="Health Check 삭제", manual_parameters=[appkey_header], responses={200: "삭제 결과"}, ) def delete(self, request, health_check_id): try: appkey = get_appkey(request) api = ApiDnsPlus(appkey) result = api.delete_health_checks([health_check_id]) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== Load Balancer API ==================== class LoadBalancerListView(APIView): """로드밸런서 목록 조회 API""" @swagger_auto_schema( operation_summary="로드밸런서 목록 조회", manual_parameters=[region_header, token_header], responses={200: "로드밸런서 목록"}, ) def get(self, request): logger.info(f"[LB] 로드밸런서 목록 조회 요청") headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.get_loadbalancer_list( name=request.query_params.get("name"), provisioning_status=request.query_params.get("provisioning_status"), vip_address=request.query_params.get("vip_address"), ) lb_count = len(result.get("loadbalancers", [])) logger.info(f"[LB] 로드밸런서 목록 조회 성공 - count={lb_count}") return Response(result) except NHNCloudAPIError as e: logger.error(f"[LB] 로드밸런서 목록 조회 실패 - error={e.message}") return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LoadBalancerCreateView(APIView): """로드밸런서 생성 API (비동기)""" @swagger_auto_schema( operation_summary="로드밸런서 생성 (비동기)", manual_parameters=[region_header, token_header], request_body=LoadBalancerSerializer, responses={202: "작업 ID 반환"}, ) def post(self, request): headers = get_nhn_headers(request) serializer = LoadBalancerSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: from .tasks import create_loadbalancer_async task = create_loadbalancer_async( region=headers["region"], token=headers["token"], lb_data=serializer.validated_data, ) return Response( {"task_id": str(task.id), "status": task.status, "message": "로드밸런서 생성 작업이 시작되었습니다."}, status=status.HTTP_202_ACCEPTED, ) except Exception as e: logger.exception("로드밸런서 생성 작업 시작 실패") return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) class LoadBalancerDetailView(APIView): """로드밸런서 상세/수정/삭제 API""" @swagger_auto_schema( operation_summary="로드밸런서 상세 조회", manual_parameters=[region_header, token_header], responses={200: "로드밸런서 상세 정보"}, ) def get(self, request, loadbalancer_id): logger.info(f"[LB] 로드밸런서 상세 조회 요청 - loadbalancer_id={loadbalancer_id}") headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.get_loadbalancer_info(loadbalancer_id) logger.info(f"[LB] 로드밸런서 상세 조회 성공 - loadbalancer_id={loadbalancer_id}") return Response(result) except NHNCloudAPIError as e: logger.error(f"[LB] 로드밸런서 상세 조회 실패 - loadbalancer_id={loadbalancer_id}, error={e.message}") return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="로드밸런서 수정", manual_parameters=[region_header, token_header], request_body=LoadBalancerUpdateSerializer, responses={200: "수정된 로드밸런서 정보"}, ) def put(self, request, loadbalancer_id): headers = get_nhn_headers(request) serializer = LoadBalancerUpdateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.update_loadbalancer( loadbalancer_id=loadbalancer_id, name=serializer.validated_data.get("name"), description=serializer.validated_data.get("description"), admin_state_up=serializer.validated_data.get("admin_state_up"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="로드밸런서 삭제 (비동기)", manual_parameters=[region_header, token_header], responses={202: "작업 ID 반환"}, ) def delete(self, request, loadbalancer_id): logger.info(f"[LB] 로드밸런서 삭제 요청 - loadbalancer_id={loadbalancer_id}") headers = get_nhn_headers(request) try: from .tasks import delete_loadbalancer_async task = delete_loadbalancer_async( region=headers["region"], token=headers["token"], loadbalancer_id=loadbalancer_id, loadbalancer_name=request.query_params.get("name", ""), ) logger.info(f"[LB] 로드밸런서 삭제 작업 시작 - loadbalancer_id={loadbalancer_id}, task_id={task.id}") return Response( {"task_id": str(task.id), "status": task.status, "message": "로드밸런서 삭제 작업이 시작되었습니다."}, status=status.HTTP_202_ACCEPTED, ) except Exception as e: logger.exception(f"[LB] 로드밸런서 삭제 작업 시작 실패 - loadbalancer_id={loadbalancer_id}") return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) # ==================== LB Listener API ==================== class LBListenerListView(APIView): """리스너 목록 조회 API""" @swagger_auto_schema( operation_summary="리스너 목록 조회", manual_parameters=[region_header, token_header], responses={200: "리스너 목록"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.get_listener_list( loadbalancer_id=request.query_params.get("loadbalancer_id"), protocol=request.query_params.get("protocol"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBListenerCreateView(APIView): """리스너 생성 API""" @swagger_auto_schema( operation_summary="리스너 생성", manual_parameters=[region_header, token_header], request_body=LBListenerSerializer, responses={200: "생성된 리스너 정보"}, ) def post(self, request): headers = get_nhn_headers(request) serializer = LBListenerSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.create_listener( loadbalancer_id=serializer.validated_data["loadbalancer_id"], protocol=serializer.validated_data["protocol"], protocol_port=serializer.validated_data["protocol_port"], name=serializer.validated_data.get("name"), description=serializer.validated_data.get("description"), default_pool_id=serializer.validated_data.get("default_pool_id"), connection_limit=serializer.validated_data.get("connection_limit", -1), keepalive_timeout=serializer.validated_data.get("keepalive_timeout", 300), admin_state_up=serializer.validated_data.get("admin_state_up", True), default_tls_container_ref=serializer.validated_data.get("default_tls_container_ref"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBListenerDetailView(APIView): """리스너 상세/수정/삭제 API""" @swagger_auto_schema( operation_summary="리스너 상세 조회", manual_parameters=[region_header, token_header], responses={200: "리스너 상세 정보"}, ) def get(self, request, listener_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) return Response(api.get_listener_info(listener_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="리스너 수정", manual_parameters=[region_header, token_header], request_body=LBListenerUpdateSerializer, responses={200: "수정된 리스너 정보"}, ) def put(self, request, listener_id): headers = get_nhn_headers(request) serializer = LBListenerUpdateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.update_listener( listener_id=listener_id, name=serializer.validated_data.get("name"), description=serializer.validated_data.get("description"), default_pool_id=serializer.validated_data.get("default_pool_id"), connection_limit=serializer.validated_data.get("connection_limit"), keepalive_timeout=serializer.validated_data.get("keepalive_timeout"), admin_state_up=serializer.validated_data.get("admin_state_up"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="리스너 삭제", manual_parameters=[region_header, token_header], responses={200: "삭제 결과"}, ) def delete(self, request, listener_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.delete_listener(listener_id) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== LB Pool API ==================== class LBPoolListView(APIView): """풀 목록 조회 API""" @swagger_auto_schema( operation_summary="풀 목록 조회", manual_parameters=[region_header, token_header], responses={200: "풀 목록"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.get_pool_list( loadbalancer_id=request.query_params.get("loadbalancer_id"), protocol=request.query_params.get("protocol"), lb_algorithm=request.query_params.get("lb_algorithm"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBPoolCreateView(APIView): """풀 생성 API""" @swagger_auto_schema( operation_summary="풀 생성", manual_parameters=[region_header, token_header], request_body=LBPoolSerializer, responses={200: "생성된 풀 정보"}, ) def post(self, request): headers = get_nhn_headers(request) serializer = LBPoolSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.create_pool( lb_algorithm=serializer.validated_data["lb_algorithm"], protocol=serializer.validated_data["protocol"], loadbalancer_id=serializer.validated_data.get("loadbalancer_id"), listener_id=serializer.validated_data.get("listener_id"), name=serializer.validated_data.get("name"), description=serializer.validated_data.get("description"), admin_state_up=serializer.validated_data.get("admin_state_up", True), session_persistence=serializer.validated_data.get("session_persistence"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBPoolDetailView(APIView): """풀 상세/수정/삭제 API""" @swagger_auto_schema( operation_summary="풀 상세 조회", manual_parameters=[region_header, token_header], responses={200: "풀 상세 정보"}, ) def get(self, request, pool_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) return Response(api.get_pool_info(pool_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="풀 수정", manual_parameters=[region_header, token_header], request_body=LBPoolUpdateSerializer, responses={200: "수정된 풀 정보"}, ) def put(self, request, pool_id): headers = get_nhn_headers(request) serializer = LBPoolUpdateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.update_pool( pool_id=pool_id, name=serializer.validated_data.get("name"), description=serializer.validated_data.get("description"), lb_algorithm=serializer.validated_data.get("lb_algorithm"), admin_state_up=serializer.validated_data.get("admin_state_up"), session_persistence=serializer.validated_data.get("session_persistence"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="풀 삭제", manual_parameters=[region_header, token_header], responses={200: "삭제 결과"}, ) def delete(self, request, pool_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.delete_pool(pool_id) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== LB Member API ==================== class LBMemberListView(APIView): """멤버 목록 조회 API""" @swagger_auto_schema( operation_summary="멤버 목록 조회", manual_parameters=[region_header, token_header], responses={200: "멤버 목록"}, ) def get(self, request, pool_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) return Response(api.get_member_list(pool_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBMemberCreateView(APIView): """멤버 추가 API""" @swagger_auto_schema( operation_summary="멤버 추가", manual_parameters=[region_header, token_header], request_body=LBMemberSerializer, responses={200: "추가된 멤버 정보"}, ) def post(self, request, pool_id): headers = get_nhn_headers(request) serializer = LBMemberSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.create_member( pool_id=pool_id, address=serializer.validated_data["address"], protocol_port=serializer.validated_data["protocol_port"], subnet_id=serializer.validated_data.get("subnet_id"), weight=serializer.validated_data.get("weight", 1), admin_state_up=serializer.validated_data.get("admin_state_up", True), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBMemberDetailView(APIView): """멤버 상세/수정/삭제 API""" @swagger_auto_schema( operation_summary="멤버 상세 조회", manual_parameters=[region_header, token_header], responses={200: "멤버 상세 정보"}, ) def get(self, request, pool_id, member_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) return Response(api.get_member_info(pool_id, member_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="멤버 수정", manual_parameters=[region_header, token_header], request_body=LBMemberUpdateSerializer, responses={200: "수정된 멤버 정보"}, ) def put(self, request, pool_id, member_id): headers = get_nhn_headers(request) serializer = LBMemberUpdateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.update_member( pool_id=pool_id, member_id=member_id, weight=serializer.validated_data.get("weight"), admin_state_up=serializer.validated_data.get("admin_state_up"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="멤버 삭제", manual_parameters=[region_header, token_header], responses={200: "삭제 결과"}, ) def delete(self, request, pool_id, member_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.delete_member(pool_id, member_id) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== LB Health Monitor API ==================== class LBHealthMonitorListView(APIView): """헬스 모니터 목록 조회 API""" @swagger_auto_schema( operation_summary="헬스 모니터 목록 조회", manual_parameters=[region_header, token_header], responses={200: "헬스 모니터 목록"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.get_healthmonitor_list( pool_id=request.query_params.get("pool_id"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBHealthMonitorCreateView(APIView): """헬스 모니터 생성 API""" @swagger_auto_schema( operation_summary="헬스 모니터 생성", manual_parameters=[region_header, token_header], request_body=LBHealthMonitorSerializer, responses={200: "생성된 헬스 모니터 정보"}, ) def post(self, request): headers = get_nhn_headers(request) serializer = LBHealthMonitorSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.create_healthmonitor( pool_id=serializer.validated_data["pool_id"], type=serializer.validated_data["type"], delay=serializer.validated_data["delay"], timeout=serializer.validated_data["timeout"], max_retries=serializer.validated_data["max_retries"], max_retries_down=serializer.validated_data.get("max_retries_down", 3), http_method=serializer.validated_data.get("http_method", "GET"), url_path=serializer.validated_data.get("url_path", "/"), expected_codes=serializer.validated_data.get("expected_codes", "200"), admin_state_up=serializer.validated_data.get("admin_state_up", True), host_header=serializer.validated_data.get("host_header"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBHealthMonitorDetailView(APIView): """헬스 모니터 상세/수정/삭제 API""" @swagger_auto_schema( operation_summary="헬스 모니터 상세 조회", manual_parameters=[region_header, token_header], responses={200: "헬스 모니터 상세 정보"}, ) def get(self, request, healthmonitor_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) return Response(api.get_healthmonitor_info(healthmonitor_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="헬스 모니터 수정", manual_parameters=[region_header, token_header], request_body=LBHealthMonitorUpdateSerializer, responses={200: "수정된 헬스 모니터 정보"}, ) def put(self, request, healthmonitor_id): headers = get_nhn_headers(request) serializer = LBHealthMonitorUpdateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.update_healthmonitor( healthmonitor_id=healthmonitor_id, delay=serializer.validated_data.get("delay"), timeout=serializer.validated_data.get("timeout"), max_retries=serializer.validated_data.get("max_retries"), max_retries_down=serializer.validated_data.get("max_retries_down"), http_method=serializer.validated_data.get("http_method"), url_path=serializer.validated_data.get("url_path"), expected_codes=serializer.validated_data.get("expected_codes"), admin_state_up=serializer.validated_data.get("admin_state_up"), host_header=serializer.validated_data.get("host_header"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="헬스 모니터 삭제", manual_parameters=[region_header, token_header], responses={200: "삭제 결과"}, ) def delete(self, request, healthmonitor_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.delete_healthmonitor(healthmonitor_id) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== LB L7 Policy API ==================== class LBL7PolicyListView(APIView): """L7 정책 목록 조회 API""" @swagger_auto_schema( operation_summary="L7 정책 목록 조회", manual_parameters=[region_header, token_header], responses={200: "L7 정책 목록"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.get_l7policy_list( listener_id=request.query_params.get("listener_id"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBL7PolicyCreateView(APIView): """L7 정책 생성 API""" @swagger_auto_schema( operation_summary="L7 정책 생성", manual_parameters=[region_header, token_header], request_body=LBL7PolicySerializer, responses={200: "생성된 L7 정책 정보"}, ) def post(self, request): headers = get_nhn_headers(request) serializer = LBL7PolicySerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.create_l7policy( listener_id=serializer.validated_data["listener_id"], action=serializer.validated_data["action"], position=serializer.validated_data.get("position", 1), name=serializer.validated_data.get("name"), description=serializer.validated_data.get("description"), redirect_pool_id=serializer.validated_data.get("redirect_pool_id"), redirect_url=serializer.validated_data.get("redirect_url"), admin_state_up=serializer.validated_data.get("admin_state_up", True), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBL7PolicyDetailView(APIView): """L7 정책 상세/수정/삭제 API""" @swagger_auto_schema( operation_summary="L7 정책 상세 조회", manual_parameters=[region_header, token_header], responses={200: "L7 정책 상세 정보"}, ) def get(self, request, l7policy_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) return Response(api.get_l7policy_info(l7policy_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="L7 정책 수정", manual_parameters=[region_header, token_header], request_body=LBL7PolicyUpdateSerializer, responses={200: "수정된 L7 정책 정보"}, ) def put(self, request, l7policy_id): headers = get_nhn_headers(request) serializer = LBL7PolicyUpdateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.update_l7policy( l7policy_id=l7policy_id, name=serializer.validated_data.get("name"), description=serializer.validated_data.get("description"), action=serializer.validated_data.get("action"), position=serializer.validated_data.get("position"), redirect_pool_id=serializer.validated_data.get("redirect_pool_id"), redirect_url=serializer.validated_data.get("redirect_url"), admin_state_up=serializer.validated_data.get("admin_state_up"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="L7 정책 삭제", manual_parameters=[region_header, token_header], responses={200: "삭제 결과"}, ) def delete(self, request, l7policy_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.delete_l7policy(l7policy_id) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== LB L7 Rule API ==================== class LBL7RuleListView(APIView): """L7 룰 목록 조회 API""" @swagger_auto_schema( operation_summary="L7 룰 목록 조회", manual_parameters=[region_header, token_header], responses={200: "L7 룰 목록"}, ) def get(self, request, l7policy_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) return Response(api.get_l7rule_list(l7policy_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBL7RuleCreateView(APIView): """L7 룰 생성 API""" @swagger_auto_schema( operation_summary="L7 룰 생성", manual_parameters=[region_header, token_header], request_body=LBL7RuleSerializer, responses={200: "생성된 L7 룰 정보"}, ) def post(self, request, l7policy_id): headers = get_nhn_headers(request) serializer = LBL7RuleSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.create_l7rule( l7policy_id=l7policy_id, type=serializer.validated_data["type"], compare_type=serializer.validated_data["compare_type"], value=serializer.validated_data["value"], key=serializer.validated_data.get("key"), invert=serializer.validated_data.get("invert", False), admin_state_up=serializer.validated_data.get("admin_state_up", True), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBL7RuleDetailView(APIView): """L7 룰 상세/수정/삭제 API""" @swagger_auto_schema( operation_summary="L7 룰 상세 조회", manual_parameters=[region_header, token_header], responses={200: "L7 룰 상세 정보"}, ) def get(self, request, l7policy_id, l7rule_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) return Response(api.get_l7rule_info(l7policy_id, l7rule_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="L7 룰 수정", manual_parameters=[region_header, token_header], request_body=LBL7RuleUpdateSerializer, responses={200: "수정된 L7 룰 정보"}, ) def put(self, request, l7policy_id, l7rule_id): headers = get_nhn_headers(request) serializer = LBL7RuleUpdateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.update_l7rule( l7policy_id=l7policy_id, l7rule_id=l7rule_id, type=serializer.validated_data.get("type"), compare_type=serializer.validated_data.get("compare_type"), value=serializer.validated_data.get("value"), key=serializer.validated_data.get("key"), invert=serializer.validated_data.get("invert"), admin_state_up=serializer.validated_data.get("admin_state_up"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="L7 룰 삭제", manual_parameters=[region_header, token_header], responses={200: "삭제 결과"}, ) def delete(self, request, l7policy_id, l7rule_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.delete_l7rule(l7policy_id, l7rule_id) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== LB IP ACL Group API ==================== class LBIpAclGroupListView(APIView): """IP ACL 그룹 목록 조회 API""" @swagger_auto_schema( operation_summary="IP ACL 그룹 목록 조회", manual_parameters=[region_header, token_header], responses={200: "IP ACL 그룹 목록"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) return Response(api.get_ipacl_group_list()) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBIpAclGroupCreateView(APIView): """IP ACL 그룹 생성 API""" @swagger_auto_schema( operation_summary="IP ACL 그룹 생성", manual_parameters=[region_header, token_header], request_body=LBIpAclGroupSerializer, responses={200: "생성된 IP ACL 그룹 정보"}, ) def post(self, request): headers = get_nhn_headers(request) serializer = LBIpAclGroupSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.create_ipacl_group( action=serializer.validated_data["action"], name=serializer.validated_data.get("name"), description=serializer.validated_data.get("description"), ipacl_targets=serializer.validated_data.get("ipacl_targets"), loadbalancers=serializer.validated_data.get("loadbalancers"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBIpAclGroupDetailView(APIView): """IP ACL 그룹 상세/수정/삭제 API""" @swagger_auto_schema( operation_summary="IP ACL 그룹 상세 조회", manual_parameters=[region_header, token_header], responses={200: "IP ACL 그룹 상세 정보"}, ) def get(self, request, ipacl_group_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) return Response(api.get_ipacl_group_info(ipacl_group_id)) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="IP ACL 그룹 수정", manual_parameters=[region_header, token_header], request_body=LBIpAclGroupUpdateSerializer, responses={200: "수정된 IP ACL 그룹 정보"}, ) def put(self, request, ipacl_group_id): headers = get_nhn_headers(request) serializer = LBIpAclGroupUpdateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.update_ipacl_group( ipacl_group_id=ipacl_group_id, name=serializer.validated_data.get("name"), description=serializer.validated_data.get("description"), loadbalancers=serializer.validated_data.get("loadbalancers"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="IP ACL 그룹 삭제", manual_parameters=[region_header, token_header], responses={200: "삭제 결과"}, ) def delete(self, request, ipacl_group_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.delete_ipacl_group(ipacl_group_id) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== LB IP ACL Target API ==================== class LBIpAclTargetCreateView(APIView): """IP ACL 타깃 추가 API""" @swagger_auto_schema( operation_summary="IP ACL 타깃 추가", manual_parameters=[region_header, token_header], request_body=LBIpAclTargetCreateSerializer, responses={200: "추가된 IP ACL 타깃 정보"}, ) def post(self, request): headers = get_nhn_headers(request) serializer = LBIpAclTargetCreateSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.create_ipacl_target( ipacl_group_id=serializer.validated_data["ipacl_group_id"], ip_address=serializer.validated_data["ip_address"], description=serializer.validated_data.get("description"), ) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) class LBIpAclTargetDetailView(APIView): """IP ACL 타깃 수정/삭제 API""" @swagger_auto_schema( operation_summary="IP ACL 타깃 수정 (설명만)", manual_parameters=[region_header, token_header], request_body=openapi.Schema( type=openapi.TYPE_OBJECT, properties={ "description": openapi.Schema(type=openapi.TYPE_STRING, description="설명"), }, required=["description"], ), responses={200: "수정된 IP ACL 타깃 정보"}, ) def put(self, request, ipacl_target_id): headers = get_nhn_headers(request) description = request.data.get("description") if description is None: return Response({"error": "description is required"}, status=status.HTTP_400_BAD_REQUEST) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.update_ipacl_target(ipacl_target_id, description) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_summary="IP ACL 타깃 삭제", manual_parameters=[region_header, token_header], responses={200: "삭제 결과"}, ) def delete(self, request, ipacl_target_id): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) result = api.delete_ipacl_target(ipacl_target_id) return Response(result) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST) # ==================== LB Quota API ==================== class LBQuotaView(APIView): """로드밸런서 쿼타 조회 API""" @swagger_auto_schema( operation_summary="로드밸런서 쿼타 조회", manual_parameters=[region_header, token_header], responses={200: "쿼타 정보"}, ) def get(self, request): headers = get_nhn_headers(request) try: api = ApiLoadBalancer(headers["region"], headers["token"]) return Response(api.get_quota()) except NHNCloudAPIError as e: return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)