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