From 0056bdd19bd0a00768cc11e246a054ca0fe529f9 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Wed, 11 Mar 2026 15:14:23 +0800 Subject: [PATCH 1/2] fix(iou): return 405 error for unsupported suspend operation Fixed IOU node suspend API to return proper HTTP 405 Method Not Allowed error instead of misleading 204 No Content response. Changes: - Added HTTPException import to iou_nodes.py - Fixed suspend_iou_node route from /stop to /suspend (bug fix) - Changed response from 204 No Content to 405 Method Not Allowed - Added clear error message: "Suspend is not supported for IOU nodes" This fix ensures clients receive explicit feedback when attempting to suspend IOU nodes, which do not support suspend functionality. Related issue: IOU nodes previously returned 404 when suspend was called due to incorrect route registration. Co-Authored-By: Claude Sonnet 4.5 --- gns3server/api/routes/compute/iou_nodes.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/gns3server/api/routes/compute/iou_nodes.py b/gns3server/api/routes/compute/iou_nodes.py index 4453c9bff..9175d8442 100644 --- a/gns3server/api/routes/compute/iou_nodes.py +++ b/gns3server/api/routes/compute/iou_nodes.py @@ -20,7 +20,7 @@ API routes for IOU nodes. import os -from fastapi import APIRouter, WebSocket, Depends, Body, status +from fastapi import APIRouter, WebSocket, Depends, Body, status, HTTPException from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from typing import Union @@ -186,8 +186,7 @@ async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None: @router.post( - "/{node_id}/stop", - status_code=status.HTTP_204_NO_CONTENT, + "/{node_id}/suspend", dependencies=[Depends(compute_authentication)] ) def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None: @@ -196,7 +195,10 @@ def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None: Does nothing since IOU doesn't support being suspended. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Suspend is not supported for IOU nodes" + ) @router.post( From 6e7738a14fb3dd44821e74173620e4a57d84d6b4 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 15 Mar 2026 22:38:51 +0800 Subject: [PATCH 2/2] Return 405 error for unsupported actions for other node types --- .../api/routes/compute/atm_switch_nodes.py | 19 ++++--- gns3server/api/routes/compute/cloud_nodes.py | 13 +++-- .../api/routes/compute/ethernet_hub_nodes.py | 19 ++++--- .../routes/compute/ethernet_switch_nodes.py | 25 ++++++--- .../compute/frame_relay_switch_nodes.py | 20 ++++--- gns3server/api/routes/compute/iou_nodes.py | 1 - gns3server/api/routes/compute/nat_nodes.py | 14 +++-- gns3server/api/routes/compute/vpcs_nodes.py | 8 +-- gns3server/api/routes/controller/nodes.py | 54 ++++++++++++++----- tests/api/routes/compute/test_cloud_nodes.py | 42 ++++++++++++++- .../compute/test_ethernet_switch_nodes.py | 20 ++++--- tests/api/routes/compute/test_iou_nodes.py | 10 ++++ tests/api/routes/compute/test_vpcs_nodes.py | 16 +++++- 13 files changed, 198 insertions(+), 63 deletions(-) diff --git a/gns3server/api/routes/compute/atm_switch_nodes.py b/gns3server/api/routes/compute/atm_switch_nodes.py index c50d745d1..2dbe10a1d 100644 --- a/gns3server/api/routes/compute/atm_switch_nodes.py +++ b/gns3server/api/routes/compute/atm_switch_nodes.py @@ -20,7 +20,7 @@ API routes for ATM switch nodes. import os -from fastapi import APIRouter, Depends, Body, Path, Response, status +from fastapi import APIRouter, Depends, Body, Path, status, HTTPException from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -121,20 +121,24 @@ async def delete_atm_switch_node(node: ATMSwitch = Depends(dep_node)) -> None: def start_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None: """ Start an ATM switch node. - This endpoint results in no action since ATM switch nodes are always on. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Start is not supported for ATM switches" + ) @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) def stop_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None: """ Stop an ATM switch node. - This endpoint results in no action since ATM switch nodes are always on. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Stop is not supported for ATM switches" + ) @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) @@ -144,7 +148,10 @@ def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None: This endpoint results in no action since ATM switch nodes are always on. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Suspend is not supported for ATM switches" + ) @router.post( diff --git a/gns3server/api/routes/compute/cloud_nodes.py b/gns3server/api/routes/compute/cloud_nodes.py index 3c62dbb54..8453ba0d8 100644 --- a/gns3server/api/routes/compute/cloud_nodes.py +++ b/gns3server/api/routes/compute/cloud_nodes.py @@ -20,7 +20,7 @@ API routes for cloud nodes. import os -from fastapi import APIRouter, Depends, Path, Response, status +from fastapi import APIRouter, Depends, Path, status, HTTPException from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from typing import Union @@ -120,10 +120,12 @@ async def start_cloud(node: Cloud = Depends(dep_node)) -> None: async def stop_cloud(node: Cloud = Depends(dep_node)) -> None: """ Stop a cloud node. - This endpoint results in no action since cloud nodes cannot be stopped. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Stop is not supported for cloud nodes" + ) @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) @@ -133,7 +135,10 @@ async def suspend_cloud(node: Cloud = Depends(dep_node)) -> None: This endpoint results in no action since cloud nodes cannot be suspended. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Suspend is not supported for cloud nodes" + ) @router.post( diff --git a/gns3server/api/routes/compute/ethernet_hub_nodes.py b/gns3server/api/routes/compute/ethernet_hub_nodes.py index c3b3af6ca..22f7a3b6d 100644 --- a/gns3server/api/routes/compute/ethernet_hub_nodes.py +++ b/gns3server/api/routes/compute/ethernet_hub_nodes.py @@ -20,7 +20,7 @@ API routes for Ethernet hub nodes. import os -from fastapi import APIRouter, Depends, Body, Path, Response, status +from fastapi import APIRouter, Depends, Body, Path, status, HTTPException from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -123,27 +123,34 @@ def start_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None: This endpoint results in no action since Ethernet hub nodes are always on. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Start is not supported for Ethernet hubs" + ) @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None: """ Stop an Ethernet hub. - This endpoint results in no action since Ethernet hub nodes are always on. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Stop is not supported for Ethernet hubs" + ) @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None: """ Suspend an Ethernet hub. - This endpoint results in no action since Ethernet hub nodes are always on. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Suspend is not supported for Ethernet hubs" + ) @router.post( diff --git a/gns3server/api/routes/compute/ethernet_switch_nodes.py b/gns3server/api/routes/compute/ethernet_switch_nodes.py index e672954bc..a8f755047 100644 --- a/gns3server/api/routes/compute/ethernet_switch_nodes.py +++ b/gns3server/api/routes/compute/ethernet_switch_nodes.py @@ -20,7 +20,7 @@ API routes for Ethernet switch nodes. import os -from fastapi import APIRouter, Depends, Body, Path, Response, status +from fastapi import APIRouter, Depends, Body, Path, status, HTTPException from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -124,30 +124,36 @@ async def delete_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> No def start_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None: """ Start an Ethernet switch. - This endpoint results in no action since Ethernet switch nodes are always on. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Start is not supported for Ethernet switches" + ) @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None: """ Stop an Ethernet switch. - This endpoint results in no action since Ethernet switch nodes are always on. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Stop is not supported for Ethernet switches" + ) @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None: """ Suspend an Ethernet switch. - This endpoint results in no action since Ethernet switch nodes are always on. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Suspend is not supported for Ethernet switches" + ) @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT) @@ -157,7 +163,10 @@ def reload_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None: This endpoint results in no action since Ethernet switch nodes are always on. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Reload is not supported for Ethernet switches" + ) @router.post( diff --git a/gns3server/api/routes/compute/frame_relay_switch_nodes.py b/gns3server/api/routes/compute/frame_relay_switch_nodes.py index 32482621c..aa81896e3 100644 --- a/gns3server/api/routes/compute/frame_relay_switch_nodes.py +++ b/gns3server/api/routes/compute/frame_relay_switch_nodes.py @@ -20,7 +20,7 @@ API routes for Frame Relay switch nodes. import os -from fastapi import APIRouter, Depends, Body, Path, Response, status +from fastapi import APIRouter, Depends, Body, Path, status, HTTPException from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -124,30 +124,36 @@ async def delete_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) def start_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None: """ Start a Frame Relay switch node. - This endpoint results in no action since Frame Relay switch nodes are always on. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Start is not supported for Frame Relay switches" + ) @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None: """ Stop a Frame Relay switch node. - This endpoint results in no action since Frame Relay switch nodes are always on. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Stop is not supported for Frame Relay switches" + ) @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None: """ Suspend a Frame Relay switch node. - This endpoint results in no action since Frame Relay switch nodes are always on. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Suspend is not supported for Frame Relay switches" + ) @router.post( diff --git a/gns3server/api/routes/compute/iou_nodes.py b/gns3server/api/routes/compute/iou_nodes.py index 9175d8442..5be4fdd23 100644 --- a/gns3server/api/routes/compute/iou_nodes.py +++ b/gns3server/api/routes/compute/iou_nodes.py @@ -192,7 +192,6 @@ async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None: def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None: """ Suspend an IOU node. - Does nothing since IOU doesn't support being suspended. """ raise HTTPException( diff --git a/gns3server/api/routes/compute/nat_nodes.py b/gns3server/api/routes/compute/nat_nodes.py index 03283c9b0..d11b7975d 100644 --- a/gns3server/api/routes/compute/nat_nodes.py +++ b/gns3server/api/routes/compute/nat_nodes.py @@ -20,7 +20,7 @@ API routes for NAT nodes. import os -from fastapi import APIRouter, Depends, Path, Response, status +from fastapi import APIRouter, Depends, Path, status, HTTPException from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from typing import Union @@ -115,20 +115,24 @@ async def start_nat_node(node: Nat = Depends(dep_node)) -> None: async def stop_nat_node(node: Nat = Depends(dep_node)) -> None: """ Stop a NAT node. - This endpoint results in no action since cloud nodes cannot be stopped. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Stop is not supported for NAT nodes" + ) @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) async def suspend_nat_node(node: Nat = Depends(dep_node)) -> None: """ Suspend a NAT node. - This endpoint results in no action since NAT nodes cannot be suspended. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Suspend is not supported for NAT nodes" + ) @router.post( diff --git a/gns3server/api/routes/compute/vpcs_nodes.py b/gns3server/api/routes/compute/vpcs_nodes.py index 545421d2e..c51f0254d 100644 --- a/gns3server/api/routes/compute/vpcs_nodes.py +++ b/gns3server/api/routes/compute/vpcs_nodes.py @@ -20,7 +20,7 @@ API routes for VPCS nodes. import os -from fastapi import APIRouter, WebSocket, Depends, Body, Path, status +from fastapi import APIRouter, WebSocket, Depends, Body, Path, status, HTTPException from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from typing import Union @@ -174,10 +174,12 @@ async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None: async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None: """ Suspend a VPCS node. - Does nothing, suspend is not supported by VPCS. """ - pass + raise HTTPException( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + detail="Suspend is not supported for VPCS nodes" + ) @router.post( diff --git a/gns3server/api/routes/controller/nodes.py b/gns3server/api/routes/controller/nodes.py index d38384470..2b07cbe42 100644 --- a/gns3server/api/routes/controller/nodes.py +++ b/gns3server/api/routes/controller/nodes.py @@ -22,7 +22,7 @@ import aiohttp import asyncio import ipaddress -from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect, Request, Response, status, Query +from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect, Request, Response, status, Query, HTTPException from fastapi.encoders import jsonable_encoder from fastapi.routing import APIRoute from typing import List, Callable, Optional @@ -179,7 +179,11 @@ async def start_all_nodes(project: Project = Depends(dep_project)) -> None: Required privilege: Node.PowerMgmt """ - await project.start_all() + try: + await project.start_all() + except HTTPException as e: + if not e.status_code == status.HTTP_405_METHOD_NOT_ALLOWED: + raise @router.post("/stop", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Node.PowerMgmt"))]) @@ -190,7 +194,11 @@ async def stop_all_nodes(project: Project = Depends(dep_project)) -> None: Required privilege: Node.PowerMgmt """ - await project.stop_all() + try: + await project.stop_all() + except HTTPException as e: + if not e.status_code == status.HTTP_405_METHOD_NOT_ALLOWED: + raise @router.post("/suspend", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Node.PowerMgmt"))]) @@ -201,7 +209,11 @@ async def suspend_all_nodes(project: Project = Depends(dep_project)) -> None: Required privilege: Node.PowerMgmt """ - await project.suspend_all() + try: + await project.suspend_all() + except HTTPException as e: + if not e.status_code == status.HTTP_405_METHOD_NOT_ALLOWED: + raise @router.post("/reload", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Node.PowerMgmt"))]) @@ -212,8 +224,12 @@ async def reload_all_nodes(project: Project = Depends(dep_project)) -> None: Required privilege: Node.PowerMgmt """ - await project.stop_all() - await project.start_all() + try: + await project.stop_all() + await project.start_all() + except HTTPException as e: + if not e.status_code == status.HTTP_405_METHOD_NOT_ALLOWED: + raise @router.get("/{node_id}", response_model=schemas.Node, dependencies=[Depends(has_privilege("Node.Audit"))]) @@ -300,8 +316,11 @@ async def start_node(start_data: dict, node: Node = Depends(dep_node)) -> None: Required privilege: Node.PowerMgmt """ - await node.start(data=start_data) - + try: + await node.start(data=start_data) + except HTTPException as e: + if not e.status_code == status.HTTP_405_METHOD_NOT_ALLOWED: + raise @router.post( "/{node_id}/stop", @@ -315,7 +334,11 @@ async def stop_node(node: Node = Depends(dep_node)) -> None: Required privilege: Node.PowerMgmt """ - await node.stop() + try: + await node.stop() + except HTTPException as e: + if not e.status_code == status.HTTP_405_METHOD_NOT_ALLOWED: + raise @router.post( @@ -330,7 +353,11 @@ async def suspend_node(node: Node = Depends(dep_node)) -> None: Required privilege: Node.PowerMgmt """ - await node.suspend() + try: + await node.suspend() + except HTTPException as e: + if not e.status_code == status.HTTP_405_METHOD_NOT_ALLOWED: + raise @router.post( @@ -345,8 +372,11 @@ async def reload_node(node: Node = Depends(dep_node)) -> None: Required privilege: Node.PowerMgmt """ - await node.reload() - + try: + await node.reload() + except HTTPException as e: + if not e.status_code == status.HTTP_405_METHOD_NOT_ALLOWED: + raise @router.post( "/{node_id}/isolate", diff --git a/tests/api/routes/compute/test_cloud_nodes.py b/tests/api/routes/compute/test_cloud_nodes.py index 7a3b6bbac..b19895c11 100644 --- a/tests/api/routes/compute/test_cloud_nodes.py +++ b/tests/api/routes/compute/test_cloud_nodes.py @@ -163,10 +163,50 @@ class TestCloudNodesRoutes: vm: dict ) -> None: - response = await compute_client.delete(app.url_path_for("compute:delete_cloud", project_id=vm["project_id"], node_id=vm["node_id"])) + response = await compute_client.delete( + app.url_path_for( + "compute:delete_cloud", + project_id=vm["project_id"], + node_id=vm["node_id"] + ) + ) assert response.status_code == status.HTTP_204_NO_CONTENT + async def test_cloud_stop( + self, app: FastAPI, + compute_client: AsyncClient, + compute_project: Project, + vm: dict + ) -> None: + + response = await compute_client.post( + app.url_path_for( + "compute:stop_cloud", + project_id=vm["project_id"], + node_id=vm["node_id"] + ) + ) + assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED + + + async def test_cloud_suspend( + self, app: FastAPI, + compute_client: AsyncClient, + compute_project: Project, + vm: dict + ) -> None: + + response = await compute_client.post( + app.url_path_for( + "compute:suspend_cloud", + project_id=vm["project_id"], + node_id=vm["node_id"] + ) + ) + assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED + + async def test_cloud_update( self, app: FastAPI, compute_client: AsyncClient, diff --git a/tests/api/routes/compute/test_ethernet_switch_nodes.py b/tests/api/routes/compute/test_ethernet_switch_nodes.py index f0dc400b6..cac3c3163 100644 --- a/tests/api/routes/compute/test_ethernet_switch_nodes.py +++ b/tests/api/routes/compute/test_ethernet_switch_nodes.py @@ -326,9 +326,10 @@ class TestEthernetSwitchNodesRoutes: app.url_path_for( "compute:start_ethernet_switch", project_id=ethernet_switch["project_id"], - node_id=ethernet_switch["node_id"]) + node_id=ethernet_switch["node_id"] + ) ) - assert response.status_code == status.HTTP_204_NO_CONTENT + assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED async def test_ethernet_switch_stop( @@ -341,9 +342,10 @@ class TestEthernetSwitchNodesRoutes: app.url_path_for( "compute:stop_ethernet_switch", project_id=ethernet_switch["project_id"], - node_id=ethernet_switch["node_id"]) + node_id=ethernet_switch["node_id"] + ) ) - assert response.status_code == status.HTTP_204_NO_CONTENT + assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED async def test_ethernet_switch_suspend( @@ -356,9 +358,10 @@ class TestEthernetSwitchNodesRoutes: app.url_path_for( "compute:suspend_ethernet_switch", project_id=ethernet_switch["project_id"], - node_id=ethernet_switch["node_id"]) + node_id=ethernet_switch["node_id"] + ) ) - assert response.status_code == status.HTTP_204_NO_CONTENT + assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED async def test_ethernet_switch_reload( @@ -371,9 +374,10 @@ class TestEthernetSwitchNodesRoutes: app.url_path_for( "compute:reload_ethernet_switch", project_id=ethernet_switch["project_id"], - node_id=ethernet_switch["node_id"]) + node_id=ethernet_switch["node_id"] + ) ) - assert response.status_code == status.HTTP_204_NO_CONTENT + assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED async def test_ethernet_switch_create_udp( diff --git a/tests/api/routes/compute/test_iou_nodes.py b/tests/api/routes/compute/test_iou_nodes.py index b0de85b57..391e89583 100644 --- a/tests/api/routes/compute/test_iou_nodes.py +++ b/tests/api/routes/compute/test_iou_nodes.py @@ -246,6 +246,16 @@ class TestIOUNodesRoutes: assert response.status_code == status.HTTP_204_NO_CONTENT + async def test_iou_suspend(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None: + + response = await compute_client.delete( + app.url_path_for( + "compute:suspend_iou_node", + project_id=vm["project_id"], + node_id=vm["node_id"]) + ) + assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED + async def test_iou_update( self, app: FastAPI, compute_client: AsyncClient, diff --git a/tests/api/routes/compute/test_vpcs_nodes.py b/tests/api/routes/compute/test_vpcs_nodes.py index 294ab7df8..7de416690 100644 --- a/tests/api/routes/compute/test_vpcs_nodes.py +++ b/tests/api/routes/compute/test_vpcs_nodes.py @@ -219,8 +219,20 @@ class TestVPCSNodesRoutes: node_id=vm["node_id"])) assert mock.called assert response.status_code == status.HTTP_204_NO_CONTENT - - + + + async def test_vpcs_suspend(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None: + + response = await compute_client.post( + app.url_path_for( + "compute:suspend_vpcs_node", + project_id=vm["project_id"], + node_id=vm["node_id"] + ) + ) + assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED + + async def test_vpcs_duplicate( self, app: FastAPI,