Add NHN Cloud API integration with async task support
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled

- NHN Cloud API packages: token, vpc, compute, nks, storage
- REST API endpoints with Swagger documentation
- Async task processing for long-running operations
- CORS configuration for frontend integration
- Enhanced logging for debugging API calls

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-14 01:29:21 +09:00
parent 256fed485e
commit 8c7739ffad
32 changed files with 4059 additions and 0 deletions

849
nhn/views.py Normal file
View File

@ -0,0 +1,849 @@
"""
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,
StorageContainerSerializer,
ErrorResponseSerializer,
)
from .packages import NHNCloudToken, ApiCompute, ApiVpc, ApiNks, ApiStorageObject
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 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, tenant_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:
from .tasks import create_nks_cluster_async
task = create_nks_cluster_async(
region=headers["region"],
tenant_id=headers["tenant_id"],
token=headers["token"],
cluster_data=serializer.validated_data,
)
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)
# ==================== 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)