Compare commits
6 Commits
839a1316a4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b30cebb603 | |||
| 48fc246301 | |||
| 8aede4f2f5 | |||
| 76cd46ec86 | |||
| 2eade2ee9b | |||
| 9bf41ebf21 |
10
.github/workflows/build.yaml
vendored
10
.github/workflows/build.yaml
vendored
@ -5,8 +5,12 @@ run-name: ${{ gitea.actor }} is runs ci pipeline
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'version'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'version'
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
@ -19,12 +23,6 @@ jobs:
|
||||
id: img-ver
|
||||
run: echo "content=$(cat ./version | tr -d '\n')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install Docker
|
||||
run: |
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sh get-docker.sh
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
|
||||
101
API_SPEC.md
101
API_SPEC.md
@ -387,6 +387,107 @@ DELETE /api/nhn/subnet/{subnet_id}/
|
||||
|
||||
---
|
||||
|
||||
### 3.7.1 서브넷에 라우팅 테이블 연결
|
||||
|
||||
```
|
||||
PUT /api/nhn/subnet/{subnet_id}/attach-routing-table/
|
||||
```
|
||||
|
||||
**Request Body**
|
||||
```json
|
||||
{
|
||||
"routingtable_id": "rt-id-123"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.7.2 인터넷 게이트웨이 목록 조회
|
||||
|
||||
```
|
||||
GET /api/nhn/internet-gateway/
|
||||
```
|
||||
|
||||
**Response (200 OK)** NHN Cloud Internet Gateway Public API `/v2.0/internetgateways` 통과 응답.
|
||||
|
||||
---
|
||||
|
||||
### 3.7.3 인터넷 게이트웨이 생성
|
||||
|
||||
```
|
||||
POST /api/nhn/internet-gateway/create/
|
||||
```
|
||||
|
||||
**Request Body**
|
||||
```json
|
||||
{
|
||||
"name": "igw-1",
|
||||
"external_network_id": "ext-net-id (선택, 생략 시 자동 조회)"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.7.4 인터넷 게이트웨이 상세/삭제
|
||||
|
||||
```
|
||||
GET /api/nhn/internet-gateway/{internetgateway_id}/
|
||||
DELETE /api/nhn/internet-gateway/{internetgateway_id}/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.7.5 라우팅 테이블 목록 조회
|
||||
|
||||
```
|
||||
GET /api/nhn/routing-table/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.7.6 라우팅 테이블 생성
|
||||
|
||||
```
|
||||
POST /api/nhn/routing-table/create/
|
||||
```
|
||||
|
||||
**Request Body**
|
||||
```json
|
||||
{
|
||||
"name": "rt-1",
|
||||
"vpc_id": "vpc-id-123",
|
||||
"distributed": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.7.7 라우팅 테이블 상세/삭제
|
||||
|
||||
```
|
||||
GET /api/nhn/routing-table/{routingtable_id}/
|
||||
DELETE /api/nhn/routing-table/{routingtable_id}/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.7.8 라우팅 테이블 기본 설정 / 게이트웨이 연결·해제
|
||||
|
||||
```
|
||||
PUT /api/nhn/routing-table/{routingtable_id}/set-default/
|
||||
PUT /api/nhn/routing-table/{routingtable_id}/attach-gateway/
|
||||
PUT /api/nhn/routing-table/{routingtable_id}/detach-gateway/
|
||||
```
|
||||
|
||||
**attach-gateway Request Body**
|
||||
```json
|
||||
{
|
||||
"gateway_id": "igw-id-123"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.8 Floating IP 목록 조회
|
||||
|
||||
```
|
||||
|
||||
@ -196,6 +196,46 @@ class ApiVpc(BaseAPI):
|
||||
payload = {"routingtable_id": routingtable_id}
|
||||
return self._put(url, payload)
|
||||
|
||||
# ==================== Internet Gateway ====================
|
||||
|
||||
def get_internet_gateway_list(self) -> dict:
|
||||
"""인터넷 게이트웨이 목록 조회"""
|
||||
url = f"{self.vpc_url}/v2.0/internetgateways"
|
||||
logger.info(f"[NHN API] IGW 목록 조회 요청 - URL={url}")
|
||||
result = self._get(url)
|
||||
count = len(result.get("internetgateways", []))
|
||||
logger.info(f"[NHN API] IGW 목록 조회 완료 - count={count}")
|
||||
return result
|
||||
|
||||
def get_internet_gateway_info(self, internetgateway_id: str) -> dict:
|
||||
"""인터넷 게이트웨이 상세 조회"""
|
||||
url = f"{self.vpc_url}/v2.0/internetgateways/{internetgateway_id}"
|
||||
return self._get(url)
|
||||
|
||||
def create_internet_gateway(self, name: str, external_network_id: str) -> dict:
|
||||
"""
|
||||
인터넷 게이트웨이 생성
|
||||
|
||||
Args:
|
||||
name: IGW 이름
|
||||
external_network_id: 외부 네트워크 ID (get_external_network_id 로 조회)
|
||||
"""
|
||||
url = f"{self.vpc_url}/v2.0/internetgateways"
|
||||
payload = {
|
||||
"internetgateway": {
|
||||
"name": name,
|
||||
"external_network_id": external_network_id,
|
||||
}
|
||||
}
|
||||
logger.info(f"IGW 생성 요청: {name} (external_network={external_network_id})")
|
||||
return self._post(url, payload)
|
||||
|
||||
def delete_internet_gateway(self, internetgateway_id: str) -> dict:
|
||||
"""인터넷 게이트웨이 삭제"""
|
||||
url = f"{self.vpc_url}/v2.0/internetgateways/{internetgateway_id}"
|
||||
logger.info(f"IGW 삭제 요청: {internetgateway_id}")
|
||||
return self._delete(url)
|
||||
|
||||
# ==================== Floating IP ====================
|
||||
|
||||
def get_external_network_id(self) -> dict:
|
||||
@ -254,6 +294,103 @@ class ApiVpc(BaseAPI):
|
||||
url = f"{self.vpc_url}/v2.0/security-groups/{security_group_id}"
|
||||
return self._get(url)
|
||||
|
||||
def create_security_group(self, name: str, description: str = "") -> dict:
|
||||
"""
|
||||
보안 그룹 생성
|
||||
|
||||
Args:
|
||||
name: 보안 그룹 이름
|
||||
description: 설명
|
||||
|
||||
Returns:
|
||||
dict: 생성된 보안 그룹 정보
|
||||
"""
|
||||
url = f"{self.vpc_url}/v2.0/security-groups"
|
||||
payload = {"security_group": {"name": name, "description": description}}
|
||||
logger.info(f"보안 그룹 생성 요청: {name}")
|
||||
return self._post(url, payload)
|
||||
|
||||
def update_security_group(self, security_group_id: str, name: str = None, description: str = None) -> dict:
|
||||
"""보안 그룹 수정"""
|
||||
url = f"{self.vpc_url}/v2.0/security-groups/{security_group_id}"
|
||||
payload = {"security_group": {}}
|
||||
if name is not None:
|
||||
payload["security_group"]["name"] = name
|
||||
if description is not None:
|
||||
payload["security_group"]["description"] = description
|
||||
logger.info(f"보안 그룹 수정 요청: {security_group_id}")
|
||||
return self._put(url, payload)
|
||||
|
||||
def delete_security_group(self, security_group_id: str) -> dict:
|
||||
"""보안 그룹 삭제"""
|
||||
url = f"{self.vpc_url}/v2.0/security-groups/{security_group_id}"
|
||||
logger.info(f"보안 그룹 삭제 요청: {security_group_id}")
|
||||
return self._delete(url)
|
||||
|
||||
# ==================== Security Group Rule ====================
|
||||
|
||||
def get_security_group_rule_list(self, security_group_id: str = None) -> dict:
|
||||
"""보안 그룹 규칙 목록 조회"""
|
||||
url = f"{self.vpc_url}/v2.0/security-group-rules"
|
||||
params = {}
|
||||
if security_group_id:
|
||||
params["security_group_id"] = security_group_id
|
||||
return self._get(url, params=params if params else None)
|
||||
|
||||
def create_security_group_rule(
|
||||
self,
|
||||
security_group_id: str,
|
||||
direction: str,
|
||||
ethertype: str = "IPv4",
|
||||
protocol: str = None,
|
||||
port_range_min: int = None,
|
||||
port_range_max: int = None,
|
||||
remote_ip_prefix: str = None,
|
||||
remote_group_id: str = None,
|
||||
description: str = "",
|
||||
) -> dict:
|
||||
"""
|
||||
보안 그룹 규칙 생성
|
||||
|
||||
Args:
|
||||
security_group_id: 보안 그룹 ID
|
||||
direction: 방향 (ingress/egress)
|
||||
ethertype: IPv4/IPv6
|
||||
protocol: 프로토콜 (tcp/udp/icmp 등)
|
||||
port_range_min: 최소 포트
|
||||
port_range_max: 최대 포트
|
||||
remote_ip_prefix: 원격 IP 대역 (CIDR)
|
||||
remote_group_id: 원격 보안 그룹 ID
|
||||
description: 설명
|
||||
"""
|
||||
url = f"{self.vpc_url}/v2.0/security-group-rules"
|
||||
rule = {
|
||||
"security_group_id": security_group_id,
|
||||
"direction": direction,
|
||||
"ethertype": ethertype,
|
||||
}
|
||||
if protocol:
|
||||
rule["protocol"] = protocol
|
||||
if port_range_min is not None:
|
||||
rule["port_range_min"] = port_range_min
|
||||
if port_range_max is not None:
|
||||
rule["port_range_max"] = port_range_max
|
||||
if remote_ip_prefix:
|
||||
rule["remote_ip_prefix"] = remote_ip_prefix
|
||||
if remote_group_id:
|
||||
rule["remote_group_id"] = remote_group_id
|
||||
if description:
|
||||
rule["description"] = description
|
||||
payload = {"security_group_rule": rule}
|
||||
logger.info(f"보안 그룹 규칙 생성 요청: sg={security_group_id}, dir={direction}, proto={protocol}")
|
||||
return self._post(url, payload)
|
||||
|
||||
def delete_security_group_rule(self, rule_id: str) -> dict:
|
||||
"""보안 그룹 규칙 삭제"""
|
||||
url = f"{self.vpc_url}/v2.0/security-group-rules/{rule_id}"
|
||||
logger.info(f"보안 그룹 규칙 삭제 요청: {rule_id}")
|
||||
return self._delete(url)
|
||||
|
||||
# ==================== Port (NIC) ====================
|
||||
|
||||
def get_port_list(self) -> dict:
|
||||
|
||||
@ -99,7 +99,7 @@ class VpcSerializer(serializers.Serializer):
|
||||
help_text="VPC 이름",
|
||||
max_length=255,
|
||||
)
|
||||
cidr = serializers.CharField(
|
||||
cidrv4 = serializers.CharField(
|
||||
help_text="CIDR 블록 (예: 10.0.0.0/16)",
|
||||
)
|
||||
|
||||
@ -119,6 +119,144 @@ class SubnetSerializer(serializers.Serializer):
|
||||
)
|
||||
|
||||
|
||||
class SubnetAttachRoutingTableSerializer(serializers.Serializer):
|
||||
"""서브넷에 라우팅 테이블 연결 요청"""
|
||||
|
||||
routingtable_id = serializers.CharField(
|
||||
help_text="라우팅 테이블 ID",
|
||||
)
|
||||
|
||||
|
||||
# ==================== Routing Table ====================
|
||||
|
||||
|
||||
class RoutingTableSerializer(serializers.Serializer):
|
||||
"""라우팅 테이블 생성 요청"""
|
||||
|
||||
name = serializers.CharField(
|
||||
help_text="라우팅 테이블 이름",
|
||||
max_length=255,
|
||||
)
|
||||
vpc_id = serializers.CharField(
|
||||
help_text="VPC ID",
|
||||
)
|
||||
distributed = serializers.BooleanField(
|
||||
help_text="분산 라우팅 여부 (기본 True)",
|
||||
default=True,
|
||||
)
|
||||
|
||||
|
||||
class RoutingTableAttachGatewaySerializer(serializers.Serializer):
|
||||
"""라우팅 테이블에 인터넷 게이트웨이 연결 요청"""
|
||||
|
||||
gateway_id = serializers.CharField(
|
||||
help_text="인터넷 게이트웨이 ID",
|
||||
)
|
||||
|
||||
|
||||
# ==================== Internet Gateway ====================
|
||||
|
||||
|
||||
class InternetGatewaySerializer(serializers.Serializer):
|
||||
"""인터넷 게이트웨이 생성 요청"""
|
||||
|
||||
name = serializers.CharField(
|
||||
help_text="인터넷 게이트웨이 이름",
|
||||
max_length=255,
|
||||
)
|
||||
external_network_id = serializers.CharField(
|
||||
help_text="외부 네트워크 ID (생략 시 자동 조회)",
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
)
|
||||
|
||||
|
||||
# ==================== Security Group ====================
|
||||
|
||||
|
||||
class SecurityGroupSerializer(serializers.Serializer):
|
||||
"""보안 그룹 생성 요청"""
|
||||
|
||||
name = serializers.CharField(
|
||||
help_text="보안 그룹 이름",
|
||||
max_length=255,
|
||||
)
|
||||
description = serializers.CharField(
|
||||
help_text="설명",
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
default="",
|
||||
)
|
||||
|
||||
|
||||
class SecurityGroupUpdateSerializer(serializers.Serializer):
|
||||
"""보안 그룹 수정 요청"""
|
||||
|
||||
name = serializers.CharField(
|
||||
help_text="보안 그룹 이름",
|
||||
required=False,
|
||||
max_length=255,
|
||||
)
|
||||
description = serializers.CharField(
|
||||
help_text="설명",
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
)
|
||||
|
||||
|
||||
class SecurityGroupRuleSerializer(serializers.Serializer):
|
||||
"""보안 그룹 규칙 생성 요청"""
|
||||
|
||||
security_group_id = serializers.CharField(
|
||||
help_text="보안 그룹 ID",
|
||||
)
|
||||
direction = serializers.ChoiceField(
|
||||
choices=["ingress", "egress"],
|
||||
help_text="방향 (ingress: 인바운드, egress: 아웃바운드)",
|
||||
)
|
||||
ethertype = serializers.ChoiceField(
|
||||
choices=["IPv4", "IPv6"],
|
||||
help_text="IP 버전",
|
||||
default="IPv4",
|
||||
)
|
||||
protocol = serializers.ChoiceField(
|
||||
choices=["tcp", "udp", "icmp", ""],
|
||||
help_text="프로토콜 (tcp, udp, icmp, 빈값=전체)",
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
)
|
||||
port_range_min = serializers.IntegerField(
|
||||
help_text="최소 포트 번호",
|
||||
required=False,
|
||||
allow_null=True,
|
||||
min_value=1,
|
||||
max_value=65535,
|
||||
)
|
||||
port_range_max = serializers.IntegerField(
|
||||
help_text="최대 포트 번호",
|
||||
required=False,
|
||||
allow_null=True,
|
||||
min_value=1,
|
||||
max_value=65535,
|
||||
)
|
||||
remote_ip_prefix = serializers.CharField(
|
||||
help_text="원격 IP 대역 (CIDR, 예: 0.0.0.0/0)",
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
)
|
||||
remote_group_id = serializers.CharField(
|
||||
help_text="원격 보안 그룹 ID",
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
)
|
||||
description = serializers.CharField(
|
||||
help_text="설명",
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
default="",
|
||||
)
|
||||
|
||||
|
||||
# ==================== NKS ====================
|
||||
|
||||
|
||||
|
||||
22
nhn/urls.py
22
nhn/urls.py
@ -28,12 +28,34 @@ urlpatterns = [
|
||||
path("subnet/", views.SubnetListView.as_view(), name="subnet-list"),
|
||||
path("subnet/create/", views.SubnetCreateView.as_view(), name="subnet-create"),
|
||||
path("subnet/<str:subnet_id>/", views.SubnetDetailView.as_view(), name="subnet-detail"),
|
||||
path("subnet/<str:subnet_id>/attach-routing-table/", views.SubnetAttachRoutingTableView.as_view(), name="subnet-attach-routing-table"),
|
||||
|
||||
# ==================== Internet Gateway ====================
|
||||
path("internet-gateway/", views.InternetGatewayListView.as_view(), name="internet-gateway-list"),
|
||||
path("internet-gateway/create/", views.InternetGatewayCreateView.as_view(), name="internet-gateway-create"),
|
||||
path("internet-gateway/<str:internetgateway_id>/", views.InternetGatewayDetailView.as_view(), name="internet-gateway-detail"),
|
||||
|
||||
# ==================== Routing Table ====================
|
||||
path("routing-table/", views.RoutingTableListView.as_view(), name="routing-table-list"),
|
||||
path("routing-table/create/", views.RoutingTableCreateView.as_view(), name="routing-table-create"),
|
||||
path("routing-table/<str:routingtable_id>/", views.RoutingTableDetailView.as_view(), name="routing-table-detail"),
|
||||
path("routing-table/<str:routingtable_id>/set-default/", views.RoutingTableSetDefaultView.as_view(), name="routing-table-set-default"),
|
||||
path("routing-table/<str:routingtable_id>/attach-gateway/", views.RoutingTableAttachGatewayView.as_view(), name="routing-table-attach-gateway"),
|
||||
path("routing-table/<str:routingtable_id>/detach-gateway/", views.RoutingTableDetachGatewayView.as_view(), name="routing-table-detach-gateway"),
|
||||
|
||||
# ==================== Floating IP ====================
|
||||
path("floatingip/", views.FloatingIpListView.as_view(), name="floatingip-list"),
|
||||
path("floatingip/<str:floating_ip_id>/attach/", views.FloatingIpAttachView.as_view(), name="floatingip-attach"),
|
||||
path("floatingip/<str:floating_ip_id>/detach/", views.FloatingIpDetachView.as_view(), name="floatingip-detach"),
|
||||
|
||||
# ==================== Security Group ====================
|
||||
path("securitygroup/", views.SecurityGroupListView.as_view(), name="securitygroup-list"),
|
||||
path("securitygroup/create/", views.SecurityGroupCreateView.as_view(), name="securitygroup-create"),
|
||||
path("securitygroup/<str:security_group_id>/", views.SecurityGroupDetailView.as_view(), name="securitygroup-detail"),
|
||||
|
||||
# ==================== Security Group Rule ====================
|
||||
path("securitygroup-rule/create/", views.SecurityGroupRuleCreateView.as_view(), name="securitygroup-rule-create"),
|
||||
path("securitygroup-rule/<str:rule_id>/", views.SecurityGroupRuleDeleteView.as_view(), name="securitygroup-rule-delete"),
|
||||
|
||||
# ==================== Async Task ====================
|
||||
path("tasks/", views.AsyncTaskListView.as_view(), name="task-list"),
|
||||
|
||||
469
nhn/views.py
469
nhn/views.py
@ -19,6 +19,13 @@ from .serializers import (
|
||||
ComputeInstanceSerializer,
|
||||
VpcSerializer,
|
||||
SubnetSerializer,
|
||||
SubnetAttachRoutingTableSerializer,
|
||||
RoutingTableSerializer,
|
||||
RoutingTableAttachGatewaySerializer,
|
||||
InternetGatewaySerializer,
|
||||
SecurityGroupSerializer,
|
||||
SecurityGroupUpdateSerializer,
|
||||
SecurityGroupRuleSerializer,
|
||||
NksClusterSerializer,
|
||||
NksNodeGroupSerializer,
|
||||
NksNodeActionSerializer,
|
||||
@ -431,7 +438,7 @@ class VpcCreateView(NHNBaseView):
|
||||
region=headers["region"],
|
||||
token=headers["token"],
|
||||
name=serializer.validated_data["name"],
|
||||
cidr=serializer.validated_data["cidr"],
|
||||
cidr=serializer.validated_data["cidrv4"],
|
||||
)
|
||||
return Response(
|
||||
{"task_id": str(task.id), "status": task.status, "message": "VPC 생성 작업이 시작되었습니다."},
|
||||
@ -599,6 +606,327 @@ class FloatingIpListView(NHNBaseView):
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class FloatingIpAttachView(NHNBaseView):
|
||||
"""Floating IP를 로드밸런서에 연결"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="Floating IP를 로드밸런서 VIP 포트에 연결",
|
||||
manual_parameters=[region_header, token_header],
|
||||
responses={200: "연결된 Floating IP 정보"},
|
||||
)
|
||||
def post(self, request, floating_ip_id):
|
||||
headers = get_nhn_headers(request)
|
||||
loadbalancer_id = request.data.get("loadbalancer_id")
|
||||
if not loadbalancer_id:
|
||||
return Response({"error": "loadbalancer_id가 필요합니다."}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
# LB 상세 조회하여 vip_port_id 추출
|
||||
lb_api = ApiLoadBalancer(headers["region"], headers["token"])
|
||||
lb_info = lb_api.get_loadbalancer_info(loadbalancer_id)
|
||||
vip_port_id = lb_info.get("loadbalancer", {}).get("vip_port_id")
|
||||
if not vip_port_id:
|
||||
return Response({"error": "로드밸런서 VIP 포트를 찾을 수 없습니다."}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# FIP를 VIP 포트에 연결
|
||||
vpc_api = ApiVpc(headers["region"], headers["token"])
|
||||
result = vpc_api.attach_floating_ip(floating_ip_id, vip_port_id)
|
||||
logger.info(f"[FIP] Floating IP {floating_ip_id} → LB {loadbalancer_id} (port={vip_port_id}) 연결 성공")
|
||||
return Response(result)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[FIP] Floating IP 연결 실패 - error={e.message}")
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class FloatingIpDetachView(NHNBaseView):
|
||||
"""Floating IP 연결 해제"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="Floating IP 연결 해제",
|
||||
manual_parameters=[region_header, token_header],
|
||||
responses={200: "해제된 Floating IP 정보"},
|
||||
)
|
||||
def post(self, request, floating_ip_id):
|
||||
headers = get_nhn_headers(request)
|
||||
try:
|
||||
vpc_api = ApiVpc(headers["region"], headers["token"])
|
||||
result = vpc_api.detach_floating_ip(floating_ip_id)
|
||||
logger.info(f"[FIP] Floating IP {floating_ip_id} 연결 해제 성공")
|
||||
return Response(result)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[FIP] Floating IP 연결 해제 실패 - error={e.message}")
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
# ==================== Internet Gateway API ====================
|
||||
|
||||
|
||||
class InternetGatewayListView(NHNBaseView):
|
||||
"""인터넷 게이트웨이 목록 조회 API"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="인터넷 게이트웨이 목록 조회",
|
||||
manual_parameters=[region_header, token_header],
|
||||
responses={200: "IGW 목록"},
|
||||
)
|
||||
def get(self, request):
|
||||
headers = get_nhn_headers(request)
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
return Response(api.get_internet_gateway_list())
|
||||
except NHNCloudAPIError as e:
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class InternetGatewayCreateView(NHNBaseView):
|
||||
"""인터넷 게이트웨이 생성 API"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="인터넷 게이트웨이 생성",
|
||||
manual_parameters=[region_header, token_header],
|
||||
request_body=InternetGatewaySerializer,
|
||||
responses={201: "생성된 IGW 정보"},
|
||||
)
|
||||
def post(self, request):
|
||||
headers = get_nhn_headers(request)
|
||||
serializer = InternetGatewaySerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
external_network_id = serializer.validated_data.get("external_network_id", "")
|
||||
if not external_network_id:
|
||||
networks = api.get_external_network_id().get("networks", [])
|
||||
if not networks:
|
||||
return Response(
|
||||
{"error": "외부 네트워크를 찾을 수 없습니다."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
external_network_id = networks[0].get("id")
|
||||
|
||||
result = api.create_internet_gateway(
|
||||
name=serializer.validated_data["name"],
|
||||
external_network_id=external_network_id,
|
||||
)
|
||||
logger.info(f"[IGW] 생성 성공 - name={serializer.validated_data['name']}")
|
||||
return Response(result, status=status.HTTP_201_CREATED)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[IGW] 생성 실패 - error={e.message}")
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class InternetGatewayDetailView(NHNBaseView):
|
||||
"""인터넷 게이트웨이 상세/삭제 API"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="인터넷 게이트웨이 상세 조회",
|
||||
manual_parameters=[region_header, token_header],
|
||||
responses={200: "IGW 상세 정보"},
|
||||
)
|
||||
def get(self, request, internetgateway_id):
|
||||
headers = get_nhn_headers(request)
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
return Response(api.get_internet_gateway_info(internetgateway_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={204: "삭제 완료"},
|
||||
)
|
||||
def delete(self, request, internetgateway_id):
|
||||
headers = get_nhn_headers(request)
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
api.delete_internet_gateway(internetgateway_id)
|
||||
logger.info(f"[IGW] 삭제 성공 - id={internetgateway_id}")
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[IGW] 삭제 실패 - id={internetgateway_id}, error={e.message}")
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
# ==================== Routing Table API ====================
|
||||
|
||||
|
||||
class RoutingTableListView(NHNBaseView):
|
||||
"""라우팅 테이블 목록 조회 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_routing_table_list())
|
||||
except NHNCloudAPIError as e:
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class RoutingTableCreateView(NHNBaseView):
|
||||
"""라우팅 테이블 생성 API"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="라우팅 테이블 생성",
|
||||
manual_parameters=[region_header, token_header],
|
||||
request_body=RoutingTableSerializer,
|
||||
responses={201: "생성된 라우팅 테이블 정보"},
|
||||
)
|
||||
def post(self, request):
|
||||
headers = get_nhn_headers(request)
|
||||
serializer = RoutingTableSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
result = api.create_routing_table(
|
||||
name=serializer.validated_data["name"],
|
||||
vpc_id=serializer.validated_data["vpc_id"],
|
||||
distributed=serializer.validated_data.get("distributed", True),
|
||||
)
|
||||
logger.info(f"[RT] 생성 성공 - name={serializer.validated_data['name']}")
|
||||
return Response(result, status=status.HTTP_201_CREATED)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[RT] 생성 실패 - error={e.message}")
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class RoutingTableDetailView(NHNBaseView):
|
||||
"""라우팅 테이블 상세/삭제 API"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="라우팅 테이블 상세 조회",
|
||||
manual_parameters=[region_header, token_header],
|
||||
responses={200: "라우팅 테이블 상세"},
|
||||
)
|
||||
def get(self, request, routingtable_id):
|
||||
headers = get_nhn_headers(request)
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
return Response(api.get_routing_table_info(routingtable_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={204: "삭제 완료"},
|
||||
)
|
||||
def delete(self, request, routingtable_id):
|
||||
headers = get_nhn_headers(request)
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
api.delete_routing_table(routingtable_id)
|
||||
logger.info(f"[RT] 삭제 성공 - id={routingtable_id}")
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[RT] 삭제 실패 - id={routingtable_id}, error={e.message}")
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class RoutingTableSetDefaultView(NHNBaseView):
|
||||
"""라우팅 테이블을 기본으로 설정"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="라우팅 테이블 기본 설정",
|
||||
manual_parameters=[region_header, token_header],
|
||||
responses={200: "설정 결과"},
|
||||
)
|
||||
def put(self, request, routingtable_id):
|
||||
headers = get_nhn_headers(request)
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
result = api.set_default_routing_table(routingtable_id)
|
||||
logger.info(f"[RT] 기본 설정 성공 - id={routingtable_id}")
|
||||
return Response(result)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[RT] 기본 설정 실패 - id={routingtable_id}, error={e.message}")
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class RoutingTableAttachGatewayView(NHNBaseView):
|
||||
"""라우팅 테이블에 인터넷 게이트웨이 연결"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="라우팅 테이블에 인터넷 게이트웨이 연결",
|
||||
manual_parameters=[region_header, token_header],
|
||||
request_body=RoutingTableAttachGatewaySerializer,
|
||||
responses={200: "연결 결과"},
|
||||
)
|
||||
def put(self, request, routingtable_id):
|
||||
headers = get_nhn_headers(request)
|
||||
serializer = RoutingTableAttachGatewaySerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
result = api.attach_gateway_to_routing_table(
|
||||
routingtable_id=routingtable_id,
|
||||
gateway_id=serializer.validated_data["gateway_id"],
|
||||
)
|
||||
logger.info(f"[RT] 게이트웨이 연결 성공 - rt={routingtable_id}")
|
||||
return Response(result)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[RT] 게이트웨이 연결 실패 - rt={routingtable_id}, error={e.message}")
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class RoutingTableDetachGatewayView(NHNBaseView):
|
||||
"""라우팅 테이블에서 인터넷 게이트웨이 분리"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="라우팅 테이블에서 인터넷 게이트웨이 분리",
|
||||
manual_parameters=[region_header, token_header],
|
||||
responses={200: "분리 결과"},
|
||||
)
|
||||
def put(self, request, routingtable_id):
|
||||
headers = get_nhn_headers(request)
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
result = api.detach_gateway_from_routing_table(routingtable_id)
|
||||
logger.info(f"[RT] 게이트웨이 분리 성공 - rt={routingtable_id}")
|
||||
return Response(result)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[RT] 게이트웨이 분리 실패 - rt={routingtable_id}, error={e.message}")
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class SubnetAttachRoutingTableView(NHNBaseView):
|
||||
"""서브넷에 라우팅 테이블 연결"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="서브넷에 라우팅 테이블 연결",
|
||||
manual_parameters=[region_header, token_header],
|
||||
request_body=SubnetAttachRoutingTableSerializer,
|
||||
responses={200: "연결 결과"},
|
||||
)
|
||||
def put(self, request, subnet_id):
|
||||
headers = get_nhn_headers(request)
|
||||
serializer = SubnetAttachRoutingTableSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
result = api.attach_routing_table_to_subnet(
|
||||
subnet_id=subnet_id,
|
||||
routingtable_id=serializer.validated_data["routingtable_id"],
|
||||
)
|
||||
logger.info(f"[Subnet] 라우팅 테이블 연결 성공 - subnet={subnet_id}")
|
||||
return Response(result)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[Subnet] 라우팅 테이블 연결 실패 - subnet={subnet_id}, error={e.message}")
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
# ==================== Security Group API ====================
|
||||
|
||||
|
||||
@ -619,6 +947,145 @@ class SecurityGroupListView(NHNBaseView):
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class SecurityGroupCreateView(NHNBaseView):
|
||||
"""보안 그룹 생성 API"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="보안 그룹 생성",
|
||||
manual_parameters=[region_header, token_header],
|
||||
request_body=SecurityGroupSerializer,
|
||||
responses={201: "생성된 보안 그룹 정보"},
|
||||
)
|
||||
def post(self, request):
|
||||
headers = get_nhn_headers(request)
|
||||
serializer = SecurityGroupSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
result = api.create_security_group(
|
||||
name=serializer.validated_data["name"],
|
||||
description=serializer.validated_data.get("description", ""),
|
||||
)
|
||||
logger.info(f"[SG] 보안 그룹 생성 성공: {serializer.validated_data['name']}")
|
||||
return Response(result, status=status.HTTP_201_CREATED)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[SG] 보안 그룹 생성 실패 - error={e.message}")
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class SecurityGroupDetailView(NHNBaseView):
|
||||
"""보안 그룹 상세 조회/수정/삭제 API"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="보안 그룹 상세 조회",
|
||||
manual_parameters=[region_header, token_header],
|
||||
responses={200: "보안 그룹 상세 정보"},
|
||||
)
|
||||
def get(self, request, security_group_id):
|
||||
headers = get_nhn_headers(request)
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
return Response(api.get_security_group_info(security_group_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=SecurityGroupUpdateSerializer,
|
||||
responses={200: "수정된 보안 그룹 정보"},
|
||||
)
|
||||
def put(self, request, security_group_id):
|
||||
headers = get_nhn_headers(request)
|
||||
serializer = SecurityGroupUpdateSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
result = api.update_security_group(
|
||||
security_group_id,
|
||||
name=serializer.validated_data.get("name"),
|
||||
description=serializer.validated_data.get("description"),
|
||||
)
|
||||
logger.info(f"[SG] 보안 그룹 수정 성공: {security_group_id}")
|
||||
return Response(result)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[SG] 보안 그룹 수정 실패 - 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],
|
||||
responses={204: "삭제 성공"},
|
||||
)
|
||||
def delete(self, request, security_group_id):
|
||||
headers = get_nhn_headers(request)
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
api.delete_security_group(security_group_id)
|
||||
logger.info(f"[SG] 보안 그룹 삭제 성공: {security_group_id}")
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[SG] 보안 그룹 삭제 실패 - error={e.message}")
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class SecurityGroupRuleCreateView(NHNBaseView):
|
||||
"""보안 그룹 규칙 생성 API"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="보안 그룹 규칙 생성",
|
||||
manual_parameters=[region_header, token_header],
|
||||
request_body=SecurityGroupRuleSerializer,
|
||||
responses={201: "생성된 규칙 정보"},
|
||||
)
|
||||
def post(self, request):
|
||||
headers = get_nhn_headers(request)
|
||||
serializer = SecurityGroupRuleSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
data = serializer.validated_data
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
result = api.create_security_group_rule(
|
||||
security_group_id=data["security_group_id"],
|
||||
direction=data["direction"],
|
||||
ethertype=data.get("ethertype", "IPv4"),
|
||||
protocol=data.get("protocol") or None,
|
||||
port_range_min=data.get("port_range_min"),
|
||||
port_range_max=data.get("port_range_max"),
|
||||
remote_ip_prefix=data.get("remote_ip_prefix") or None,
|
||||
remote_group_id=data.get("remote_group_id") or None,
|
||||
description=data.get("description", ""),
|
||||
)
|
||||
logger.info(f"[SG Rule] 규칙 생성 성공: sg={data['security_group_id']}")
|
||||
return Response(result, status=status.HTTP_201_CREATED)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[SG Rule] 규칙 생성 실패 - error={e.message}")
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class SecurityGroupRuleDeleteView(NHNBaseView):
|
||||
"""보안 그룹 규칙 삭제 API"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_summary="보안 그룹 규칙 삭제",
|
||||
manual_parameters=[region_header, token_header],
|
||||
responses={204: "삭제 성공"},
|
||||
)
|
||||
def delete(self, request, rule_id):
|
||||
headers = get_nhn_headers(request)
|
||||
try:
|
||||
api = ApiVpc(headers["region"], headers["token"])
|
||||
api.delete_security_group_rule(rule_id)
|
||||
logger.info(f"[SG Rule] 규칙 삭제 성공: {rule_id}")
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
except NHNCloudAPIError as e:
|
||||
logger.error(f"[SG Rule] 규칙 삭제 실패 - error={e.message}")
|
||||
return Response({"error": e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
# ==================== Async Task API ====================
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user